IDEA-209947 Try to deduce relation from contracts

GitOrigin-RevId: 93c921e8b72b89658aadcc4a665cef820e25ebe7
This commit is contained in:
Tagir Valeev
2019-05-20 14:07:12 +03:00
committed by intellij-monorepo-bot
parent 0fdc4afedf
commit b415bd616a
5 changed files with 75 additions and 24 deletions
@@ -1342,7 +1342,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
@Override
public <T> void forceVariableFact(@NotNull DfaVariableValue var, @NotNull DfaFactType<T> factType, @Nullable T value) {
DfaVariableState state = getVariableState(var);
removeEquivalenceForVariableAndWrappers(var);
if (factType.equals(DfaFactType.NULLABILITY) && value == DfaNullability.NOT_NULL && isNull(var)) {
removeEquivalenceForVariableAndWrappers(var);
}
setVariableState(var, state.withFact(factType, value));
updateEqClassesByState(var);
}
@@ -823,26 +823,31 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
PsiExpression expression = change.getExpression();
if (expression != null) {
Collection<DfaRelationValue> relations = Collections.emptyList();
if (expression instanceof PsiBinaryExpression) {
DfaRelationValue rel = fromBinaryExpression(change, (PsiBinaryExpression)expression);
DfaRelationValue rel = getBinaryExpressionRelation(change, (PsiBinaryExpression)expression);
if (rel != null) {
if (isSameRelation(rel, value, relation)) {
return new CauseItem(new CustomDfaProblemType("condition '" + condition + "' was checked before"), expression);
}
List<DfaRelationValue> chain = findDeductionChain(change, Collections.singletonList(rel), value, relation);
if (!chain.isEmpty()) {
CauseItem[] result = new CauseItem[0];
for (DfaRelationValue deduced : chain) {
CauseItem[] cause =
findRelationCause(deduced.getRelation(), change, deduced.getLeftOperand(), change, deduced.getRightOperand());
result = ArrayUtil.mergeArrays(result, cause);
}
if (result.length > 1) {
CauseItem item = new CauseItem(new CustomDfaProblemType("condition '" + condition + "' was deduced"), (PsiElement)null);
item.addChildren(result);
return item;
}
}
relations = Collections.singleton(rel);
}
}
if (expression instanceof PsiCallExpression) {
relations = getCallRelations((PsiCallExpression)expression);
}
List<DfaRelationValue> chain = findDeductionChain(change, relations, value, relation);
if (!chain.isEmpty()) {
CauseItem[] result = new CauseItem[0];
for (DfaRelationValue deduced : chain) {
CauseItem[] cause =
findRelationCause(deduced.getRelation(), change, deduced.getLeftOperand(), change, deduced.getRightOperand());
result = ArrayUtil.mergeArrays(result, cause);
}
if (result.length > 1) {
CauseItem item = new CauseItem(new CustomDfaProblemType("condition '" + condition + "' was deduced"), (PsiElement)null);
item.addChildren(result);
return item;
}
}
return new CauseItem(new CustomDfaProblemType("result of '" + condition + "' is known from #ref"), expression);
@@ -851,16 +856,19 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
private List<DfaRelationValue> findDeductionChain(MemoryStateChange change,
List<DfaRelationValue> knownRelations,
Collection<DfaRelationValue> knownRelations,
DfaVariableValue value,
Relation relation) {
for (DfaRelationValue rel : knownRelations) {
if (isSameRelation(rel, value, relation)) {
continue;
}
for (Map.Entry<DfaVariableValue, TrackingDfaMemoryState.Change> entry : change.myChanges.entrySet()) {
DfaVariableValue actualVar = entry.getKey();
for (Relation actualRelation : entry.getValue().myAddedRelations) {
if (isSameRelation(rel, actualVar, actualRelation)) {
DfaValue left;
DfaValue right = actualRelation.myCounterpart;
DfaValue right;
RelationType type;
if (actualRelation.myRelationType == RelationType.EQ ||
(relation.myRelationType != RelationType.NE && relation.myRelationType == actualRelation.myRelationType)) {
@@ -873,11 +881,20 @@ public class TrackingRunner extends StandardDataFlowRunner {
continue;
}
if (actualVar == value) {
left = relation.myCounterpart;
type = Objects.requireNonNull(type.getFlipped());
left = actualRelation.myCounterpart;
right = relation.myCounterpart;
}
else if (actualVar == relation.myCounterpart) {
left = value;
right = actualRelation.myCounterpart;
}
else if (actualRelation.myCounterpart == relation.myCounterpart) {
left = value;
right = actualVar;
}
else if (actualRelation.myCounterpart == value) {
left = actualVar;
right = relation.myCounterpart;
}
else {
continue;
@@ -910,7 +927,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
@Nullable
private DfaRelationValue fromBinaryExpression(MemoryStateChange change, PsiBinaryExpression binOp) {
private DfaRelationValue getBinaryExpressionRelation(MemoryStateChange change, PsiBinaryExpression binOp) {
PsiExpression lOperand = binOp.getLOperand();
PsiExpression rOperand = binOp.getROperand();
MemoryStateChange leftPos = change.findExpressionPush(lOperand);
@@ -926,6 +943,18 @@ public class TrackingRunner extends StandardDataFlowRunner {
return null;
}
private Collection<DfaRelationValue> getCallRelations(PsiCallExpression callExpression) {
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts(callExpression);
Set<DfaRelationValue> results = new LinkedHashSet<>();
for (MethodContract contract : contracts) {
for (ContractValue condition : contract.getConditions()) {
DfaValue rel = condition.fromCall(getFactory(), callExpression);
ContainerUtil.addIfNotNull(results, ObjectUtils.tryCast(rel, DfaRelationValue.class));
}
}
return results;
}
private CauseItem findNullabilityCause(MemoryStateChange factUse, DfaNullability nullability) {
PsiExpression expression = factUse.getExpression();
if (expression instanceof PsiTypeCastExpression) {
@@ -2,7 +2,7 @@
Value is always false (b == c; line#14)
Condition 'b != c' was deduced
Condition 'b != a' was checked before (a == b; line#11)
and condition 'c == a' was checked before (c == a; line#13)
and condition 'a == c' was checked before (c == a; line#13)
*/
public class T {
@@ -0,0 +1,18 @@
/*
Value is always false (b.equals("x"); line#15)
According to hard-coded contract, method 'equals' returns 'false' value when this != parameter (equals; line#15)
Condition 'b != "x"' was deduced
Result of 'b != a' is known from line #12 (a.equals(b); line#12)
and result of 'a == "x"' is known from line #14 (a.equals("x"); line#14)
*/
public class T {
void test(String a, String b) {
if(a.equals(b)) {
} else if (a.equals("x")) {
if (<selection>b.equals("x")</selection>) {}
}
}
}
@@ -55,8 +55,9 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
assertTrue("Selected element is not an expression: " + selectedText, element instanceof PsiExpression);
PsiExpression expression = (PsiExpression)element;
TrackingRunner.DfaProblemType problemType = getProblemType(selectedText, expression);
List<TrackingRunner.CauseItem> items = TrackingRunner.findProblemCause(true, false, expression, problemType);
String dump = StreamEx.of(items).map(item -> item.dump(getEditor().getDocument())).joining("\n----\n");
TrackingRunner.CauseItem item = TrackingRunner.findProblemCause(true, false, expression, problemType);
assertNotNull(item);
String dump = item.dump(getEditor().getDocument());
PsiComment firstComment = PsiTreeUtil.findChildOfType(file, PsiComment.class);
if (firstComment == null) {
fail("Comment not found");
@@ -175,4 +176,5 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
public void testConstructorSimple() { doTest(); }
public void testConstructorDependOnInitializer() { doTest(); }
public void testBasedOnPreviousRelation() { doTest(); }
public void testBasedOnPreviousRelationContracts() { doTest(); }
}