mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] Unreachable code inspection (IDEA-342966)
GitOrigin-RevId: 4bcbf2d43bddca5ded941c2b1497715ff977ca0b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a08b01a489
commit
d01d523de1
+44
-30
@@ -1,13 +1,18 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.interpreter.ReachabilityCountingInterpreter;
|
||||
import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult;
|
||||
import com.intellij.codeInspection.dataFlow.interpreter.StandardDataFlowInterpreter;
|
||||
import com.intellij.codeInspection.dataFlow.java.JavaDfaListener;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.*;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.descriptors.AssertionDisabledDescriptor;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.problems.ContractFailureProblem;
|
||||
import com.intellij.codeInspection.dataFlow.lang.DfaAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.lang.DfaListener;
|
||||
import com.intellij.codeInspection.dataFlow.lang.UnsatisfiedConditionProblem;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.ControlFlow;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.DataFlowIRProvider;
|
||||
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
|
||||
@@ -16,6 +21,7 @@ import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
@@ -80,10 +86,11 @@ public final class CommonDataflow {
|
||||
*/
|
||||
public static final class DataflowResult {
|
||||
private final @NotNull Map<JavaDfaAnchor, DataflowPoint> myData = new HashMap<>();
|
||||
private final @NotNull List<TextRange> myUnreachable = new ArrayList<>();
|
||||
private @NotNull Map<JavaDfaAnchor, DataflowPoint> myDataAssertionsDisabled = myData;
|
||||
private final RunnerResult myResult;
|
||||
private final @NotNull RunnerResult myResult;
|
||||
|
||||
public DataflowResult(RunnerResult result) {
|
||||
public DataflowResult(@NotNull RunnerResult result) {
|
||||
myResult = result;
|
||||
}
|
||||
|
||||
@@ -91,6 +98,7 @@ public final class CommonDataflow {
|
||||
DataflowResult copy() {
|
||||
DataflowResult copy = new DataflowResult(myResult);
|
||||
myData.forEach((anchor, point) -> copy.myData.put(anchor, new DataflowPoint(point)));
|
||||
copy.myUnreachable.addAll(myUnreachable);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -137,23 +145,9 @@ public final class CommonDataflow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given expression was visited by dataflow. Note that dataflow usually tracks deparenthesized expressions only,
|
||||
* so you should deparenthesize it in advance if necessary.
|
||||
*
|
||||
* @param expression expression to check, not parenthesized
|
||||
* @return true if given expression was visited by dataflow.
|
||||
* If false is returned, it's possible that the expression exists in unreachable branch or this expression is not tracked due to
|
||||
* the dataflow implementation details.
|
||||
* @param anchor anchor to check
|
||||
* @return true if a given anchor appeared during the analysis
|
||||
*/
|
||||
@Contract("null -> false")
|
||||
public boolean expressionWasAnalyzed(@Nullable PsiExpression expression) {
|
||||
if (expression == null) return false;
|
||||
if (expression instanceof PsiParenthesizedExpression) {
|
||||
throw new IllegalArgumentException("Should not pass parenthesized expression");
|
||||
}
|
||||
return myData.containsKey(new JavaExpressionAnchor(expression));
|
||||
}
|
||||
|
||||
public boolean anchorWasAnalyzed(@NotNull JavaDfaAnchor anchor) {
|
||||
return myData.containsKey(anchor);
|
||||
}
|
||||
@@ -171,6 +165,10 @@ public final class CommonDataflow {
|
||||
DataflowPoint point = myData.get(new JavaExpressionAnchor(call));
|
||||
return point != null && !point.myMayFailByContract;
|
||||
}
|
||||
|
||||
public @NotNull Collection<TextRange> getUnreachableRanges() {
|
||||
return myResult != RunnerResult.OK ? Collections.emptyList() : myUnreachable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of expression values if known. If non-empty set is returned, then given expression
|
||||
@@ -230,27 +228,43 @@ public final class CommonDataflow {
|
||||
@NotNull
|
||||
private static DataflowResult runDFA(@Nullable PsiElement block) {
|
||||
if (block == null) return new DataflowResult(RunnerResult.NOT_APPLICABLE);
|
||||
StandardDataFlowRunner runner = new StandardDataFlowRunner(block.getProject(), ThreeState.UNSURE);
|
||||
var interceptor = new CommonDataflowListener();
|
||||
RunnerResult result = runner.analyzeMethodRecursively(block, interceptor);
|
||||
var listener = new CommonDataflowListener();
|
||||
var runner = new StandardDataFlowRunner(block.getProject(), ThreeState.UNSURE) {
|
||||
@Override
|
||||
protected @NotNull StandardDataFlowInterpreter createInterpreter(@NotNull DfaListener listener, @NotNull ControlFlow flow) {
|
||||
return new ReachabilityCountingInterpreter(flow, listener, false, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterInterpretation(@NotNull ControlFlow flow,
|
||||
@NotNull StandardDataFlowInterpreter interpreter,
|
||||
@NotNull RunnerResult result) {
|
||||
if (result == RunnerResult.OK) {
|
||||
Set<PsiElement> unreachable = ((ReachabilityCountingInterpreter)interpreter).getUnreachable();
|
||||
listener.myResult.myUnreachable.addAll(DataFlowIRProvider.computeUnreachableSegments(block, unreachable));
|
||||
}
|
||||
super.afterInterpretation(flow, interpreter, result);
|
||||
}
|
||||
};
|
||||
RunnerResult result = runner.analyzeMethodRecursively(block, listener);
|
||||
if (result != RunnerResult.OK) return new DataflowResult(result);
|
||||
if (!(block instanceof PsiClass)) return interceptor.myResult;
|
||||
DataflowResult dfr = interceptor.myResult.copy();
|
||||
List<DfaMemoryState> states = interceptor.myEndOfInitializerStates;
|
||||
for (PsiMethod method : ((PsiClass)block).getConstructors()) {
|
||||
if (!(block instanceof PsiClass psiClass)) return listener.myResult;
|
||||
DataflowResult dfr = listener.myResult.copy();
|
||||
List<DfaMemoryState> states = listener.myEndOfInitializerStates;
|
||||
for (PsiMethod method : psiClass.getConstructors()) {
|
||||
List<DfaMemoryState> initialStates;
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body == null) continue;
|
||||
PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method);
|
||||
if (JavaPsiConstructorUtil.isChainedConstructorCall(call) || (call == null && hasImplicitImpureSuperCall((PsiClass)block, method))) {
|
||||
if (JavaPsiConstructorUtil.isChainedConstructorCall(call) || (call == null && hasImplicitImpureSuperCall(psiClass, method))) {
|
||||
initialStates = Collections.singletonList(runner.createMemoryState());
|
||||
} else {
|
||||
initialStates = ContainerUtil.map(states, DfaMemoryState::createCopy);
|
||||
}
|
||||
if(runner.analyzeBlockRecursively(body, initialStates, interceptor) == RunnerResult.OK) {
|
||||
dfr = interceptor.myResult.copy();
|
||||
if (runner.analyzeBlockRecursively(body, initialStates, listener) == RunnerResult.OK) {
|
||||
dfr = listener.myResult.copy();
|
||||
} else {
|
||||
interceptor.myResult = dfr;
|
||||
listener.myResult = dfr;
|
||||
}
|
||||
}
|
||||
return dfr;
|
||||
@@ -262,7 +276,7 @@ public final class CommonDataflow {
|
||||
* @return the dataflow result or null if dataflow cannot be launched for this context (e.g. we are inside too complex method)
|
||||
*/
|
||||
@Nullable
|
||||
public static DataflowResult getDataflowResult(PsiExpression context) {
|
||||
public static DataflowResult getDataflowResult(@NotNull PsiElement context) {
|
||||
PsiElement body = DfaUtil.getDataflowContext(context);
|
||||
if (body == null) return null;
|
||||
ConcurrentHashMap<PsiElement, DataflowResult> fileMap =
|
||||
|
||||
@@ -245,15 +245,14 @@ public final class DfaUtil {
|
||||
/**
|
||||
* Returns a surrounding PSI element which should be analyzed via DFA
|
||||
* (e.g. passed to {@link StandardDataFlowRunner#analyzeMethodRecursively(PsiElement, DfaListener)}) to cover
|
||||
* given expression.
|
||||
* given element.
|
||||
*
|
||||
* @param expression expression to cover
|
||||
* @param anchor element to cover
|
||||
* @return a dataflow context; null if no applicable context found.
|
||||
*/
|
||||
static @Nullable PsiElement getDataflowContext(PsiExpression expression) {
|
||||
PsiElement element = expression;
|
||||
static @Nullable PsiElement getDataflowContext(@NotNull PsiElement anchor) {
|
||||
PsiElement element = anchor;
|
||||
while (true) {
|
||||
element = element.getParent();
|
||||
if (element == null || element instanceof PsiAnnotation) return null;
|
||||
if (element instanceof PsiMethod method && !method.isConstructor()) {
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
@@ -263,6 +262,7 @@ public final class DfaUtil {
|
||||
}
|
||||
}
|
||||
if (element instanceof PsiClass psiClass && !PsiUtil.isLocalOrAnonymousClass(psiClass)) return psiClass;
|
||||
element = element.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ public final class DfaUtil {
|
||||
* @return evaluated value or null if cannot be evaluated
|
||||
*/
|
||||
public static @Nullable Boolean evaluateCondition(@Nullable PsiExpression condition) {
|
||||
if (condition == null) return null;
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(condition);
|
||||
if (result == null) return null;
|
||||
return tryCast(ContainerUtil.getOnlyItem(result.getExpressionValues(condition)), Boolean.class);
|
||||
|
||||
+7
-1
@@ -171,7 +171,13 @@ public class StandardDataFlowRunner {
|
||||
@NotNull ControlFlow flow,
|
||||
@NotNull List<DfaInstructionState> startingStates) {
|
||||
myInterpreter = createInterpreter(listener, flow);
|
||||
return myInterpreter.interpret(startingStates);
|
||||
RunnerResult result = myInterpreter.interpret(startingStates);
|
||||
afterInterpretation(flow, myInterpreter, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void afterInterpretation(@NotNull ControlFlow flow, @NotNull StandardDataFlowInterpreter interpreter, @NotNull RunnerResult result) {
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+6
-2
@@ -2365,18 +2365,22 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
startElement(castExpression);
|
||||
PsiExpression operand = castExpression.getOperand();
|
||||
|
||||
PsiType operandType = operand == null ? null : operand.getType();
|
||||
if (operand != null) {
|
||||
operand.accept(this);
|
||||
generateBoxingUnboxingInstructionFor(castExpression, operand.getType(), castExpression.getType(), true);
|
||||
generateBoxingUnboxingInstructionFor(castExpression, operandType, castExpression.getType(), true);
|
||||
}
|
||||
else {
|
||||
addInstruction(new PushValueInstruction(DfTypes.typedObject(castExpression.getType(), Nullability.UNKNOWN)));
|
||||
}
|
||||
|
||||
final PsiTypeElement typeElement = castExpression.getCastType();
|
||||
if (typeElement != null && operand != null && operand.getType() != null && !(typeElement.getType() instanceof PsiPrimitiveType)) {
|
||||
if (typeElement != null && operandType != null && !(typeElement.getType() instanceof PsiPrimitiveType) &&
|
||||
!(operandType instanceof PsiLambdaExpressionType) && !(operandType instanceof PsiMethodReferenceType)) {
|
||||
DfaControlTransferValue transfer = createTransfer("java.lang.ClassCastException");
|
||||
addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType(), transfer));
|
||||
} else {
|
||||
addInstruction(new ResultOfInstruction(new JavaExpressionAnchor(castExpression)));
|
||||
}
|
||||
finishElement(castExpression);
|
||||
}
|
||||
|
||||
+26
-11
@@ -55,8 +55,13 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
}
|
||||
}
|
||||
if (unreachable instanceof PsiCodeBlock) {
|
||||
if (unreachable.getParent() instanceof PsiCatchSection && unreachable.getParent().getParent() instanceof PsiTryStatement &&
|
||||
!allUnreachable.contains(unreachable.getParent().getParent())) {
|
||||
if (unreachable.getParent() instanceof PsiCatchSection catchSection &&
|
||||
catchSection.getParent() instanceof PsiTryStatement tryStatement &&
|
||||
!allUnreachable.contains(tryStatement)) {
|
||||
return unreachable.getTextRange();
|
||||
}
|
||||
if (unreachable.getParent() instanceof PsiClassInitializer initializer &&
|
||||
!allUnreachable.contains(initializer)) {
|
||||
return unreachable.getTextRange();
|
||||
}
|
||||
}
|
||||
@@ -66,8 +71,8 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
if (unreachable instanceof PsiSwitchLabelStatement) return null;
|
||||
if (allUnreachable.contains(statementParent)) return null;
|
||||
if (parent instanceof PsiStatement) {
|
||||
if (parent instanceof PsiIfStatement && ((PsiIfStatement)parent).getElseBranch() == unreachable) {
|
||||
PsiKeyword elseKeyword = ((PsiIfStatement)parent).getElseElement();
|
||||
if (parent instanceof PsiIfStatement ifStatement && ifStatement.getElseBranch() == unreachable) {
|
||||
PsiKeyword elseKeyword = ifStatement.getElseElement();
|
||||
if (elseKeyword != null) {
|
||||
return TextRange.create(elseKeyword.getTextRange().getStartOffset(), unreachable.getTextRange().getEndOffset());
|
||||
}
|
||||
@@ -75,10 +80,10 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
return statement.getTextRange();
|
||||
}
|
||||
if (parent instanceof PsiCodeBlock) {
|
||||
if (statement instanceof PsiSwitchLabeledRuleStatement) {
|
||||
PsiSwitchBlock block = ((PsiSwitchLabeledRuleStatement)statement).getEnclosingSwitchBlock();
|
||||
if (statement instanceof PsiSwitchLabeledRuleStatement ruleStatement) {
|
||||
PsiSwitchBlock block = ruleStatement.getEnclosingSwitchBlock();
|
||||
if (!allUnreachable.contains(block)) {
|
||||
PsiStatement body = ((PsiSwitchLabeledRuleStatement)statement).getBody();
|
||||
PsiStatement body = ruleStatement.getBody();
|
||||
if (body != null) {
|
||||
return body.getTextRange();
|
||||
}
|
||||
@@ -86,8 +91,8 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
return null;
|
||||
}
|
||||
PsiStatement prevStatement = ObjectUtils.tryCast(PsiTreeUtil.skipWhitespacesAndCommentsBackward(statement), PsiStatement.class);
|
||||
if (prevStatement instanceof PsiSwitchLabelStatement) {
|
||||
PsiSwitchBlock block = ((PsiSwitchLabelStatement)prevStatement).getEnclosingSwitchBlock();
|
||||
if (prevStatement instanceof PsiSwitchLabelStatement labelStatement) {
|
||||
PsiSwitchBlock block = labelStatement.getEnclosingSwitchBlock();
|
||||
if (block != null && !allUnreachable.contains(block)) {
|
||||
PsiElement last = ((PsiCodeBlock)statementParent).getRBrace();
|
||||
PsiSwitchLabelStatement nextLabel = PsiTreeUtil.getNextSiblingOfType(statement, PsiSwitchLabelStatement.class);
|
||||
@@ -102,8 +107,8 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
return null;
|
||||
}
|
||||
if (allUnreachable.contains(prevStatement)) return null;
|
||||
PsiElement lastStatement = PsiTreeUtil.skipWhitespacesAndCommentsBackward(((PsiCodeBlock)statementParent).getRBrace());
|
||||
if (lastStatement != null && prevStatement != null) {
|
||||
PsiElement lastStatement = getLastStatement(statement);
|
||||
if (prevStatement != null) {
|
||||
if (prevStatement instanceof PsiLoopStatement && PsiTreeUtil.isAncestor(prevStatement, startAnchor, false)) {
|
||||
return null;
|
||||
}
|
||||
@@ -113,4 +118,14 @@ public final class JavaDataFlowIRProvider implements DataFlowIRProvider {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NotNull PsiElement getLastStatement(@NotNull PsiStatement statement) {
|
||||
PsiElement lastStatement = statement;
|
||||
while (true) {
|
||||
PsiElement nextStatement = PsiTreeUtil.skipWhitespacesAndCommentsForward(lastStatement);
|
||||
if (!(nextStatement instanceof PsiStatement) || nextStatement instanceof PsiSwitchLabelStatement) break;
|
||||
lastStatement = nextStatement;
|
||||
}
|
||||
return lastStatement;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -402,7 +402,7 @@ public final class ConstantValueInspection extends AbstractBaseJavaLocalInspecti
|
||||
|| parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement;
|
||||
}
|
||||
|
||||
private static boolean isFlagCheck(PsiElement element) {
|
||||
static boolean isFlagCheck(PsiElement element) {
|
||||
PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class);
|
||||
PsiExpression topExpression = scope instanceof PsiIfStatement ifStatement ? ifStatement.getCondition() :
|
||||
scope instanceof PsiVariable variable ? variable.getInitializer() :
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.SuppressionUtil;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class UnreachableCodeInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
public boolean ignoreTrivialReturns = true;
|
||||
public boolean respectConstantValueSuppression = true;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("ignoreTrivialReturns", JavaBundle.message("inspection.data.flow.unreachable.code.option.ignore.trivial.name"))
|
||||
.description(HtmlChunk.raw(JavaBundle.message("inspection.data.flow.unreachable.code.option.ignore.trivial.description"))),
|
||||
checkbox("respectConstantValueSuppression", JavaBundle.message("inspection.data.flow.unreachable.code.option.respect.suppression.name"))
|
||||
.description(HtmlChunk.raw(JavaBundle.message("inspection.data.flow.unreachable.code.option.respect.suppression.description")))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
processElement(aClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
if (!method.isConstructor()) {
|
||||
processElement(method);
|
||||
}
|
||||
}
|
||||
|
||||
private void processElement(@NotNull PsiElement element) {
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(element);
|
||||
if (result == null) return;
|
||||
|
||||
PsiFile file = holder.getFile();
|
||||
for (TextRange range : result.getUnreachableRanges()) {
|
||||
PsiElement psiElement = file.findElementAt(range.getStartOffset());
|
||||
while (psiElement != null && !psiElement.getTextRange().contains(range)) {
|
||||
psiElement = psiElement.getParent();
|
||||
}
|
||||
if (psiElement != null) {
|
||||
if (psiElement.getTextRange().equals(range) && shouldSuppress(psiElement)) continue;
|
||||
if (element != StreamEx.iterate(psiElement, Objects::nonNull, PsiElement::getParent)
|
||||
.findFirst(e -> e instanceof PsiClass ||
|
||||
e instanceof PsiMethod method && !method.isConstructor()).orElse(null)) {
|
||||
// Nested method or class: will be reported for the corresponding anchor
|
||||
continue;
|
||||
}
|
||||
holder.registerProblem(psiElement, JavaBundle.message("inspection.data.flow.unreachable.code.display.name"),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
range.shiftLeft(psiElement.getTextRange().getStartOffset()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSuppress(PsiElement psiElement) {
|
||||
if (psiElement instanceof PsiExpressionStatement statement && isTrivialExpression(statement.getExpression()) &&
|
||||
psiElement.getParent() instanceof PsiSwitchLabeledRuleStatement rule && rule.isDefaultCase()) {
|
||||
// Default case in switch might be required by compiler
|
||||
return true;
|
||||
}
|
||||
Condition condition = findCondition(psiElement);
|
||||
if (condition != null) {
|
||||
if (ConstantValueInspection.isFlagCheck(condition.expression)) {
|
||||
return true;
|
||||
}
|
||||
if (respectConstantValueSuppression &&
|
||||
(SuppressionUtil.isSuppressed(condition.expression, "ConstantValue") ||
|
||||
SuppressionUtil.isSuppressed(condition.expression, "ConstantConditions"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (psiElement instanceof PsiCodeBlock block) {
|
||||
if (block.getParent() instanceof PsiCatchSection catchSection) {
|
||||
PsiType type = catchSection.getCatchType();
|
||||
if (mayCover(type, "java.lang.LinkageError") ||
|
||||
mayCover(type, "java.lang.VirtualMachineError")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (block.getStatementCount() == 1) {
|
||||
psiElement = block.getStatements()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return psiElement instanceof PsiThrowStatement || ignoreTrivialReturns && isTrivialReturn(psiElement);
|
||||
}
|
||||
|
||||
record Condition(boolean value, @NotNull PsiExpression expression) {}
|
||||
|
||||
private static @Nullable Condition findCondition(PsiElement deadCode) {
|
||||
if (deadCode instanceof PsiExpression) {
|
||||
if (deadCode.getParent() instanceof PsiConditionalExpression ternary) {
|
||||
if (ternary.getElseExpression() == deadCode) {
|
||||
return new Condition(false, ternary.getCondition());
|
||||
}
|
||||
else if (ternary.getThenExpression() == deadCode) {
|
||||
return new Condition(true, ternary.getCondition());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deadCode instanceof PsiCodeBlock block && block.getParent() instanceof PsiBlockStatement blockStatement) {
|
||||
deadCode = blockStatement;
|
||||
}
|
||||
if (deadCode.getParent() instanceof PsiIfStatement ifStatement) {
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
if (condition != null) {
|
||||
if (ifStatement.getThenBranch() == deadCode) {
|
||||
return new Condition(true, condition);
|
||||
}
|
||||
if (ifStatement.getElseBranch() == deadCode) {
|
||||
return new Condition(false, condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deadCode instanceof PsiStatement statement) {
|
||||
if (PsiTreeUtil.skipWhitespacesAndCommentsBackward(statement) instanceof PsiIfStatement prevIf) {
|
||||
if (prevIf.getElseBranch() == null && !ControlFlowUtils.statementMayCompleteNormally(prevIf.getThenBranch())) {
|
||||
PsiExpression condition = prevIf.getCondition();
|
||||
if (condition != null) {
|
||||
return new Condition(false, condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean mayCover(PsiType type, String exceptionFqn) {
|
||||
if (type == null) return false;
|
||||
if (type instanceof PsiDisjunctionType disjunctionType) {
|
||||
for (PsiType disjunction : disjunctionType.getDisjunctions()) {
|
||||
if (mayCover(disjunction, exceptionFqn)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (type instanceof PsiClassType classType) {
|
||||
PsiClass psiClass = classType.resolve();
|
||||
if (psiClass == null) return false;
|
||||
if (InheritanceUtil.isInheritor(psiClass, exceptionFqn)) return true;
|
||||
PsiClass exceptionClass = JavaPsiFacade.getInstance(psiClass.getProject()).findClass(exceptionFqn, psiClass.getResolveScope());
|
||||
if (exceptionClass != null && exceptionClass.isInheritor(psiClass, true)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isTrivialReturn(@NotNull PsiElement element) {
|
||||
if (element instanceof PsiBreakStatement || element instanceof PsiContinueStatement) return true;
|
||||
if (element instanceof PsiYieldStatement yieldStatement) {
|
||||
return isTrivialExpression(yieldStatement.getExpression());
|
||||
}
|
||||
if (element instanceof PsiReturnStatement returnStatement) {
|
||||
return isTrivialExpression(returnStatement.getReturnValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isTrivialExpression(PsiExpression expression) {
|
||||
if (expression == null) return true;
|
||||
if (expression instanceof PsiThisExpression thisExpression && thisExpression.getQualifier() == null) return true;
|
||||
if (expression instanceof PsiLiteralExpression literal) {
|
||||
Object value = literal.getValue();
|
||||
if (value instanceof Boolean || "".equals(value) || value instanceof Number number && number.doubleValue() == 0.0) {
|
||||
return true;
|
||||
}
|
||||
if (literal.textMatches(PsiKeyword.NULL)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1873,6 +1873,9 @@
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="ConstantValue" bundle="messages.JavaBundle" key="inspection.data.flow.constant.values.display.name"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.dataFlow.ConstantValueInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="UnreachableCode" bundle="messages.JavaBundle" key="inspection.data.flow.unreachable.code.display.name"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.dataFlow.UnreachableCodeInspection"/>
|
||||
<inspectionElementsMerger implementation="com.intellij.codeInspection.dataFlow.OptionalOfNullableMisuseInspectionMerger" />
|
||||
<inspectionElementsMerger implementation="com.intellij.codeInspection.dataFlow.ConstantValueInspectionMerger" />
|
||||
<inspectionElementsMerger implementation="com.intellij.codeInspection.dataFlow.DataFlowInspectionMerger" />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports the code which is never reached according to data flow analysis.
|
||||
It can be the result of previous always-true or always-false condition, unreachable loop body or
|
||||
catch section. Usually (though not always) unreachable code is a consequence of a previous warning,
|
||||
so check inspection warnings form "Nullability and data flow problems", "Constant values", or
|
||||
"Redundant operation on empty container" to better understand the cause.
|
||||
<p>Example:</p>
|
||||
<pre><code>
|
||||
void finishApplication() {
|
||||
System.exit(0);
|
||||
System.out.println("Application is terminated"); // Unreachable code
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>New in 2024.1</small></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
class Test {
|
||||
static class Cls2 {
|
||||
static int value;
|
||||
}
|
||||
|
||||
void test() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (NoClassDefFoundError ex) {
|
||||
System.out.println("No class");
|
||||
}
|
||||
}
|
||||
|
||||
void test2() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (StackOverflowError ex) {
|
||||
System.out.println("SO");
|
||||
}
|
||||
}
|
||||
|
||||
void test3() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (AssertionError ex) <warning descr="Unreachable code">{
|
||||
System.out.println("AE");
|
||||
}</warning>
|
||||
}
|
||||
|
||||
void test4() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (LinkageError ex) {
|
||||
System.out.println("No class");
|
||||
}
|
||||
}
|
||||
|
||||
void test5() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (Error ex) {
|
||||
System.out.println("No class");
|
||||
}
|
||||
}
|
||||
|
||||
void test6() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
System.out.println("No class");
|
||||
}
|
||||
}
|
||||
|
||||
void test7() {
|
||||
try {
|
||||
int x = Cls2.value;
|
||||
}
|
||||
catch (Exception ex) <warning descr="Unreachable code">{
|
||||
System.out.println("No class");
|
||||
}</warning>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
class Test {
|
||||
Test() {
|
||||
if (true) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
<warning descr="Unreachable code">System.out.println("Hello");</warning>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
class Test {
|
||||
static {
|
||||
if (true) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
<warning descr="Unreachable code">System.out.println("Unreachable");</warning>
|
||||
}
|
||||
|
||||
static <warning descr="Unreachable code">{
|
||||
System.out.println("You cannot see me");
|
||||
}</warning>
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
class Test {
|
||||
public interface Visitor<Type> {
|
||||
boolean visit(Type component);
|
||||
}
|
||||
|
||||
public static native void iterate(final Visitor visitor);
|
||||
|
||||
public static void analyze() {
|
||||
try {
|
||||
iterate((Visitor<String>)(String component) -> true);
|
||||
System.out.println("hello");
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
class Test {
|
||||
void test(int x) {
|
||||
int y;
|
||||
try {
|
||||
y = x;
|
||||
}
|
||||
catch (NumberFormatException nfe) <warning descr="Unreachable code">{
|
||||
throw new IllegalArgumentException();
|
||||
}</warning>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
class Test {
|
||||
void test(int x) {
|
||||
if (x < 0 || x > 2) return;
|
||||
int result = switch(x) {
|
||||
case 0 -> 1;
|
||||
case 1 -> 2;
|
||||
case 2 -> 3;
|
||||
default -> 0; // unreachable but required by compiler
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
class Test {
|
||||
void test(int x) {
|
||||
switch(x) {
|
||||
default:
|
||||
System.exit(0);
|
||||
<warning descr="Unreachable code">System.out.println("oops");
|
||||
return;</warning>
|
||||
case 0:
|
||||
System.out.println(0);
|
||||
case 1:
|
||||
System.out.println(1);
|
||||
case 2:
|
||||
System.out.println(2);
|
||||
case 3:
|
||||
System.out.println(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
class Test {
|
||||
void test(int x) {
|
||||
if (x < 0 || x > 2) return;
|
||||
switch(x) {
|
||||
default:
|
||||
<warning descr="Unreachable code">System.out.println("Impossible");</warning>
|
||||
case 0:
|
||||
System.out.println(0);
|
||||
case 1:
|
||||
System.out.println(1);
|
||||
case 2:
|
||||
System.out.println(2);
|
||||
case 3:
|
||||
System.out.println(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class Test {
|
||||
void test() {
|
||||
System.exit(0);
|
||||
<warning descr="Unreachable code">System.out.println("unreachable");</warning>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
class Test {
|
||||
int test(int x) {
|
||||
if (x < 0) {
|
||||
System.exit(0);
|
||||
throw new AssertionError("unreachable");
|
||||
}
|
||||
System.out.println(x);
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
class Test {
|
||||
int test(int x) {
|
||||
if (x < 0) {
|
||||
return 2;
|
||||
}
|
||||
if (x > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (x == 0) {
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInspection.dataFlow.UnreachableCodeInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class UnreachableCodeInspectionTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return JavaTestUtil.getJavaTestDataPath() + "/inspection/dataFlow/unreachableCode/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_17_ANNOTATED;
|
||||
}
|
||||
|
||||
public void testSystemExit() { doTest(); }
|
||||
|
||||
public void testThrow() { doTest(); }
|
||||
|
||||
public void testTrivialReturn() { doTest(); }
|
||||
|
||||
public void testSimpleCatch() { doTest(); }
|
||||
|
||||
public void testSwitchFallthrough() { doTest(); }
|
||||
|
||||
public void testSwitchFail() { doTest(); }
|
||||
|
||||
public void testSwitchDefault() { doTest(); }
|
||||
|
||||
public void testInitializer() { doTest(); }
|
||||
|
||||
public void testConstructor() { doTest(); }
|
||||
|
||||
public void testCatchLinkageError() { doTest(); }
|
||||
|
||||
public void testLambdaCast() { doTest(); }
|
||||
|
||||
private void doTest() {
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
myFixture.enableInspections(new UnreachableCodeInspection());
|
||||
myFixture.checkHighlighting();
|
||||
}
|
||||
}
|
||||
@@ -389,6 +389,11 @@ inspection.convert.to.local.quickfix=Convert to local
|
||||
inspection.data.flow.display.name=Nullability and data flow problems
|
||||
inspection.data.flow.optional.of.nullable.misuse.display.name=Use of Optional.ofNullable with null or not-null argument
|
||||
inspection.data.flow.constant.values.display.name=Constant values
|
||||
inspection.data.flow.unreachable.code.display.name=Unreachable code
|
||||
inspection.data.flow.unreachable.code.option.ignore.trivial.name=Ignore trivial exit statements
|
||||
inspection.data.flow.unreachable.code.option.ignore.trivial.description=Do not report unreachable statements like <code>return 0</code>, <code>break</code>, etc., as they could be necessary to satisfy Java control flow rules.
|
||||
inspection.data.flow.unreachable.code.option.respect.suppression.name=Respect 'Constant value' inspection suppressions
|
||||
inspection.data.flow.unreachable.code.option.respect.suppression.description=Try to avoid reporting unreachable code if it caused by a constant condition, where a corresponding warning is suppressed.
|
||||
inspection.data.flow.filter.notnull.quickfix=Insert 'filter(Objects::nonNull)' step
|
||||
inspection.data.flow.nullable.quickfix.option=Suggest @Nullable annotation for methods/fields/parameters where nullable values are used
|
||||
inspection.data.flow.true.asserts.option=Don't report assertions with condition statically proven to be always true
|
||||
|
||||
+6
@@ -17,6 +17,12 @@ import java.util.Set;
|
||||
public class ReachabilityCountingInterpreter extends StandardDataFlowInterpreter {
|
||||
protected final @NotNull BitSet myReached = new BitSet();
|
||||
|
||||
/**
|
||||
* @param flow control flow to interpret
|
||||
* @param listener listener to use
|
||||
* @param stopOnNull whether to stop interpretation on inevitable NullPointerException
|
||||
* @param startingIndex starting instruction index (usually, 0)
|
||||
*/
|
||||
public ReachabilityCountingInterpreter(@NotNull ControlFlow flow, @NotNull DfaListener listener, boolean stopOnNull, int startingIndex) {
|
||||
super(flow, listener, stopOnNull);
|
||||
myReached.set(0, startingIndex);
|
||||
|
||||
Reference in New Issue
Block a user