DFA instruction visitor refactoring wave#1

InstructionVisitor#beforeExpressionPush method which is called before every push of PsiExpression result (were it simple reference, operation or method call)
CustomMethodHandlers: do not modify states; only return resulting value (current handlers do not produce more than one value)
Xor polyadic now supports reporting for subexpressions
This commit is contained in:
Tagir Valeev
2018-06-15 15:24:16 +07:00
parent 0a52895406
commit 3e4cd658e9
20 changed files with 293 additions and 232 deletions
@@ -555,11 +555,11 @@ public class GuessManagerImpl extends GuessManager {
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
if (myForPlace == instruction.getPlace()) {
if (myForPlace == instruction.getExpression()) {
addToResult(((ExpressionTypeMemoryState)memState).getStates());
}
DfaInstructionState[] states = super.visitPush(instruction, runner, memState);
if (myForPlace == instruction.getPlace()) {
if (myForPlace == instruction.getExpression()) {
addConstraints(states);
}
return states;
@@ -125,6 +125,21 @@ public class CFGBuilder {
return add(new PushInstruction(value, null));
}
/**
* Generate instructions to push given DfaValue on stack and bind it to given expression.
* <p>
* Stack before: ...
* <p>
* 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
* <p>
@@ -1,17 +1,13 @@
// 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.*;
import com.intellij.codeInspection.dataFlow.instructions.EndOfInitializerInstruction;
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.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.*;
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;
@@ -42,6 +38,12 @@ 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);
}
}
}
@@ -166,76 +168,14 @@ public class CommonDataflow {
}
@Override
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());
}
public void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
if (range == null && value != myFail) {
// Do not track instructions which cover part of expression
myResult.add(expression, (DfaMemoryStateImpl)state, value);
}
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;
}
}
}
@@ -1454,8 +1454,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
operand = operands[i];
operand.accept(this);
generateBoxingUnboxingInstructionFor(operand, exprType);
PsiElement psiAnchor = i == operands.length - 1 && expression.isPhysical() ? expression : null;
addInstruction(new BinopInstruction(JavaTokenType.NE, psiAnchor, exprType));
PsiExpression psiAnchor = expression.isPhysical() ? expression : null;
addInstruction(new BinopInstruction(JavaTokenType.NE, psiAnchor, exprType, i));
}
}
@@ -1932,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());
addInstruction(new NotInstruction(expression));
}
else if (expression.getOperationTokenType() == JavaTokenType.MINUS && (PsiType.INT.equals(type) || PsiType.LONG.equals(type))) {
addInstruction(new PushInstruction(myFactory.getConstFactory().createDefault(type), null));
@@ -36,7 +36,6 @@ 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.*;
@@ -53,12 +52,14 @@ class CustomMethodHandlers {
interface CustomMethodHandler {
List<DfaMemoryState> handle(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory);
@Nullable
DfaValue getMethodResult(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory);
default CustomMethodHandler compose(CustomMethodHandler other) {
if (other == null) return this;
return (args, memState, factory) -> {
List<DfaMemoryState> result = this.handle(args, memState, factory);
return result.isEmpty() ? other.handle(args, memState, factory) : result;
DfaValue result = this.getMethodResult(args, memState, factory);
return result == null ? other.getMethodResult(args, memState, factory) : result;
};
}
@@ -71,16 +72,15 @@ 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));
(args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true))
.register(DfaOptionalSupport.OPTIONAL_OF_NULLABLE,
(args, memState, factory) -> ofNullable(args.myArguments[0], memState, factory));
public static CustomMethodHandler find(MethodCallInstruction instruction) {
PsiMethod method = instruction.getTargetMethod();
CustomMethodHandler handler = null;
if (isConstantCall(method)) {
handler = (args, memState, factory) -> {
DfaValue value = handleConstantCall(args, memState, factory, method);
return value == null ? Collections.emptyList() : singleResult(memState, value);
};
handler = (args, memState, factory) -> handleConstantCall(args, memState, factory, method);
}
CustomMethodHandler handler2 = CUSTOM_METHOD_HANDLERS.mapFirst(method);
return handler == null ? handler2 : handler.compose(handler2);
@@ -190,27 +190,32 @@ class CustomMethodHandlers {
});
}
private static List<DfaMemoryState> indexOf(DfaValue qualifier,
DfaMemoryState memState,
DfaValueFactory factory,
SpecialField specialField) {
private static DfaValue 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 singleResult(memState, factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1)));
return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1));
}
private static List<DfaMemoryState> mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
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) {
DfaValue arg = ArrayUtil.getFirstElement(args);
if(arg == null) return Collections.emptyList();
if (arg == null) return null;
LongRangeSet range = memState.getValueFact(arg, DfaFactType.RANGE);
if (range == null) return Collections.emptyList();
return singleResult(memState, factory.getFactValue(DfaFactType.RANGE, range.abs(isLong)));
}
private static List<DfaMemoryState> singleResult(DfaMemoryState state, DfaValue value) {
state.push(value);
return Collections.singletonList(state);
if (range == null) return null;
return factory.getFactValue(DfaFactType.RANGE, range.abs(isLong));
}
private static Object getConstantValue(DfaMemoryState memoryState, DfaValue value) {
@@ -477,7 +477,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
Set<PsiElement> reportedAnchors) {
for (Instruction instruction : runner.getInstructions()) {
if (instruction instanceof PushInstruction) {
PsiExpression place = ((PushInstruction)instruction).getPlace();
PsiExpression place = ((PushInstruction)instruction).getExpression();
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)) {
@@ -489,20 +489,16 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
private static void reportOptionalOfNullableImprovements(ProblemsHolder holder,
Set<PsiElement> reportedAnchors,
Map<MethodCallInstruction, ThreeState> nullArgs) {
nullArgs.forEach((call, nullArg) -> {
PsiElement arg = call.getArgumentAnchor(0);
if (reportedAnchors.add(arg)) {
switch (nullArg) {
case YES:
holder.registerProblem(arg, "Passing <code>null</code> argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg));
break;
case NO:
holder.registerProblem(arg, "Passing a non-null argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg));
break;
default:
Map<PsiElement, ThreeState> 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 <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(anchor));
} else {
holder.registerProblem(anchor, "Passing <code>null</code> argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(anchor));
}
}
});
@@ -653,10 +649,12 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
InspectionsBundle.message("dataflow.message.unreachable.switch.label"));
}
}
else if (psiAnchor != null && !reportedAnchors.contains(psiAnchor) && !isFlagCheck(psiAnchor)) {
else if (psiAnchor != null && !isFlagCheck(psiAnchor)) {
boolean evaluatesToTrue = trueSet.contains(instruction);
final PsiElement parent = psiAnchor.getParent();
if (parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getLExpression() == psiAnchor) {
if (parent instanceof PsiAssignmentExpression &&
((PsiAssignmentExpression)parent).getLExpression() == psiAnchor &&
reportedAnchors.add(psiAnchor)) {
holder.registerProblem(
psiAnchor,
InspectionsBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)),
@@ -664,19 +662,17 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
);
}
else {
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;
}
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);
}
reportConstantCondition(holder, psiAnchor, evaluatesToTrue);
}
reportedAnchors.add(psiAnchor);
}
}
@@ -5,6 +5,7 @@ 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;
@@ -27,7 +28,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final Set<Instruction> myCCEInstructions = ContainerUtil.newHashSet();
private final Map<MethodCallInstruction, Boolean> myFailingCalls = new HashMap<>();
private final Map<PsiMethodCallExpression, ThreeState> myBooleanCalls = new HashMap<>();
private final Map<MethodCallInstruction, ThreeState> myOfNullableCalls = new HashMap<>();
private final Map<PsiElement, ThreeState> myOfNullableCalls = new HashMap<>();
private final Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> myArrayStoreProblems = new HashMap<>();
private final Map<PsiMethodReferenceExpression, DfaValue> myMethodReferenceResults = new HashMap<>();
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
@@ -123,7 +124,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return myArrayStoreProblems;
}
Map<MethodCallInstruction, ThreeState> getOfNullableCalls() {
Map<PsiElement, ThreeState> getOfNullableCalls() {
return myOfNullableCalls;
}
@@ -161,17 +162,37 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
ContainerUtil.exists(instructions, i -> i instanceof ReturnInstruction && ((ReturnInstruction)i).getAnchor() instanceof PsiReturnStatement);
}
@Override
public void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState memState) {
PsiElement anchor = extractOptionalOfNullableAnchor(expression);
if (anchor != null) {
Boolean fact = memState.getValueFact(value, DfaFactType.OPTIONAL_PRESENCE);
ThreeState present = fact == null ? ThreeState.UNSURE : ThreeState.fromBoolean(fact);
myOfNullableCalls.merge(anchor, present, ThreeState::merge);
}
}
private static PsiElement extractOptionalOfNullableAnchor(PsiExpression expression) {
if (expression instanceof PsiMethodCallExpression &&
DfaOptionalSupport.OPTIONAL_OF_NULLABLE.test((PsiMethodCallExpression)expression)) {
return ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0];
}
if (expression instanceof PsiMethodReferenceExpression) {
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expression;
if (DfaOptionalSupport.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) {
return methodRef.getReferenceNameElement();
}
}
return null;
}
@Override
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();
@@ -226,7 +247,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression place = instruction.getPlace();
PsiExpression place = instruction.getExpression();
if (!instruction.isReferenceWrite() && place instanceof PsiReferenceExpression) {
DfaValue dfaValue = instruction.getValue();
if (dfaValue instanceof DfaVariableValue) {
@@ -253,7 +274,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
if (values.size() == 1) {
Object singleValue = values.iterator().next();
if (singleValue != ANY_VALUE) {
result.add(Pair.create((PsiReferenceExpression)instruction.getPlace(), (DfaConstValue)singleValue));
result.add(Pair.create((PsiReferenceExpression)instruction.getExpression(), (DfaConstValue)singleValue));
}
}
}
@@ -18,7 +18,6 @@ import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.treeStructure.NullNode;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
@@ -381,7 +380,7 @@ public class DfaUtil {
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression place = instruction.getPlace();
PsiExpression place = instruction.getExpression();
if (place != null) {
PlaceResult result = myResults.computeIfAbsent(place, __ -> new PlaceResult());
((ValuableDataFlowRunner.MyDfaMemoryState)memState).forVariableStates((variableValue, value) -> {
@@ -18,12 +18,14 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiArrayAccessExpression;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -32,6 +34,23 @@ import java.util.ArrayList;
*/
public abstract class InstructionVisitor {
public void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
}
void pushExpressionResult(@NotNull DfaValue value,
@NotNull ExpressionPushingInstruction instruction,
@NotNull DfaMemoryState state) {
PsiExpression anchor = instruction.getExpression();
if (anchor != null && !(instruction instanceof PushInstruction && ((PushInstruction)instruction).isReferenceWrite())) {
beforeExpressionPush(value, anchor, instruction.getExpressionRange(), state);
}
state.push(value);
}
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop();
DfaValue dest = memState.pop();
@@ -89,7 +108,7 @@ public abstract class InstructionVisitor {
public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop();
memState.pop();
memState.push(DfaUnknownValue.getInstance());
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -183,7 +202,7 @@ public abstract class InstructionVisitor {
}
memState.pop(); //qualifier
memState.push(DfaUnknownValue.getInstance());
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -195,19 +214,19 @@ public abstract class InstructionVisitor {
DfaValue dfaValue = memState.pop();
dfaValue = dfaValue.createNegated();
memState.push(dfaValue);
pushExpressionResult(dfaValue, instruction, memState);
return nextInstruction(instruction, runner, memState);
}
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.push(instruction.getValue());
pushExpressionResult(instruction.getValue(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
public DfaInstructionState[] visitArrayAccess(ArrayAccessInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop(); // index
memState.pop(); // array reference
memState.push(instruction.getValue());
pushExpressionResult(instruction.getValue(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -195,7 +195,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (arrayElementValue != DfaUnknownValue.getInstance()) {
result = arrayElementValue;
}
memState.push(result);
pushExpressionResult(result, instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -234,7 +234,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (contracts.isEmpty()) return;
PsiType returnType = substitutor.substitute(method.getReturnType());
DfaValue defaultResult = runner.getFactory().createTypeValue(returnType, DfaPsiUtil.getElementNullability(returnType, method));
Stream<DfaValue> returnValues = possibleReturnValues(callArguments, state, contracts, runner.getFactory(), defaultResult);
Stream<DfaValue> returnValues = possibleReturnValues(callArguments, state, contracts, runner.getFactory(), defaultResult, methodRef);
returnValues.forEach(res -> processMethodReferenceResult(methodRef, contracts, res));
}
@@ -268,14 +268,16 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return new DfaCallArguments(qualifier, arguments, JavaMethodContractUtil.isPure(method));
}
private static Stream<DfaValue> possibleReturnValues(DfaCallArguments callArguments,
DfaMemoryState state,
List<? extends MethodContract> contracts,
DfaValueFactory factory, DfaValue defaultResult) {
private Stream<DfaValue> possibleReturnValues(DfaCallArguments callArguments,
DfaMemoryState state,
List<? extends MethodContract> contracts,
DfaValueFactory factory,
DfaValue defaultResult,
PsiMethodReferenceExpression methodRef) {
Set<DfaCallState> currentStates = Collections.singleton(new DfaCallState(state.createClosureState(), callArguments));
Set<DfaMemoryState> finalStates = ContainerUtil.newLinkedHashSet();
for (MethodContract contract : contracts) {
currentStates = addContractResults(contract, currentStates, factory, finalStates, defaultResult);
currentStates = addContractResults(contract, currentStates, factory, finalStates, defaultResult, methodRef);
}
return StreamEx.of(finalStates).map(DfaMemoryState::peek)
.append(currentStates.isEmpty() ? StreamEx.empty() : StreamEx.of(defaultResult)).distinct();
@@ -316,7 +318,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue defaultResult = getMethodResultValue(instruction, callArguments.myQualifier, memState, runner.getFactory());
if (callArguments.myArguments != null) {
for (MethodContract contract : instruction.getContracts()) {
currentStates = addContractResults(contract, currentStates, runner.getFactory(), finalStates, defaultResult);
currentStates = addContractResults(contract, currentStates, runner.getFactory(), finalStates, defaultResult, instruction.getExpression());
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
if (LOG.isDebugEnabled()) {
LOG.debug("Too complex contract on " + instruction.getContext() + ", skipping contract processing");
@@ -328,7 +330,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
for (DfaCallState callState : currentStates) {
callState.myMemoryState.push(defaultResult);
pushExpressionResult(defaultResult, instruction, callState.myMemoryState);
finalStates.add(callState.myMemoryState);
}
}
@@ -356,8 +358,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (handler == null) return Collections.emptyList();
memState = memState.createCopy();
DfaCallArguments callArguments = popCall(instruction, runner, memState, false);
return callArguments.myArguments == null ? Collections.emptyList() :
handler.handle(callArguments, memState, runner.getFactory());
DfaValue result = callArguments.myArguments == null ? null : handler.getMethodResult(callArguments, memState, runner.getFactory());
if (result != null) {
pushExpressionResult(result, instruction, memState);
return Collections.singletonList(memState);
}
return Collections.emptyList();
}
@NotNull
@@ -447,15 +453,16 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return value;
}
private static Set<DfaCallState> addContractResults(MethodContract contract,
Set<DfaCallState> states,
DfaValueFactory factory,
Set<DfaMemoryState> finalStates,
DfaValue defaultResult) {
private Set<DfaCallState> addContractResults(MethodContract contract,
Set<DfaCallState> states,
DfaValueFactory factory,
Set<DfaMemoryState> finalStates,
DfaValue defaultResult,
PsiExpression expression) {
if(contract.isTrivial()) {
for (DfaCallState callState : states) {
DfaValue result = contract.getReturnValue().getDfaValue(factory, defaultResult, callState);
callState.myMemoryState.push(result);
pushExpressionResult(result, () -> expression, callState.myMemoryState);
finalStates.add(callState.myMemoryState);
}
return Collections.emptySet();
@@ -484,7 +491,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
if(state != null) {
DfaValue result = contract.getReturnValue().getDfaValue(factory, defaultResult, new DfaCallState(state, arguments));
state.push(result);
pushExpressionResult(result, () -> expression, state);
finalStates.add(state);
}
}
@@ -630,7 +637,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return states;
}
}
DfaValue result = null;
DfaValue result = DfaUnknownValue.getInstance();
PsiType type = instruction.getResultType();
if (PsiType.INT.equals(type) || PsiType.LONG.equals(type)) {
LongRangeSet left = memState.getValueFact(dfaLeft, DfaFactType.RANGE);
@@ -642,10 +649,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
}
if (result == null && JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
if (result == DfaUnknownValue.getInstance() && JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
result = runner.getFactory().createTypeValue(type, Nullability.NOT_NULL);
}
memState.push(result == null ? DfaUnknownValue.getInstance() : result);
pushExpressionResult(result, instruction, memState);
instruction.setTrueReachable(); // Not a branching instruction actually.
instruction.setFalseReachable();
@@ -654,12 +661,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
@Nullable
private static DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft,
RelationType relationType) {
private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft,
RelationType relationType) {
DfaValueFactory factory = runner.getFactory();
RelationType[] relations = splitRelation(relationType);
@@ -767,11 +774,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
@Nullable
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft, RelationType relationType) {
private DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft, RelationType relationType) {
if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaVariableValue) {
Number leftValue = getKnownNumberValue(memState, (DfaVariableValue)dfaLeft);
Number rightValue = getKnownNumberValue(memState, (DfaVariableValue)dfaRight);
@@ -810,11 +817,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
@Nullable
private static DfaInstructionState[] checkComparingWithConstant(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaVariableValue var,
RelationType opSign, Number comparedWith) {
private DfaInstructionState[] checkComparingWithConstant(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaVariableValue var,
RelationType opSign, Number comparedWith) {
Number knownValue = getKnownNumberValue(memState, var);
if (knownValue != null) {
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, knownValue, comparedWith);
@@ -828,12 +835,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return knownConstantValue != null && knownConstantValue.getValue() instanceof Number ? (Number)knownConstantValue.getValue() : null;
}
private static DfaInstructionState[] checkComparisonWithKnownValue(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
RelationType opSign,
Number leftValue,
Number rightValue) {
private DfaInstructionState[] checkComparisonWithKnownValue(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
RelationType opSign,
Number leftValue,
Number rightValue) {
int cmp = compare(leftValue, rightValue);
Boolean result = null;
boolean hasNaN = DfaUtil.isNaN(leftValue) || DfaUtil.isNaN(rightValue);
@@ -867,12 +874,19 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return Double.compare(a.doubleValue(), b.doubleValue());
}
private static DfaInstructionState[] makeBooleanResultArray(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, boolean result) {
private DfaInstructionState[] makeBooleanResultArray(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
boolean result) {
return new DfaInstructionState[]{makeBooleanResult(instruction, runner, memState, ThreeState.fromBoolean(result))};
}
private static DfaInstructionState makeBooleanResult(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, @NotNull ThreeState result) {
memState.push(result == ThreeState.UNSURE ? DfaUnknownValue.getInstance() : runner.getFactory().getBoolean(result.toBoolean()));
private DfaInstructionState makeBooleanResult(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
@NotNull ThreeState result) {
DfaValue value = result == ThreeState.UNSURE ? DfaUnknownValue.getInstance() : runner.getFactory().getBoolean(result.toBoolean());
pushExpressionResult(value, instruction, memState);
if (result != ThreeState.NO) {
instruction.setTrueReachable();
}
@@ -256,14 +256,23 @@ public class OptionalChainInliner implements CallInliner {
private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, PsiMethodCallExpression qualifierCall) {
PsiExpression argument = qualifierCall.getArgumentList().getExpressions()[0];
builder.pushExpression(argument)
.boxUnbox(argument, optionalElementType)
.pushUnknown() // ... arg, ?
.splice(2, 1, 0, 1) // ... arg, ?, arg
.invoke(qualifierCall) // ... arg, opt -- keep original call in CFG so some warnings like "ofNullable for null" can work
.pop(); // ... arg
builder
.pushExpression(argument)
.boxUnbox(argument, optionalElementType);
if ("of".equals(qualifierCall.getMethodExpression().getReferenceName())) {
builder.checkNotNull(argument, NullabilityProblemKind.passingNullableToNotNullParameter);
builder.checkNotNull(argument, NullabilityProblemKind.passingNullableToNotNullParameter)
.push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true), qualifierCall)
.pop();
}
else {
builder
.dup()
.ifNull()
.push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, false), qualifierCall)
.elseBranch()
.push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true), qualifierCall)
.end()
.pop();
}
}
@@ -163,8 +163,10 @@ public class StreamChainInliner implements CallInliner {
myNext.pushResult(builder);
}
else {
builder.push(builder.getFactory()
.createTypeValue(myCall.getType(), DfaPsiUtil.getElementNullability(myCall.getType(), myCall.resolveMethod())));
DfaValue resultValue =
builder.getFactory().createTypeValue(myCall.getType(),
DfaPsiUtil.getElementNullability(myCall.getType(), myCall.resolveMethod()));
builder.push(resultValue, myCall);
}
}
@@ -214,7 +216,7 @@ public class StreamChainInliner implements CallInliner {
@Override
void pushResult(CFGBuilder builder) {
builder.push(myResult);
builder.push(myResult, myCall);
}
}
@@ -630,7 +632,7 @@ public class StreamChainInliner implements CallInliner {
.ifConditionIs(true)
.chain(b -> buildStreamCFG(b, firstStep, originalQualifier))
.end()
.push(builder.getFactory().createTypeValue(call.getType(), Nullability.NOT_NULL));
.push(builder.getFactory().createTypeValue(call.getType(), Nullability.NOT_NULL), call);
return true;
}
@@ -23,7 +23,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.PsiArrayAccessExpression;
import org.jetbrains.annotations.NotNull;
public class ArrayAccessInstruction extends Instruction {
public class ArrayAccessInstruction extends Instruction implements ExpressionPushingInstruction {
private final @NotNull DfaValue myValue;
private final @NotNull PsiArrayAccessExpression myExpression;
@@ -21,7 +21,6 @@ import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiPolyadicExpression;
import com.intellij.psi.PsiType;
@@ -31,18 +30,18 @@ import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.JavaTokenType.*;
public class BinopInstruction extends BranchingInstruction {
public class BinopInstruction extends BranchingInstruction implements ExpressionPushingInstruction {
private static final TokenSet ourSignificantOperations =
TokenSet.create(EQEQ, NE, LT, GT, LE, GE, INSTANCEOF_KEYWORD, PLUS, MINUS, AND, PERC, DIV, GTGT, GTGTGT);
private final IElementType myOperationSign;
private final @Nullable PsiType myResultType;
private final int myLastOperand;
public BinopInstruction(IElementType opSign, @Nullable PsiElement psiAnchor, @Nullable PsiType resultType) {
public BinopInstruction(IElementType opSign, @Nullable PsiExpression psiAnchor, @Nullable PsiType resultType) {
this(opSign, psiAnchor, resultType, -1);
}
public BinopInstruction(IElementType opSign, @Nullable PsiElement psiAnchor, @Nullable PsiType resultType, int lastOperand) {
public BinopInstruction(IElementType opSign, @Nullable PsiExpression psiAnchor, @Nullable PsiType resultType, int lastOperand) {
super(psiAnchor);
myResultType = resultType;
myOperationSign = ourSignificantOperations.contains(opSign) ? opSign : null;
@@ -53,7 +52,7 @@ public class BinopInstruction extends BranchingInstruction {
* @return range inside the anchor which evaluates this instruction, or null if the whole anchor evaluates this instruction
*/
@Nullable
public TextRange getAnchorRange() {
public TextRange getExpressionRange() {
if (myLastOperand != -1 && getPsiAnchor() instanceof PsiPolyadicExpression) {
PsiPolyadicExpression anchor = (PsiPolyadicExpression)getPsiAnchor();
PsiExpression[] operands = anchor.getOperands();
@@ -64,6 +63,12 @@ public class BinopInstruction extends BranchingInstruction {
return null;
}
@Nullable
@Override
public PsiExpression getExpression() {
return (PsiExpression)getPsiAnchor();
}
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
return visitor.visitBinop(this, runner, stateBefore);
@@ -0,0 +1,25 @@
// 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.instructions;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.Nullable;
/**
* An instruction which pushes a result of {@link PsiExpression} (or its part) evaluation to the stack
*
*/
public interface ExpressionPushingInstruction {
/**
* @return a PsiExpression which result is pushed to the stack, or null if this instruction is not bound to any particular PsiExpression
*/
@Nullable
PsiExpression getExpression();
/**
* @return if non-null, a part of PsiExpression, returned by {@link #getExpression()} which this instruction actually evaluates.
* Usable for polyadic expressions like {@code a == b == c}: here instruction may evaluate only {@code a == b} part.
*/
@Nullable
default TextRange getExpressionRange() {return null;}
}
@@ -19,7 +19,10 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner;
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
import com.intellij.psi.*;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,7 +33,7 @@ public class InstanceofInstruction extends BinopInstruction {
@Nullable private final PsiExpression myLeft;
@Nullable private final PsiType myCastType;
public InstanceofInstruction(PsiElement psiAnchor, @Nullable PsiExpression left, @NotNull PsiType castType) {
public InstanceofInstruction(PsiExpression psiAnchor, @Nullable PsiExpression left, @NotNull PsiType castType) {
super(JavaTokenType.INSTANCEOF_KEYWORD, psiAnchor, PsiType.BOOLEAN);
myLeft = left;
myCastType = castType;
@@ -21,14 +21,13 @@ import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class MethodCallInstruction extends Instruction {
public class MethodCallInstruction extends Instruction implements ExpressionPushingInstruction {
private static final Nullability[] EMPTY_NULLABILITY_ARRAY = new Nullability[0];
@Nullable private final PsiType myType;
@@ -120,15 +119,10 @@ public class MethodCallInstruction extends Instruction {
myReturnNullability = call instanceof PsiNewExpression ? Nullability.NOT_NULL : DfaPsiUtil.getElementNullability(myType, myTargetMethod);
}
public boolean matches(CallMatcher matcher) {
switch (myMethodType) {
case REGULAR_METHOD_CALL:
return myContext instanceof PsiMethodCallExpression && matcher.test((PsiMethodCallExpression)myContext);
case METHOD_REFERENCE_CALL:
return matcher.methodReferenceMatches((PsiMethodReferenceExpression)myContext);
default:
return false;
}
@Nullable
@Override
public PsiExpression getExpression() {
return ObjectUtils.tryCast(myContext, PsiExpression.class);
}
/**
@@ -16,10 +16,18 @@
package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.DataFlowRunner;
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
import com.intellij.psi.PsiPrefixExpression;
public class NotInstruction extends Instruction {
public class NotInstruction extends Instruction implements ExpressionPushingInstruction {
private final PsiPrefixExpression myAnchor;
public NotInstruction(PsiPrefixExpression anchor) {
myAnchor = anchor;
}
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
@@ -29,4 +37,9 @@ public class NotInstruction extends Instruction {
public String toString() {
return "NOT";
}
@Override
public PsiPrefixExpression getExpression() {
return myAnchor;
}
}
@@ -26,7 +26,7 @@ import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PushInstruction extends Instruction {
public class PushInstruction extends Instruction implements ExpressionPushingInstruction {
private final DfaValue myValue;
private final PsiExpression myPlace;
private final boolean myReferenceWrite;
@@ -50,7 +50,8 @@ public class PushInstruction extends Instruction {
return myValue;
}
public PsiExpression getPlace() {
@Override
public PsiExpression getExpression() {
return myPlace;
}
@@ -4,7 +4,7 @@ class Some {
public static void main(String[] args) {
boolean x = true, y = true, z = true, t = true;
boolean r = <warning descr="Condition 'x ^ y ^ z ^ t' is always 'false'">x ^ y ^ z ^ t</warning>;
boolean r = <warning descr="Condition 'x ^ y ^ z ^ t' is always 'false'"><warning descr="Condition 'x ^ y ^ z' is always 'true'"><warning descr="Condition 'x ^ y' is always 'false'">x ^ y</warning> ^ z</warning> ^ t</warning>;
System.out.println("r: " + r);
}