diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java
index 3b58de19b10c..1472cd8b3b0c 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java
@@ -555,11 +555,11 @@ public class GuessManagerImpl extends GuessManager {
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
- if (myForPlace == instruction.getExpression()) {
+ if (myForPlace == instruction.getPlace()) {
addToResult(((ExpressionTypeMemoryState)memState).getStates());
}
DfaInstructionState[] states = super.visitPush(instruction, runner, memState);
- if (myForPlace == instruction.getExpression()) {
+ if (myForPlace == instruction.getPlace()) {
addConstraints(states);
}
return states;
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java
index e9c854bd7213..d2b872f612b0 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java
@@ -125,21 +125,6 @@ public class CFGBuilder {
return add(new PushInstruction(value, null));
}
- /**
- * Generate instructions to push given DfaValue on stack and bind it to given expression.
- *
- * Stack before: ...
- *
- * Stack after: ... value
- *
- * @param value value to push
- * @param expression expression which result is being pushed
- * @return this builder
- */
- public CFGBuilder push(DfaValue value, PsiExpression expression) {
- return add(new PushInstruction(value, expression));
- }
-
/**
* Generate instructions to pop single DfaValue from stack
*
@@ -207,16 +192,6 @@ public class CFGBuilder {
return add(new ObjectOfInstruction());
}
- /**
- * Generate instructions to bind top-of-stack value to the given expression. Stack remains unchanged.
- *
- * @param expression expression to bind top-of-stack value to
- * @return this builder
- */
- public CFGBuilder resultOf(PsiExpression expression) {
- return add(new ResultOfInstruction(expression));
- }
-
/**
* Generate instructions to perform an Class.isInstance operation
*
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java
index 88334d5bac9b..8d4f91402088 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java
@@ -1,13 +1,17 @@
// Copyright 2000-2018 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.instructions.EndOfInitializerInstruction;
+import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
-import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
-import com.intellij.psi.util.*;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.util.PsiModificationTracker;
+import com.intellij.psi.util.PsiUtil;
import com.intellij.util.JavaPsiConstructorUtil;
+import com.intellij.util.ObjectUtils;
+import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -38,12 +42,6 @@ public class CommonDataflow {
newMap = newMap.with(DfaFactType.CAN_BE_NULL, false);
}
myFacts.put(expression, existing == null ? newMap : existing.union(newMap));
-
- PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
- if (parent instanceof PsiConditionalExpression &&
- !PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expression, false)) {
- add((PsiExpression)parent, memState, value);
- }
}
}
@@ -93,7 +91,7 @@ public class CommonDataflow {
private static DataflowResult runDFA(@Nullable PsiElement block) {
if (block == null) return null;
DataFlowRunner runner = new DataFlowRunner(false, block);
- CommonDataflowVisitor visitor = new CommonDataflowVisitor();
+ CommonDataflowVisitor visitor = new CommonDataflowVisitor(runner);
RunnerResult result = runner.analyzeMethodRecursively(block, visitor);
if (result != RunnerResult.OK) return null;
if (!(block instanceof PsiClass)) return visitor.myResult;
@@ -148,9 +146,15 @@ public class CommonDataflow {
}
private static class CommonDataflowVisitor extends StandardInstructionVisitor {
- private DataflowResult myResult = new DataflowResult();
+ private DataflowResult myResult;
+ private final DfaConstValue myFail;
private final List myEndOfInitializerStates = new ArrayList<>();
+ public CommonDataflowVisitor(DataFlowRunner runner) {
+ myFail = runner.getFactory().getConstFactory().getContractFail();
+ myResult = new DataflowResult();
+ }
+
@Override
public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction instruction,
DataFlowRunner runner,
@@ -162,14 +166,76 @@ public class CommonDataflow {
}
@Override
- protected void beforeExpressionPush(@NotNull DfaValue value,
- @NotNull PsiExpression expression,
- @Nullable TextRange range,
- @NotNull DfaMemoryState state) {
- if (range == null && !DfaConstValue.isContractFail(value)) {
- // Do not track instructions which cover part of expression
- myResult.add(expression, (DfaMemoryStateImpl)state, value);
+ public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ DfaInstructionState[] states = super.visitPush(instruction, runner, memState);
+ PsiExpression place = instruction.getPlace();
+ if (place != null && !instruction.isReferenceWrite()) {
+ for (DfaInstructionState state : states) {
+ DfaMemoryState afterState = state.getMemoryState();
+ myResult.add(place, (DfaMemoryStateImpl)afterState, instruction.getValue());
+ }
}
+ return states;
+ }
+
+ @Override
+ public DfaInstructionState[] visitArrayAccess(ArrayAccessInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ DfaInstructionState[] states = super.visitArrayAccess(instruction, runner, memState);
+ PsiArrayAccessExpression anchor = instruction.getExpression();
+ for (DfaInstructionState state : states) {
+ DfaMemoryState afterState = state.getMemoryState();
+ myResult.add(anchor, (DfaMemoryStateImpl)afterState, afterState.peek());
+ }
+ return states;
+ }
+
+ @Override
+ public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ DfaInstructionState[] states = super.visitBinop(instruction, runner, memState);
+ PsiElement anchor = instruction.getPsiAnchor();
+ if (anchor instanceof PsiExpression) {
+ for (DfaInstructionState state : states) {
+ DfaMemoryState afterState = state.getMemoryState();
+ myResult.add((PsiExpression)anchor, (DfaMemoryStateImpl)afterState, afterState.peek());
+ }
+ }
+ return states;
+ }
+
+ @NotNull
+ @Override
+ protected DfaCallArguments popCall(MethodCallInstruction instruction,
+ DataFlowRunner runner,
+ DfaMemoryState memState,
+ boolean contractOnly) {
+ DfaCallArguments arguments = super.popCall(instruction, runner, memState, contractOnly);
+ PsiElement context = instruction.getContext();
+ if (instruction.getMethodType() == MethodCallInstruction.MethodType.REGULAR_METHOD_CALL &&
+ context instanceof PsiMethodCallExpression) {
+ PsiExpression qualifier =
+ PsiUtil.skipParenthesizedExprDown(((PsiMethodCallExpression)context).getMethodExpression().getQualifierExpression());
+ if (qualifier != null) {
+ myResult.add(qualifier, (DfaMemoryStateImpl)memState, arguments.myQualifier);
+ }
+ }
+ return arguments;
+ }
+
+ @Override
+ public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction,
+ DataFlowRunner runner,
+ DfaMemoryState memState) {
+ DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
+ PsiExpression context = ObjectUtils.tryCast(instruction.getContext(), PsiExpression.class);
+ if (context != null && ExpressionUtils.getCallForQualifier(context) == null) {
+ for (DfaInstructionState state : states) {
+ DfaValue value = state.getMemoryState().peek();
+ if (value != myFail) {
+ myResult.add(context, (DfaMemoryStateImpl)state.getMemoryState(), value);
+ }
+ }
+ }
+ return states;
}
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java
index 327eb928f44b..0add795dda55 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java
@@ -3,7 +3,8 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInspection.dataFlow.StandardMethodContract.ValueConstraint;
-import com.intellij.codeInspection.dataFlow.instructions.ControlTransferInstruction;
+import com.intellij.codeInspection.dataFlow.instructions.CheckReturnValueInstruction;
+import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
import com.intellij.codeInspection.dataFlow.instructions.ReturnInstruction;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
@@ -25,111 +26,32 @@ import java.util.Set;
/**
* @author peter
*/
-class ContractChecker {
- private static class ContractCheckerVisitor extends StandardInstructionVisitor {
- private final PsiMethod myMethod;
- private final StandardMethodContract myContract;
- private final boolean myOwnContract;
- private final Set myViolations = ContainerUtil.newHashSet();
- private final Set myNonViolations = ContainerUtil.newHashSet();
- private final Set myFailures = ContainerUtil.newHashSet();
- private boolean myMayReturnNormally = false;
+class ContractChecker extends DataFlowRunner {
+ private final PsiMethod myMethod;
+ private final StandardMethodContract myContract;
+ private final boolean myOwnContract;
+ private final Set myViolations = ContainerUtil.newHashSet();
+ private final Set myNonViolations = ContainerUtil.newHashSet();
+ private final Set myFailures = ContainerUtil.newHashSet();
+ private boolean myMayReturnNormally = false;
- ContractCheckerVisitor(PsiMethod method, StandardMethodContract contract, boolean ownContract) {
- myMethod = method;
- myContract = contract;
- myOwnContract = ownContract;
- }
-
- @Override
- protected void checkReturnValue(@NotNull DfaValue value,
- @NotNull PsiExpression expression,
- @NotNull PsiParameterListOwner context,
- @NotNull DfaMemoryState state) {
- if (context != myMethod || state.isEphemeral()) return;
- if (!myContract.getReturnValue().isValueCompatible(state, value)) {
- myViolations.add(expression);
- } else {
- myNonViolations.add(expression);
- }
- }
-
- @Override
- public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction,
- DataFlowRunner runner,
- DfaMemoryState memState) {
- if (!memState.isEphemeral() && instruction.getMethodType() == MethodCallInstruction.MethodType.REGULAR_METHOD_CALL) {
- if (myContract.getReturnValue().isFail()) {
- ContainerUtil.addIfNotNull(myFailures, instruction.getCallExpression());
- return DfaInstructionState.EMPTY_ARRAY;
- }
- if (weCannotInferAnythingAboutMethodReturnValue(instruction)) {
- DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
- for (DfaInstructionState state: states) {
- state.getMemoryState().markEphemeral();
- }
- return states;
- }
- }
- return super.visitMethodCall(instruction, runner, memState);
- }
-
- @NotNull
- @Override
- public DfaInstructionState[] visitControlTransfer(@NotNull ControlTransferInstruction instruction,
- @NotNull DataFlowRunner runner,
- @NotNull DfaMemoryState state) {
- if (!state.isEphemeral()) {
- if (instruction instanceof ReturnInstruction && ((ReturnInstruction)instruction).isViaException()) {
- ContainerUtil.addIfNotNull(myFailures, ((ReturnInstruction)instruction).getAnchor());
- }
- else {
- myMayReturnNormally = true;
- }
- }
- return super.visitControlTransfer(instruction, runner, state);
- }
-
- private Map getErrors() {
- HashMap errors = ContainerUtil.newHashMap();
- for (PsiElement element : myViolations) {
- if (!myNonViolations.contains(element)) {
- errors.put(element, "Contract clause '" + myContract + "' is violated");
- }
- }
-
- if (!myContract.getReturnValue().isFail()) {
- if (myOwnContract && !myMayReturnNormally &&
- !(PsiUtil.canBeOverridden(myMethod) && ControlFlowUtils.methodAlwaysThrowsException(myMethod))) {
- for (PsiElement element : myFailures) {
- errors.put(element, "Return value of clause '" + myContract + "' could be replaced with 'fail' as method always fails"+
- (myContract.isTrivial() ? "" : " in this case"));
- }
- }
- } else if (myFailures.isEmpty() && errors.isEmpty()) {
- PsiIdentifier nameIdentifier = myMethod.getNameIdentifier();
- errors.put(nameIdentifier != null ? nameIdentifier : myMethod,
- "Contract clause '" + myContract + "' is violated: no exception is thrown");
- }
-
- return errors;
- }
-
- private static boolean weCannotInferAnythingAboutMethodReturnValue(MethodCallInstruction instruction) {
- PsiMethod target = instruction.getTargetMethod();
- return instruction.getContracts().isEmpty() && target != null && !target.isConstructor() && !NullableNotNullManager.isNotNull(target);
- }
+ private ContractChecker(PsiMethod method, StandardMethodContract contract, boolean ownContract) {
+ super(false, null);
+ myMethod = method;
+ myContract = contract;
+ myOwnContract = ownContract;
}
static Map checkContractClause(PsiMethod method, StandardMethodContract contract, boolean ownContract) {
+
PsiCodeBlock body = method.getBody();
if (body == null) return Collections.emptyMap();
- DataFlowRunner runner = new StandardDataFlowRunner(false, null);
+ ContractChecker checker = new ContractChecker(method, contract, ownContract);
PsiParameter[] parameters = method.getParameterList().getParameters();
- final DfaMemoryState initialState = runner.createMemoryState();
- final DfaValueFactory factory = runner.getFactory();
+ final DfaMemoryState initialState = checker.createMemoryState();
+ final DfaValueFactory factory = checker.getFactory();
for (int i = 0; i < contract.getParameterCount(); i++) {
ValueConstraint constraint = contract.getParameterConstraint(i);
DfaConstValue comparisonValue = constraint.getComparisonValue(factory);
@@ -140,8 +62,89 @@ class ContractChecker {
}
}
- ContractCheckerVisitor visitor = new ContractCheckerVisitor(method, contract, ownContract);
- runner.analyzeMethod(body, visitor, false, Collections.singletonList(initialState));
- return visitor.getErrors();
+ checker.analyzeMethod(body, new StandardInstructionVisitor(), false, Collections.singletonList(initialState));
+ return checker.getErrors();
+ }
+
+ @NotNull
+ @Override
+ protected DfaInstructionState[] acceptInstruction(@NotNull InstructionVisitor visitor, @NotNull DfaInstructionState instructionState) {
+ DfaMemoryState memState = instructionState.getMemoryState();
+ if (memState.isEphemeral()) {
+ return super.acceptInstruction(visitor, instructionState);
+ }
+ Instruction instruction = instructionState.getInstruction();
+ if (instruction instanceof CheckReturnValueInstruction) {
+ PsiElement anchor = ((CheckReturnValueInstruction)instruction).getReturn();
+ DfaValue retValue = memState.pop();
+ if (!myContract.getReturnValue().isValueCompatible(memState, retValue)) {
+ myViolations.add(anchor);
+ } else {
+ myNonViolations.add(anchor);
+ }
+ return InstructionVisitor.nextInstruction(instruction, this, memState);
+
+ }
+
+ if (instruction instanceof ReturnInstruction) {
+ if (((ReturnInstruction)instruction).isViaException()) {
+ ContainerUtil.addIfNotNull(myFailures, ((ReturnInstruction)instruction).getAnchor());
+ } else {
+ myMayReturnNormally = true;
+ }
+ }
+
+ if (instruction instanceof MethodCallInstruction &&
+ ((MethodCallInstruction)instruction).getMethodType() == MethodCallInstruction.MethodType.REGULAR_METHOD_CALL) {
+ if (myContract.getReturnValue().isFail()) {
+ ContainerUtil.addIfNotNull(myFailures, ((MethodCallInstruction)instruction).getCallExpression());
+ return DfaInstructionState.EMPTY_ARRAY;
+ }
+ if (weCannotInferAnythingAboutMethodReturnValue((MethodCallInstruction)instruction)) {
+ return markEverythingEphemeral(visitor, instructionState);
+ }
+ }
+
+ return super.acceptInstruction(visitor, instructionState);
+ }
+
+ private static boolean weCannotInferAnythingAboutMethodReturnValue(MethodCallInstruction instruction) {
+ PsiMethod target = instruction.getTargetMethod();
+ return instruction.getContracts().isEmpty() && target != null && !target.isConstructor() && !NullableNotNullManager.isNotNull(target);
+ }
+
+ @NotNull
+ private DfaInstructionState[] markEverythingEphemeral(@NotNull InstructionVisitor visitor,
+ @NotNull DfaInstructionState instructionState) {
+ DfaInstructionState[] result = super.acceptInstruction(visitor, instructionState);
+ for (DfaInstructionState state : result) {
+ state.getMemoryState().markEphemeral();
+ }
+ return result;
+ }
+
+ private Map getErrors() {
+ HashMap errors = ContainerUtil.newHashMap();
+ for (PsiElement element : myViolations) {
+ if (!myNonViolations.contains(element)) {
+ errors.put(element, "Contract clause '" + myContract + "' is violated");
+ }
+ }
+
+ if (!myContract.getReturnValue().isFail()) {
+ if (myOwnContract && !myMayReturnNormally &&
+ !(PsiUtil.canBeOverridden(myMethod) && ControlFlowUtils.methodAlwaysThrowsException(myMethod))) {
+ for (PsiElement element : myFailures) {
+ errors.put(element, "Return value of clause '" + myContract + "' could be replaced with 'fail' as method always fails"+
+ (myContract.isTrivial() ? "" : " in this case"));
+ }
+ }
+ } else if (myFailures.isEmpty() && errors.isEmpty()) {
+ PsiIdentifier nameIdentifier = myMethod.getNameIdentifier();
+ errors.put(nameIdentifier != null ? nameIdentifier : myMethod,
+ "Contract clause '" + myContract + "' is violated: no exception is thrown");
+ }
+
+ return errors;
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
index b648dde79d63..31eb41637794 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
@@ -140,8 +140,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (parent instanceof PsiLambdaExpression && myCodeFragment instanceof PsiExpression) {
generateBoxingUnboxingInstructionFor((PsiExpression)myCodeFragment,
LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)parent));
- addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.nullableReturn.problem((PsiExpression)myCodeFragment)));
- addInstruction(new PopInstruction());
+ addInstruction(new CheckReturnValueInstruction((PsiExpression)myCodeFragment));
}
addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, FList.emptyList()), null));
@@ -832,8 +831,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
generateBoxingUnboxingInstructionFor(returnValue, LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression));
}
}
- addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.nullableReturn.problem(returnValue)));
- addInstruction(new PopInstruction());
+ addInstruction(new CheckReturnValueInstruction(returnValue));
}
addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement));
@@ -1294,19 +1292,19 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
PsiType type = expression.getType();
if (op == JavaTokenType.ANDAND) {
- generateAndOrExpression(expression, operands, type, true, true);
+ generateAndExpression(operands, type, true);
}
else if (op == JavaTokenType.OROR) {
- generateAndOrExpression(expression, operands, type, false, true);
+ generateOrExpression(operands, type, true);
}
else if (op == JavaTokenType.XOR && PsiType.BOOLEAN.equals(type)) {
generateXorExpression(expression, operands, type, false);
}
else if (op == JavaTokenType.AND && PsiType.BOOLEAN.equals(type)) {
- generateAndOrExpression(expression, operands, type, true, false);
+ generateAndExpression(operands, type, false);
}
else if (op == JavaTokenType.OR && PsiType.BOOLEAN.equals(type)) {
- generateAndOrExpression(expression, operands, type, false, false);
+ generateOrExpression(operands, type, false);
}
else if (isBinaryDivision(op) && operands.length == 2 &&
type != null && PsiType.LONG.isAssignableFrom(type)) {
@@ -1456,8 +1454,29 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
operand = operands[i];
operand.accept(this);
generateBoxingUnboxingInstructionFor(operand, exprType);
- PsiExpression psiAnchor = expression.isPhysical() ? expression : null;
- addInstruction(new BinopInstruction(JavaTokenType.NE, psiAnchor, exprType, i));
+ PsiElement psiAnchor = i == operands.length - 1 && expression.isPhysical() ? expression : null;
+ addInstruction(new BinopInstruction(JavaTokenType.NE, psiAnchor, exprType));
+ }
+ }
+
+ private void generateOrExpression(PsiExpression[] operands, final PsiType exprType, boolean shortCircuit) {
+ for (int i = 0; i < operands.length; i++) {
+ PsiExpression operand = operands[i];
+ operand.accept(this);
+ generateBoxingUnboxingInstructionFor(operand, exprType);
+ if (!shortCircuit) {
+ if (i > 0) {
+ combineStackBooleans(false, operand);
+ }
+ continue;
+ }
+
+ PsiExpression nextOperand = i == operands.length - 1 ? null : operands[i + 1];
+ if (nextOperand != null) {
+ addInstruction(new ConditionalGotoInstruction(getStartOffset(nextOperand), true, operand));
+ addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null));
+ addInstruction(new GotoInstruction(getEndOffset(operands[operands.length - 1])));
+ }
}
}
@@ -1489,32 +1508,39 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
overPushSuccess.setOffset(pushSuccess.getIndex() + 1);
}
- private void generateAndOrExpression(PsiExpression expression,
- PsiExpression[] operands,
- final PsiType exprType,
- boolean and,
- boolean shortCircuit) {
+ private void generateAndExpression(PsiExpression[] operands, final PsiType exprType, boolean shortCircuit) {
+ List branchToFail = new ArrayList<>();
for (int i = 0; i < operands.length; i++) {
PsiExpression operand = operands[i];
operand.accept(this);
generateBoxingUnboxingInstructionFor(operand, exprType);
+
if (!shortCircuit) {
if (i > 0) {
- combineStackBooleans(and, operand);
+ combineStackBooleans(true, operand);
}
continue;
}
- PsiExpression nextOperand = i == operands.length - 1 ? null : operands[i + 1];
- if (nextOperand != null) {
- addInstruction(new ConditionalGotoInstruction(getStartOffset(nextOperand), !and, operand));
- addInstruction(new PushInstruction(myFactory.getBoolean(!and), expression));
- addInstruction(new GotoInstruction(getEndOffset(operands[operands.length - 1])));
- }
+ ConditionalGotoInstruction onFail = new ConditionalGotoInstruction(null, true, operand);
+ branchToFail.add(onFail);
+ addInstruction(onFail);
}
- if (shortCircuit) {
- addInstruction(new ResultOfInstruction(expression));
+
+ if (!shortCircuit) {
+ return;
}
+
+ addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null));
+ GotoInstruction toSuccess = new GotoInstruction(null);
+ addInstruction(toSuccess);
+ PushInstruction pushFalse = new PushInstruction(myFactory.getConstFactory().getFalse(), null);
+ addInstruction(pushFalse);
+ for (ConditionalGotoInstruction toFail : branchToFail) {
+ toFail.setOffset(pushFalse.getIndex());
+ }
+ toSuccess.setOffset(pushFalse.getIndex()+1);
+
}
@Override public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) {
@@ -1906,7 +1932,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new AssignInstruction(operand, null, myFactory.createValue(operand)));
}
else if (expression.getOperationTokenType() == JavaTokenType.EXCL) {
- addInstruction(new NotInstruction(expression));
+ addInstruction(new NotInstruction());
}
else if (expression.getOperationTokenType() == JavaTokenType.MINUS && (PsiType.INT.equals(type) || PsiType.LONG.equals(type))) {
addInstruction(new PushInstruction(myFactory.getConstFactory().createDefault(type), null));
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java
index d6914948701e..a8f212296a4e 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java
@@ -15,6 +15,7 @@
*/
package com.intellij.codeInspection.dataFlow;
+import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
@@ -35,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import static com.intellij.psi.CommonClassNames.*;
@@ -51,14 +53,12 @@ class CustomMethodHandlers {
interface CustomMethodHandler {
- @Nullable
- DfaValue getMethodResult(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory);
-
+ List handle(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory);
default CustomMethodHandler compose(CustomMethodHandler other) {
if (other == null) return this;
return (args, memState, factory) -> {
- DfaValue result = this.getMethodResult(args, memState, factory);
- return result == null ? other.getMethodResult(args, memState, factory) : result;
+ List result = this.handle(args, memState, factory);
+ return result.isEmpty() ? other.handle(args, memState, factory) : result;
};
}
@@ -71,14 +71,16 @@ class CustomMethodHandlers {
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("int"),
(args, memState, factory) -> mathAbs(args.myArguments, memState, factory, false))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
- (args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true))
- .register(DfaOptionalSupport.OPTIONAL_OF_NULLABLE,
- (args, memState, factory) -> ofNullable(args.myArguments[0], memState, factory));
+ (args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true));
- public static CustomMethodHandler find(PsiMethod method) {
+ public static CustomMethodHandler find(MethodCallInstruction instruction) {
+ PsiMethod method = instruction.getTargetMethod();
CustomMethodHandler handler = null;
if (isConstantCall(method)) {
- handler = (args, memState, factory) -> handleConstantCall(args, memState, factory, method);
+ handler = (args, memState, factory) -> {
+ DfaValue value = handleConstantCall(args, memState, factory, method);
+ return value == null ? Collections.emptyList() : singleResult(memState, value);
+ };
}
CustomMethodHandler handler2 = CUSTOM_METHOD_HANDLERS.mapFirst(method);
return handler == null ? handler2 : handler.compose(handler2);
@@ -188,32 +190,27 @@ class CustomMethodHandlers {
});
}
- private static DfaValue indexOf(DfaValue qualifier,
- DfaMemoryState memState,
- DfaValueFactory factory,
- SpecialField specialField) {
+ private static List indexOf(DfaValue qualifier,
+ DfaMemoryState memState,
+ DfaValueFactory factory,
+ SpecialField specialField) {
DfaValue length = specialField.createValue(factory, qualifier);
LongRangeSet range = memState.getValueFact(length, DfaFactType.RANGE);
long maxLen = range == null || range.isEmpty() ? Integer.MAX_VALUE : range.max();
- return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1));
+ return singleResult(memState, factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1)));
}
- private static DfaValue ofNullable(DfaValue argument, DfaMemoryState state, DfaValueFactory factory) {
- if (state.isNull(argument)) {
- return factory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, false);
- }
- if (state.isNotNull(argument)) {
- return factory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, true);
- }
- return null;
- }
-
- private static DfaValue mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
+ private static List mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
DfaValue arg = ArrayUtil.getFirstElement(args);
- if (arg == null) return null;
+ if(arg == null) return Collections.emptyList();
LongRangeSet range = memState.getValueFact(arg, DfaFactType.RANGE);
- if (range == null) return null;
- return factory.getFactValue(DfaFactType.RANGE, range.abs(isLong));
+ if (range == null) return Collections.emptyList();
+ return singleResult(memState, factory.getFactValue(DfaFactType.RANGE, range.abs(isLong)));
+ }
+
+ private static List singleResult(DfaMemoryState state, DfaValue value) {
+ state.push(value);
+ return Collections.singletonList(state);
}
private static Object getConstantValue(DfaMemoryState memoryState, DfaValue value) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
index d23cae024866..f6cdeca3d005 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
@@ -14,6 +14,7 @@ import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix;
import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
+import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.nullable.NullableStuffInspectionBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -29,7 +30,6 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
-import com.siyeh.ig.bugs.EqualsWithItselfInspection;
import com.siyeh.ig.fixes.EqualsToEqualityFix;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.IntStreamEx;
@@ -253,7 +253,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
for (Instruction instruction : allProblems) {
if (instruction instanceof TypeCastInstruction &&
- reportedAnchors.add(((TypeCastInstruction)instruction).getExpression().getCastType())) {
+ reportedAnchors.add(((TypeCastInstruction)instruction).getCastExpression().getCastType())) {
reportCastMayFail(holder, (TypeCastInstruction)instruction);
}
else if (instruction instanceof BranchingInstruction) {
@@ -263,6 +263,8 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportAlwaysFailingCalls(holder, visitor, reportedAnchors);
+ reportConstantPushes(runner, holder, reportedAnchors);
+
reportNullabilityProblems(holder, visitor, reportedAnchors);
reportNullableReturns(visitor, holder, reportedAnchors, scope);
if (SUGGEST_NULLABLE_ANNOTATIONS) {
@@ -272,9 +274,9 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportOptionalOfNullableImprovements(holder, reportedAnchors, visitor.getOfNullableCalls());
- visitor.getBooleanExpressions().forEach((expression, state) -> {
- if (state != ThreeState.UNSURE && reportedAnchors.add(expression)) {
- reportConstantBoolean(holder, expression, state.toBoolean());
+ visitor.getBooleanCalls().forEach((call, state) -> {
+ if (state != ThreeState.UNSURE && reportedAnchors.add(call)) {
+ reportConstantCondition(holder, call, state.toBoolean());
}
});
@@ -435,15 +437,17 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
private static void reportAlwaysFailingCalls(ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
HashSet reportedAnchors) {
- visitor.alwaysFailingCalls().remove(TestUtils::isExceptionExpected).forEach(call -> {
- if (reportedAnchors.add(call)) {
- holder.registerProblem(getElementToHighlight(call), getContractMessage(JavaMethodContractUtil.getMethodCallContracts(call)));
+ visitor.getAlwaysFailingCalls().forEach((call, contracts) -> {
+ if (TestUtils.isExceptionExpected(call)) return;
+ PsiMethod method = call.resolveMethod();
+ if (method != null && reportedAnchors.add(call)) {
+ holder.registerProblem(getElementToHighlight(call), getContractMessage(contracts));
}
});
}
@NotNull
- private static String getContractMessage(List extends MethodContract> contracts) {
+ private static String getContractMessage(List contracts) {
if (contracts.stream().allMatch(mc -> mc.getConditions().stream().allMatch(ContractValue::isBoundCheckingCondition))) {
return InspectionsBundle.message("dataflow.message.contract.fail.index");
}
@@ -468,37 +472,61 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
return call;
}
+ private void reportConstantPushes(StandardDataFlowRunner runner,
+ ProblemsHolder holder,
+ Set reportedAnchors) {
+ for (Instruction instruction : runner.getInstructions()) {
+ if (instruction instanceof PushInstruction) {
+ PsiExpression place = ((PushInstruction)instruction).getPlace();
+ DfaValue value = ((PushInstruction)instruction).getValue();
+ Object constant = value instanceof DfaConstValue ? ((DfaConstValue)value).getValue() : null;
+ if (place instanceof PsiPolyadicExpression && constant instanceof Boolean && !isFlagCheck(place) && reportedAnchors.add(place)) {
+ reportConstantCondition(holder, place, (Boolean)constant);
+ }
+ }
+ }
+ }
+
private static void reportOptionalOfNullableImprovements(ProblemsHolder holder,
Set reportedAnchors,
- Map nullArgs) {
- nullArgs.forEach((anchor, alwaysPresent) -> {
- if (alwaysPresent == ThreeState.UNSURE) return;
- if (reportedAnchors.add(anchor)) {
- if (alwaysPresent.toBoolean()) {
- holder.registerProblem(anchor, "Passing a non-null argument to Optional",
- DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(anchor));
- } else {
- holder.registerProblem(anchor, "Passing null argument to Optional",
- DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(anchor));
+ Map nullArgs) {
+ nullArgs.forEach((call, nullArg) -> {
+ PsiElement arg = call.getArgumentAnchor(0);
+ if (reportedAnchors.add(arg)) {
+ switch (nullArg) {
+ case YES:
+ holder.registerProblem(arg, "Passing null argument to Optional",
+ DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg));
+ break;
+ case NO:
+ holder.registerProblem(arg, "Passing a non-null argument to Optional",
+ DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg));
+ break;
+ default:
}
}
});
}
private void reportConstantReferenceValues(ProblemsHolder holder, DataFlowInstructionVisitor visitor, Set reportedAnchors) {
- visitor.getConstantReferenceValues().forEach((ref, dfaConst) -> {
- if (ref.getParent() instanceof PsiReferenceExpression || DfaConstValue.isSentinel(dfaConst)) return;
- if (!reportedAnchors.add(ref)) return;
+ for (Pair pair : visitor.getConstantReferenceValues()) {
+ PsiReferenceExpression ref = pair.first;
+ if (ref.getParent() instanceof PsiReferenceExpression || !reportedAnchors.add(ref)) {
+ continue;
+ }
- final Object value = dfaConst.getValue();
- PsiVariable constant = dfaConst.getConstant();
+ final Object value = pair.second.getValue();
+ PsiVariable constant = pair.second.getConstant();
+ final String presentableName = constant != null ? constant.getName() : String.valueOf(value);
final String exprText = String.valueOf(value);
- final String presentableName = constant != null ? constant.getName() : exprText;
+ if (presentableName == null || exprText == null) {
+ continue;
+ }
List fixes = new SmartList<>();
fixes.add(new ReplaceWithConstantValueFix(presentableName, exprText));
boolean isAssertion = value instanceof Boolean && isAssertionEffectively(ref, (Boolean)value);
- if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) return;
+ if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) continue;
if (holder.isOnTheFly()) {
fixes.add(new SetInspectionOptionFix(this, "REPORT_CONSTANT_REFERENCE_VALUES",
InspectionsBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"),
@@ -510,8 +538,9 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
holder.registerProblem(ref, "Value #ref #loc is always '" + presentableName + "'",
- ProblemHighlightType.WEAK_WARNING, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
- });
+ ProblemHighlightType.WEAK_WARNING,
+ fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
+ }
}
private void reportNullableArgumentsPassedToNonAnnotated(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) {
@@ -593,7 +622,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private static void reportCastMayFail(ProblemsHolder holder, TypeCastInstruction instruction) {
- PsiTypeCastExpression typeCast = instruction.getExpression();
+ PsiTypeCastExpression typeCast = instruction.getCastExpression();
PsiExpression operand = typeCast.getOperand();
PsiTypeElement castType = typeCast.getCastType();
assert castType != null;
@@ -624,12 +653,10 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
InspectionsBundle.message("dataflow.message.unreachable.switch.label"));
}
}
- else if (psiAnchor != null && !isFlagCheck(psiAnchor)) {
+ else if (psiAnchor != null && !reportedAnchors.contains(psiAnchor) && !isFlagCheck(psiAnchor)) {
boolean evaluatesToTrue = trueSet.contains(instruction);
final PsiElement parent = psiAnchor.getParent();
- if (parent instanceof PsiAssignmentExpression &&
- ((PsiAssignmentExpression)parent).getLExpression() == psiAnchor &&
- reportedAnchors.add(psiAnchor)) {
+ if (parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getLExpression() == psiAnchor) {
holder.registerProblem(
psiAnchor,
InspectionsBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)),
@@ -637,17 +664,19 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
);
}
else {
- TextRange range =
- instruction instanceof ExpressionPushingInstruction ? ((ExpressionPushingInstruction)instruction).getExpressionRange() : null;
- if (range != null) {
- // report rare cases like a == b == c where "a == b" part is constant
- String message = InspectionsBundle.message("dataflow.message.constant.condition", Boolean.toString(evaluatesToTrue));
- holder.registerProblem(psiAnchor, range, message);
- // do not add to reported anchors if only part of expression was reported
- } else if (reportedAnchors.add(psiAnchor)) {
- reportConstantCondition(holder, psiAnchor, evaluatesToTrue);
+ if (instruction instanceof BinopInstruction) {
+ TextRange range = ((BinopInstruction)instruction).getAnchorRange();
+ if (range != null) {
+ // report rare cases like a == b == c where "a == b" part is constant
+ String message = InspectionsBundle.message("dataflow.message.constant.condition", Boolean.toString(evaluatesToTrue));
+ holder.registerProblem(psiAnchor, range, message);
+ // do not add to reported anchors if only part of expression was reported
+ return;
+ }
}
+ reportConstantCondition(holder, psiAnchor, evaluatesToTrue);
}
+ reportedAnchors.add(psiAnchor);
}
}
@@ -673,7 +702,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private void reportConstantBoolean(ProblemsHolder holder, PsiElement psiAnchor, boolean evaluatesToTrue) {
- if (shouldBeSuppressed(psiAnchor)) return;
boolean isAssertion = isAssertionEffectively(psiAnchor, evaluatesToTrue);
if (!DONT_REPORT_TRUE_ASSERT_STATEMENTS || !isAssertion) {
List fixes = new ArrayList<>();
@@ -690,17 +718,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
}
- private static boolean shouldBeSuppressed(PsiElement anchor) {
- if (!(anchor instanceof PsiExpression)) return false;
- PsiExpression expression = (PsiExpression)anchor;
- while (expression != null && BoolUtils.isNegation(expression)) {
- expression = BoolUtils.getNegated(expression);
- }
- PsiMethodCallExpression call = ObjectUtils.tryCast(expression, PsiMethodCallExpression.class);
- // Reported by "Equals with itself" inspection; avoid double reporting
- return call != null && EqualsWithItselfInspection.isEqualsWithItself(call);
- }
-
private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) {
if (evaluatesToTrue) return null;
if (!(psiAnchor instanceof PsiMethodCallExpression) || !MethodCallUtils.isEqualsCall((PsiMethodCallExpression)psiAnchor)) return null;
@@ -864,7 +881,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
return false;
}
- static boolean isFlagCheck(PsiElement element) {
+ 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() :
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java
index 55a929705418..494e69a90d7f 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java
@@ -5,13 +5,13 @@ import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
-import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -20,19 +20,18 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Stream;
-import static com.intellij.util.ObjectUtils.tryCast;
-
final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowInstructionVisitor");
+ private static final Object ANY_VALUE = ObjectUtils.sentinel("ANY_VALUE");
private final Map, StateInfo> myStateInfos = new LinkedHashMap<>();
private final Set myCCEInstructions = ContainerUtil.newHashSet();
- private final Map myFailingCalls = new HashMap<>();
- private final Map myBooleanExpressions = new HashMap<>();
- private final Map myOfNullableCalls = new HashMap<>();
+ private final Map myFailingCalls = new HashMap<>();
+ private final Map myBooleanCalls = new HashMap<>();
+ private final Map myOfNullableCalls = new HashMap<>();
private final Map> myArrayStoreProblems = new HashMap<>();
private final Map myMethodReferenceResults = new HashMap<>();
private final Map myOutOfBoundsArrayAccesses = new HashMap<>();
- private final Map myValues = new HashMap<>();
+ private final MultiMap myPossibleVariableValues = MultiMap.createSet();
private final Set myReceiverMutabilityViolation = new HashSet<>();
private final Set myArgumentMutabilityViolation = new HashSet<>();
private final Map mySameValueAssigned = new HashMap<>();
@@ -124,12 +123,12 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return myArrayStoreProblems;
}
- Map getOfNullableCalls() {
+ Map getOfNullableCalls() {
return myOfNullableCalls;
}
- Map getBooleanExpressions() {
- return myBooleanExpressions;
+ Map getBooleanCalls() {
+ return myBooleanCalls;
}
Map getMethodReferenceResults() {
@@ -152,8 +151,9 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals);
}
- StreamEx alwaysFailingCalls() {
- return StreamEx.ofKeys(myFailingCalls, v -> v);
+ Map> getAlwaysFailingCalls() {
+ return StreamEx.ofKeys(myFailingCalls, v -> v)
+ .mapToEntry(MethodCallInstruction::getCallExpression, MethodCallInstruction::getContracts).toMap();
}
boolean isAlwaysReturnsNotNull(Instruction[] instructions) {
@@ -162,37 +162,46 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
@Override
- protected void beforeExpressionPush(@NotNull DfaValue value,
- @NotNull PsiExpression expression,
- @Nullable TextRange range,
- @NotNull DfaMemoryState memState) {
- expression.accept(new ExpressionVisitor(value, memState));
- handleBooleanResults(value, memState, expression);
+ public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction,
+ DataFlowRunner runner,
+ DfaMemoryState memState) {
+ if (instruction.matches(DfaOptionalSupport.OPTIONAL_OF_NULLABLE)) {
+ DfaValue arg = memState.peek();
+ ThreeState nullArg = memState.isNull(arg) ? ThreeState.YES : memState.isNotNull(arg) ? ThreeState.NO : ThreeState.UNSURE;
+ // Passing variable with unknown nullity to ofNullable assumes that it can be null
+ memState.applyFact(arg, DfaFactType.CAN_BE_NULL, true);
+ myOfNullableCalls.merge(instruction, nullArg, ThreeState::merge);
+ }
+ DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
+ if (hasNonTrivialFailingContracts(instruction)) {
+ DfaConstValue fail = runner.getFactory().getConstFactory().getContractFail();
+ boolean allFail = Arrays.stream(states).allMatch(s -> s.getMemoryState().peek() == fail);
+ myFailingCalls.merge(instruction, allFail, Boolean::logicalAnd);
+ }
+ handleBooleanCalls(instruction, states);
+ return states;
}
- @Override
- protected void beforeMethodReferenceResultPush(@NotNull DfaValue value,
- @NotNull PsiMethodReferenceExpression methodRef,
- @NotNull DfaMemoryState state) {
- if (DfaOptionalSupport.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) {
- processOfNullableResult(value, state, methodRef.getReferenceNameElement());
- }
- PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class);
- if (method != null) {
- List contracts = JavaMethodContractUtil.getMethodContracts(method);
- if (contracts.isEmpty() || !contracts.get(0).isTrivial()) {
- // Do not track if method reference may have different results
- myMethodReferenceResults.merge(methodRef, value, (a, b) -> a == b ? a : DfaUnknownValue.getInstance());
+ void handleBooleanCalls(MethodCallInstruction instruction, DfaInstructionState[] states) {
+ if (!hasNonTrivialBooleanContracts(instruction)) return;
+ PsiMethod method = instruction.getTargetMethod();
+ if (method == null || !JavaMethodContractUtil.isPure(method)) return;
+ PsiMethodCallExpression call = ObjectUtils.tryCast(instruction.getCallExpression(), PsiMethodCallExpression.class);
+ if (call == null || myBooleanCalls.get(call) == ThreeState.UNSURE) return;
+ if (ExpressionUtils.isVoidContext(call)) return;
+ for (DfaInstructionState s : states) {
+ DfaValue val = s.getMemoryState().peek();
+ ThreeState state = ThreeState.UNSURE;
+ if (val instanceof DfaConstValue) {
+ Object value = ((DfaConstValue)val).getValue();
+ if (value instanceof Boolean) {
+ state = ThreeState.fromBoolean((Boolean)value);
+ }
}
+ myBooleanCalls.merge(call, state, ThreeState::merge);
}
}
- private void processOfNullableResult(@NotNull DfaValue value, @NotNull DfaMemoryState memState, PsiElement anchor) {
- Boolean fact = memState.getValueFact(value, DfaFactType.OPTIONAL_PRESENCE);
- ThreeState present = fact == null ? ThreeState.UNSURE : ThreeState.fromBoolean(fact);
- myOfNullableCalls.merge(anchor, present, ThreeState::merge);
- }
-
@Override
protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) {
myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge);
@@ -205,6 +214,30 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
}
+ @Override
+ protected void processMethodReferenceResult(PsiMethodReferenceExpression methodRef,
+ List extends MethodContract> contracts,
+ DfaValue res) {
+ if(contracts.isEmpty() || !contracts.get(0).isTrivial()) {
+ // Do not track if method reference may have different results
+ myMethodReferenceResults.merge(methodRef, res, (a, b) -> a == b ? a : DfaUnknownValue.getInstance());
+ }
+ }
+
+ @Override
+ public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ PsiExpression place = instruction.getPlace();
+ if (!instruction.isReferenceWrite() && place instanceof PsiReferenceExpression) {
+ DfaValue dfaValue = instruction.getValue();
+ if (dfaValue instanceof DfaVariableValue) {
+ DfaConstValue constValue = memState.getConstantValue((DfaVariableValue)dfaValue);
+ boolean report = constValue != null && shouldReportConstValue(constValue.getValue());
+ myPossibleVariableValues.putValue(instruction, report ? constValue : ANY_VALUE);
+ }
+ }
+ return super.visitPush(instruction, runner, memState);
+ }
+
@Override
public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction instruction, DataFlowRunner runner, DfaMemoryState state) {
if (!instruction.isStatic()) {
@@ -213,58 +246,31 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return super.visitEndOfInitializer(instruction, runner, state);
}
- public Map getConstantReferenceValues() {
- return myValues;
- }
-
- private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) {
- List extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts(call);
- return !contracts.isEmpty() &&
- contracts.stream().anyMatch(contract -> contract.getReturnValue().isFail() && !contract.isTrivial());
- }
-
- private void handleBooleanResults(DfaValue value, DfaMemoryState memState, PsiExpression expression) {
- ThreeState curState = myBooleanExpressions.get(expression);
- if (curState == ThreeState.UNSURE) return;
- ThreeState nextState = ThreeState.UNSURE;
- value = value instanceof DfaVariableValue ? memState.getConstantValue((DfaVariableValue)value) : value;
- if (value instanceof DfaConstValue) {
- Object val = ((DfaConstValue)value).getValue();
- if (val instanceof Boolean) {
- nextState = ThreeState.fromBoolean((Boolean)val);
- if (curState != null && curState != nextState) {
- nextState = ThreeState.UNSURE;
+ public List> getConstantReferenceValues() {
+ List> result = ContainerUtil.newArrayList();
+ for (PushInstruction instruction : myPossibleVariableValues.keySet()) {
+ Collection