mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 05:21:29 +07:00
[java-dfa] Extract "Constant conditions" into separate "Constant values" inspection (IDEA-58235)
GitOrigin-RevId: 95a81fcd1546afec31afc2a044a9ba5fa1337411
This commit is contained in:
committed by
intellij-monorepo-bot
parent
339e3a45d4
commit
6ffb7e417e
@@ -3,12 +3,11 @@ package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult;
|
||||
import com.intellij.codeInspection.dataFlow.java.JavaDfaListener;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaDfaAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaMethodReferenceArgumentAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.*;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.SpecialField;
|
||||
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.UnsatisfiedConditionProblem;
|
||||
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
@@ -220,6 +219,12 @@ public final class CommonDataflow {
|
||||
return point == null ? DfType.TOP : point.myDfType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DfType getDfTypeNoAssertions(@NotNull JavaDfaAnchor anchor) {
|
||||
DataflowPoint point = myDataAssertionsDisabled.get(anchor);
|
||||
return point == null ? DfType.TOP : point.myDfType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expression an expression to infer the DfType, must be deparenthesized.
|
||||
* @return DfType for that expression, assuming assertions are disabled.
|
||||
@@ -315,9 +320,20 @@ public final class CommonDataflow {
|
||||
*/
|
||||
@NotNull
|
||||
public static DfType getDfType(PsiExpression expression) {
|
||||
return getDfType(expression, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expression an expression to infer the DfType
|
||||
* @param ignoreAssertions whether to ignore assertion statement during the analysis
|
||||
* @return DfType for that expression. May return {@link DfType#TOP} if no information from dataflow is known about this expression
|
||||
*/
|
||||
@NotNull
|
||||
public static DfType getDfType(PsiExpression expression, boolean ignoreAssertions) {
|
||||
DataflowResult result = getDataflowResult(expression);
|
||||
if (result == null) return DfType.TOP;
|
||||
return result.getDfType(PsiUtil.skipParenthesizedExprDown(expression));
|
||||
expression = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
return ignoreAssertions ? result.getDfTypeNoAssertions(expression) : result.getDfType(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,10 +387,15 @@ public final class CommonDataflow {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeMethodReferenceArgumentPush(@NotNull DfaValue value,
|
||||
@NotNull PsiMethodReferenceExpression expression,
|
||||
@NotNull DfaMemoryState state) {
|
||||
myResult.add(new JavaMethodReferenceArgumentAnchor(expression), state, value);
|
||||
public void beforePush(@NotNull DfaValue @NotNull [] args,
|
||||
@NotNull DfaValue value,
|
||||
@NotNull DfaAnchor anchor,
|
||||
@NotNull DfaMemoryState state) {
|
||||
JavaDfaListener.super.beforePush(args, value, anchor, state);
|
||||
if (anchor instanceof JavaMethodReferenceArgumentAnchor || anchor instanceof JavaPolyadicPartAnchor ||
|
||||
anchor instanceof JavaMethodReferenceReturnAnchor) {
|
||||
myResult.add((JavaDfaAnchor)anchor, state, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,37 +12,32 @@ import com.intellij.codeInspection.dataFlow.fix.*;
|
||||
import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaMethodReferenceReturnAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaPolyadicPartAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.ControlFlow;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.Instruction;
|
||||
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.nullable.NullableStuffInspectionBase;
|
||||
import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import com.intellij.psi.impl.source.PsiFieldImpl;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.bugs.EqualsWithItselfInspection;
|
||||
import com.siyeh.ig.fixes.EqualsToEqualityFix;
|
||||
import com.siyeh.ig.numeric.ComparisonToNaNInspection;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.*;
|
||||
@@ -51,13 +46,10 @@ import java.util.function.Consumer;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool {
|
||||
static final Logger LOG = Logger.getInstance(DataFlowInspectionBase.class);
|
||||
@NonNls private static final String SHORT_NAME = "ConstantConditions";
|
||||
public boolean SUGGEST_NULLABLE_ANNOTATIONS;
|
||||
public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS;
|
||||
public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE;
|
||||
public boolean IGNORE_ASSERT_STATEMENTS;
|
||||
public boolean REPORT_CONSTANT_REFERENCE_VALUES = true;
|
||||
public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
|
||||
public boolean REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true;
|
||||
public boolean REPORT_UNSOUND_WARNINGS = true;
|
||||
@@ -70,13 +62,11 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
node.addContent(new Element("option").setAttribute("name", "SUGGEST_NULLABLE_ANNOTATIONS").setAttribute("value", String.valueOf(SUGGEST_NULLABLE_ANNOTATIONS)));
|
||||
node.addContent(new Element("option").setAttribute("name", "DONT_REPORT_TRUE_ASSERT_STATEMENTS").setAttribute("value", String.valueOf(DONT_REPORT_TRUE_ASSERT_STATEMENTS)));
|
||||
// Preserved for serialization compatibility
|
||||
node.addContent(new Element("option").setAttribute("name", "DONT_REPORT_TRUE_ASSERT_STATEMENTS").setAttribute("value", "false"));
|
||||
if (IGNORE_ASSERT_STATEMENTS) {
|
||||
node.addContent(new Element("option").setAttribute("name", "IGNORE_ASSERT_STATEMENTS").setAttribute("value", "true"));
|
||||
}
|
||||
if (!REPORT_CONSTANT_REFERENCE_VALUES) {
|
||||
node.addContent(new Element("option").setAttribute("name", "REPORT_CONSTANT_REFERENCE_VALUES").setAttribute("value", "false"));
|
||||
}
|
||||
if (TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) {
|
||||
node.addContent(new Element("option").setAttribute("name", "TREAT_UNKNOWN_MEMBERS_AS_NULLABLE").setAttribute("value", "true"));
|
||||
}
|
||||
@@ -152,25 +142,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIfStatement(@NotNull PsiIfStatement statement) {
|
||||
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition());
|
||||
if (BoolUtils.isBooleanLiteral(condition)) {
|
||||
LocalQuickFix fix = createSimplifyBooleanExpressionFix(condition, condition.textMatches(PsiKeyword.TRUE));
|
||||
holder.registerProblem(condition, JavaAnalysisBundle
|
||||
.message("dataflow.message.constant.no.ref", condition.textMatches(PsiKeyword.TRUE) ? 1 : 0), fix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDoWhileStatement(@NotNull PsiDoWhileStatement statement) {
|
||||
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition());
|
||||
if (condition != null && condition.textMatches(PsiKeyword.FALSE)) {
|
||||
holder.registerProblem(condition, JavaAnalysisBundle.message("dataflow.message.constant.no.ref", 0),
|
||||
createSimplifyBooleanExpressionFix(condition, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,32 +233,25 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return null;
|
||||
}
|
||||
|
||||
protected LocalQuickFix createReplaceWithTrivialLambdaFix(Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createDescription(ProblemsHolder holder,
|
||||
final DataFlowInstructionVisitor visitor,
|
||||
PsiElement scope,
|
||||
Instruction @NotNull [] instructions) {
|
||||
ProblemReporter reporter = new ProblemReporter(holder, scope);
|
||||
|
||||
Map<PsiExpression, ConstantResult> constantExpressions = visitor.getConstantExpressions();
|
||||
reportFailingCasts(reporter, visitor, constantExpressions);
|
||||
reportFailingCasts(reporter, visitor);
|
||||
reportUnreachableSwitchBranches(visitor.getSwitchLabelsReachability(), holder);
|
||||
|
||||
reportAlwaysFailingCalls(reporter, visitor);
|
||||
|
||||
List<NullabilityProblem<?>> problems = NullabilityProblemKind.postprocessNullabilityProblems(visitor.problems().toList());
|
||||
reportNullabilityProblems(reporter, problems, constantExpressions);
|
||||
reportNullableReturns(reporter, problems, constantExpressions, scope);
|
||||
reportNullabilityProblems(reporter, problems);
|
||||
reportNullableReturns(reporter, problems, scope);
|
||||
|
||||
reportOptionalOfNullableImprovements(reporter, visitor.getOfNullableCalls());
|
||||
|
||||
reportRedundantInstanceOf(visitor, reporter);
|
||||
|
||||
reportConstants(reporter, visitor);
|
||||
|
||||
reportArrayAccessProblems(holder, visitor);
|
||||
|
||||
reportArrayStoreProblems(holder, visitor);
|
||||
@@ -432,143 +396,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return branchCount > 1;
|
||||
}
|
||||
|
||||
private void reportConstants(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
|
||||
visitor.getConstantExpressionChunks().forEach((anchor, result) -> {
|
||||
if (result == ConstantResult.UNKNOWN) return;
|
||||
Object value = result.value();
|
||||
if (anchor instanceof JavaPolyadicPartAnchor) {
|
||||
if (value instanceof Boolean) {
|
||||
// report rare cases like a == b == c where "a == b" part is constant
|
||||
String message = JavaAnalysisBundle.message("dataflow.message.constant.condition",
|
||||
((Boolean)value).booleanValue() ? 1 : 0);
|
||||
reporter.registerProblem(((JavaPolyadicPartAnchor)anchor).getExpression(),
|
||||
((JavaPolyadicPartAnchor)anchor).getTextRange(), message);
|
||||
// do not add to reported anchors if only part of expression was reported
|
||||
}
|
||||
}
|
||||
else if (anchor instanceof JavaExpressionAnchor) {
|
||||
PsiExpression expression = ((JavaExpressionAnchor)anchor).getExpression();
|
||||
if (isCondition(expression)) {
|
||||
if (value instanceof Boolean) {
|
||||
reportConstantBoolean(reporter, expression, (Boolean)value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportConstantReferenceValue(reporter, expression, result);
|
||||
}
|
||||
}
|
||||
else if (anchor instanceof JavaMethodReferenceReturnAnchor) {
|
||||
PsiMethodReferenceExpression methodRef = ((JavaMethodReferenceReturnAnchor)anchor).getMethodReferenceExpression();
|
||||
PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class);
|
||||
if (method != null && JavaMethodContractUtil.isPure(method)) {
|
||||
List<StandardMethodContract> contracts = JavaMethodContractUtil.getMethodContracts(method);
|
||||
if (contracts.isEmpty() || !contracts.get(0).isTrivial()) {
|
||||
reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.constant.method.reference", value),
|
||||
createReplaceWithTrivialLambdaFix(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isCondition(@NotNull PsiExpression expression) {
|
||||
PsiType type = expression.getType();
|
||||
if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) return false;
|
||||
if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) return true;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
|
||||
if (parent instanceof PsiStatement) return !(parent instanceof PsiReturnStatement);
|
||||
if (parent instanceof PsiPolyadicExpression) {
|
||||
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
|
||||
return tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR) ||
|
||||
tokenType.equals(JavaTokenType.AND) || tokenType.equals(JavaTokenType.OR);
|
||||
}
|
||||
if (parent instanceof PsiConditionalExpression) {
|
||||
return PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expression, false);
|
||||
}
|
||||
return PsiUtil.isAccessedForWriting(expression);
|
||||
}
|
||||
|
||||
private void reportConstantReferenceValue(ProblemReporter reporter, PsiExpression ref, ConstantResult constant) {
|
||||
if (!REPORT_CONSTANT_REFERENCE_VALUES && ref instanceof PsiReferenceExpression) return;
|
||||
if (shouldBeSuppressed(ref) || constant == ConstantResult.UNKNOWN) return;
|
||||
List<LocalQuickFix> fixes = new SmartList<>();
|
||||
String presentableName = constant.toString();
|
||||
if (Integer.valueOf(0).equals(constant.value()) && !shouldReportZero(ref)) return;
|
||||
if (constant.value() instanceof Boolean) {
|
||||
fixes.add(createSimplifyBooleanExpressionFix(ref, (Boolean)constant.value()));
|
||||
} else {
|
||||
fixes.add(new ReplaceWithConstantValueFix(presentableName, presentableName));
|
||||
}
|
||||
Object value = constant.value();
|
||||
boolean isAssertion = isAssertionEffectively(ref, constant);
|
||||
if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) return;
|
||||
if (value instanceof Boolean) {
|
||||
ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(ref, (Boolean)value));
|
||||
}
|
||||
if (reporter.isOnTheFly()) {
|
||||
if (ref instanceof PsiReferenceExpression) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "REPORT_CONSTANT_REFERENCE_VALUES",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"),
|
||||
false));
|
||||
}
|
||||
if (isAssertion) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
|
||||
}
|
||||
}
|
||||
ContainerUtil.addIfNotNull(fixes, createExplainFix(ref, new TrackingRunner.ValueDfaProblemType(value)));
|
||||
|
||||
ProblemHighlightType type;
|
||||
String message;
|
||||
if (ref instanceof PsiMethodCallExpression || ref instanceof PsiPolyadicExpression || ref instanceof PsiTypeCastExpression) {
|
||||
type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
message = JavaAnalysisBundle.message("dataflow.message.constant.expression", presentableName);
|
||||
}
|
||||
else {
|
||||
type = ProblemHighlightType.WEAK_WARNING;
|
||||
message = JavaAnalysisBundle.message("dataflow.message.constant.value", presentableName);
|
||||
}
|
||||
reporter.registerProblem(ref, message, type, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private static boolean shouldReportZero(PsiExpression ref) {
|
||||
if (ref instanceof PsiPolyadicExpression) {
|
||||
if (PsiUtil.isConstantExpression(ref)) return false;
|
||||
PsiPolyadicExpression polyadic = (PsiPolyadicExpression)ref;
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType.equals(JavaTokenType.ASTERISK)) {
|
||||
PsiMethod method = PsiTreeUtil.getParentOfType(ref, PsiMethod.class, true, PsiLambdaExpression.class, PsiClass.class);
|
||||
if (MethodUtils.isHashCode(method)) {
|
||||
// Standard hashCode template generates int result = 0; result = result * 31 + ...;
|
||||
// so annoying warnings might be produced there
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ref instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)ref;
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (PsiUtil.isConstantExpression(qualifier) &&
|
||||
ContainerUtil.and(call.getArgumentList().getExpressions(), PsiUtil::isConstantExpression)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (ref instanceof PsiTypeCastExpression) {
|
||||
PsiExpression operand = ((PsiTypeCastExpression)ref).getOperand();
|
||||
return operand != null && TypeConversionUtil.isFloatOrDoubleType(operand.getType());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
PsiBinaryExpression binOp = tryCast(parent, PsiBinaryExpression.class);
|
||||
if (binOp != null && ComparisonUtils.isEqualityComparison(binOp) &&
|
||||
(ExpressionUtils.isZero(binOp.getLOperand()) || ExpressionUtils.isZero(binOp.getROperand()))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void reportPointlessSameArguments(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
|
||||
visitor.pointlessSameArguments().forKeyValue((expr, eq) -> {
|
||||
PsiElement name = expr.getReferenceNameElement();
|
||||
@@ -581,16 +408,16 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (eq.firstArgEqualToResult) {
|
||||
String message = eq.argsEqual ? JavaAnalysisBundle.message("dataflow.message.pointless.same.arguments") :
|
||||
if (eq.firstArgEqualToResult()) {
|
||||
String message = eq.argsEqual() ? JavaAnalysisBundle.message("dataflow.message.pointless.same.arguments") :
|
||||
JavaAnalysisBundle.message("dataflow.message.pointless.same.argument.and.result", 1);
|
||||
LocalQuickFix fix = expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[0], 0) : null;
|
||||
reporter.registerProblem(name, message, fix);
|
||||
}
|
||||
else if (eq.argsEqual) {
|
||||
else if (eq.argsEqual()) {
|
||||
reporter.registerProblem(name, JavaAnalysisBundle.message("dataflow.message.pointless.same.arguments"));
|
||||
}
|
||||
else if (eq.secondArgEqualToResult) {
|
||||
else if (eq.secondArgEqualToResult()) {
|
||||
LocalQuickFix fix = expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[1], 1) : null;
|
||||
reporter.registerProblem(name, JavaAnalysisBundle.message("dataflow.message.pointless.same.argument.and.result", 2), fix);
|
||||
}
|
||||
@@ -627,6 +454,9 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
}
|
||||
}
|
||||
}
|
||||
DfType value = CommonDataflow.getDfType(expr, IGNORE_ASSERT_STATEMENTS);
|
||||
// reported by ConstantValueInspection
|
||||
if (value == DfTypes.TRUE || value == DfTypes.FALSE) return;
|
||||
String message = assignment != null && !assignment.getOperationTokenType().equals(JavaTokenType.EQ)
|
||||
? JavaAnalysisBundle.message("dataflow.message.redundant.update")
|
||||
: JavaAnalysisBundle.message("dataflow.message.redundant.assignment");
|
||||
@@ -644,31 +474,30 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void reportNullabilityProblems(ProblemReporter reporter,
|
||||
List<NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, ConstantResult> expressions) {
|
||||
protected void reportNullabilityProblems(ProblemReporter reporter, List<NullabilityProblem<?>> problems) {
|
||||
for (NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
boolean nullLiteral = ExpressionUtils.isNullLiteral(PsiUtil.skipParenthesizedExprDown(expression));
|
||||
if (!REPORT_UNSOUND_WARNINGS) {
|
||||
if (expression == null || !nullLiteral && expressions.get(expression) != ConstantResult.NULL) continue;
|
||||
if (expression == null || !nullLiteral && CommonDataflow.getDfType(expression, IGNORE_ASSERT_STATEMENTS) != DfTypes.NULL) continue;
|
||||
}
|
||||
// Expression of null type: could be failed LVTI, skip it to avoid confusion
|
||||
if (expression != null && !nullLiteral && PsiType.NULL.equals(expression.getType())) continue;
|
||||
boolean alwaysNull = problem.isAlwaysNull(expressions);
|
||||
boolean alwaysNull = problem.isAlwaysNull(IGNORE_ASSERT_STATEMENTS);
|
||||
NullabilityProblemKind.innerClassNPE.ifMyProblem(problem, newExpression -> {
|
||||
List<LocalQuickFix> fixes = createNPEFixes(newExpression.getQualifier(), newExpression, reporter.isOnTheFly(), alwaysNull);
|
||||
reporter
|
||||
.registerProblem(getElementToHighlight(newExpression), problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
.registerProblem(getElementToHighlight(newExpression), problem.getMessage(IGNORE_ASSERT_STATEMENTS),
|
||||
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
});
|
||||
NullabilityProblemKind.callMethodRefNPE.ifMyProblem(problem, methodRef ->
|
||||
reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.npe.methodref.invocation"),
|
||||
createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY)));
|
||||
NullabilityProblemKind.callNPE.ifMyProblem(problem, call ->
|
||||
reportCallMayProduceNpe(reporter, problem.getMessage(expressions), call, alwaysNull));
|
||||
reportCallMayProduceNpe(reporter, problem.getMessage(IGNORE_ASSERT_STATEMENTS), call, alwaysNull));
|
||||
NullabilityProblemKind.passingToNotNullParameter.ifMyProblem(problem, expr -> {
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, expression, reporter.isOnTheFly(), alwaysNull);
|
||||
reporter.registerProblem(expression, problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
reporter.registerProblem(expression, problem.getMessage(IGNORE_ASSERT_STATEMENTS), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
});
|
||||
NullabilityProblemKind.passingToNotNullMethodRefParameter.ifMyProblem(problem, methodRef -> {
|
||||
LocalQuickFix[] fixes = createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
@@ -681,13 +510,13 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, arrayAccess -> {
|
||||
LocalQuickFix[] fixes = createNPEFixes(arrayAccess.getArrayExpression(), arrayAccess, reporter.isOnTheFly(),
|
||||
alwaysNull).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(arrayAccess, problem.getMessage(expressions), fixes);
|
||||
reporter.registerProblem(arrayAccess, problem.getMessage(IGNORE_ASSERT_STATEMENTS), fixes);
|
||||
});
|
||||
NullabilityProblemKind.fieldAccessNPE.ifMyProblem(problem, element -> {
|
||||
PsiElement parent = element.getParent();
|
||||
PsiExpression fieldAccess = parent instanceof PsiReferenceExpression ? (PsiExpression)parent : element;
|
||||
LocalQuickFix[] fix = createNPEFixes(element, fieldAccess, reporter.isOnTheFly(), alwaysNull).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(element, problem.getMessage(expressions), fix);
|
||||
reporter.registerProblem(element, problem.getMessage(IGNORE_ASSERT_STATEMENTS), fix);
|
||||
});
|
||||
NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> {
|
||||
PsiExpression anchor = expression;
|
||||
@@ -696,32 +525,34 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
}
|
||||
if (anchor != null) {
|
||||
LocalQuickFix[] fixes = createUnboxingNullableFixes(anchor, element, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(anchor, problem.getMessage(expressions), fixes);
|
||||
reporter.registerProblem(anchor, problem.getMessage(IGNORE_ASSERT_STATEMENTS), fixes);
|
||||
}
|
||||
});
|
||||
NullabilityProblemKind.nullableFunctionReturn.ifMyProblem(
|
||||
problem, expr -> reporter.registerProblem(expression == null ? expr : expression, problem.getMessage(expressions)));
|
||||
Consumer<PsiExpression> reportNullability = expr -> reportNullabilityProblem(reporter, problem, expression, expressions);
|
||||
problem, expr -> reporter.registerProblem(expression == null ? expr : expression, problem.getMessage(IGNORE_ASSERT_STATEMENTS)));
|
||||
Consumer<PsiExpression> reportNullability = expr -> reportNullabilityProblem(reporter, problem, expression);
|
||||
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, reportNullability);
|
||||
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, reportNullability);
|
||||
if (SUGGEST_NULLABLE_ANNOTATIONS) {
|
||||
NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.ifMyProblem(
|
||||
problem, methodRef -> reportNullableArgumentPassedToNonAnnotatedMethodRef(reporter, expressions, problem, methodRef));
|
||||
problem, methodRef -> reportNullableArgumentPassedToNonAnnotatedMethodRef(reporter, problem, methodRef));
|
||||
NullabilityProblemKind.passingToNonAnnotatedParameter.ifMyProblem(
|
||||
problem, top -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(expressions), expression, top, alwaysNull));
|
||||
problem,
|
||||
top -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(IGNORE_ASSERT_STATEMENTS), expression, top,
|
||||
alwaysNull));
|
||||
NullabilityProblemKind.assigningToNonAnnotatedField.ifMyProblem(
|
||||
problem, top -> reportNullableAssignedToNonAnnotatedField(reporter, top, expression, problem.getMessage(expressions), alwaysNull));
|
||||
problem, top -> reportNullableAssignedToNonAnnotatedField(reporter, top, expression, problem.getMessage(IGNORE_ASSERT_STATEMENTS),
|
||||
alwaysNull));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportNullabilityProblem(ProblemReporter reporter,
|
||||
NullabilityProblem<?> problem,
|
||||
PsiExpression expr,
|
||||
Map<PsiExpression, ConstantResult> expressions) {
|
||||
LocalQuickFix[] fixes = createNPEFixes(expr, expr, reporter.isOnTheFly(), problem.isAlwaysNull(expressions))
|
||||
PsiExpression expr) {
|
||||
LocalQuickFix[] fixes = createNPEFixes(expr, expr, reporter.isOnTheFly(), problem.isAlwaysNull(IGNORE_ASSERT_STATEMENTS))
|
||||
.toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(expr, problem.getMessage(expressions), fixes);
|
||||
reporter.registerProblem(expr, problem.getMessage(IGNORE_ASSERT_STATEMENTS), fixes);
|
||||
}
|
||||
|
||||
private static void reportArrayAccessProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) {
|
||||
@@ -819,17 +650,16 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
});
|
||||
}
|
||||
|
||||
private static void reportNullableArgumentPassedToNonAnnotatedMethodRef(@NotNull ProblemReporter reporter,
|
||||
@NotNull Map<PsiExpression, ConstantResult> expressions,
|
||||
@NotNull NullabilityProblem<?> problem,
|
||||
@NotNull PsiMethodReferenceExpression methodRef) {
|
||||
private void reportNullableArgumentPassedToNonAnnotatedMethodRef(@NotNull ProblemReporter reporter,
|
||||
@NotNull NullabilityProblem<?> problem,
|
||||
@NotNull PsiMethodReferenceExpression methodRef) {
|
||||
PsiMethod target = tryCast(methodRef.resolve(), PsiMethod.class);
|
||||
if (target == null) return;
|
||||
PsiParameter[] parameters = target.getParameterList().getParameters();
|
||||
if (parameters.length == 0) return;
|
||||
PsiParameter parameter = parameters[0];
|
||||
if (!BaseIntentionAction.canModify(parameter) || !AnnotationUtil.isAnnotatingApplicable(parameter)) return;
|
||||
reporter.registerProblem(methodRef, problem.getMessage(expressions),
|
||||
reporter.registerProblem(methodRef, problem.getMessage(IGNORE_ASSERT_STATEMENTS),
|
||||
parameters.length == 1 ? AddAnnotationPsiFix.createAddNullableFix(parameter) : null);
|
||||
}
|
||||
|
||||
@@ -880,18 +710,17 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
reporter.registerProblem(toHighlight, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private void reportFailingCasts(@NotNull ProblemReporter reporter,
|
||||
@NotNull DataFlowInstructionVisitor visitor,
|
||||
@NotNull Map<PsiExpression, ConstantResult> constantExpressions) {
|
||||
private void reportFailingCasts(@NotNull ProblemReporter reporter, @NotNull DataFlowInstructionVisitor visitor) {
|
||||
visitor.getFailingCastExpressions().forKeyValue((typeCast, info) -> {
|
||||
boolean alwaysFails = info.getFirst();
|
||||
PsiType realType = info.getSecond();
|
||||
if (!REPORT_UNSOUND_WARNINGS && !alwaysFails) return;
|
||||
PsiExpression operand = typeCast.getOperand();
|
||||
PsiTypeElement castType = typeCast.getCastType();
|
||||
ConstantResult result = constantExpressions.get(PsiUtil.skipParenthesizedExprDown(operand));
|
||||
// Skip reporting if cast operand is always null: null can be cast to anything
|
||||
if (result == ConstantResult.NULL || ExpressionUtils.isNullLiteral(operand)) return;
|
||||
if (ExpressionUtils.isNullLiteral(operand) || DfTypes.NULL.equals(CommonDataflow.getDfType(operand, IGNORE_ASSERT_STATEMENTS))) {
|
||||
// Skip reporting if cast operand is always null: null can be cast to anything
|
||||
return;
|
||||
}
|
||||
assert castType != null;
|
||||
assert operand != null;
|
||||
List<LocalQuickFix> fixes = new ArrayList<>(createCastFixes(typeCast, realType, reporter.isOnTheFly(), alwaysFails));
|
||||
@@ -904,162 +733,22 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
});
|
||||
}
|
||||
|
||||
private void reportConstantBoolean(ProblemReporter reporter, PsiElement psiAnchor, boolean evaluatesToTrue) {
|
||||
while (psiAnchor instanceof PsiParenthesizedExpression parenthesized) {
|
||||
psiAnchor = parenthesized.getExpression();
|
||||
}
|
||||
if (psiAnchor == null || shouldBeSuppressed(psiAnchor)) return;
|
||||
boolean isAssertion = psiAnchor instanceof PsiExpression expr && isAssertionEffectively(expr, evaluatesToTrue);
|
||||
if (DONT_REPORT_TRUE_ASSERT_STATEMENTS && isAssertion) return;
|
||||
|
||||
if (PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()) instanceof PsiAssignmentExpression assignment &&
|
||||
PsiTreeUtil.isAncestor(assignment.getLExpression(), psiAnchor, false)) {
|
||||
reporter.registerProblem(
|
||||
psiAnchor,
|
||||
JavaAnalysisBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)),
|
||||
createConditionalAssignmentFixes(evaluatesToTrue, assignment, reporter.isOnTheFly())
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
List<LocalQuickFix> fixes = new ArrayList<>();
|
||||
if (!isCoveredBySurroundingFix(psiAnchor, evaluatesToTrue)) {
|
||||
ContainerUtil.addIfNotNull(fixes, createSimplifyBooleanExpressionFix(psiAnchor, evaluatesToTrue));
|
||||
if (isAssertion && reporter.isOnTheFly()) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
|
||||
}
|
||||
ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(psiAnchor, evaluatesToTrue));
|
||||
}
|
||||
if (psiAnchor instanceof PsiExpression) {
|
||||
ContainerUtil.addIfNotNull(fixes, createExplainFix(
|
||||
(PsiExpression)psiAnchor, new TrackingRunner.ValueDfaProblemType(evaluatesToTrue)));
|
||||
}
|
||||
String message = JavaAnalysisBundle.message(isAtRHSOfBooleanAnd(psiAnchor) ?
|
||||
"dataflow.message.constant.condition.when.reached" :
|
||||
"dataflow.message.constant.condition", evaluatesToTrue ? 1 : 0);
|
||||
reporter.registerProblem(psiAnchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
protected @Nullable LocalQuickFix createExplainFix(PsiExpression anchor, TrackingRunner.DfaProblemType problemType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isCoveredBySurroundingFix(PsiElement anchor, boolean evaluatesToTrue) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent());
|
||||
if (parent instanceof PsiPolyadicExpression) {
|
||||
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
|
||||
return tokenType.equals(JavaTokenType.ANDAND) && !evaluatesToTrue ||
|
||||
tokenType.equals(JavaTokenType.OROR) && evaluatesToTrue;
|
||||
}
|
||||
return parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent);
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
private static boolean shouldBeSuppressed(PsiElement anchor) {
|
||||
if (!(anchor instanceof PsiExpression)) return false;
|
||||
// Don't report System.out.println(b = false) or doSomething((Type)null)
|
||||
if (anchor instanceof PsiAssignmentExpression ||
|
||||
(anchor instanceof PsiTypeCastExpression && !(((PsiTypeCastExpression)anchor).getType() instanceof PsiPrimitiveType))) {
|
||||
return true;
|
||||
}
|
||||
// For conditional the root cause (constant condition or both branches constant) should be already reported for branches
|
||||
if (anchor instanceof PsiConditionalExpression) return true;
|
||||
PsiExpression expression = (PsiExpression)anchor;
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
PsiReferenceExpression ref = (PsiReferenceExpression)expression;
|
||||
if ("TRUE".equals(ref.getReferenceName()) || "FALSE".equals(ref.getReferenceName())) {
|
||||
PsiElement target = ref.resolve();
|
||||
if (target instanceof PsiField) {
|
||||
PsiClass containingClass = ((PsiField)target).getContainingClass();
|
||||
if (containingClass != null && CommonClassNames.JAVA_LANG_BOOLEAN.equals(containingClass.getQualifiedName())) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression instanceof PsiBinaryExpression) {
|
||||
PsiExpression lOperand = ((PsiBinaryExpression)expression).getLOperand();
|
||||
PsiExpression rOperand = ((PsiBinaryExpression)expression).getROperand();
|
||||
IElementType tokenType = ((PsiBinaryExpression)expression).getOperationTokenType();
|
||||
// Suppress on type mismatch compilation errors
|
||||
if (rOperand == null) return true;
|
||||
PsiType lType = lOperand.getType();
|
||||
PsiType rType = rOperand.getType();
|
||||
if (lType == null || rType == null) return true;
|
||||
if (!TypeConversionUtil.isBinaryOperatorApplicable(tokenType, lType, rType, false)) return true;
|
||||
}
|
||||
if (expression instanceof PsiInstanceOfExpression) {
|
||||
PsiType type = ((PsiInstanceOfExpression)expression).getOperand().getType();
|
||||
private static boolean shouldBeSuppressed(@NotNull PsiExpression anchor) {
|
||||
if (anchor instanceof PsiInstanceOfExpression) {
|
||||
PsiType type = ((PsiInstanceOfExpression)anchor).getOperand().getType();
|
||||
if (type == null || !TypeConstraints.instanceOf(type).isResolved()) return true;
|
||||
PsiPattern pattern = ((PsiInstanceOfExpression)expression).getPattern();
|
||||
PsiPattern pattern = ((PsiInstanceOfExpression)anchor).getPattern();
|
||||
if (pattern instanceof PsiTypeTestPattern && ((PsiTypeTestPattern)pattern).getPatternVariable() != null) {
|
||||
PsiTypeElement checkType = ((PsiTypeTestPattern)pattern).getCheckType();
|
||||
if (checkType != null && checkType.getType().isAssignableFrom(type)) {
|
||||
// Reported as compilation error
|
||||
return true;
|
||||
}
|
||||
// Reported as compilation error
|
||||
return checkType != null && checkType.getType().isAssignableFrom(type);
|
||||
}
|
||||
}
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
|
||||
// Don't report "x" in "x == null" as will be anyway reported as "always true"
|
||||
if (parent instanceof PsiBinaryExpression && ExpressionUtils.getValueComparedWithNull((PsiBinaryExpression)parent) != null) return true;
|
||||
// Dereference of null will be covered by other warning
|
||||
if (ExpressionUtils.isVoidContext(expression) || isDereferenceContext(expression)) return true;
|
||||
// We assume all Void variables as null because you cannot instantiate it without dirty hacks
|
||||
// However reporting them as "always null" looks redundant (dereferences or comparisons will be reported though).
|
||||
if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_VOID, expression.getType())) return true;
|
||||
if (isFlagCheck(anchor)) return true;
|
||||
if (!isCondition(expression) && expression instanceof PsiMethodCallExpression) {
|
||||
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts((PsiCallExpression)expression);
|
||||
ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(contracts);
|
||||
if (value != null) return true;
|
||||
if (!(parent instanceof PsiAssignmentExpression) && !(parent instanceof PsiVariable) &&
|
||||
!(parent instanceof PsiReturnStatement)) {
|
||||
PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
|
||||
if (method == null || !JavaMethodContractUtil.isPure(method)) return true;
|
||||
}
|
||||
}
|
||||
while (expression != null && BoolUtils.isNegation(expression)) {
|
||||
expression = BoolUtils.getNegated(expression);
|
||||
}
|
||||
if (expression == null) return false;
|
||||
if (!isCondition(expression) && expression instanceof PsiReferenceExpression) {
|
||||
PsiVariable variable = tryCast(((PsiReferenceExpression)expression).resolve(), PsiVariable.class);
|
||||
if (variable instanceof PsiField &&
|
||||
variable.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
ExpressionUtils.isNullLiteral(PsiFieldImpl.getDetachedInitializer(variable))) {
|
||||
return true;
|
||||
}
|
||||
if (variable instanceof PsiLocalVariable && variable.hasInitializer()) {
|
||||
boolean effectivelyFinal = variable.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
!VariableAccessUtils.variableIsAssigned(variable, PsiUtil.getVariableCodeBlock(variable, null));
|
||||
return effectivelyFinal && PsiUtil.isConstantExpression(variable.getInitializer());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Avoid double reporting
|
||||
return expression instanceof PsiMethodCallExpression && EqualsWithItselfInspection.isEqualsWithItself((PsiMethodCallExpression)expression) ||
|
||||
expression instanceof PsiBinaryExpression && ComparisonToNaNInspection.extractNaNFromComparison((PsiBinaryExpression)expression) != null;
|
||||
}
|
||||
|
||||
private static boolean isDereferenceContext(PsiExpression ref) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
return parent instanceof PsiReferenceExpression || parent instanceof PsiArrayAccessExpression
|
||||
|| parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement;
|
||||
}
|
||||
|
||||
private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) {
|
||||
if (evaluatesToTrue) return null;
|
||||
if (!(psiAnchor instanceof PsiMethodCallExpression)) return null;
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)psiAnchor;
|
||||
if (!MethodCallUtils.isEqualsCall(methodCallExpression)) return null;
|
||||
PsiExpression arg = ArrayUtil.getFirstElement(methodCallExpression.getArgumentList().getExpressions());
|
||||
if (!ExpressionUtils.isNullLiteral(arg)) return null;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent());
|
||||
return EqualsToEqualityFix.buildFix(methodCallExpression, parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent));
|
||||
}
|
||||
|
||||
protected LocalQuickFix[] createConditionalAssignmentFixes(boolean evaluatesToTrue, PsiAssignmentExpression parent, final boolean onTheFly) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable PsiMethod getScopeMethod(PsiElement block) {
|
||||
@@ -1071,7 +760,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
|
||||
private void reportNullableReturns(ProblemReporter reporter,
|
||||
List<NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, ConstantResult> expressions,
|
||||
@NotNull PsiElement block) {
|
||||
final PsiMethod method = getScopeMethod(block);
|
||||
if (method == null) return;
|
||||
@@ -1099,7 +787,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
final PsiExpression anchor = problem.getAnchor();
|
||||
PsiExpression expr = problem.getDereferencedExpression();
|
||||
|
||||
boolean exactlyNull = problem.isAlwaysNull(expressions);
|
||||
boolean exactlyNull = problem.isAlwaysNull(IGNORE_ASSERT_STATEMENTS);
|
||||
if (!REPORT_UNSOUND_WARNINGS && !exactlyNull) continue;
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
String presentable = NullableStuffInspectionBase.getPresentableAnnoName(anno);
|
||||
@@ -1123,198 +811,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssertionEffectively(@NotNull PsiExpression anchor, ConstantResult result) {
|
||||
Object value = result.value();
|
||||
if (value instanceof Boolean) {
|
||||
return isAssertionEffectively(anchor, (Boolean)value);
|
||||
}
|
||||
if (value != null) return false;
|
||||
return isAssertCallArgument(anchor, ContractValue.nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param anchor boolean expression
|
||||
* @param expectedValue the expected result of boolean expression
|
||||
* @return true if this boolean expression is effectively an assertion (code throws if its value is not equal to expectedValue)
|
||||
*/
|
||||
public static boolean isAssertionEffectively(@NotNull PsiExpression anchor, boolean expectedValue) {
|
||||
PsiElement parent;
|
||||
while (true) {
|
||||
parent = anchor.getParent();
|
||||
if (parent instanceof PsiExpression parentExpr && BoolUtils.isNegation(parentExpr)) {
|
||||
expectedValue = !expectedValue;
|
||||
anchor = parentExpr;
|
||||
continue;
|
||||
}
|
||||
if (parent instanceof PsiParenthesizedExpression parenthesized) {
|
||||
anchor = parenthesized;
|
||||
continue;
|
||||
}
|
||||
if (parent instanceof PsiPolyadicExpression polyadic) {
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) {
|
||||
// always true operand makes always true OR-chain and does not affect the result of AND-chain
|
||||
// Note that in `assert unknownExpression && trueExpression;` the trueExpression should not be reported
|
||||
// because this assertion is essentially the shortened `assert unknownExpression; assert trueExpression;`
|
||||
// which is not reported.
|
||||
boolean causesShortCircuit = (tokenType.equals(JavaTokenType.OROR) == expectedValue) &&
|
||||
ArrayUtil.getLastElement(polyadic.getOperands()) != anchor;
|
||||
if (!causesShortCircuit) {
|
||||
// We still report `assert trueExpression || unknownExpression`, because here `unknownExpression` is never checked
|
||||
// which is probably not intended.
|
||||
anchor = polyadic;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parent instanceof PsiAssertStatement) {
|
||||
return expectedValue;
|
||||
}
|
||||
if (parent instanceof PsiIfStatement ifStatement && anchor == ifStatement.getCondition()) {
|
||||
PsiStatement thenBranch = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
|
||||
if (thenBranch instanceof PsiThrowStatement) {
|
||||
return !expectedValue;
|
||||
}
|
||||
}
|
||||
return isAssertCallArgument(anchor, ContractValue.booleanValue(expectedValue));
|
||||
}
|
||||
|
||||
private static boolean isAssertCallArgument(@NotNull PsiElement anchor, @NotNull ContractValue wantedConstraint) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent());
|
||||
if (parent instanceof PsiExpressionList) {
|
||||
int index = ArrayUtil.indexOf(((PsiExpressionList)parent).getExpressions(), anchor);
|
||||
if (index >= 0) {
|
||||
PsiMethodCallExpression call = tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (call != null) {
|
||||
MethodContract contract = ContainerUtil.getOnlyItem(JavaMethodContractUtil.getMethodCallContracts(call));
|
||||
if (contract != null && contract.getReturnValue().isFail()) {
|
||||
ContractValue condition = ContainerUtil.getOnlyItem(contract.getConditions());
|
||||
if (condition != null) {
|
||||
return condition.getArgumentComparedTo(wantedConstraint, false).orElse(-1) == index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isAtRHSOfBooleanAnd(PsiElement expr) {
|
||||
PsiElement cur = expr;
|
||||
|
||||
while (cur != null && !(cur instanceof PsiMember)) {
|
||||
PsiElement parent = cur.getParent();
|
||||
|
||||
if (parent instanceof PsiBinaryExpression && cur == ((PsiBinaryExpression)parent).getROperand()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cur = parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isFlagCheck(PsiElement element) {
|
||||
PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class);
|
||||
PsiExpression topExpression = scope instanceof PsiIfStatement ? ((PsiIfStatement)scope).getCondition() :
|
||||
scope instanceof PsiVariable ? ((PsiVariable)scope).getInitializer() :
|
||||
null;
|
||||
if (!PsiTreeUtil.isAncestor(topExpression, element, false)) return false;
|
||||
|
||||
return StreamEx.<PsiElement>ofTree(topExpression, e -> StreamEx.of(e.getChildren()))
|
||||
.anyMatch(DataFlowInspectionBase::isCompileTimeFlagCheck);
|
||||
}
|
||||
|
||||
private static boolean isCompileTimeFlagCheck(PsiElement element) {
|
||||
if(element instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression binOp = (PsiBinaryExpression)element;
|
||||
if(ComparisonUtils.isComparisonOperation(binOp.getOperationTokenType())) {
|
||||
PsiExpression comparedWith = null;
|
||||
if(ExpressionUtils.isLiteral(binOp.getROperand())) {
|
||||
comparedWith = binOp.getLOperand();
|
||||
} else if(ExpressionUtils.isLiteral(binOp.getLOperand())) {
|
||||
comparedWith = binOp.getROperand();
|
||||
}
|
||||
comparedWith = PsiUtil.skipParenthesizedExprDown(comparedWith);
|
||||
if (isConstantOfType(comparedWith, PsiType.INT, PsiType.LONG)) {
|
||||
// like "if(DEBUG_LEVEL > 2)"
|
||||
return true;
|
||||
}
|
||||
if(comparedWith instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression subOp = (PsiBinaryExpression)comparedWith;
|
||||
if(subOp.getOperationTokenType().equals(JavaTokenType.AND)) {
|
||||
PsiExpression left = PsiUtil.skipParenthesizedExprDown(subOp.getLOperand());
|
||||
PsiExpression right = PsiUtil.skipParenthesizedExprDown(subOp.getROperand());
|
||||
if(isConstantOfType(left, PsiType.INT, PsiType.LONG) ||
|
||||
isConstantOfType(right, PsiType.INT, PsiType.LONG)) {
|
||||
// like "if((FLAGS & SOME_FLAG) != 0)"
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// like "if(DEBUG)"
|
||||
return isConstantOfType(element, PsiType.BOOLEAN);
|
||||
}
|
||||
|
||||
private static boolean isConstantOfType(PsiElement element, PsiPrimitiveType... types) {
|
||||
PsiElement resolved = element instanceof PsiReferenceExpression ? ((PsiReferenceExpression)element).resolve() : null;
|
||||
if (!(resolved instanceof PsiField)) return false;
|
||||
PsiField field = (PsiField)resolved;
|
||||
PsiModifierList modifierList = field.getModifierList();
|
||||
if (modifierList == null ||
|
||||
!modifierList.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
!modifierList.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return false;
|
||||
}
|
||||
if (!ArrayUtil.contains(field.getType(), types)) return false;
|
||||
return field.hasInitializer() && PsiUtil.isConstantExpression(field.getInitializer());
|
||||
}
|
||||
|
||||
private @Nullable LocalQuickFix createSimplifyBooleanExpressionFix(PsiElement element, final boolean value) {
|
||||
LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(element, value);
|
||||
if (fix == null) return null;
|
||||
final String text = fix.getText();
|
||||
return new LocalQuickFix() {
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement psiElement = descriptor.getPsiElement();
|
||||
if (psiElement == null) return;
|
||||
final LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(psiElement, value);
|
||||
if (fix == null) return;
|
||||
try {
|
||||
LOG.assertTrue(psiElement.isValid());
|
||||
fix.applyFix();
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return JavaAnalysisBundle.message("inspection.data.flow.simplify.boolean.expression.quickfix");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static @NotNull LocalQuickFix createSimplifyToAssignmentFix() {
|
||||
return new SimplifyToAssignmentFix();
|
||||
}
|
||||
|
||||
protected LocalQuickFixOnPsiElement createSimplifyBooleanFix(PsiElement element, boolean value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getGroupDisplayName() {
|
||||
return InspectionsBundle.message("group.names.probable.bugs");
|
||||
@@ -1325,44 +821,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return SHORT_NAME;
|
||||
}
|
||||
|
||||
protected enum ConstantResult {
|
||||
TRUE, FALSE, NULL, ZERO, UNKNOWN;
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return this == ZERO ? "0" : StringUtil.toLowerCase(name());
|
||||
}
|
||||
|
||||
public Object value() {
|
||||
switch (this) {
|
||||
case TRUE:
|
||||
return Boolean.TRUE;
|
||||
case FALSE:
|
||||
return Boolean.FALSE;
|
||||
case ZERO:
|
||||
return 0;
|
||||
case NULL:
|
||||
return null;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
static @NotNull ConstantResult fromDfType(@NotNull DfType dfType) {
|
||||
if (dfType == DfTypes.NULL) return NULL;
|
||||
if (dfType == DfTypes.TRUE) return TRUE;
|
||||
if (dfType == DfTypes.FALSE) return FALSE;
|
||||
if (dfType.isConst(0) || dfType.isConst(0L)) return ZERO;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
static @NotNull ConstantResult mergeValue(@Nullable ConstantResult state, @NotNull DfaMemoryState memState, @Nullable DfaValue value) {
|
||||
if (state == UNKNOWN || value == null) return UNKNOWN;
|
||||
ConstantResult nextState = fromDfType(DfaUtil.getUnboxedDfType(memState, value));
|
||||
return state == null || state == nextState ? nextState : UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProblemsHolder} wrapper to avoid reporting two problems on the same anchor
|
||||
*/
|
||||
@@ -1382,21 +840,6 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
}
|
||||
}
|
||||
|
||||
void registerProblem(PsiElement element, @InspectionMessage String message, ProblemHighlightType type, LocalQuickFix... fixes) {
|
||||
if (register(element)) {
|
||||
myHolder.registerProblem(element, message, type, fixes);
|
||||
}
|
||||
}
|
||||
|
||||
void registerProblem(PsiElement element, TextRange range, @InspectionMessage String message, LocalQuickFix... fixes) {
|
||||
if (range == null) {
|
||||
registerProblem(element, message, fixes);
|
||||
}
|
||||
else {
|
||||
myHolder.registerProblem(element, range, message, fixes);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean register(PsiElement element) {
|
||||
// Suppress reporting for inlined simple methods
|
||||
if (!PsiTreeUtil.isAncestor(myScope, element, false)) return false;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspectionBase.ConstantResult;
|
||||
import com.intellij.codeInspection.dataFlow.java.JavaDfaListener;
|
||||
import com.intellij.codeInspection.dataFlow.java.JavaDfaValueFactory;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
|
||||
@@ -52,7 +51,6 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
private final Map<PsiTypeCastExpression, StateInfo> myClassCastProblems = new HashMap<>();
|
||||
private final Map<PsiTypeCastExpression, TypeConstraint> myRealOperandTypes = new HashMap<>();
|
||||
private final Map<ContractFailureProblem, Boolean> myFailingCalls = new HashMap<>();
|
||||
private final Map<DfaAnchor, ConstantResult> myConstantExpressions = new HashMap<>();
|
||||
private final Map<PsiElement, ThreeState> myOfNullableCalls = new HashMap<>();
|
||||
private final Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> myArrayStoreProblems = new HashMap<>();
|
||||
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
|
||||
@@ -65,6 +63,7 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
private boolean myAlwaysReturnsNotNull = true;
|
||||
private final List<DfaMemoryState> myEndOfInitializerStates = new ArrayList<>();
|
||||
private final Set<DfaAnchor> myPotentiallyRedundantInstanceOf = new HashSet<>();
|
||||
private final Map<DfaAnchor, ThreeState> myConstantInstanceOf = new HashMap<>();
|
||||
private final boolean myStrictMode;
|
||||
|
||||
private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf(
|
||||
@@ -174,15 +173,6 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
return myOfNullableCalls;
|
||||
}
|
||||
|
||||
Map<PsiExpression, ConstantResult> getConstantExpressions() {
|
||||
return EntryStream.of(myConstantExpressions).selectKeys(JavaExpressionAnchor.class)
|
||||
.mapKeys(JavaExpressionAnchor::getExpression).toMap();
|
||||
}
|
||||
|
||||
Map<DfaAnchor, ConstantResult> getConstantExpressionChunks() {
|
||||
return myConstantExpressions;
|
||||
}
|
||||
|
||||
Map<PsiCaseLabelElement, ThreeState> getSwitchLabelsReachability() {
|
||||
return mySwitchLabelsReachability;
|
||||
}
|
||||
@@ -218,7 +208,7 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
}
|
||||
|
||||
StreamEx<DfaAnchor> redundantInstanceOfs() {
|
||||
return StreamEx.of(myPotentiallyRedundantInstanceOf).filter(anchor -> myConstantExpressions.get(anchor) == ConstantResult.UNKNOWN);
|
||||
return StreamEx.of(myPotentiallyRedundantInstanceOf).filter(anchor -> myConstantInstanceOf.get(anchor) == ThreeState.UNSURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -242,15 +232,22 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
}
|
||||
if (anchor instanceof JavaSwitchLabelTakenAnchor) {
|
||||
DfType type = state.getDfType(value);
|
||||
mySwitchLabelsReachability.merge(((JavaSwitchLabelTakenAnchor)anchor).getLabelElement(),
|
||||
type.equals(DfTypes.TRUE) ? ThreeState.YES :
|
||||
type.equals(DfTypes.FALSE) ? ThreeState.NO : ThreeState.UNSURE, ThreeState::merge);
|
||||
mySwitchLabelsReachability.merge(((JavaSwitchLabelTakenAnchor)anchor).getLabelElement(), fromDfType(type), ThreeState::merge);
|
||||
return;
|
||||
}
|
||||
if (myPotentiallyRedundantInstanceOf.contains(anchor) && isUsefulInstanceof(args, value, state)) {
|
||||
myPotentiallyRedundantInstanceOf.remove(anchor);
|
||||
if (myPotentiallyRedundantInstanceOf.contains(anchor)) {
|
||||
if (isUsefulInstanceof(args, value, state)) {
|
||||
myPotentiallyRedundantInstanceOf.remove(anchor);
|
||||
} else {
|
||||
myConstantInstanceOf.merge(anchor, fromDfType(state.getDfType(value)), ThreeState::merge);
|
||||
}
|
||||
}
|
||||
myConstantExpressions.compute(anchor, (c, curState) -> ConstantResult.mergeValue(curState, state, value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ThreeState fromDfType(DfType type) {
|
||||
return type.equals(DfTypes.TRUE) ? ThreeState.YES :
|
||||
type.equals(DfTypes.FALSE) ? ThreeState.NO : ThreeState.UNSURE;
|
||||
}
|
||||
|
||||
private static boolean isUsefulInstanceof(@NotNull DfaValue @NotNull [] args,
|
||||
@@ -436,24 +433,14 @@ final class DataFlowInstructionVisitor implements JavaDfaListener {
|
||||
}
|
||||
}
|
||||
|
||||
static class ArgResultEquality {
|
||||
final boolean argsEqual;
|
||||
final boolean firstArgEqualToResult;
|
||||
final boolean secondArgEqualToResult;
|
||||
|
||||
ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) {
|
||||
this.argsEqual = argsEqual;
|
||||
this.firstArgEqualToResult = firstArgEqualToResult;
|
||||
this.secondArgEqualToResult = secondArgEqualToResult;
|
||||
}
|
||||
|
||||
record ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) {
|
||||
ArgResultEquality merge(ArgResultEquality other) {
|
||||
return new ArgResultEquality(argsEqual && other.argsEqual, firstArgEqualToResult && other.firstArgEqualToResult,
|
||||
secondArgEqualToResult && other.secondArgEqualToResult);
|
||||
}
|
||||
return new ArgResultEquality(argsEqual && other.argsEqual, firstArgEqualToResult && other.firstArgEqualToResult,
|
||||
secondArgEqualToResult && other.secondArgEqualToResult);
|
||||
}
|
||||
|
||||
boolean hasEquality() {
|
||||
return argsEqual || firstArgEqualToResult || secondArgEqualToResult;
|
||||
boolean hasEquality() {
|
||||
return argsEqual || firstArgEqualToResult || secondArgEqualToResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.BoolUtils;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -51,6 +53,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.psi.CommonClassNames.*;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
|
||||
|
||||
public final class DfaPsiUtil {
|
||||
@@ -698,4 +701,78 @@ public final class DfaPsiUtil {
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param anchor boolean expression
|
||||
* @param expectedValue the expected result of boolean expression
|
||||
* @return true if this boolean expression is effectively an assertion (code throws if its value is not equal to expectedValue)
|
||||
*/
|
||||
public static boolean isAssertionEffectively(@NotNull PsiExpression anchor, boolean expectedValue) {
|
||||
PsiElement parent;
|
||||
while (true) {
|
||||
parent = anchor.getParent();
|
||||
if (parent instanceof PsiExpression parentExpr && BoolUtils.isNegation(parentExpr)) {
|
||||
expectedValue = !expectedValue;
|
||||
anchor = parentExpr;
|
||||
continue;
|
||||
}
|
||||
if (parent instanceof PsiParenthesizedExpression parenthesized) {
|
||||
anchor = parenthesized;
|
||||
continue;
|
||||
}
|
||||
if (parent instanceof PsiPolyadicExpression polyadic) {
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) {
|
||||
// always true operand makes always true OR-chain and does not affect the result of AND-chain
|
||||
// Note that in `assert unknownExpression && trueExpression;` the trueExpression should not be reported
|
||||
// because this assertion is essentially the shortened `assert unknownExpression; assert trueExpression;`
|
||||
// which is not reported.
|
||||
boolean causesShortCircuit = (tokenType.equals(JavaTokenType.OROR) == expectedValue) &&
|
||||
ArrayUtil.getLastElement(polyadic.getOperands()) != anchor;
|
||||
if (!causesShortCircuit) {
|
||||
// We still report `assert trueExpression || unknownExpression`, because here `unknownExpression` is never checked
|
||||
// which is probably not intended.
|
||||
anchor = polyadic;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parent instanceof PsiAssertStatement) {
|
||||
return expectedValue;
|
||||
}
|
||||
if (parent instanceof PsiIfStatement ifStatement && anchor == ifStatement.getCondition()) {
|
||||
PsiStatement thenBranch = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
|
||||
if (thenBranch instanceof PsiThrowStatement) {
|
||||
return !expectedValue;
|
||||
}
|
||||
}
|
||||
return isAssertCallArgument(anchor, ContractValue.booleanValue(expectedValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param anchor expression
|
||||
* @param wantedConstraint expected contract value
|
||||
* @return true if this expression is an argument for an assertion call that ensures that the expression is equal to the expected value
|
||||
*/
|
||||
public static boolean isAssertCallArgument(@NotNull PsiElement anchor, @NotNull ContractValue wantedConstraint) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent());
|
||||
if (parent instanceof PsiExpressionList) {
|
||||
int index = ArrayUtil.indexOf(((PsiExpressionList)parent).getExpressions(), anchor);
|
||||
if (index >= 0) {
|
||||
PsiMethodCallExpression call = tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (call != null) {
|
||||
MethodContract contract = ContainerUtil.getOnlyItem(JavaMethodContractUtil.getMethodCallContracts(call));
|
||||
if (contract != null && contract.getReturnValue().isFail()) {
|
||||
ContractValue condition = ContainerUtil.getOnlyItem(contract.getConditions());
|
||||
if (condition != null) {
|
||||
return condition.getArgumentComparedTo(wantedConstraint, false).orElse(-1) == index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,15 +251,19 @@ public final class DfaUtil {
|
||||
* @return a dataflow context; null if no applicable context found.
|
||||
*/
|
||||
static @Nullable PsiElement getDataflowContext(PsiExpression expression) {
|
||||
PsiMember member = PsiTreeUtil.getParentOfType(expression, PsiMember.class);
|
||||
while (member instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)member).getArgumentList(), expression, true)) {
|
||||
member = PsiTreeUtil.getParentOfType(member, PsiMember.class);
|
||||
PsiElement element = expression;
|
||||
while (true) {
|
||||
element = element.getParent();
|
||||
if (element == null || element instanceof PsiAnnotation) return null;
|
||||
if (element instanceof PsiMethod method && !method.isConstructor()) {
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass != null &&
|
||||
(!PsiUtil.isLocalOrAnonymousClass(containingClass) || containingClass instanceof PsiEnumConstantInitializer)) {
|
||||
return method.getBody();
|
||||
}
|
||||
}
|
||||
if (element instanceof PsiClass psiClass && !PsiUtil.isLocalOrAnonymousClass(psiClass)) return psiClass;
|
||||
}
|
||||
if (member instanceof PsiField || member instanceof PsiClassInitializer) return member.getContainingClass();
|
||||
if (member instanceof PsiMethod) {
|
||||
return ((PsiMethod)member).isConstructor() ? member.getContainingClass() : ((PsiMethod)member).getBody();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.Nullability;
|
||||
import com.intellij.codeInspection.dataFlow.java.CFGBuilder;
|
||||
import com.intellij.codeInspection.dataFlow.java.ControlFlowAnalyzer;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.problems.JvmDfaProblem;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -579,19 +580,19 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
return myFromUnknown;
|
||||
}
|
||||
|
||||
public boolean isAlwaysNull(@NotNull Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
public boolean isAlwaysNull(boolean ignoreAssertions) {
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(getDereferencedExpression());
|
||||
return expression != null &&
|
||||
(ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInspectionBase.ConstantResult.NULL);
|
||||
(ExpressionUtils.isNullLiteral(expression) || CommonDataflow.getDfType(expression, ignoreAssertions) == DfTypes.NULL);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public @InspectionMessage String getMessage(@NotNull Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
public @InspectionMessage String getMessage(boolean ignoreAssertions) {
|
||||
if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) {
|
||||
throw new IllegalStateException("This problem kind has no message associated: " + myKind);
|
||||
}
|
||||
String suffix = myFromUnknown ? JavaAnalysisBundle.message("dataflow.message.unknown.nullability") : "";
|
||||
Supplier<@Nls String> msg = isAlwaysNull(expressions) ? myKind.myAlwaysNullMessage : myKind.myNormalMessage;
|
||||
Supplier<@Nls String> msg = isAlwaysNull(ignoreAssertions) ? myKind.myAlwaysNullMessage : myKind.myNormalMessage;
|
||||
return msg.get() + suffix;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,13 +69,6 @@ public interface JavaDfaListener extends DfaListener {
|
||||
!PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), anchor, false)) {
|
||||
callBeforeExpressionPush(value, expression, state, (PsiConditionalExpression)parent);
|
||||
}
|
||||
else if (parent instanceof PsiPolyadicExpression) {
|
||||
PsiPolyadicExpression polyadic = (PsiPolyadicExpression)parent;
|
||||
if ((polyadic.getOperationTokenType().equals(JavaTokenType.ANDAND) || polyadic.getOperationTokenType().equals(JavaTokenType.OROR)) &&
|
||||
PsiTreeUtil.isAncestor(ArrayUtil.getLastElement(polyadic.getOperands()), anchor, false)) {
|
||||
callBeforeExpressionPush(value, expression, state, polyadic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
// Copyright 2000-2022 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.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFix;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow.DataflowResult;
|
||||
import com.intellij.codeInspection.dataFlow.fix.FindDfaProblemCauseFix;
|
||||
import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix;
|
||||
import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaDfaAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaMethodReferenceReturnAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaPolyadicPartAnchor;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.SpecialField;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.ui.InspectionOptionsPanel;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiFieldImpl;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.bugs.EqualsWithItselfInspection;
|
||||
import com.siyeh.ig.fixes.EqualsToEqualityFix;
|
||||
import com.siyeh.ig.numeric.ComparisonToNaNInspection;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.java.JavaBundle.message;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
public class ConstantValueInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance(ConstantValueInspection.class);
|
||||
public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS;
|
||||
public boolean IGNORE_ASSERT_STATEMENTS;
|
||||
public boolean REPORT_CONSTANT_REFERENCE_VALUES = true;
|
||||
|
||||
@Override
|
||||
public @Nullable JComponent createOptionsPanel() {
|
||||
InspectionOptionsPanel panel = new InspectionOptionsPanel();
|
||||
panel.addCheckbox(message("inspection.data.flow.true.asserts.option"),
|
||||
"DONT_REPORT_TRUE_ASSERT_STATEMENTS");
|
||||
panel.addCheckbox(message("inspection.data.flow.ignore.assert.statements"),
|
||||
"IGNORE_ASSERT_STATEMENTS");
|
||||
panel.addCheckbox(message("inspection.data.flow.warn.when.reading.a.value.guaranteed.to.be.constant"),
|
||||
"REPORT_CONSTANT_REFERENCE_VALUES");
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
visitExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitExpression(@NotNull PsiExpression expression) {
|
||||
if (expression instanceof PsiLiteralExpression) return;
|
||||
DataflowResult dfr = CommonDataflow.getDataflowResult(expression);
|
||||
if (dfr == null) return;
|
||||
JavaDfaAnchor anchor = expression instanceof PsiMethodReferenceExpression methodRef ?
|
||||
new JavaMethodReferenceReturnAnchor(methodRef) :
|
||||
new JavaExpressionAnchor(expression);
|
||||
processAnchor(dfr, anchor, holder);
|
||||
if (expression instanceof PsiPolyadicExpression polyadic) {
|
||||
PsiExpression[] operands = polyadic.getOperands();
|
||||
for (int i = 1; i < operands.length - 1; i++) {
|
||||
processAnchor(dfr, new JavaPolyadicPartAnchor(polyadic, i), holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIfStatement(@NotNull PsiIfStatement statement) {
|
||||
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition());
|
||||
if (BoolUtils.isBooleanLiteral(condition)) {
|
||||
LocalQuickFix fix = createSimplifyBooleanExpressionFix(condition, condition.textMatches(PsiKeyword.TRUE));
|
||||
holder.registerProblem(condition, JavaAnalysisBundle
|
||||
.message("dataflow.message.constant.no.ref", condition.textMatches(PsiKeyword.TRUE) ? 1 : 0), fix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDoWhileStatement(@NotNull PsiDoWhileStatement statement) {
|
||||
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition());
|
||||
if (condition != null && condition.textMatches(PsiKeyword.FALSE)) {
|
||||
holder.registerProblem(condition, JavaAnalysisBundle.message("dataflow.message.constant.no.ref", 0),
|
||||
createSimplifyBooleanExpressionFix(condition, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void processAnchor(@NotNull DataflowResult dfr,
|
||||
@NotNull JavaDfaAnchor anchor,
|
||||
@NotNull ProblemsHolder holder) {
|
||||
DfType dfType;
|
||||
if (IGNORE_ASSERT_STATEMENTS) {
|
||||
dfType = dfr.getDfTypeNoAssertions(anchor);
|
||||
} else {
|
||||
dfType = dfr.getDfType(anchor);
|
||||
}
|
||||
processAnchor(dfType, anchor, holder);
|
||||
}
|
||||
|
||||
private void processAnchor(@NotNull DfType dfType, @NotNull JavaDfaAnchor anchor, @NotNull ProblemsHolder reporter) {
|
||||
ConstantResult result = ConstantResult.fromDfType(dfType);
|
||||
if (result == ConstantResult.UNKNOWN && dfType instanceof DfReferenceType refType && refType.getSpecialField() == SpecialField.UNBOX) {
|
||||
result = ConstantResult.fromDfType(refType.getSpecialFieldType());
|
||||
}
|
||||
if (result == ConstantResult.UNKNOWN) return;
|
||||
Object value = result.value();
|
||||
if (anchor instanceof JavaPolyadicPartAnchor) {
|
||||
if (value instanceof Boolean) {
|
||||
// report rare cases like a == b == c where "a == b" part is constant
|
||||
String message = JavaAnalysisBundle.message("dataflow.message.constant.condition",
|
||||
((Boolean)value).booleanValue() ? 1 : 0);
|
||||
reporter.registerProblem(((JavaPolyadicPartAnchor)anchor).getExpression(),
|
||||
((JavaPolyadicPartAnchor)anchor).getTextRange(), message);
|
||||
// do not add to reported anchors if only part of expression was reported
|
||||
}
|
||||
}
|
||||
else if (anchor instanceof JavaExpressionAnchor) {
|
||||
PsiExpression expression = ((JavaExpressionAnchor)anchor).getExpression();
|
||||
if (isCondition(expression)) {
|
||||
if (value instanceof Boolean) {
|
||||
reportConstantBoolean(reporter, expression, (Boolean)value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportConstantReferenceValue(reporter, expression, result);
|
||||
}
|
||||
}
|
||||
else if (anchor instanceof JavaMethodReferenceReturnAnchor methodRefAnchor) {
|
||||
PsiMethodReferenceExpression methodRef = methodRefAnchor.getMethodReferenceExpression();
|
||||
PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class);
|
||||
if (method != null && JavaMethodContractUtil.isPure(method)) {
|
||||
List<StandardMethodContract> contracts = JavaMethodContractUtil.getMethodContracts(method);
|
||||
if (contracts.isEmpty() || !contracts.get(0).isTrivial()) {
|
||||
reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.constant.method.reference", value),
|
||||
new ReplaceWithTrivialLambdaFix(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldReportZero(PsiExpression ref) {
|
||||
if (ref instanceof PsiPolyadicExpression) {
|
||||
if (PsiUtil.isConstantExpression(ref)) return false;
|
||||
PsiPolyadicExpression polyadic = (PsiPolyadicExpression)ref;
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType.equals(JavaTokenType.ASTERISK)) {
|
||||
PsiMethod method = PsiTreeUtil.getParentOfType(ref, PsiMethod.class, true, PsiLambdaExpression.class, PsiClass.class);
|
||||
if (MethodUtils.isHashCode(method)) {
|
||||
// Standard hashCode template generates int result = 0; result = result * 31 + ...;
|
||||
// so annoying warnings might be produced there
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ref instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)ref;
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (PsiUtil.isConstantExpression(qualifier) &&
|
||||
ContainerUtil.and(call.getArgumentList().getExpressions(), PsiUtil::isConstantExpression)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (ref instanceof PsiTypeCastExpression) {
|
||||
PsiExpression operand = ((PsiTypeCastExpression)ref).getOperand();
|
||||
return operand != null && TypeConversionUtil.isFloatOrDoubleType(operand.getType());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
PsiBinaryExpression binOp = tryCast(parent, PsiBinaryExpression.class);
|
||||
return binOp == null || !ComparisonUtils.isEqualityComparison(binOp) ||
|
||||
(!ExpressionUtils.isZero(binOp.getLOperand()) && !ExpressionUtils.isZero(binOp.getROperand()));
|
||||
}
|
||||
|
||||
private void reportConstantReferenceValue(ProblemsHolder reporter, PsiExpression ref, ConstantResult constant) {
|
||||
if (!REPORT_CONSTANT_REFERENCE_VALUES && ref instanceof PsiReferenceExpression) return;
|
||||
if (shouldBeSuppressed(ref) || constant == ConstantResult.UNKNOWN) return;
|
||||
List<LocalQuickFix> fixes = new SmartList<>();
|
||||
String presentableName = constant.toString();
|
||||
if (Integer.valueOf(0).equals(constant.value()) && !shouldReportZero(ref)) return;
|
||||
if (constant.value() instanceof Boolean) {
|
||||
fixes.add(createSimplifyBooleanExpressionFix(ref, (Boolean)constant.value()));
|
||||
} else {
|
||||
fixes.add(new ReplaceWithConstantValueFix(presentableName, presentableName));
|
||||
}
|
||||
Object value = constant.value();
|
||||
boolean isAssertion = isAssertionEffectively(ref, constant);
|
||||
if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) return;
|
||||
if (value instanceof Boolean) {
|
||||
ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(ref, (Boolean)value));
|
||||
}
|
||||
if (reporter.isOnTheFly()) {
|
||||
if (ref instanceof PsiReferenceExpression) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "REPORT_CONSTANT_REFERENCE_VALUES",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"),
|
||||
false));
|
||||
}
|
||||
if (isAssertion) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
|
||||
}
|
||||
}
|
||||
ContainerUtil.addIfNotNull(fixes, new FindDfaProblemCauseFix(IGNORE_ASSERT_STATEMENTS, ref, new TrackingRunner.ValueDfaProblemType(value)));
|
||||
|
||||
ProblemHighlightType type;
|
||||
String message;
|
||||
if (ref instanceof PsiMethodCallExpression || ref instanceof PsiPolyadicExpression || ref instanceof PsiTypeCastExpression) {
|
||||
type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
message = JavaAnalysisBundle.message("dataflow.message.constant.expression", presentableName);
|
||||
}
|
||||
else {
|
||||
type = ProblemHighlightType.WEAK_WARNING;
|
||||
message = JavaAnalysisBundle.message("dataflow.message.constant.value", presentableName);
|
||||
}
|
||||
reporter.registerProblem(ref, message, type, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private static boolean isCoveredBySurroundingFix(PsiElement anchor, boolean evaluatesToTrue) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent());
|
||||
if (parent instanceof PsiPolyadicExpression) {
|
||||
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
|
||||
return tokenType.equals(JavaTokenType.ANDAND) && !evaluatesToTrue ||
|
||||
tokenType.equals(JavaTokenType.OROR) && evaluatesToTrue;
|
||||
}
|
||||
return parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent);
|
||||
}
|
||||
|
||||
private static boolean isCondition(@NotNull PsiExpression expression) {
|
||||
PsiType type = expression.getType();
|
||||
if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) return false;
|
||||
if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) return true;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
|
||||
if (parent instanceof PsiStatement) return !(parent instanceof PsiReturnStatement);
|
||||
if (parent instanceof PsiPolyadicExpression) {
|
||||
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
|
||||
return tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR) ||
|
||||
tokenType.equals(JavaTokenType.AND) || tokenType.equals(JavaTokenType.OR);
|
||||
}
|
||||
if (parent instanceof PsiConditionalExpression) {
|
||||
return PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expression, false);
|
||||
}
|
||||
return PsiUtil.isAccessedForWriting(expression);
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
private static boolean shouldBeSuppressed(PsiElement anchor) {
|
||||
if (!(anchor instanceof PsiExpression)) return false;
|
||||
// Don't report System.out.println(b = false) or doSomething((Type)null)
|
||||
if (anchor instanceof PsiAssignmentExpression ||
|
||||
(anchor instanceof PsiTypeCastExpression && !(((PsiTypeCastExpression)anchor).getType() instanceof PsiPrimitiveType))) {
|
||||
return true;
|
||||
}
|
||||
// For conditional the root cause (constant condition or both branches constant) should be already reported for branches
|
||||
if (anchor instanceof PsiConditionalExpression) return true;
|
||||
PsiExpression expression = (PsiExpression)anchor;
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
PsiReferenceExpression ref = (PsiReferenceExpression)expression;
|
||||
if ("TRUE".equals(ref.getReferenceName()) || "FALSE".equals(ref.getReferenceName())) {
|
||||
PsiElement target = ref.resolve();
|
||||
if (target instanceof PsiField) {
|
||||
PsiClass containingClass = ((PsiField)target).getContainingClass();
|
||||
if (containingClass != null && CommonClassNames.JAVA_LANG_BOOLEAN.equals(containingClass.getQualifiedName())) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression instanceof PsiBinaryExpression) {
|
||||
PsiExpression lOperand = ((PsiBinaryExpression)expression).getLOperand();
|
||||
PsiExpression rOperand = ((PsiBinaryExpression)expression).getROperand();
|
||||
IElementType tokenType = ((PsiBinaryExpression)expression).getOperationTokenType();
|
||||
// Suppress on type mismatch compilation errors
|
||||
if (rOperand == null) return true;
|
||||
PsiType lType = lOperand.getType();
|
||||
PsiType rType = rOperand.getType();
|
||||
if (lType == null || rType == null) return true;
|
||||
if (!TypeConversionUtil.isBinaryOperatorApplicable(tokenType, lType, rType, false)) return true;
|
||||
}
|
||||
if (expression instanceof PsiInstanceOfExpression) {
|
||||
PsiType type = ((PsiInstanceOfExpression)expression).getOperand().getType();
|
||||
if (type == null || !TypeConstraints.instanceOf(type).isResolved()) return true;
|
||||
PsiPattern pattern = ((PsiInstanceOfExpression)expression).getPattern();
|
||||
if (pattern instanceof PsiTypeTestPattern && ((PsiTypeTestPattern)pattern).getPatternVariable() != null) {
|
||||
PsiTypeElement checkType = ((PsiTypeTestPattern)pattern).getCheckType();
|
||||
if (checkType != null && checkType.getType().isAssignableFrom(type)) {
|
||||
// Reported as compilation error
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
|
||||
// Don't report "x" in "x == null" as will be anyway reported as "always true"
|
||||
if (parent instanceof PsiBinaryExpression && ExpressionUtils.getValueComparedWithNull((PsiBinaryExpression)parent) != null) return true;
|
||||
// Dereference of null will be covered by other warning
|
||||
if (ExpressionUtils.isVoidContext(expression) || isDereferenceContext(expression)) return true;
|
||||
// We assume all Void variables as null because you cannot instantiate it without dirty hacks
|
||||
// However reporting them as "always null" looks redundant (dereferences or comparisons will be reported though).
|
||||
if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_VOID, expression.getType())) return true;
|
||||
if (isFlagCheck(anchor)) return true;
|
||||
if (!isCondition(expression) && expression instanceof PsiMethodCallExpression) {
|
||||
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts((PsiCallExpression)expression);
|
||||
ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(contracts);
|
||||
if (value != null) return true;
|
||||
if (!(parent instanceof PsiAssignmentExpression) && !(parent instanceof PsiVariable) &&
|
||||
!(parent instanceof PsiReturnStatement)) {
|
||||
PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
|
||||
if (method == null || !JavaMethodContractUtil.isPure(method)) return true;
|
||||
}
|
||||
}
|
||||
while (expression != null && BoolUtils.isNegation(expression)) {
|
||||
expression = BoolUtils.getNegated(expression);
|
||||
}
|
||||
if (expression == null) return false;
|
||||
if (!isCondition(expression) && expression instanceof PsiReferenceExpression) {
|
||||
PsiVariable variable = tryCast(((PsiReferenceExpression)expression).resolve(), PsiVariable.class);
|
||||
if (variable instanceof PsiField &&
|
||||
variable.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
ExpressionUtils.isNullLiteral(PsiFieldImpl.getDetachedInitializer(variable))) {
|
||||
return true;
|
||||
}
|
||||
if (variable instanceof PsiLocalVariable && variable.hasInitializer()) {
|
||||
boolean effectivelyFinal = variable.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
!VariableAccessUtils.variableIsAssigned(variable, PsiUtil.getVariableCodeBlock(variable, null));
|
||||
return effectivelyFinal && PsiUtil.isConstantExpression(variable.getInitializer());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Avoid double reporting
|
||||
return expression instanceof PsiMethodCallExpression && EqualsWithItselfInspection.isEqualsWithItself((PsiMethodCallExpression)expression) ||
|
||||
expression instanceof PsiBinaryExpression && ComparisonToNaNInspection.extractNaNFromComparison((PsiBinaryExpression)expression) != null;
|
||||
}
|
||||
|
||||
private static boolean isDereferenceContext(PsiExpression ref) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
return parent instanceof PsiReferenceExpression || parent instanceof PsiArrayAccessExpression
|
||||
|| parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement;
|
||||
}
|
||||
|
||||
private static boolean isFlagCheck(PsiElement element) {
|
||||
PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class);
|
||||
PsiExpression topExpression = scope instanceof PsiIfStatement ? ((PsiIfStatement)scope).getCondition() :
|
||||
scope instanceof PsiVariable ? ((PsiVariable)scope).getInitializer() :
|
||||
null;
|
||||
if (!PsiTreeUtil.isAncestor(topExpression, element, false)) return false;
|
||||
|
||||
return StreamEx.<PsiElement>ofTree(topExpression, e -> StreamEx.of(e.getChildren()))
|
||||
.anyMatch(ConstantValueInspection::isCompileTimeFlagCheck);
|
||||
}
|
||||
|
||||
private static boolean isCompileTimeFlagCheck(PsiElement element) {
|
||||
if(element instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression binOp = (PsiBinaryExpression)element;
|
||||
if(ComparisonUtils.isComparisonOperation(binOp.getOperationTokenType())) {
|
||||
PsiExpression comparedWith = null;
|
||||
if(ExpressionUtils.isLiteral(binOp.getROperand())) {
|
||||
comparedWith = binOp.getLOperand();
|
||||
} else if(ExpressionUtils.isLiteral(binOp.getLOperand())) {
|
||||
comparedWith = binOp.getROperand();
|
||||
}
|
||||
comparedWith = PsiUtil.skipParenthesizedExprDown(comparedWith);
|
||||
if (isConstantOfType(comparedWith, PsiType.INT, PsiType.LONG)) {
|
||||
// like "if(DEBUG_LEVEL > 2)"
|
||||
return true;
|
||||
}
|
||||
if(comparedWith instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression subOp = (PsiBinaryExpression)comparedWith;
|
||||
if(subOp.getOperationTokenType().equals(JavaTokenType.AND)) {
|
||||
PsiExpression left = PsiUtil.skipParenthesizedExprDown(subOp.getLOperand());
|
||||
PsiExpression right = PsiUtil.skipParenthesizedExprDown(subOp.getROperand());
|
||||
if(isConstantOfType(left, PsiType.INT, PsiType.LONG) ||
|
||||
isConstantOfType(right, PsiType.INT, PsiType.LONG)) {
|
||||
// like "if((FLAGS & SOME_FLAG) != 0)"
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// like "if(DEBUG)"
|
||||
return isConstantOfType(element, PsiType.BOOLEAN);
|
||||
}
|
||||
|
||||
private static boolean isConstantOfType(PsiElement element, PsiPrimitiveType... types) {
|
||||
PsiElement resolved = element instanceof PsiReferenceExpression ? ((PsiReferenceExpression)element).resolve() : null;
|
||||
if (!(resolved instanceof PsiField)) return false;
|
||||
PsiField field = (PsiField)resolved;
|
||||
PsiModifierList modifierList = field.getModifierList();
|
||||
if (modifierList == null ||
|
||||
!modifierList.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
!modifierList.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return false;
|
||||
}
|
||||
if (!ArrayUtil.contains(field.getType(), types)) return false;
|
||||
return field.hasInitializer() && PsiUtil.isConstantExpression(field.getInitializer());
|
||||
}
|
||||
|
||||
private static boolean isAtRHSOfBooleanAnd(PsiElement expr) {
|
||||
PsiElement cur = expr;
|
||||
|
||||
while (cur != null && !(cur instanceof PsiMember)) {
|
||||
PsiElement parent = cur.getParent();
|
||||
|
||||
if (parent instanceof PsiBinaryExpression && cur == ((PsiBinaryExpression)parent).getROperand()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cur = parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reportConstantBoolean(ProblemsHolder reporter, PsiElement psiAnchor, boolean evaluatesToTrue) {
|
||||
while (psiAnchor instanceof PsiParenthesizedExpression parenthesized) {
|
||||
psiAnchor = parenthesized.getExpression();
|
||||
}
|
||||
if (psiAnchor == null || shouldBeSuppressed(psiAnchor)) return;
|
||||
boolean isAssertion = psiAnchor instanceof PsiExpression expr && DfaPsiUtil.isAssertionEffectively(expr, evaluatesToTrue);
|
||||
if (DONT_REPORT_TRUE_ASSERT_STATEMENTS && isAssertion) return;
|
||||
|
||||
if (PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()) instanceof PsiAssignmentExpression assignment &&
|
||||
PsiTreeUtil.isAncestor(assignment.getLExpression(), psiAnchor, false)) {
|
||||
reporter.registerProblem(
|
||||
psiAnchor,
|
||||
JavaAnalysisBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)),
|
||||
createConditionalAssignmentFixes(evaluatesToTrue, assignment, reporter.isOnTheFly())
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
List<LocalQuickFix> fixes = new ArrayList<>();
|
||||
if (!isCoveredBySurroundingFix(psiAnchor, evaluatesToTrue)) {
|
||||
ContainerUtil.addIfNotNull(fixes, createSimplifyBooleanExpressionFix(psiAnchor, evaluatesToTrue));
|
||||
if (isAssertion && reporter.isOnTheFly()) {
|
||||
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
|
||||
JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
|
||||
}
|
||||
ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(psiAnchor, evaluatesToTrue));
|
||||
}
|
||||
if (psiAnchor instanceof PsiExpression) {
|
||||
ContainerUtil.addIfNotNull(fixes, new FindDfaProblemCauseFix(IGNORE_ASSERT_STATEMENTS,
|
||||
(PsiExpression)psiAnchor, new TrackingRunner.ValueDfaProblemType(evaluatesToTrue)));
|
||||
}
|
||||
String message = JavaAnalysisBundle.message(isAtRHSOfBooleanAnd(psiAnchor) ?
|
||||
"dataflow.message.constant.condition.when.reached" :
|
||||
"dataflow.message.constant.condition", evaluatesToTrue ? 1 : 0);
|
||||
reporter.registerProblem(psiAnchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private @Nullable LocalQuickFix createSimplifyBooleanExpressionFix(PsiElement element, final boolean value) {
|
||||
LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(element, value);
|
||||
if (fix == null) return null;
|
||||
final String text = fix.getText();
|
||||
return new LocalQuickFix() {
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement psiElement = descriptor.getPsiElement();
|
||||
if (psiElement == null) return;
|
||||
final LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(psiElement, value);
|
||||
if (fix == null) return;
|
||||
try {
|
||||
LOG.assertTrue(psiElement.isValid());
|
||||
fix.applyFix();
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return JavaAnalysisBundle.message("inspection.data.flow.simplify.boolean.expression.quickfix");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) {
|
||||
if (evaluatesToTrue) return null;
|
||||
if (!(psiAnchor instanceof PsiMethodCallExpression)) return null;
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)psiAnchor;
|
||||
if (!MethodCallUtils.isEqualsCall(methodCallExpression)) return null;
|
||||
PsiExpression arg = ArrayUtil.getFirstElement(methodCallExpression.getArgumentList().getExpressions());
|
||||
if (!ExpressionUtils.isNullLiteral(arg)) return null;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent());
|
||||
return EqualsToEqualityFix.buildFix(methodCallExpression, parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent));
|
||||
}
|
||||
|
||||
private static LocalQuickFix[] createConditionalAssignmentFixes(boolean evaluatesToTrue,
|
||||
PsiAssignmentExpression assignment,
|
||||
final boolean onTheFly) {
|
||||
IElementType op = assignment.getOperationTokenType();
|
||||
boolean toRemove = op == JavaTokenType.ANDEQ && !evaluatesToTrue || op == JavaTokenType.OREQ && evaluatesToTrue;
|
||||
if (toRemove && !onTheFly) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
}
|
||||
return new LocalQuickFix[]{toRemove ? new RemoveAssignmentFix() : new SimplifyToAssignmentFix()};
|
||||
}
|
||||
|
||||
protected LocalQuickFixOnPsiElement createSimplifyBooleanFix(PsiElement element, boolean value) {
|
||||
if (!(element instanceof PsiExpression)) return null;
|
||||
if (PsiTreeUtil.findChildOfType(element, PsiAssignmentExpression.class) != null) return null;
|
||||
|
||||
final PsiExpression expression = (PsiExpression)element;
|
||||
while (element.getParent() instanceof PsiExpression) {
|
||||
element = element.getParent();
|
||||
}
|
||||
final SimplifyBooleanExpressionFix fix = new SimplifyBooleanExpressionFix(expression, value);
|
||||
// simplify intention already active
|
||||
if (!fix.isAvailable() || SimplifyBooleanExpressionFix.canBeSimplified((PsiExpression)element)) return null;
|
||||
return fix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param anchor expression
|
||||
* @param result the expected value of expression
|
||||
* @return true if this expression is effectively an assertion (code throws if its value is not equal to expectedValue)
|
||||
*/
|
||||
private static boolean isAssertionEffectively(@NotNull PsiExpression anchor, ConstantResult result) {
|
||||
Object value = result.value();
|
||||
if (value instanceof Boolean) {
|
||||
return DfaPsiUtil.isAssertionEffectively(anchor, (Boolean)value);
|
||||
}
|
||||
if (value != null) return false;
|
||||
return DfaPsiUtil.isAssertCallArgument(anchor, ContractValue.nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Values that especially tracked by data flow inspection
|
||||
*/
|
||||
private enum ConstantResult {
|
||||
TRUE, FALSE, NULL, ZERO, UNKNOWN;
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return this == ZERO ? "0" : StringUtil.toLowerCase(name());
|
||||
}
|
||||
|
||||
public Object value() {
|
||||
return switch (this) {
|
||||
case TRUE -> Boolean.TRUE;
|
||||
case FALSE -> Boolean.FALSE;
|
||||
case ZERO -> 0;
|
||||
case NULL -> null;
|
||||
default -> throw new UnsupportedOperationException();
|
||||
};
|
||||
}
|
||||
|
||||
public static @NotNull ConstantResult fromDfType(@NotNull DfType dfType) {
|
||||
if (dfType == DfTypes.NULL) return NULL;
|
||||
if (dfType == DfTypes.TRUE) return TRUE;
|
||||
if (dfType == DfTypes.FALSE) return FALSE;
|
||||
if (dfType.isConst(0) || dfType.isConst(0L)) return ZERO;
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2022 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.ex.InspectionElementsMergerBase;
|
||||
import com.intellij.openapi.util.JDOMExternalizerUtil;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ConstantValueInspectionMerger extends InspectionElementsMergerBase {
|
||||
@Override
|
||||
public @NotNull String getMergedToolName() {
|
||||
return "ConstantValue";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNls String @NotNull [] getSourceToolNames() {
|
||||
return new String[] {"ConstantConditions"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Element transformElement(@NotNull String sourceToolName, @NotNull Element sourceElement, @NotNull Element toolElement) {
|
||||
ConstantValueInspection inspection = new ConstantValueInspection();
|
||||
inspection.DONT_REPORT_TRUE_ASSERT_STATEMENTS =
|
||||
Boolean.parseBoolean(JDOMExternalizerUtil.readField(sourceElement, "DONT_REPORT_TRUE_ASSERT_STATEMENTS", "false"));
|
||||
inspection.IGNORE_ASSERT_STATEMENTS =
|
||||
Boolean.parseBoolean(JDOMExternalizerUtil.readField(sourceElement, "IGNORE_ASSERT_STATEMENTS", "false"));
|
||||
inspection.REPORT_CONSTANT_REFERENCE_VALUES =
|
||||
Boolean.parseBoolean(JDOMExternalizerUtil.readField(sourceElement, "REPORT_CONSTANT_REFERENCE_VALUES", "true"));
|
||||
inspection.writeSettings(toolElement);
|
||||
return toolElement;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package com.intellij.codeInspection.dataFlow;
|
||||
import com.intellij.codeInsight.NullableNotNullDialog;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteSideEffectsAwareFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.UnwrapSwitchLabelFix;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.dataFlow.fix.BoxPrimitiveInTernaryFix;
|
||||
@@ -18,9 +17,7 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiPrecedenceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
@@ -52,26 +49,11 @@ import static javax.swing.SwingConstants.TOP;
|
||||
public class DataFlowInspection extends DataFlowInspectionBase {
|
||||
private static final Logger LOG = Logger.getInstance(DataFlowInspection.class);
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix[] createConditionalAssignmentFixes(boolean evaluatesToTrue, PsiAssignmentExpression assignment, final boolean onTheFly) {
|
||||
IElementType op = assignment.getOperationTokenType();
|
||||
boolean toRemove = op == JavaTokenType.ANDEQ && !evaluatesToTrue || op == JavaTokenType.OREQ && evaluatesToTrue;
|
||||
if (toRemove && !onTheFly) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
}
|
||||
return new LocalQuickFix[]{toRemove ? new RemoveAssignmentFix() : createSimplifyToAssignmentFix()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent createOptionsPanel() {
|
||||
return new OptionsPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix createReplaceWithTrivialLambdaFix(Object value) {
|
||||
return new ReplaceWithTrivialLambdaFix(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix createMutabilityViolationFix(PsiElement violation, boolean onTheFly) {
|
||||
return WrapWithMutableCollectionFix.createFix(violation, onTheFly);
|
||||
@@ -94,21 +76,6 @@ public class DataFlowInspection extends DataFlowInspectionBase {
|
||||
return new IntroduceVariableFix(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFixOnPsiElement createSimplifyBooleanFix(PsiElement element, boolean value) {
|
||||
if (!(element instanceof PsiExpression)) return null;
|
||||
if (PsiTreeUtil.findChildOfType(element, PsiAssignmentExpression.class) != null) return null;
|
||||
|
||||
final PsiExpression expression = (PsiExpression)element;
|
||||
while (element.getParent() instanceof PsiExpression) {
|
||||
element = element.getParent();
|
||||
}
|
||||
final SimplifyBooleanExpressionFix fix = new SimplifyBooleanExpressionFix(expression, value);
|
||||
// simplify intention already active
|
||||
if (!fix.isAvailable() || SimplifyBooleanExpressionFix.canBeSimplified((PsiExpression)element)) return null;
|
||||
return fix;
|
||||
}
|
||||
|
||||
private static boolean isVolatileFieldReference(PsiExpression qualifier) {
|
||||
PsiElement target = qualifier instanceof PsiReferenceExpression ? ((PsiReferenceExpression)qualifier).resolve() : null;
|
||||
return target instanceof PsiField && ((PsiField)target).hasModifierProperty(PsiModifier.VOLATILE);
|
||||
@@ -277,18 +244,10 @@ public class DataFlowInspection extends DataFlowInspectionBase {
|
||||
message("inspection.data.flow.report.nullable.methods.that.always.return.a.non.null.value"),
|
||||
"REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL");
|
||||
|
||||
JCheckBox dontReportTrueAsserts = createCheckBoxWithHTML(
|
||||
message("inspection.data.flow.true.asserts.option"),
|
||||
"DONT_REPORT_TRUE_ASSERT_STATEMENTS");
|
||||
|
||||
JCheckBox ignoreAssertions = createCheckBoxWithHTML(
|
||||
message("inspection.data.flow.ignore.assert.statements"),
|
||||
"IGNORE_ASSERT_STATEMENTS");
|
||||
|
||||
JCheckBox reportConstantReferences = createCheckBoxWithHTML(
|
||||
message("inspection.data.flow.warn.when.reading.a.value.guaranteed.to.be.constant"),
|
||||
"REPORT_CONSTANT_REFERENCE_VALUES");
|
||||
|
||||
JCheckBox reportUnsoundWarnings = createCheckBoxWithHTML(
|
||||
message("inspection.data.flow.report.problems.that.happen.only.on.some.code.paths"),
|
||||
"REPORT_UNSOUND_WARNINGS");
|
||||
@@ -316,15 +275,9 @@ public class DataFlowInspection extends DataFlowInspectionBase {
|
||||
gc.gridy++;
|
||||
add(reportNullableMethodsReturningNotNull, gc);
|
||||
|
||||
gc.gridy++;
|
||||
add(dontReportTrueAsserts, gc);
|
||||
|
||||
gc.gridy++;
|
||||
add(ignoreAssertions, gc);
|
||||
|
||||
gc.gridy++;
|
||||
add(reportConstantReferences, gc);
|
||||
|
||||
gc.gridy++;
|
||||
add(reportUnsoundWarnings, gc);
|
||||
}
|
||||
|
||||
@@ -1793,6 +1793,10 @@
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="ConstantConditions" bundle="messages.JavaBundle" key="inspection.data.flow.display.name"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.dataFlow.DataFlowInspection"/>
|
||||
<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"/>
|
||||
<inspectionElementsMerger implementation="com.intellij.codeInspection.dataFlow.ConstantValueInspectionMerger" />
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="Java9UndeclaredServiceUsage"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.visibility.issues"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.intellij.codeInsight.Nullability;
|
||||
import com.intellij.codeInsight.NullabilityAnnotationInfo;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationFix;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspectionBase;
|
||||
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
|
||||
import com.intellij.codeInspection.dataFlow.DfaUtil;
|
||||
import com.intellij.codeInspection.dataFlow.NullabilityUtil;
|
||||
import com.intellij.codeInspection.dataFlow.inference.JavaSourceInference;
|
||||
@@ -394,7 +394,7 @@ public class NullityInferrer {
|
||||
opposite = lOperand;
|
||||
}
|
||||
if (opposite != null && opposite.getType() == PsiType.NULL) {
|
||||
if (DataFlowInspectionBase.isAssertionEffectively(binOp, binOp.getOperationTokenType() == JavaTokenType.NE)) {
|
||||
if (DfaPsiUtil.isAssertionEffectively(binOp, binOp.getOperationTokenType() == JavaTokenType.NE)) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports code constructs that always produce the same result, may throw exceptions, or violates nullability contracts.
|
||||
Reports code constructs that always violate nullability contracts, may throw exceptions, or just redundant, based on data flow analysis.
|
||||
<p>Examples:</p>
|
||||
<pre><code>if (array.length < index) {
|
||||
System.out.println(array[index]);
|
||||
@@ -34,13 +34,9 @@ Integer square(@Nullable Integer input) {
|
||||
null (e.g. immediately dereferenced in the method body), but there are call sites where a <code>null</code> literal is passed.</li>
|
||||
<li>Use the <b>Report nullable methods that always return a non-null value</b> option to report methods that are annotated as
|
||||
<code>@Nullable</code>, but always return non-null value. In this case, it's suggested that you change the annotation to <code>@NotNull</code>.</li>
|
||||
<li>Use the <b>Don't report assertions with condition statically proven to be always true</b> option to avoid reporting assertions that were
|
||||
statically proven to be always true. This also includes conditions like <code>if (alwaysFalseCondition) throw new IllegalArgumentException();</code>.</li>
|
||||
<li>Use the <b>Ignore assert statements</b> option to control how the inspection treats <code>assert</code> statements. By default, the option
|
||||
is disabled, which means that the assertions are assumed to be executed (-ea mode). If the option is enabled, the assertions will be completely ignored
|
||||
(-da mode).</li>
|
||||
<li>Use the <b>Warn when reading a value guaranteed to be constant</b> option to add warnings on reading variables that contain some constant values,
|
||||
for example: <code>true</code>, <code>false</code>, or <code>null</code>.</li>
|
||||
<li>Use the <b>Report problems that happen only on some code paths</b> option to control whether to report problems that may happen only
|
||||
on some code path. If this option is disabled, warnings like <i>exception is possible</i> will not be reported. The inspection will report
|
||||
only warnings like <i>exception will definitely occur</i>. This mode may greatly reduce the number of false-positives, especially if the code
|
||||
@@ -48,6 +44,9 @@ Integer square(@Nullable Integer input) {
|
||||
important problems in legacy code bases.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Before IntelliJ IDEA 2022.3, this inspection was part of "Constant Conditions & Exceptions" inspection. Now, it split into two inspections:
|
||||
"Constant Values" and "Nullability and data flow problems".
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
37
java/java-impl/src/inspectionDescriptions/ConstantValue.html
Normal file
37
java/java-impl/src/inspectionDescriptions/ConstantValue.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports expressions and conditions that always produce the same result, like true, false, null, or zero.
|
||||
Such expressions could be replaced with the corresponding constant value. Very often though they signal about a bug
|
||||
in the code.
|
||||
<p>Examples:</p>
|
||||
<pre><code> // always true
|
||||
// root cause: || is used instead of &&
|
||||
if (x > 0 || x < 10) {}
|
||||
|
||||
System.out.println(str.trim());
|
||||
// always false
|
||||
// root cause: variable was dereferenced before null-check
|
||||
if (str == null) {}
|
||||
</code></pre>
|
||||
<p>
|
||||
The inspection behavior may be controlled by a number of annotations, such as
|
||||
<a href="https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html">nullability</a> annotations,
|
||||
<code><a href="https://www.jetbrains.com/help/idea/contract-annotations.html">@Contract</a></code> annotation,
|
||||
<code>@Range</code> annotation and so on.
|
||||
</p>
|
||||
<!-- tooltip end -->
|
||||
<p>Configure the inspection:</p>
|
||||
<ul>
|
||||
<li>Use the <b>Don't report assertions with condition statically proven to be always true</b> option to avoid reporting assertions that were
|
||||
statically proven to be always true. This also includes conditions like <code>if (alwaysFalseCondition) throw new IllegalArgumentException();</code>.</li>
|
||||
<li>Use the <b>Ignore assert statements</b> option to control how the inspection treats <code>assert</code> statements. By default, the option
|
||||
is disabled, which means that the assertions are assumed to be executed (-ea mode). If the option is enabled, the assertions will be completely ignored
|
||||
(-da mode).</li>
|
||||
<li>Use the <b>Warn when constant is stored in variable</b> option to display warnings when variable is used, whose value is known to be a constant.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Before IntelliJ IDEA 2022.3, this inspection was part of "Constant Conditions & Exceptions" inspection. Now, it split into two inspections:
|
||||
"Constant Values" and "Nullability and data flow problems".
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +1,7 @@
|
||||
// "Assert 'container != null'" "true-preview"
|
||||
class A{
|
||||
void test(){
|
||||
Integer container = null;
|
||||
Integer container = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = 0;
|
||||
assert container != null;
|
||||
for (int limit = container.intValue(); i < limit; i++){}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// "Assert 'container != null'" "true-preview"
|
||||
class A{
|
||||
void test(){
|
||||
Integer container = null;
|
||||
Integer container = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = 0;
|
||||
for (int limit = container.int<caret>Value(); i < limit; i++){}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Nullability and data flow problems' problems in file" "true"
|
||||
class Main {
|
||||
void t() {
|
||||
int i = 5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Nullability and data flow problems' problems in file" "true"
|
||||
class Main {
|
||||
void t() {
|
||||
int i = 5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo1() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo2() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo2() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo1() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo2() {
|
||||
int k = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
public class Test {
|
||||
void foo2() {
|
||||
int k = 0;
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.lang.Integer;
|
||||
|
||||
class A{
|
||||
void test(){
|
||||
Integer integer = null;
|
||||
Integer integer = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = integer != null ? integer.toString().length() : 0<caret>;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import java.lang.Integer;
|
||||
|
||||
class A{
|
||||
void test(){
|
||||
Integer integer = null;
|
||||
Integer integer = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = integer != null ? integer.intValue() : 0<caret>;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import java.lang.Integer;
|
||||
|
||||
class A{
|
||||
void test(){
|
||||
Integer integer = null;
|
||||
Integer integer = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = integer.to<caret>String().length();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import java.lang.Integer;
|
||||
|
||||
class A{
|
||||
void test(){
|
||||
Integer integer = null;
|
||||
Integer integer = Math.random() > 0.5 ? null : 1.0;
|
||||
int i = integer.int<caret>Value();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -11,7 +11,7 @@ public class MethodReferenceConstantValue {
|
||||
}
|
||||
|
||||
public void test(Optional<String> opt) {
|
||||
X x = MethodReferenceConstantValue::strangeMethod;
|
||||
X x = (methodReferenceConstantValue, s1) -> false;
|
||||
Boolean aBoolean = opt.map(s -> false)
|
||||
.map(o1 -> true)
|
||||
.map(o -> false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Fix all 'Constant conditions & exceptions' problems in file" "true"
|
||||
// "Fix all 'Constant values' problems in file" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class Example {
|
||||
System.out.println("null");
|
||||
}
|
||||
|
||||
@NotNull Class x = <warning descr="'null' is assigned to a variable that is annotated with @NotNull">ClassUtils.primitiveToWrapper(null)</warning>;
|
||||
@NotNull Class x = <warning descr="'null' is assigned to a variable that is annotated with @NotNull"><warning descr="Result of 'ClassUtils.primitiveToWrapper(null)' is always 'null'">ClassUtils.primitiveToWrapper(null)</warning></warning>;
|
||||
}
|
||||
|
||||
void writeBytes(@Nullable byte[] bytes, FilterOutputStream stream) throws IOException {
|
||||
|
||||
@@ -21,7 +21,7 @@ public class MethodReferenceConstantValue {
|
||||
}
|
||||
|
||||
public void test(Optional<String> opt) {
|
||||
X x = <warning descr="Method reference invocation 'MethodReferenceConstantValue::strangeMethod' may produce 'NullPointerException'">MethodReferenceConstantValue::strangeMethod</warning>;
|
||||
X x = <warning descr="Method reference invocation 'MethodReferenceConstantValue::strangeMethod' may produce 'NullPointerException'"><warning descr="Method reference result is always 'false'">MethodReferenceConstantValue::strangeMethod</warning></warning>;
|
||||
Boolean aBoolean = opt.map(<warning descr="Method reference result is always 'false'">this::strangeMethod</warning>)
|
||||
.map(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>)
|
||||
.map(<warning descr="Method reference result is always 'false'">Objects::isNull</warning>)
|
||||
|
||||
@@ -24,14 +24,14 @@ class Test {
|
||||
|
||||
void parens() {
|
||||
Object x = null;
|
||||
doNotNull(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">x</warning>);
|
||||
doNotNull(<warning descr="Passing 'null' argument to parameter annotated as @NotNull"><weak_warning descr="Value 'x' is always 'null'">x</weak_warning></warning>);
|
||||
x = null;
|
||||
doNotNull((<warning descr="Passing 'null' argument to parameter annotated as @NotNull">x</warning>));
|
||||
doNotNull((<warning descr="Passing 'null' argument to parameter annotated as @NotNull"><weak_warning descr="Value 'x' is always 'null'">x</weak_warning></warning>));
|
||||
}
|
||||
|
||||
@NotNull Object testReturn(Object x1, Object x2) {
|
||||
if(x1 == null) return <warning descr="'null' is returned by the method declared as @NotNull">x1</warning>;
|
||||
if(x2 == null) return (<warning descr="'null' is returned by the method declared as @NotNull">x2</warning>);
|
||||
if(x1 == null) return <warning descr="'null' is returned by the method declared as @NotNull"><weak_warning descr="Value 'x1' is always 'null'">x1</weak_warning></warning>;
|
||||
if(x2 == null) return (<warning descr="'null' is returned by the method declared as @NotNull"><weak_warning descr="Value 'x2' is always 'null'">x2</weak_warning></warning>);
|
||||
return new Object();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ class Test {
|
||||
if (foo == null) {
|
||||
println(<weak_warning descr="Value 'foo' is always 'null'"><caret>foo</weak_warning>);
|
||||
println(<weak_warning descr="Value 'foo' is always 'null'">foo</weak_warning>);
|
||||
return <warning descr="'null' is returned by the method which is not declared as @Nullable">foo</warning>;
|
||||
return <warning descr="'null' is returned by the method which is not declared as @Nullable"><weak_warning descr="Value 'foo' is always 'null'">foo</weak_warning></warning>;
|
||||
}
|
||||
if (bar == null) {
|
||||
test2(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">bar</warning>);
|
||||
test2(<warning descr="Passing 'null' argument to parameter annotated as @NotNull"><weak_warning descr="Value 'bar' is always 'null'">bar</weak_warning></warning>);
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class Test {
|
||||
void noSuppress(int x) {
|
||||
if (<warning descr="Condition 'x < 0 && x > 20' is always 'false'">x < 0 && <warning descr="Condition 'x > 20' is always 'false' when reached">x > 20</warning></warning>) {}
|
||||
Object s = "23";
|
||||
System.out.println(((<warning descr="Casting 's' to 'Number' will produce 'ClassCastException' for any non-null value">Number</warning>)s).byteValue());
|
||||
}
|
||||
|
||||
void suppressOld(int x) {
|
||||
//noinspection ConstantConditions
|
||||
if (x < 0 && x > 20) {}
|
||||
Object s = "23";
|
||||
//noinspection ConstantConditions
|
||||
System.out.println(((Number)s).byteValue());
|
||||
}
|
||||
|
||||
void suppressNew(int x) {
|
||||
//noinspection ConstantValue
|
||||
if (x < 0 && x > 20) {}
|
||||
Object s = "23";
|
||||
//noinspection ConstantConditions
|
||||
System.out.println(((Number)s).byteValue());
|
||||
}
|
||||
|
||||
void wrongSuppress(int x) {
|
||||
//noinspection ConstantValue
|
||||
if (x < 0 && x > 20) {}
|
||||
Object s = "23";
|
||||
//noinspection ConstantValue
|
||||
System.out.println(((<warning descr="Casting 's' to 'Number' will produce 'ClassCastException' for any non-null value">Number</warning>)s).byteValue());
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class XorNullity {
|
||||
}
|
||||
|
||||
if(createForm.getOpenIdIdentity() != null) {
|
||||
findByOpenIdIdentity(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">createForm.getOpenIdProvider()</warning>); // nullable
|
||||
findByOpenIdIdentity(<warning descr="Passing 'null' argument to parameter annotated as @NotNull"><warning descr="Result of 'createForm.getOpenIdProvider()' is always 'null'">createForm.getOpenIdProvider()</warning></warning>); // nullable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
|
||||
import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
|
||||
@@ -25,7 +26,7 @@ public class FixAllQuickfixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{
|
||||
new DataFlowInspection(),
|
||||
new ConstantValueInspection(),
|
||||
new UnnecessaryFullyQualifiedNameInspection(),
|
||||
new MyLocalInspectionTool()
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
@@ -11,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class ReplaceWithConstantValueFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new DataFlowInspection()};
|
||||
return new LocalInspectionTool[]{new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -4,13 +4,13 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReplaceWithNullCheckFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new DataFlowInspection()};
|
||||
return new LocalInspectionTool[]{new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,13 +4,14 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReplaceWithTrivialLambdaFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new DataFlowInspection()};
|
||||
return new LocalInspectionTool[]{new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -10,9 +11,7 @@ public class SimplifyBooleanExpressionFixTest extends LightQuickFixParameterized
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
DataFlowInspection inspection = new DataFlowInspection();
|
||||
inspection.SUGGEST_NULLABLE_ANNOTATIONS = true;
|
||||
return new LocalInspectionTool[]{inspection};
|
||||
return new LocalInspectionTool[]{new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
@@ -12,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class UnwrapIfStatementFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new DataFlowInspection()};
|
||||
return new LocalInspectionTool[]{new DataFlowInspection(), new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.java.codeInspection;
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -36,7 +37,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
public void testMethodReferenceOnNullable() { doTest(); }
|
||||
public void testObjectsNonNullWithUnknownNullable() {
|
||||
setupTypeUseAnnotations("typeUse", myFixture);
|
||||
doTestWith(insp -> insp.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
doTestWith((insp, __) -> insp.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
}
|
||||
public void testNullableVoidLambda() { doTest(); }
|
||||
public void testNullableForeachVariable() { doTestWithCustomAnnotations(); }
|
||||
@@ -108,7 +109,9 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
setupCustomAnnotations();
|
||||
DataFlowInspection inspection = new DataFlowInspection();
|
||||
inspection.IGNORE_ASSERT_STATEMENTS = true;
|
||||
myFixture.enableInspections(inspection);
|
||||
ConstantValueInspection cvInspection = new ConstantValueInspection();
|
||||
cvInspection.IGNORE_ASSERT_STATEMENTS = true;
|
||||
myFixture.enableInspections(inspection, cvInspection);
|
||||
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
@@ -216,7 +219,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
public void testStreamReduceLogicalAnd() { doTest(); }
|
||||
public void testStreamSingleElementReduce() { doTest(); }
|
||||
public void testRequireNonNullMethodRef() {
|
||||
doTestWith(dfa -> dfa.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((dfa, __) -> dfa.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
}
|
||||
|
||||
public void testMapGetWithValueNullability() { doTestWithCustomAnnotations(); }
|
||||
@@ -360,9 +363,9 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
myFixture.addClass("package org.jetbrains.annotations;\nimport java.lang.annotation.*;\n" +
|
||||
"@Target(ElementType.TYPE_USE)\n" +
|
||||
"public @interface UnknownNullability { }");
|
||||
doTestWith(insp -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false);
|
||||
doTestWith((insp, __) -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false);
|
||||
}
|
||||
public void testReturnOrElseNull() { doTestWith(insp -> insp.REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true); }
|
||||
public void testReturnOrElseNull() { doTestWith((insp, __) -> insp.REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true); }
|
||||
public void testArrayIntersectionType() { doTest(); }
|
||||
public void testFunctionType() { doTest(); }
|
||||
public void testIteratorHasNextModifiesPrivateField() { doTest(); }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
@@ -21,8 +22,9 @@ public class DataFlowInspectionAncientTest extends LightJavaCodeInsightFixtureTe
|
||||
|
||||
private void doTest() {
|
||||
DataFlowInspection inspection = new DataFlowInspection();
|
||||
inspection.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
myFixture.enableInspections(inspection);
|
||||
ConstantValueInspection cvInspection = new ConstantValueInspection();
|
||||
cvInspection.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
myFixture.enableInspections(inspection, cvInspection);
|
||||
myFixture.testHighlighting(getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.java.codeInspection
|
||||
|
||||
import com.intellij.JavaTestUtil
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection
|
||||
import com.intellij.openapi.module.StdModuleTypes
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
@@ -69,7 +70,7 @@ class DataFlowInspectionHeavyTest extends JavaCodeInsightFixtureTestCase {
|
||||
}
|
||||
'''
|
||||
myFixture.configureFromExistingVirtualFile(testFile.virtualFile)
|
||||
myFixture.enableInspections(new DataFlowInspection())
|
||||
myFixture.enableInspections(new ConstantValueInspection())
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.java.codeInspection;
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -135,25 +136,25 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
|
||||
public void testPassingNullableIntoVararg() { doTest(); }
|
||||
public void testEqualsImpliesNotNull() {
|
||||
doTestWith(i -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((i, __) -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
}
|
||||
public void testEffectivelyUnqualified() { doTest(); }
|
||||
|
||||
public void testQualifierEquality() { doTest(); }
|
||||
|
||||
public void testSkipAssertions() {
|
||||
doTestWith(i -> {
|
||||
doTestWith((__, i) -> {
|
||||
i.DONT_REPORT_TRUE_ASSERT_STATEMENTS = true;
|
||||
i.REPORT_CONSTANT_REFERENCE_VALUES = true;
|
||||
});
|
||||
}
|
||||
|
||||
public void testParanoidMode() {
|
||||
doTestWith(i -> i.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
doTestWith((i, __) -> i.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
}
|
||||
|
||||
public void testReportConstantReferences() {
|
||||
doTestWith(i -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((i, __) -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
String hint = "Replace with 'null'";
|
||||
checkIntentionResult(hint);
|
||||
}
|
||||
@@ -165,12 +166,12 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
}
|
||||
|
||||
public void testReportConstantReferences_OverloadedCall() {
|
||||
doTestWith(i -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((i, __) -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
checkIntentionResult("Replace with 'null'");
|
||||
}
|
||||
|
||||
public void testReportConstantReferencesAfterFinalFieldAccess() {
|
||||
doTestWith(i -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((i, __) -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
}
|
||||
|
||||
public void testCheckFieldInitializers() {
|
||||
@@ -204,9 +205,9 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testFinalFieldInConstructorAnonymous() { doTest(); }
|
||||
|
||||
public void testFinalFieldNotDuringInitialization() {
|
||||
doTestWith(i -> {
|
||||
doTestWith((i, cv) -> {
|
||||
i.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true;
|
||||
i.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
cv.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,7 +231,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testHonorGetterAnnotation() { doTest(); }
|
||||
|
||||
public void testIgnoreAssertions() {
|
||||
doTestWith(i -> i.IGNORE_ASSERT_STATEMENTS = true);
|
||||
doTestWith((__, i) -> i.IGNORE_ASSERT_STATEMENTS = true);
|
||||
}
|
||||
|
||||
public void testContractAnnotation() { doTest(); }
|
||||
@@ -350,7 +351,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
|
||||
public void testNullabilityDefaultVsMethodImplementing() {
|
||||
addJavaxDefaultNullabilityAnnotations(myFixture);
|
||||
doTestWith(i -> i.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
doTestWith((i, __) -> i.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
|
||||
}
|
||||
|
||||
public void testTypeQualifierNickname() {
|
||||
@@ -412,7 +413,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
myFixture.addClass("package foo; public class AnotherPackageNotNull { public static native Object foo(String s); }");
|
||||
myFixture.addFileToProject("foo/package-info.java", "@bar.MethodsAreNotNullByDefault package foo;");
|
||||
|
||||
myFixture.enableInspections(new DataFlowInspection());
|
||||
myFixture.enableInspections(new DataFlowInspection(), new ConstantValueInspection());
|
||||
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
@@ -427,7 +428,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
myFixture.addFileToProject("foo/package-info.java", "@NonnullByDefault package foo;");
|
||||
|
||||
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject(getTestName(false) + ".java", "foo/Classes.java"));
|
||||
myFixture.enableInspections(new DataFlowInspection());
|
||||
myFixture.enableInspections(new DataFlowInspection(), new ConstantValueInspection());
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
}
|
||||
|
||||
@@ -471,7 +472,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
}
|
||||
public void testLiteralDoWhileConditionWithBreak() {
|
||||
doTest();
|
||||
assertFalse(myFixture.getAvailableIntentions().stream().anyMatch(i -> i.getText().contains("Unwrap 'do-while' statement")));
|
||||
assertFalse(ContainerUtil.exists(myFixture.getAvailableIntentions(), i -> i.getText().contains("Unwrap 'do-while' statement")));
|
||||
}
|
||||
|
||||
public void testFalseForConditionNoInitialization() {
|
||||
@@ -520,7 +521,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testNullableMethodReturningNotNull() { doTest(); }
|
||||
|
||||
public void testDivisionByZero() {
|
||||
doTestWith(i -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
doTestWith((i, __) -> i.SUGGEST_NULLABLE_ANNOTATIONS = true);
|
||||
}
|
||||
|
||||
public void testFieldUsedBeforeInitialization() { doTest(); }
|
||||
@@ -607,12 +608,12 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testEqualsInLoopNotTooComplex() { doTest(); }
|
||||
public void testEqualsWithItself() { doTest(); }
|
||||
public void testBoxingBoolean() {
|
||||
doTestWith(i -> i.REPORT_CONSTANT_REFERENCE_VALUES = true);
|
||||
doTestWith((__, i) -> i.REPORT_CONSTANT_REFERENCE_VALUES = true);
|
||||
}
|
||||
public void testOrWithAssignment() { doTest(); }
|
||||
public void testAndAndLastOperand() { doTest(); }
|
||||
public void testReportAlwaysNull() {
|
||||
doTestWith(i -> i.REPORT_CONSTANT_REFERENCE_VALUES = true);
|
||||
doTestWith((__, i) -> i.REPORT_CONSTANT_REFERENCE_VALUES = true);
|
||||
}
|
||||
|
||||
public void testBoxUnboxArrayElement() { doTest(); }
|
||||
@@ -701,7 +702,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testFieldUpdateViaSetter() { doTest(); }
|
||||
public void testInitArrayInConstructor() { doTest(); }
|
||||
public void testGetterNullityAfterCheck() { doTest(); }
|
||||
public void testInferenceNullityMismatch() { doTestWith(insp -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false); }
|
||||
public void testInferenceNullityMismatch() { doTestWith((insp, __) -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false); }
|
||||
public void testFieldInInstanceInitializer() { doTest(); }
|
||||
public void testNullableCallWithPrecalculatedValueAndSpecialField() { doTest(); }
|
||||
public void testJoinConstantAndSubtype() { doTest(); }
|
||||
@@ -709,7 +710,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testArrayInitializerElementRewritten() { doTest(); }
|
||||
public void testFinallyEphemeralNpe() { doTest(); }
|
||||
public void testTypeParameterAsSuperClass() { doTest(); }
|
||||
public void testSuppressConstantBooleans() { doTestWith(insp -> insp.REPORT_CONSTANT_REFERENCE_VALUES = true); }
|
||||
public void testSuppressConstantBooleans() { doTestWith((__, insp) -> insp.REPORT_CONSTANT_REFERENCE_VALUES = true); }
|
||||
public void testTempVarsInContracts() { doTest(); }
|
||||
public void testNestedUnrolledLoopNotComplex() { doTest(); }
|
||||
public void testEnumOrdinal() { doTest(); }
|
||||
@@ -722,4 +723,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testBoxingInArrayDeclaration() { doTest(); }
|
||||
public void testNestedVersusSuper() { doTest(); }
|
||||
public void testChangeFieldUsedInPureMethod() { doTest(); }
|
||||
public void testSuppression() { doTest(); }
|
||||
}
|
||||
|
||||
@@ -15,27 +15,31 @@
|
||||
*/
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class DataFlowInspectionTestCase extends LightJavaCodeInsightFixtureTestCase {
|
||||
protected void doTest() {
|
||||
doTestWith(i -> {
|
||||
i.SUGGEST_NULLABLE_ANNOTATIONS = true;
|
||||
i.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
doTestWith((df, cv) -> {
|
||||
df.SUGGEST_NULLABLE_ANNOTATIONS = true;
|
||||
cv.REPORT_CONSTANT_REFERENCE_VALUES = false;
|
||||
});
|
||||
}
|
||||
|
||||
protected void doTestWith(Consumer<? super DataFlowInspection> inspectionMutator) {
|
||||
protected void doTestWith(BiConsumer<DataFlowInspection, ConstantValueInspection> inspectionMutator) {
|
||||
DataFlowInspection inspection = new DataFlowInspection();
|
||||
inspectionMutator.accept(inspection);
|
||||
myFixture.enableInspections(inspection);
|
||||
ConstantValueInspection cvInspection = new ConstantValueInspection();
|
||||
inspectionMutator.accept(inspection, cvInspection);
|
||||
myFixture.enableInspections(inspection, cvInspection);
|
||||
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
public void assertIntentionAvailable(String intentionName) {
|
||||
assertTrue(myFixture.getAvailableIntentions().stream().anyMatch(action -> action.getText().equals(intentionName)));
|
||||
assertTrue(ContainerUtil.exists(myFixture.getAvailableIntentions(), action -> action.getText().equals(intentionName)));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -37,7 +38,7 @@ public class HardcodedContractsTest extends DataFlowInspectionTestCase {
|
||||
|
||||
|
||||
private void checkHighlighting() {
|
||||
myFixture.enableInspections(new DataFlowInspection());
|
||||
myFixture.enableInspections(new DataFlowInspection(), new ConstantValueInspection());
|
||||
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.analysis.AnalysisScope;
|
||||
import com.intellij.codeInspection.InspectionManager;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.codeInspection.ex.*;
|
||||
import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase;
|
||||
@@ -71,69 +72,69 @@ public class InspectionResultExportTest extends LightJava9ModulesCodeInsightFixt
|
||||
ProgressManager.getInstance().runProcess(() -> context.launchInspectionsOffline(scope, outputPath, false, resultFiles), new ProgressWindow(false, getProject()));
|
||||
assertSize(2, resultFiles);
|
||||
|
||||
Element dfaResults = resultFiles.stream().filter(f -> f.getFileName().toString().equals("ConstantConditions.xml")).findAny().map(InspectionResultExportTest::loadFile).orElseThrow(AssertionError::new);
|
||||
Element dfaResults = resultFiles.stream().filter(f -> f.getFileName().toString().equals("ConstantValue.xml")).findAny().map(InspectionResultExportTest::loadFile).orElseThrow(AssertionError::new);
|
||||
Element unnCondResults = resultFiles.stream().filter(f -> f.getFileName().toString().equals("SimplifiableConditionalExpression.xml")).findAny().map(InspectionResultExportTest::loadFile).orElseThrow(AssertionError::new);
|
||||
|
||||
Element expectedDfaResults = JDOMUtil.load("<problems>" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>6</line>\n" +
|
||||
" <problem_class>Constant conditions & exceptions</problem_class>\n" +
|
||||
" <problem_class>Constant values</problem_class>\n" +
|
||||
" <description>Condition <code>0 == 0</code> is always <code>true</code></description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>7</line>\n" +
|
||||
" <problem_class>Constant conditions & exceptions</problem_class>\n" +
|
||||
" <problem_class>Constant values</problem_class>\n" +
|
||||
" <description>Condition <code>0 == 0</code> is always <code>true</code></description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>8</line>\n" +
|
||||
" <problem_class>Constant conditions & exceptions</problem_class>\n" +
|
||||
" <problem_class>Constant values</problem_class>\n" +
|
||||
" <description>Condition <code>0 == 0</code> is always <code>true</code></description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>4</line>\n" +
|
||||
" <problem_class>Constant conditions & exceptions</problem_class>\n" +
|
||||
" <problem_class>Constant values</problem_class>\n" +
|
||||
" <description>Condition <code>0 == 0</code> is always <code>true</code></description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>5</line>\n" +
|
||||
" <problem_class>Constant conditions & exceptions</problem_class>\n" +
|
||||
" <problem_class>Constant values</problem_class>\n" +
|
||||
" <description>Condition <code>0 == 0</code> is always <code>true</code></description>\n" +
|
||||
"</problem>" +
|
||||
"</problems>");
|
||||
Element expectedUnnCondResults = JDOMUtil.load("<problems><problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>4</line>\n" +
|
||||
" <problem_class>Redundant conditional expression</problem_class>\n" +
|
||||
" <problem_class>Simplifiable conditional expression</problem_class>\n" +
|
||||
" <description><code>0 == 0 ? 0 : 0</code> can be simplified to '0' #loc</description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>5</line>\n" +
|
||||
" <problem_class>Redundant conditional expression</problem_class>\n" +
|
||||
" <problem_class>Simplifiable conditional expression</problem_class>\n" +
|
||||
" <description><code>0 == 0 ? 0 : 0</code> can be simplified to '0' #loc</description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>6</line>\n" +
|
||||
" <problem_class>Redundant conditional expression</problem_class>\n" +
|
||||
" <problem_class>Simplifiable conditional expression</problem_class>\n" +
|
||||
" <description><code>0 == 0 ? 0 : 0</code> can be simplified to '0' #loc</description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>7</line>\n" +
|
||||
" <problem_class>Redundant conditional expression</problem_class>\n" +
|
||||
" <problem_class>Simplifiable conditional expression</problem_class>\n" +
|
||||
" <description><code>0 == 0 ? 0 : 0</code> can be simplified to '0' #loc</description>\n" +
|
||||
"</problem>\n" +
|
||||
"<problem>\n" +
|
||||
" <file>Foo.java</file>\n" +
|
||||
" <line>8</line>\n" +
|
||||
" <problem_class>Redundant conditional expression</problem_class>\n" +
|
||||
" <problem_class>Simplifiable conditional expression</problem_class>\n" +
|
||||
" <description><code>0 == 0 ? 0 : 0</code> can be simplified to '0' #loc</description>\n" +
|
||||
"</problem></problems>");
|
||||
|
||||
@@ -156,6 +157,6 @@ public class InspectionResultExportTest extends LightJava9ModulesCodeInsightFixt
|
||||
}
|
||||
|
||||
private static @NotNull List<InspectionToolWrapper<?, ?>> getTools() {
|
||||
return Arrays.asList(new LocalInspectionToolWrapper(new DataFlowInspection()), new LocalInspectionToolWrapper(new SimplifiableConditionalExpressionInspection()));
|
||||
return Arrays.asList(new LocalInspectionToolWrapper(new ConstantValueInspection()), new LocalInspectionToolWrapper(new SimplifiableConditionalExpressionInspection()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,7 @@ public class JSpecifyAnnotationOldTest extends LightJavaCodeInsightFixtureTestCa
|
||||
|
||||
@Override
|
||||
protected void reportNullabilityProblems(ProblemReporter reporter,
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, ConstantResult> expressions) {
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems) {
|
||||
for (NullabilityProblemKind.NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
if (expression != null) {
|
||||
|
||||
@@ -219,8 +219,7 @@ public class JSpecifyAnnotationTest extends LightJavaCodeInsightFixtureTestCase
|
||||
|
||||
@Override
|
||||
protected void reportNullabilityProblems(DataFlowInspectionBase.ProblemReporter reporter,
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems) {
|
||||
for (NullabilityProblemKind.NullabilityProblem<?> problem : problems) {
|
||||
String warning = getJSpecifyWarning(problem);
|
||||
if (warning != null) {
|
||||
|
||||
@@ -379,12 +379,13 @@ inspection.conditional.break.in.infinite.loop.no.conversion.with.do.while=Don't
|
||||
inspection.conditional.break.in.infinite.loop.allow.condition.fusion=Allow merging with existing loop condition
|
||||
inspection.conditional.break.in.infinite.loop.suggest.conversion.when.if.is.single.stmt.in.loop=Suggest conversion when 'if' is a single statement in loop
|
||||
inspection.convert.to.local.quickfix=Convert to local
|
||||
inspection.data.flow.display.name=Constant conditions \\& exceptions
|
||||
inspection.data.flow.display.name=Nullability and data flow problems
|
||||
inspection.data.flow.constant.values.display.name=Constant values
|
||||
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 <code>true</code>
|
||||
inspection.data.flow.ignore.assert.statements=Ignore assert statements
|
||||
inspection.data.flow.warn.when.reading.a.value.guaranteed.to.be.constant=Warn when reading a value guaranteed to be constant
|
||||
inspection.data.flow.warn.when.reading.a.value.guaranteed.to.be.constant=Warn when constant is stored in variable
|
||||
inspection.data.flow.treat.non.annotated.members.and.parameters.as.nullable=Treat non-annotated members and parameters as @Nullable
|
||||
inspection.data.flow.report.not.null.required.parameter.with.null.literal.argument.usages=Report not-null required parameter with null-literal argument usages
|
||||
inspection.data.flow.report.nullable.methods.that.always.return.a.non.null.value=Report nullable methods that always return a non-null value
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.plushnikov.intellij.plugin.inspection;
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
|
||||
|
||||
@@ -13,7 +14,7 @@ public class DataFlowInspectionTest extends LombokInspectionTest {
|
||||
|
||||
@Override
|
||||
protected InspectionProfileEntry getInspection() {
|
||||
return new DataFlowInspection();
|
||||
return new ConstantValueInspection();
|
||||
}
|
||||
|
||||
public void testDefaultBuilderFinalValueInspectionIsAlwaysThat() {
|
||||
|
||||
Reference in New Issue
Block a user