mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-02 14:38:30 +07:00
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:
+2
-2
@@ -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>
|
||||
|
||||
+16
-76
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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));
|
||||
|
||||
+28
-23
@@ -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) {
|
||||
|
||||
+24
-28
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-11
@@ -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) -> {
|
||||
|
||||
+24
-5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+60
-46
@@ -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();
|
||||
}
|
||||
|
||||
+16
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+10
-5
@@ -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);
|
||||
|
||||
+25
@@ -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;}
|
||||
}
|
||||
+5
-2
@@ -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;
|
||||
|
||||
+5
-11
@@ -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
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user