[java-inspections] Unreachable code inspection (IDEA-342966)

GitOrigin-RevId: 4bcbf2d43bddca5ded941c2b1497715ff977ca0b
This commit is contained in:
Tagir Valeev
2024-01-19 11:20:26 +00:00
committed by intellij-monorepo-bot
parent a08b01a489
commit d01d523de1
23 changed files with 558 additions and 50 deletions
@@ -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);
@@ -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
@@ -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);
}
@@ -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;
}
}
@@ -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() :
@@ -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;
}
}
@@ -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
@@ -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);