IDEA-209947 Simple non-trivial contract support

GitOrigin-RevId: a72bfec76185608927774ff5643d30970c3567bf
This commit is contained in:
Tagir Valeev
2019-05-08 13:03:51 +03:00
committed by intellij-monorepo-bot
parent 77b968c572
commit a19ec3f60c
6 changed files with 262 additions and 117 deletions
@@ -88,6 +88,18 @@ public abstract class ContractValue {
return OptionalInt.empty();
}
public String getPresentationText(PsiMethod method) {
return toString();
}
public PsiExpression findLeftPlace(PsiCallExpression call) {
return null;
}
public PsiExpression findRightPlace(PsiCallExpression call) {
return null;
}
public static ContractValue qualifier() {
return Qualifier.INSTANCE;
}
@@ -149,6 +161,18 @@ public abstract class ContractValue {
return arguments.myArguments[myIndex];
}
@Override
public String getPresentationText(PsiMethod method) {
PsiParameter[] params = method.getParameterList().getParameters();
if (myIndex == 0 && params.length == 1) {
return "parameter";
}
if (myIndex < params.length) {
return params[myIndex].getName();
}
return toString();
}
@Override
public boolean equals(Object obj) {
return obj == this || (obj instanceof Argument && myIndex == ((Argument)obj).myIndex);
@@ -217,6 +241,11 @@ public abstract class ContractValue {
return myQualifier.equals(that.myQualifier) && myField == that.myField;
}
@Override
public String getPresentationText(PsiMethod method) {
return myQualifier.getPresentationText(method) + "." + myField + (myField == SpecialField.ARRAY_LENGTH ? "" : "()");
}
@Override
public String toString() {
return myQualifier + "." + myField + "()";
@@ -316,6 +345,41 @@ public abstract class ContractValue {
return factory.createCondition(left, myRelationType, right);
}
@Override
public String getPresentationText(PsiMethod method) {
if (myLeft instanceof IndependentValue) {
return myRight.getPresentationText(method) + " " + myRelationType.getFlipped() + " " + myLeft.getPresentationText(method);
}
return myLeft.getPresentationText(method) + " " + myRelationType + " " + myRight.getPresentationText(method);
}
@Override
public PsiExpression findLeftPlace(PsiCallExpression call) {
return findPlace(call, myLeft);
}
@Override
public PsiExpression findRightPlace(PsiCallExpression call) {
return findPlace(call, myRight);
}
private static PsiExpression findPlace(PsiCallExpression call, ContractValue value) {
while (value instanceof Spec) {
value = ((Spec)value).myQualifier;
}
if (value instanceof Argument) {
PsiExpressionList list = call.getArgumentList();
if (list != null) {
PsiExpression[] args = list.getExpressions();
int index = ((Argument)value).myIndex;
if (index < args.length - 1 || (index == args.length - 1 && !MethodCallUtils.isVarArgCall(call))) {
return args[index];
}
}
}
return null;
}
@Override
public String toString() {
return myLeft + " " + myRelationType + " " + myRight;
@@ -6,7 +6,6 @@ import com.intellij.codeInspection.dataFlow.instructions.ExpressionPushingInstru
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.util.PsiTreeUtil;
@@ -340,30 +339,28 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
}
@NotNull
<T> Pair<MemoryStateChange, T> findFact(DfaValue value, DfaFactType<T> type) {
<T> FactDefinition<T> findFact(DfaValue value, DfaFactType<T> type) {
if (value instanceof DfaVariableValue) {
for (MemoryStateChange change = this; change != null; change = change.myPrevious) {
Pair<MemoryStateChange, T> factPair = factFromChange(type, change, change.myChanges.get(value));
FactDefinition<T> factPair = factFromChange(type, change, change.myChanges.get(value));
if (factPair != null) return factPair;
factPair = factFromChange(type, change, change.myBridgeChanges.get(value));
if (factPair != null) return factPair;
}
return Pair.create(null, ((DfaVariableValue)value).getInherentFacts().get(type));
return new FactDefinition<>(null, ((DfaVariableValue)value).getInherentFacts().get(type));
}
return Pair.create(null, type.fromDfaValue(value));
return new FactDefinition<>(null, type.fromDfaValue(value));
}
@Nullable
private static <T> Pair<MemoryStateChange, T> factFromChange(DfaFactType<T> type,
MemoryStateChange change,
Change varChange) {
private static <T> FactDefinition<T> factFromChange(DfaFactType<T> type, MemoryStateChange change, Change varChange) {
if (varChange != null) {
T added = varChange.myAddedFacts.get(type);
if (added != null) {
return Pair.create(change, added);
return new FactDefinition<>(change, added);
}
if (varChange.myRemovedFacts.get(type) != null) {
return Pair.create(change, null);
return new FactDefinition<>(change, null);
}
}
return null;
@@ -496,4 +493,20 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
"; Bridge changes: " + EntryStream.of(myBridgeChanges).join(": ", "\n\t", "").joining());
}
}
static class FactDefinition<T> {
final @Nullable MemoryStateChange myChange;
final @Nullable T myFact;
FactDefinition(@Nullable MemoryStateChange change, @Nullable T fact) {
myChange = change;
myFact = fact;
}
@Nullable
@Contract("!null -> !null")
T getFact(T defaultFact) {
return myFact == null ? defaultFact : myFact;
}
}
}
@@ -3,6 +3,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.FactDefinition;
import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.MemoryStateChange;
import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.Relation;
import com.intellij.codeInspection.dataFlow.instructions.*;
@@ -12,7 +13,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
@@ -111,23 +111,49 @@ public class TrackingRunner extends StandardDataFlowRunner {
StandardInstructionVisitor visitor = new StandardInstructionVisitor(true);
RunnerResult result = runner.analyzeMethodRecursively(body, visitor, ignoreAssertions);
if (result != RunnerResult.OK) return Collections.emptyList();
return ContainerUtil.createMaybeSingletonList(runner.findProblemCause(expression, type));
}
/*
TODO: 1. Find causes of other warnings:
Cause for AIOOBE
Cause for "Contract always fails"
Cause for "modifying an immutable collection"
Cause for "Collection is always empty" (separate inspection now)
TODO: 2. Describe causes in more cases:
Warning caused by contract
Warning caused by CustomMethodHandler
Warning caused by polyadic math
Warning caused by unary minus
Warning caused by final field initializer
TODO: 3. Check how it works with:
Inliners (notably: Stream API)
Boxed numbers
TODO: 4. Check for possible performance disasters (likely on some code patterns current algo might blow up)
TODO: 5. Problem when interesting state doesn't reach the current condition, need to do something with this
*/
@Nullable
private CauseItem findProblemCause(PsiExpression expression, DfaProblemType type) {
CauseItem cause = null;
for (MemoryStateChange history : runner.myHistoryForContext) {
CauseItem root = findCauseChain(expression, history, type);
for (MemoryStateChange history : myHistoryForContext) {
CauseItem item = new CauseItem(type, expression);
if (history.getExpression() == expression) {
item.addChildren(type.findCauses(this, expression, history));
}
if (cause == null) {
cause = root;
cause = item;
} else {
cause = cause.merge(root);
if (cause == null) return Collections.emptyList();
cause = cause.merge(item);
if (cause == null) return null;
}
}
return Collections.singletonList(cause);
return cause;
}
public abstract static class DfaProblemType {
public abstract String toString();
CauseItem[] findCauses(PsiExpression expression, MemoryStateChange history) {
CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) {
return new CauseItem[0];
}
}
@@ -285,12 +311,12 @@ public class TrackingRunner extends StandardDataFlowRunner {
public static class CastDfaProblemType extends DfaProblemType {
@Override
public CauseItem[] findCauses(PsiExpression expression, MemoryStateChange history) {
public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) {
if (expression instanceof PsiTypeCastExpression) {
PsiType expressionType = expression.getType();
MemoryStateChange operandPush = history.findExpressionPush(((PsiTypeCastExpression)expression).getOperand());
if (operandPush != null) {
return new CauseItem[]{findTypeCause(operandPush, expressionType, false)};
return new CauseItem[]{runner.findTypeCause(operandPush, expressionType, false)};
}
}
return new CauseItem[0];
@@ -303,10 +329,10 @@ public class TrackingRunner extends StandardDataFlowRunner {
public static class NullableDfaProblemType extends DfaProblemType {
@Override
public CauseItem[] findCauses(PsiExpression expression, MemoryStateChange history) {
Pair<MemoryStateChange, DfaNullability> nullability = history.findFact(history.myTopOfStack, DfaFactType.NULLABILITY);
if (nullability.second == DfaNullability.NULLABLE || nullability.second == DfaNullability.NULL) {
return new CauseItem[]{findNullabilityCause(history, nullability.first, nullability.second)};
public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) {
FactDefinition<DfaNullability> nullability = history.findFact(history.myTopOfStack, DfaFactType.NULLABILITY);
if (nullability.myFact == DfaNullability.NULLABLE || nullability.myFact == DfaNullability.NULL) {
return new CauseItem[]{runner.findNullabilityCause(history, nullability.myFact)};
}
return new CauseItem[0];
}
@@ -334,8 +360,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
@Override
public CauseItem[] findCauses(PsiExpression expression, MemoryStateChange history) {
return findConstantValueCause(expression, history, myValue);
public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) {
return runner.findConstantValueCause(expression, history, myValue);
}
@Override
@@ -357,34 +383,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
}
/*
TODO: 1. Find causes of other warnings:
Cause for AIOOBE
Cause for "Contract always fails"
Cause for "modifying an immutable collection"
Cause for "Collection is always empty" (separate inspection now)
TODO: 2. Describe causes in more cases:
Warning caused by contract
Warning caused by CustomMethodHandler
Warning caused by polyadic math
Warning caused by unary minus
Warning caused by final field initializer
TODO: 3. Check how it works with:
Inliners (notably: Stream API)
Boxed numbers
TODO: 4. Check for possible performance disasters (likely on some code patterns current algo might blow up)
TODO: 5. Problem when interesting state doesn't reach the current condition, need to do something with this
*/
@NotNull
private static CauseItem findCauseChain(PsiExpression expression, MemoryStateChange history, DfaProblemType type) {
CauseItem root = new CauseItem(type, expression);
if (history.getExpression() != expression) return root;
root.addChildren(type.findCauses(expression, history));
return root;
}
@NotNull
private static CauseItem[] findConstantValueCause(PsiExpression expression, MemoryStateChange history, Object expectedValue) {
private CauseItem[] findConstantValueCause(PsiExpression expression, MemoryStateChange history, Object expectedValue) {
if (expression instanceof PsiLiteralExpression) return new CauseItem[0];
Object constantExpressionValue = ExpressionUtils.computeConstantExpression(expression);
DfaValue value = history.myTopOfStack;
@@ -449,9 +449,9 @@ public class TrackingRunner extends StandardDataFlowRunner {
return new CauseItem("'" + target + "' was assigned" + suffix, anchor);
}
private static CauseItem[] findBooleanResultCauses(PsiExpression expression,
MemoryStateChange history,
boolean value) {
private CauseItem[] findBooleanResultCauses(PsiExpression expression,
MemoryStateChange history,
boolean value) {
if (BoolUtils.isNegation(expression)) {
PsiExpression negated = BoolUtils.getNegated(expression);
if (negated != null) {
@@ -522,8 +522,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
if (operandHistory != null) {
DfaValue operandValue = operandHistory.myTopOfStack;
if (!value) {
Pair<MemoryStateChange, DfaNullability> nullability = operandHistory.findFact(operandValue, DfaFactType.NULLABILITY);
if (nullability.second == DfaNullability.NULL) {
FactDefinition<DfaNullability> nullability = operandHistory.findFact(operandValue, DfaFactType.NULLABILITY);
if (nullability.myFact == DfaNullability.NULL) {
CauseItem causeItem = new CauseItem("value '" + operand.getText() + "' is always 'null'", operand);
causeItem.addChildren(findConstantValueCause(operand, operandHistory, null));
return new CauseItem[]{causeItem};
@@ -544,20 +544,20 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
@Nullable
private static CauseItem findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) {
private CauseItem findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) {
PsiExpression operand = Objects.requireNonNull(operandHistory.getExpression());
DfaValue operandValue = operandHistory.myTopOfStack;
DfaPsiType wanted = operandValue.getFactory().createDfaType(type);
DfaPsiType wanted = getFactory().createDfaType(type);
Pair<MemoryStateChange, TypeConstraint> fact = operandHistory.findFact(operandValue, DfaFactType.TYPE_CONSTRAINT);
String explanation = fact.second == null ? null : fact.second.getAssignabilityExplanation(wanted, isInstance);
FactDefinition<TypeConstraint> fact = operandHistory.findFact(operandValue, DfaFactType.TYPE_CONSTRAINT);
String explanation = fact.myFact == null ? null : fact.myFact.getAssignabilityExplanation(wanted, isInstance);
while (explanation != null) {
MemoryStateChange causeLocation = fact.first;
MemoryStateChange causeLocation = fact.myChange;
if (causeLocation == null) break;
MemoryStateChange prevHistory = causeLocation.myPrevious;
if (prevHistory == null) break;
fact = prevHistory.findFact(operandValue, DfaFactType.TYPE_CONSTRAINT);
TypeConstraint prevConstraint = fact.second == null ? TypeConstraint.empty() : fact.second;
TypeConstraint prevConstraint = fact.getFact(TypeConstraint.empty());
String prevExplanation = prevConstraint.getAssignabilityExplanation(wanted, isInstance);
if (prevExplanation == null) {
CauseItem causeItem = new CauseItem(explanation, operand);
@@ -570,28 +570,32 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
@NotNull
private static CauseItem[] findRelationCause(RelationType relationType,
MemoryStateChange leftChange,
MemoryStateChange rightChange) {
private CauseItem[] findRelationCause(RelationType relationType, MemoryStateChange leftChange, MemoryStateChange rightChange) {
return findRelationCause(relationType, leftChange, leftChange.myTopOfStack, rightChange, rightChange.myTopOfStack);
}
@NotNull
private CauseItem[] findRelationCause(RelationType relationType,
MemoryStateChange leftChange, DfaValue leftValue,
MemoryStateChange rightChange, DfaValue rightValue) {
ProgressManager.checkCanceled();
DfaValue leftValue = leftChange.myTopOfStack;
DfaValue rightValue = rightChange.myTopOfStack;
Pair<MemoryStateChange, DfaNullability> leftNullability = leftChange.findFact(leftValue, DfaFactType.NULLABILITY);
Pair<MemoryStateChange, DfaNullability> rightNullability = rightChange.findFact(rightValue, DfaFactType.NULLABILITY);
if ((leftNullability.second == DfaNullability.NULL && rightNullability.second == DfaNullability.NOT_NULL) ||
(rightNullability.second == DfaNullability.NULL && leftNullability.second == DfaNullability.NOT_NULL)) {
return new CauseItem[]{findNullabilityCause(leftChange, leftNullability.first, leftNullability.second),
findNullabilityCause(rightChange, rightNullability.first, rightNullability.second)};
FactDefinition<DfaNullability> leftNullability = leftChange.findFact(leftValue, DfaFactType.NULLABILITY);
FactDefinition<DfaNullability> rightNullability = rightChange.findFact(rightValue, DfaFactType.NULLABILITY);
if ((leftNullability.myFact == DfaNullability.NULL && rightNullability.myFact == DfaNullability.NOT_NULL) ||
(rightNullability.myFact == DfaNullability.NULL && leftNullability.myFact == DfaNullability.NOT_NULL)) {
return new CauseItem[]{
findNullabilityCause(leftChange, leftNullability.myFact),
findNullabilityCause(rightChange, rightNullability.myFact)};
}
Pair<MemoryStateChange, LongRangeSet> leftRange = leftChange.findFact(leftValue, DfaFactType.RANGE);
Pair<MemoryStateChange, LongRangeSet> rightRange = rightChange.findFact(rightValue, DfaFactType.RANGE);
if (leftRange.second != null && rightRange.second != null) {
LongRangeSet fromRelation = rightRange.second.fromRelation(relationType.getNegated());
if (fromRelation != null && !fromRelation.intersects(leftRange.second)) {
FactDefinition<LongRangeSet> leftRange = leftChange.findFact(leftValue, DfaFactType.RANGE);
FactDefinition<LongRangeSet> rightRange = rightChange.findFact(rightValue, DfaFactType.RANGE);
if (leftRange.myFact != null && rightRange.myFact != null) {
LongRangeSet fromRelation = rightRange.myFact.fromRelation(relationType.getNegated());
if (fromRelation != null && !fromRelation.intersects(leftRange.myFact)) {
return new CauseItem[]{
findRangeCause(leftChange, leftRange.first, leftRange.second, "left operand is %s"),
findRangeCause(rightChange, rightRange.first, rightRange.second, "right operand is %s")};
findRangeCause(leftChange, leftRange.myFact, "left operand is %s"),
findRangeCause(rightChange, rightRange.myFact, "right operand is %s")};
}
}
if (leftValue instanceof DfaVariableValue) {
@@ -626,9 +630,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
return new CauseItem[0];
}
private static CauseItem findRelationCause(MemoryStateChange change,
DfaVariableValue value,
Relation relation, MemoryStateChange counterPartChange) {
private CauseItem findRelationCause(MemoryStateChange change, DfaVariableValue value,
Relation relation, MemoryStateChange counterPartChange) {
Instruction instruction = change.myInstruction;
String condition = value + " " + relation;
if (instruction instanceof AssignInstruction) {
@@ -667,12 +670,12 @@ public class TrackingRunner extends StandardDataFlowRunner {
return null;
}
private static CauseItem findNullabilityCause(MemoryStateChange factUse, MemoryStateChange factDef, DfaNullability nullability) {
private CauseItem findNullabilityCause(MemoryStateChange factUse, DfaNullability nullability) {
PsiExpression expression = factUse.getExpression();
if (expression instanceof PsiTypeCastExpression) {
MemoryStateChange operandPush = factUse.findSubExpressionPush(((PsiTypeCastExpression)expression).getOperand());
if (operandPush != null) {
return findNullabilityCause(operandPush, factDef, nullability);
return findNullabilityCause(operandPush, nullability);
}
}
if (expression instanceof PsiMethodCallExpression) {
@@ -701,6 +704,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
}
}
FactDefinition<DfaNullability> info = factUse.findFact(factUse.myTopOfStack, DfaFactType.NULLABILITY);
MemoryStateChange factDef = info.myFact == nullability ? info.myChange : null;
if (nullability == DfaNullability.NOT_NULL) {
String explanation = getObviouslyNonNullExplanation(expression);
if (explanation != null) {
@@ -726,8 +731,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression);
if (rValuePush != null) {
CauseItem assignmentItem = createAssignmentCause((AssignInstruction)factDef.myInstruction, value);
Pair<MemoryStateChange, DfaNullability> rValueFact = rValuePush.findFact(rValuePush.myTopOfStack, DfaFactType.NULLABILITY);
assignmentItem.addChildren(findNullabilityCause(rValuePush, rValueFact.first, nullability));
assignmentItem.addChildren(findNullabilityCause(rValuePush, nullability));
return assignmentItem;
}
}
@@ -741,8 +745,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
return null;
}
private static CauseItem fromMemberNullability(DfaNullability nullability,
PsiModifierListOwner owner,
private static CauseItem fromMemberNullability(DfaNullability nullability, PsiModifierListOwner owner,
String memberName, PsiElement anchor) {
if (owner != null) {
NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(owner.getProject()).findEffectiveNullabilityInfo(owner);
@@ -800,7 +803,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
return null;
}
private static CauseItem fromCallContract(MemoryStateChange history, PsiCallExpression call, ContractReturnValue contractReturnValue) {
private CauseItem fromCallContract(MemoryStateChange history, PsiCallExpression call, ContractReturnValue contractReturnValue) {
PsiMethod method = call.resolveMethod();
if (method == null) return null;
List<? extends MethodContract> contracts =
@@ -808,23 +811,60 @@ public class TrackingRunner extends StandardDataFlowRunner {
mc -> contractReturnValue.isSuperValueOf(mc.getReturnValue()));
boolean explicit = JavaMethodContractUtil.hasExplicitContractAnnotation(method);
if (call instanceof PsiMethodCallExpression) {
String prefix = "according to " + (explicit ? "contract" : "inferred contract");
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)call).getMethodExpression();
String name = methodExpression.getReferenceName();
for (MethodContract contract : contracts) {
if (contract.isTrivial()) {
return new CauseItem("according to " + (explicit ? "contract" : "inferred contract") +
return new CauseItem(prefix +
", method '" + name + "' always returns '" + contract.getReturnValue() + "' value",
methodExpression.getReferenceNameElement());
}
}
if (contracts.size() == 1) {
List<ContractValue> conditions = contracts.get(0).getConditions();
String conditionsText = StringUtil.join(conditions, c -> c.getPresentationText(method), " and ");
CauseItem causeItem = new CauseItem(
prefix + ", method '" + name + "' returns '" + contracts.get(0).getReturnValue() + "' value when " + conditionsText,
methodExpression.getReferenceNameElement());
for (ContractValue condition : conditions) {
DfaRelationValue relation = ObjectUtils.tryCast(condition.fromCall(getFactory(), call), DfaRelationValue.class);
PsiExpression leftPlace = condition.findLeftPlace(call);
MemoryStateChange leftPush = history.findExpressionPush(leftPlace);
PsiExpression rightPlace = condition.findRightPlace(call);
MemoryStateChange rightPush = history.findExpressionPush(rightPlace);
if (relation != null) {
DfaValue left = relation.getLeftOperand();
DfaValue right = relation.getRightOperand();
RelationType type = relation.getRelation();
MemoryStateChange leftChange = history;
MemoryStateChange rightChange = history;
if (leftPush != null) {
if (leftPush.myTopOfStack == left) {
leftChange = leftPush;
}
else if (leftPush.myTopOfStack == right) {
rightChange = leftPush;
}
}
if (rightPush != null) {
if (rightPush.myTopOfStack == right) {
rightChange = rightPush;
}
else if (rightPush.myTopOfStack == left) {
leftChange = rightPush;
}
}
causeItem.addChildren(findRelationCause(type, leftChange, left, rightChange, right));
}
}
return causeItem;
}
}
return null;
}
private static CauseItem findRangeCause(MemoryStateChange factUse,
MemoryStateChange factDef,
LongRangeSet range,
String template) {
private static CauseItem findRangeCause(MemoryStateChange factUse, LongRangeSet range, String template) {
DfaValue value = factUse.myTopOfStack;
if (value instanceof DfaVariableValue) {
VariableDescriptor descriptor = ((DfaVariableValue)value).getDescriptor();
@@ -861,15 +901,15 @@ public class TrackingRunner extends StandardDataFlowRunner {
PsiExpression operand = ((PsiTypeCastExpression)expression).getOperand();
MemoryStateChange operandPush = factUse.findExpressionPush(operand);
if (operandPush != null) {
Pair<MemoryStateChange, LongRangeSet> operandInfo = operandPush.findFact(operandPush.myTopOfStack, DfaFactType.RANGE);
LongRangeSet operandRange = operandInfo.second == null ? LongRangeSet.fromType(type) : operandInfo.second;
FactDefinition<LongRangeSet> operandInfo = operandPush.findFact(operandPush.myTopOfStack, DfaFactType.RANGE);
LongRangeSet operandRange = operandInfo.myFact == null ? LongRangeSet.fromType(type) : operandInfo.myFact;
if (operandRange != null) {
LongRangeSet result = operandRange.castTo((PsiPrimitiveType)type);
if (range.equals(result)) {
CauseItem cause =
new CauseItem("result of '(" + type.getCanonicalText() + ")' cast is " + range.getPresentationText(null), expression);
if (!operandRange.equals(LongRangeSet.fromType(operand.getType()))) {
cause.addChildren(findRangeCause(operandPush, operandInfo.first, operandRange, "cast operand is %s"));
cause.addChildren(findRangeCause(operandPush, operandRange, "cast operand is %s"));
}
return cause;
}
@@ -888,27 +928,21 @@ public class TrackingRunner extends StandardDataFlowRunner {
MemoryStateChange leftPush = factUse.findExpressionPush(left);
MemoryStateChange rightPush = factUse.findExpressionPush(right);
if (leftPush != null && rightPush != null) {
DfaValue leftValue = leftPush.myTopOfStack;
DfaValue rightValue = rightPush.myTopOfStack;
Pair<MemoryStateChange, LongRangeSet> leftSet = leftPush.findFact(leftValue, DfaFactType.RANGE);
Pair<MemoryStateChange, LongRangeSet> rightSet = rightPush.findFact(rightValue, DfaFactType.RANGE);
FactDefinition<LongRangeSet> leftSet = leftPush.findFact(leftPush.myTopOfStack, DfaFactType.RANGE);
FactDefinition<LongRangeSet> rightSet = rightPush.findFact(rightPush.myTopOfStack, DfaFactType.RANGE);
LongRangeSet fromType = Objects.requireNonNull(LongRangeSet.fromType(type));
if (leftSet.second == null) {
leftSet = Pair.create(null, fromType);
}
if (rightSet.second == null) {
rightSet = Pair.create(null, fromType);
}
LongRangeSet result = leftSet.second.binOpFromToken(binOp.getOperationTokenType(), rightSet.second, isLong);
LongRangeSet leftRange = leftSet.getFact(fromType);
LongRangeSet rightRange = rightSet.getFact(fromType);
LongRangeSet result = leftRange.binOpFromToken(binOp.getOperationTokenType(), rightRange, isLong);
if (range.equals(result)) {
CauseItem cause = new CauseItem("result of '" + binOp.getOperationSign().getText() +
"' is " + range.getPresentationText(type), factUse);
CauseItem leftCause = null, rightCause = null;
if (!leftSet.second.equals(fromType)) {
leftCause = findRangeCause(leftPush, leftSet.first, leftSet.second, "left operand is %s");
if (!leftRange.equals(fromType)) {
leftCause = findRangeCause(leftPush, leftRange, "left operand is %s");
}
if (!rightSet.second.equals(fromType)) {
rightCause = findRangeCause(rightPush, rightSet.first, rightSet.second, "right operand is %s");
if (!rightRange.equals(fromType)) {
rightCause = findRangeCause(rightPush, rightRange, "right operand is %s");
}
cause.addChildren(leftCause, rightCause);
return cause;
@@ -919,6 +953,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
}
String rangeText = range.getPresentationText(expression != null ? expression.getType() : null);
CauseItem item = new CauseItem(String.format(template, rangeText), factUse);
FactDefinition<LongRangeSet> info = factUse.findFact(value, DfaFactType.RANGE);
MemoryStateChange factDef = range.equals(info.myFact) ? info.myChange : null;
if (factDef != null) {
if (factDef.myInstruction instanceof AssignInstruction && factDef.myTopOfStack == value) {
PsiExpression rExpression = ((AssignInstruction)factDef.myInstruction).getRExpression();
@@ -926,8 +962,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression);
if (rValuePush != null) {
CauseItem assignmentItem = createAssignmentCause((AssignInstruction)factDef.myInstruction, value);
Pair<MemoryStateChange, LongRangeSet> rValueFact = rValuePush.findFact(rValuePush.myTopOfStack, DfaFactType.RANGE);
assignmentItem.addChildren(findRangeCause(rValuePush, rValueFact.first, range, "Value is %s"));
assignmentItem.addChildren(findRangeCause(rValuePush, range, "Value is %s"));
item.addChildren(assignmentItem);
return item;
}
@@ -0,0 +1,16 @@
/*
Value is always false (Objects.isNull(s); line#12)
According to inferred contract, method 'isNull' returns 'false' value when parameter != null (isNull; line#12)
's' was dereferenced (s; line#11)
*/
import java.util.Objects;
class Test {
void test(String s) {
System.out.println(s.trim());
if (<selection>Objects.isNull(s)</selection>) {
}
}
}
@@ -0,0 +1,15 @@
/*
Value is always false (Objects.isNull(s.trim()); line#11)
According to inferred contract, method 'isNull' returns 'false' value when parameter != null (isNull; line#11)
Method 'trim' is externally annotated as 'non-null' (trim; line#11)
*/
import java.util.Objects;
class Test {
void test(String s) {
if (<selection>Objects.isNull(s.trim())</selection>) {
}
}
}
@@ -161,4 +161,6 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
public void testNumericCast() { doTest(); }
public void testNumericCast2() { doTest(); }
public void testNumericWidening() { doTest(); }
public void testSimpleContract() { doTest(); }
public void testSimpleContract2() { doTest(); }
}