From 53abafd8f0bd66f51a1936e553dbefe5dad18d01 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 14 Jun 2018 17:30:08 +0700 Subject: [PATCH] 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 (cherry picked from commit 3e4cd658e916363b032f71670bf5d92e2bd3db4d) --- .../guess/impl/GuessManagerImpl.java | 4 +- .../codeInspection/dataFlow/CFGBuilder.java | 15 +++ .../dataFlow/CommonDataflow.java | 92 +++------------ .../dataFlow/ControlFlowAnalyzer.java | 6 +- .../dataFlow/CustomMethodHandlers.java | 51 +++++---- .../dataFlow/DataFlowInspectionBase.java | 52 ++++----- .../dataFlow/DataFlowInstructionVisitor.java | 43 +++++-- .../codeInspection/dataFlow/DfaUtil.java | 3 +- .../dataFlow/InstructionVisitor.java | 29 ++++- .../dataFlow/StandardInstructionVisitor.java | 106 ++++++++++-------- .../inliner/OptionalChainInliner.java | 23 ++-- .../dataFlow/inliner/StreamChainInliner.java | 10 +- .../instructions/ArrayAccessInstruction.java | 2 +- .../instructions/BinopInstruction.java | 15 ++- .../ExpressionPushingInstruction.java | 25 +++++ .../instructions/InstanceofInstruction.java | 7 +- .../instructions/MethodCallInstruction.java | 16 +-- .../dataFlow/instructions/NotInstruction.java | 19 +++- .../instructions/PushInstruction.java | 5 +- .../inspection/dataFlow/fixture/Xor.java | 2 +- 20 files changed, 293 insertions(+), 232 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java 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 0459e035fcd5..6b4dc955d255 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 @@ -564,11 +564,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; 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 ffd8d3bcd849..b0398fe710a8 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,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. + *

+ * 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 *

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 86f70c6e7c12..882f97aa06cb 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,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.NULLABILITY, DfaNullability.NOT_NULL); } 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; } } } 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 e60f8e7e5183..071e2b95b199 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 @@ -1477,8 +1477,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)); } } @@ -1955,7 +1955,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)); 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 a8f212296a4e..db11486872b6 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 @@ -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 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 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 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 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 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) { 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 e01af5243262..4c3f59693008 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 @@ -482,7 +482,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool Set 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)) { @@ -494,20 +494,16 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, Set reportedAnchors, - 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: + 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)); } } }); @@ -658,10 +654,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)), @@ -669,19 +667,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); } } 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 c18324c0f50d..4bfc6fc1d15e 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,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 myCCEInstructions = ContainerUtil.newHashSet(); private final Map myFailingCalls = new HashMap<>(); private final Map myBooleanCalls = new HashMap<>(); - private final Map myOfNullableCalls = 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<>(); @@ -123,7 +124,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor { return myArrayStoreProblems; } - Map getOfNullableCalls() { + Map 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.NULLABILITY, DfaNullability.NULLABLE); - 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)); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index 266f27ea27e2..cdeef9777f23 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -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) -> { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java index 7f90e1f2fa17..a23a7fcff901 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index f47acd58353c..eebd4814627f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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 returnValues = possibleReturnValues(callArguments, state, contracts, runner.getFactory(), defaultResult); + Stream 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 possibleReturnValues(DfaCallArguments callArguments, - DfaMemoryState state, - List contracts, - DfaValueFactory factory, DfaValue defaultResult) { + private Stream possibleReturnValues(DfaCallArguments callArguments, + DfaMemoryState state, + List contracts, + DfaValueFactory factory, + DfaValue defaultResult, + PsiMethodReferenceExpression methodRef) { Set currentStates = Collections.singleton(new DfaCallState(state.createClosureState(), callArguments)); Set 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 addContractResults(MethodContract contract, - Set states, - DfaValueFactory factory, - Set finalStates, - DfaValue defaultResult) { + private Set addContractResults(MethodContract contract, + Set states, + DfaValueFactory factory, + Set 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); } } @@ -632,7 +639,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); @@ -644,10 +651,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(); @@ -656,12 +663,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); @@ -769,11 +776,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); @@ -812,11 +819,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); @@ -830,12 +837,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); @@ -869,12 +876,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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java index 2c1b2e355788..1c0e3e06c80f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -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(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java index 5f1b998b11bf..1c218a1dc84d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -169,8 +169,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); } } @@ -220,7 +222,7 @@ public class StreamChainInliner implements CallInliner { @Override void pushResult(CFGBuilder builder) { - builder.push(myResult); + builder.push(myResult, myCall); } } @@ -655,7 +657,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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java index f6731b89308a..b926a805dc91 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java index 6741bd4bfdf1..6960b25103c7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java new file mode 100644 index 000000000000..b2605afd260b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java @@ -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;} +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/InstanceofInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/InstanceofInstruction.java index 2631084367e6..01b51c0cce90 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/InstanceofInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/InstanceofInstruction.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java index 3ae7a6bf8116..51344892dbd8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -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); } /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/NotInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/NotInstruction.java index 5cb17b0facee..090112be3243 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/NotInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/NotInstruction.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java index b282d9cb4c9f..2b0a0c559ed6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java @@ -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; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/Xor.java b/java/java-tests/testData/inspection/dataFlow/fixture/Xor.java index 31b8f906a7cd..a13e5bf8048e 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/Xor.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/Xor.java @@ -4,7 +4,7 @@ class Some { public static void main(String[] args) { boolean x = true, y = true, z = true, t = true; - boolean r = x ^ y ^ z ^ t; + boolean r = x ^ y ^ z ^ t; System.out.println("r: " + r); }