Dataflow refactoring: DfaFactMap => DfType

GitOrigin-RevId: 3ef9e633dc92929191cc5724109b3575bf6d12a1
This commit is contained in:
Tagir Valeev
2019-12-11 09:07:22 +00:00
committed by intellij-monorepo-bot
parent 8899d33dbf
commit c79e23a713
123 changed files with 3849 additions and 2577 deletions
@@ -2,6 +2,8 @@
package com.intellij.debugger.engine.dfaassist;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.debugger.engine.JVMNameUtil;
import com.intellij.openapi.project.Project;
@@ -216,9 +218,9 @@ class DebuggerDfaRunner extends DataFlowRunner {
DfaValueFactory factory,
DfaMemoryState state, DfaVariableValue var,
Value jdiValue) {
DfaConstValue val = getConstantValue(psiFactory, factory, jdiValue);
if (val != null) {
state.applyCondition(var.eq(val));
DfType val = getConstantValue(psiFactory, factory, jdiValue);
if (val != DfTypes.TOP) {
state.applyCondition(var.eq(factory.fromDfType(val)));
}
if (jdiValue instanceof ObjectReference) {
ObjectReference ref = (ObjectReference)jdiValue;
@@ -227,8 +229,7 @@ class DebuggerDfaRunner extends DataFlowRunner {
if (psiType == null) return;
TypeConstraint exactType = TypeConstraint.exact(factory.createDfaType(psiType));
String name = type.name();
state.applyFact(var, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
state.applyFact(var, DfaFactType.TYPE_CONSTRAINT, exactType);
state.meetDfType(var, exactType.asDfType().meet(DfTypes.NOT_NULL_OBJECT));
if (jdiValue instanceof ArrayReference) {
DfaValue dfaLength = SpecialField.ARRAY_LENGTH.createValue(factory, var);
int jdiLength = ((ArrayReference)jdiValue).length();
@@ -291,28 +292,31 @@ class DebuggerDfaRunner extends DataFlowRunner {
}
}
@Nullable
private DfaConstValue getConstantValue(PsiElementFactory psiFactory, DfaValueFactory factory, Value jdiValue) {
@NotNull
private DfType getConstantValue(PsiElementFactory psiFactory, DfaValueFactory factory, Value jdiValue) {
if (jdiValue == NullConst) {
return factory.getConstFactory().getNull();
return DfTypes.NULL;
}
if (jdiValue instanceof BooleanValue) {
return factory.getBoolean(((BooleanValue)jdiValue).value());
return DfTypes.booleanValue(((BooleanValue)jdiValue).value());
}
if (jdiValue instanceof LongValue) {
return DfTypes.longValue(((LongValue)jdiValue).longValue());
}
if (jdiValue instanceof ShortValue || jdiValue instanceof CharValue ||
jdiValue instanceof ByteValue || jdiValue instanceof IntegerValue) {
return factory.getConstFactory().createFromValue(((PrimitiveValue)jdiValue).intValue(), PsiType.LONG);
return DfTypes.intValue(((PrimitiveValue)jdiValue).intValue());
}
if (jdiValue instanceof FloatValue) {
return factory.getConstFactory().createFromValue(((FloatValue)jdiValue).floatValue(), PsiType.FLOAT);
return DfTypes.floatValue(((FloatValue)jdiValue).floatValue());
}
if (jdiValue instanceof DoubleValue) {
return factory.getConstFactory().createFromValue(((DoubleValue)jdiValue).doubleValue(), PsiType.DOUBLE);
return DfTypes.doubleValue(((DoubleValue)jdiValue).doubleValue());
}
if (jdiValue instanceof StringReference) {
return factory.getConstFactory().createFromValue(
((StringReference)jdiValue).value(), psiFactory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING,
myBody.getResolveScope()));
DfaPsiType dfaType =
factory.createDfaType(psiFactory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING, myBody.getResolveScope()));
return DfTypes.constant(((StringReference)jdiValue).value(), dfaType);
}
if (jdiValue instanceof ObjectReference) {
ReferenceType type = ((ObjectReference)jdiValue).referenceType();
@@ -324,13 +328,13 @@ class DebuggerDfaRunner extends DataFlowRunner {
if (enumClass != null && enumClass.isEnum()) {
PsiField enumConst = enumClass.findFieldByName(enumConstantName, false);
if (enumConst instanceof PsiEnumConstant) {
return factory.getConstFactory().createFromValue(enumConst, psiType);
return DfTypes.constant(enumConst, factory.createDfaType(psiType));
}
}
}
}
}
return null;
return DfTypes.TOP;
}
private static String getEnumConstantName(ObjectReference ref) {
@@ -5,7 +5,8 @@ import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
import com.intellij.codeInspection.dataFlow.StandardInstructionVisitor;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
@@ -37,17 +38,15 @@ class DebuggerInstructionVisitor extends StandardInstructionVisitor {
@NotNull DfaMemoryState state) {
if (range != null) return;
DfaHint hint = DfaHint.ANY_VALUE;
if (value instanceof DfaConstValue) {
Object constVal = ((DfaConstValue)value).getValue();
if (Boolean.TRUE.equals(constVal)) {
hint = DfaHint.TRUE;
}
else if (Boolean.FALSE.equals(constVal)) {
hint = DfaHint.FALSE;
}
else if (DfaConstValue.isContractFail(value)) {
hint = DfaHint.FAIL;
}
DfType dfType = state.getDfType(value);
if (dfType == DfTypes.TRUE) {
hint = DfaHint.TRUE;
}
else if (dfType == DfTypes.FALSE) {
hint = DfaHint.FALSE;
}
else if (dfType == DfTypes.FAIL) {
hint = DfaHint.FAIL;
}
addHint(expression, hint);
}
@@ -17,13 +17,13 @@ package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaMemoryStateImpl;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.MultiMap;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
@@ -78,14 +78,14 @@ public class ExpressionTypeMemoryState extends DfaMemoryStateImpl {
}
@Override
protected DfaFactMap filterFactsOnAssignment(DfaVariableValue var, @NotNull DfaFactMap facts) {
if (myHonorAssignments) return facts;
if (ControlFlowAnalyzer.isTempVariable(var) ||
protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @NotNull DfType dfType) {
if (myHonorAssignments) return dfType;
if (ControlFlowAnalyzer.isTempVariable(var) || (!(dfType instanceof DfReferenceType)) ||
var.getPsiVariable() instanceof PsiParameter && var.getPsiVariable().getParent().getParent() instanceof PsiLambdaExpression) {
// Pass type normally for synthetic lambda parameter assignment
return facts;
return dfType;
}
return facts.with(DfaFactType.TYPE_CONSTRAINT, null);
return ((DfReferenceType)dfType).dropTypeConstraint();
}
@NotNull
@@ -118,10 +118,9 @@ public class ExpressionTypeMemoryState extends DfaMemoryStateImpl {
DfaValue leftOperand = rel.getLeftOperand();
DfaValue rightOperand = rel.getRightOperand();
RelationType relation = rel.getRelation();
if (leftOperand instanceof DfaInstanceofValue && rightOperand instanceof DfaConstValue &&
(relation == RelationType.EQ || relation == RelationType.NE)) {
if (leftOperand instanceof DfaInstanceofValue && (relation == RelationType.EQ || relation == RelationType.NE)) {
DfaInstanceofValue value = (DfaInstanceofValue)leftOperand;
Boolean val = ObjectUtils.tryCast(((DfaConstValue)rightOperand).getValue(), Boolean.class);
Boolean val = DfConstantType.getConstantOfType(rightOperand.getDfType(), Boolean.class);
if (val != null) {
boolean negated = (relation == RelationType.EQ) != val;
if (!negated) {
@@ -18,6 +18,8 @@ package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInsight.guess.GuessManager;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaCondition;
import com.intellij.codeInspection.dataFlow.value.DfaInstanceofValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
@@ -52,7 +54,7 @@ public class GuessManagerImpl extends GuessManager {
initMethodPatterns();
}
@SuppressWarnings({"HardCodedStringLiteral"})
@SuppressWarnings("HardCodedStringLiteral")
private void initMethodPatterns() {
// Collection
myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0));
@@ -571,16 +573,10 @@ public class GuessManagerImpl extends GuessManager {
for (DfaInstructionState state : states) {
DfaMemoryState memoryState = state.getMemoryState();
if (myConstraint == TypeConstraint.empty()) return;
TypeConstraint constraint = memoryState.getValueFact(memoryState.peek(), DfaFactType.TYPE_CONSTRAINT);
if (constraint == null) {
constraint = myInitial;
}
DfType type = memoryState.getDfType(memoryState.peek());
TypeConstraint constraint = type instanceof DfReferenceType ? ((DfReferenceType)type).getConstraint() : myInitial;
if (constraint != null) {
myConstraint = myConstraint == null ? constraint : myConstraint.unite(constraint);
if (myConstraint == null) {
myConstraint = TypeConstraint.empty();
return;
}
}
}
}
@@ -18,7 +18,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.inliner.CallInliner;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
@@ -79,7 +79,7 @@ public class CFGBuilder {
* @return this builder
*/
public CFGBuilder pushNull() {
return add(new PushInstruction(getFactory().getConstFactory().getNull(), null));
return add(new PushInstruction(getFactory().getNull(), null));
}
/**
@@ -419,7 +419,7 @@ public class CFGBuilder {
* @return this builder
*/
public CFGBuilder boxUnbox(PsiExpression expression, PsiType expressionType, PsiType expectedType) {
myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expressionType, expectedType);
myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expressionType, expectedType, false);
return this;
}
@@ -469,7 +469,7 @@ public class CFGBuilder {
*/
public CFGBuilder assignAndPop(DfaValue target, DfaValue source) {
if (target instanceof DfaVariableValue) {
if (source == DfaUnknownValue.getInstance()) {
if (DfaTypeValue.isUnknown(source)) {
add(new FlushVariableInstruction((DfaVariableValue)target));
} else {
pushForWrite((DfaVariableValue)target).push(source).assign().pop();
@@ -491,7 +491,7 @@ public class CFGBuilder {
*/
public CFGBuilder assign(DfaValue target, DfaValue source) {
if (target instanceof DfaVariableValue) {
if (source == DfaUnknownValue.getInstance()) {
if (DfaTypeValue.isUnknown(source)) {
add(new FlushVariableInstruction((DfaVariableValue)target)).push(target);
} else {
pushForWrite((DfaVariableValue)target).push(source).assign();
@@ -646,12 +646,12 @@ public class CFGBuilder {
if (argCount == expectedArgCount) {
if (pushQualifier) {
DfaValue qualifierValue = myMethodRefQualifiers.remove(methodRef);
push(qualifierValue == null ? DfaUnknownValue.getInstance() : qualifierValue);
push(qualifierValue == null ? getFactory().getUnknown() : qualifierValue);
moveTopValue(argCount);
}
myAnalyzer.addBareCall(null, methodRef);
myAnalyzer.generateBoxingUnboxingInstructionFor(methodRef, resolveResult.getSubstitutor().substitute(method.getReturnType()),
LambdaUtil.getFunctionalInterfaceReturnType(methodRef));
LambdaUtil.getFunctionalInterfaceReturnType(methodRef), false);
if (resultNullability == Nullability.NOT_NULL) {
myAnalyzer.addNullCheck(NullabilityProblemKind.nullableFunctionReturn.problem(methodRef, null));
}
@@ -662,7 +662,7 @@ public class CFGBuilder {
if (qualifier instanceof PsiTypeElement && ((PsiTypeElement)qualifier).getType() instanceof PsiArrayType) {
// like String[]::new
splice(argCount)
.push(getFactory().createTypeValue(((PsiTypeElement)qualifier).getType(), Nullability.NOT_NULL));
.push(getFactory().getObjectType(((PsiTypeElement)qualifier).getType(), Nullability.NOT_NULL));
return this;
}
}
@@ -678,7 +678,7 @@ public class CFGBuilder {
myAnalyzer.addMethodThrows(LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType), null);
PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
if (returnType != null) {
push(getFactory().createTypeValue(returnType, DfaPsiUtil.getTypeNullability(returnType)));
push(getFactory().getObjectType(returnType, DfaPsiUtil.getTypeNullability(returnType)));
}
else {
pushUnknown();
@@ -724,7 +724,7 @@ public class CFGBuilder {
PsiClassObjectAccessExpression.class);
if (qualifier == null) return false;
PsiType type = qualifier.getOperand().getType();
push(getFactory().createTypeValue(type, Nullability.NOT_NULL));
push(getFactory().getObjectType(type, Nullability.NOT_NULL));
add(new InstanceofInstruction(methodRef, null, type));
return true;
}
@@ -791,7 +791,7 @@ public class CFGBuilder {
myBranches.add(() -> pushUnknown().add(condGoto));
assign(targetVariable, factory.createCommonValue(expressions, targetVariable.getType()));
} else {
push(factory.getConstFactory().getSentinel());
push(factory.getSentinel());
for (PsiExpression expression : expressions) {
pushExpression(expression);
boxUnbox(expression, targetVariable.getType());
@@ -800,7 +800,7 @@ public class CFGBuilder {
add(new SpliceInstruction(expressions.length, IntStreamEx.ofIndices(expressions).toArray()));
GotoInstruction gotoInstruction = new GotoInstruction(null);
gotoInstruction.setOffset(myAnalyzer.getInstructionCount());
dup().push(factory.getConstFactory().getSentinel()).compare(JavaTokenType.EQEQ);
dup().push(factory.getSentinel()).compare(JavaTokenType.EQEQ);
ConditionalGotoInstruction condGoto = new ConditionalGotoInstruction(null, false, null);
add(condGoto);
assignTo(targetVariable);
@@ -3,8 +3,8 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.EndOfInitializerInstruction;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaFactMapValue;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.util.TextRange;
@@ -26,50 +26,28 @@ import static com.intellij.codeInspection.dataFlow.DfaUtil.hasImplicitImpureSupe
public class CommonDataflow {
private static class DataflowPoint {
// null = top; empty = bottom
@Nullable DfaFactMap myFacts = null;
@NotNull DfType myDfType = DfTypes.BOTTOM;
// empty = top; null = bottom
@Nullable Set<Object> myPossibleValues = Collections.emptySet();
// null = top; empty = bottom
@Nullable Set<Object> myNotValues = null;
boolean myMayFailByContract = false;
DataflowPoint() {}
DataflowPoint(DataflowPoint other) {
myFacts = other.myFacts;
myDfType = other.myDfType;
myPossibleValues = other.myPossibleValues;
myNotValues = other.myNotValues == null || other.myNotValues.isEmpty() ? other.myNotValues : new HashSet<>(other.myNotValues);
myMayFailByContract = other.myMayFailByContract;
}
void addNotValues(DfaMemoryStateImpl memState, DfaValue value) {
// We do not store not-values for integral numbers as this functionality is covered by range fact
if (value instanceof DfaVariableValue && !TypeConversionUtil.isIntegralNumberType(value.getType())) {
Set<Object> notValues = myNotValues;
if (notValues == null) {
Set<Object> constants = memState.getNonEqualConstants((DfaVariableValue)value);
myNotValues = constants.isEmpty() ? Collections.emptySet() : constants;
}
else if (!notValues.isEmpty()) {
notValues.retainAll(memState.getNonEqualConstants((DfaVariableValue)value));
if (notValues.isEmpty()) {
myNotValues = Collections.emptySet();
}
}
}
}
void addValue(DfaMemoryStateImpl memState, DfaValue value) {
if (myPossibleValues == null) return;
DfaConstValue constantValue = memState.getConstantValue(value);
if (constantValue == null) {
DfType dfType = memState.getDfType(value);
if (!(dfType instanceof DfConstantType)) {
myPossibleValues = null;
return;
}
Object newValue = constantValue.getValue();
Object newValue = ((DfConstantType<?>)dfType).getValue();
if (myPossibleValues.contains(newValue)) return;
myNotValues = null;
if (myPossibleValues.isEmpty()) {
myPossibleValues = Collections.singleton(newValue);
}
@@ -80,24 +58,18 @@ public class CommonDataflow {
}
void addFacts(DfaMemoryStateImpl memState, DfaValue value) {
if (myFacts == DfaFactMap.EMPTY) return;
DfaFactMap newMap = DataflowResult.getFactMap(memState, value);
if (myDfType == DfTypes.TOP) return;
DfType newType = memState.getDfType(value);
if (value instanceof DfaVariableValue) {
SpecialField field = SpecialField.fromQualifier(value);
if (field != null) {
if (field != null && newType instanceof DfReferenceType) {
DfaValue specialField = field.createValue(value.getFactory(), value);
if (specialField instanceof DfaVariableValue) {
DfaConstValue constantValue = memState.getConstantValue(specialField);
specialField = constantValue != null
? constantValue
: specialField.getFactory().getFactFactory().createValue(DataflowResult.getFactMap(memState, specialField));
}
if (specialField instanceof DfaConstValue || specialField instanceof DfaFactMapValue) {
newMap = newMap.with(DfaFactType.SPECIAL_FIELD_VALUE, field.withValue(specialField));
}
DfType withSpecialField = field.asDfType(memState.getDfType(specialField));
newType = newType
.meet(withSpecialField instanceof DfReferenceType ? ((DfReferenceType)withSpecialField).dropNullability() : withSpecialField);
}
}
myFacts = myFacts == null ? newMap : myFacts.unite(newMap);
myDfType = myDfType.join(newType);
}
}
@@ -121,11 +93,11 @@ public class CommonDataflow {
void add(PsiExpression expression, DfaMemoryStateImpl memState, DfaValue value) {
DataflowPoint point = myData.computeIfAbsent(expression, e -> new DataflowPoint());
if (DfaConstValue.isContractFail(value)) {
if (DfaTypeValue.isContractFail(value)) {
point.myMayFailByContract = true;
return;
}
if (point.myFacts != DfaFactMap.EMPTY) {
if (point.myDfType != DfTypes.TOP) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiConditionalExpression &&
!PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expression, false)) {
@@ -134,17 +106,6 @@ public class CommonDataflow {
}
point.addFacts(memState, value);
point.addValue(memState, value);
point.addNotValues(memState, value);
}
@NotNull
private static DfaFactMap getFactMap(DfaMemoryStateImpl memState, DfaValue value) {
DfaFactMap newMap = memState.getFactMap(value);
DfaNullability nullability = newMap.get(DfaFactType.NULLABILITY);
if (nullability != DfaNullability.NOT_NULL && memState.isNotNull(value)) {
newMap = newMap.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
}
return newMap;
}
/**
@@ -175,20 +136,6 @@ public class CommonDataflow {
return point != null && !point.myMayFailByContract;
}
/**
* Returns a fact of specific type which is known for given expression or null if fact is not known
*
* @param expression expression to get the fact
* @param type a fact type
* @param <T> resulting type
* @return a fact value or null if fact of given type is not known for given expression
*/
@Nullable
public <T> T getExpressionFact(PsiExpression expression, DfaFactType<T> type) {
DataflowPoint point = myData.get(expression);
return point == null || point.myFacts == null ? null : point.myFacts.get(type);
}
/**
* Returns a set of expression values if known. If non-empty set is returned, then given expression
* is guaranteed to have one of returned values.
@@ -205,37 +152,13 @@ public class CommonDataflow {
}
/**
* Returns a set of values which are known to be not equal to given expression.
* An empty list is returned if nothing is known.
*
* <p>
* This method may return nothing if {@link #getExpressionValues(PsiExpression)}
* returns some values (if expression values are known, it's not equal to any other value),
* or if expression type is an integral type (in this case use
* {@code getExpressionFact(expression, DfaFactType.RANGE)} which would provide more information anyway).
*
* @param expression an expression to get values not equal to.
* @return a set of values; empty set if nothing is known or this expression was not tracked.
* @param expression an expression to infer the DfType, must be deparenthesized.
* @return DfType for that expression. May return {@link DfTypes#TOP} if no information from dataflow is known about this expression
*/
@NotNull
public Set<Object> getValuesNotEqualToExpression(@Nullable PsiExpression expression) {
public DfType getDfType(PsiExpression expression) {
DataflowPoint point = myData.get(expression);
if (point == null) return Collections.emptySet();
Set<Object> values = point.myNotValues;
return values == null ? Collections.emptySet() : Collections.unmodifiableSet(values);
}
/**
* Returns the fact map which represents all the facts known for given expression
*
* @param expression an expression to check
* @return the fact map which represents all the facts known for given expression; empty map if the expression was
* analyzed, but no particular facts were inferred; null if the expression was not analyzed.
*/
@Nullable
public DfaFactMap getAllFacts(PsiExpression expression) {
DataflowPoint point = myData.get(expression);
return point == null ? null : point.myFacts;
return point == null ? DfTypes.TOP : point.myDfType;
}
}
@@ -315,23 +238,20 @@ public class CommonDataflow {
}
/**
* Returns a fact of specific type which is known for given expression or null if fact is not known
*
* @param expression expression to get the fact
* @param type a fact type
* @param <T> resulting type
* @return a fact value or null if fact of given type is not known for given expression
* @param expression an expression to infer the DfType
* @return DfType for that expression. May return {@link DfTypes#TOP} if no information from dataflow is known about this expression
*/
public static <T> T getExpressionFact(PsiExpression expression, DfaFactType<T> type) {
@NotNull
public static DfType getDfType(PsiExpression expression) {
DataflowResult result = getDataflowResult(expression);
if (result == null) return null;
return result.getExpressionFact(PsiUtil.skipParenthesizedExprDown(expression), type);
if (result == null) return DfTypes.TOP;
return result.getDfType(PsiUtil.skipParenthesizedExprDown(expression));
}
/**
* Returns long range set for expression or null if range is unknown.
* This method first tries to compute expression using {@link com.intellij.psi.impl.ConstantExpressionEvaluator}
* and only then calls {@link #getExpressionFact(PsiExpression, DfaFactType)}.
* and only then calls {@link #getDfType(PsiExpression)}.
*
* @param expression expression to get its range
* @return long range set
@@ -343,7 +263,8 @@ public class CommonDataflow {
Object value = ExpressionUtils.computeConstantExpression(expression);
LongRangeSet rangeSet = LongRangeSet.fromConstant(value);
if (rangeSet != null) return rangeSet;
return getExpressionFact(expression, DfaFactType.RANGE);
DfType dfType = getDfType(expression);
return dfType instanceof DfIntegralType ? ((DfIntegralType)dfType).getRange() : null;
}
private static class CommonDataflowVisitor extends StandardInstructionVisitor {
@@ -6,7 +6,10 @@ import com.intellij.codeInspection.dataFlow.StandardMethodContract.ValueConstrai
import com.intellij.codeInspection.dataFlow.instructions.ControlTransferInstruction;
import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
import com.intellij.codeInspection.dataFlow.instructions.ReturnInstruction;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -125,7 +128,7 @@ class ContractChecker {
final DfaValueFactory factory = runner.getFactory();
for (int i = 0; i < contract.getParameterCount(); i++) {
ValueConstraint constraint = contract.getParameterConstraint(i);
DfaConstValue comparisonValue = constraint.getComparisonValue(factory);
DfaValue comparisonValue = constraint.getComparisonValue(factory);
if (comparisonValue != null) {
boolean negated = constraint.shouldUseNonEqComparison();
DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i]);
@@ -1,8 +1,12 @@
// 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.value.*;
import com.intellij.openapi.util.Pair;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -98,21 +102,17 @@ public abstract class ContractReturnValue {
}
static DfaValue merge(DfaValue defaultValue, DfaValue newValue, DfaMemoryState memState) {
if (defaultValue == null || defaultValue == DfaUnknownValue.getInstance()) return newValue;
if (newValue == null || newValue == DfaUnknownValue.getInstance()) return defaultValue;
if (defaultValue instanceof DfaFactMapValue) {
DfaFactMap defaultFacts = ((DfaFactMapValue)defaultValue).getFacts();
if (newValue instanceof DfaFactMapValue) {
DfaFactMap intersection = defaultFacts.intersect(((DfaFactMapValue)newValue).getFacts());
if (intersection != null) {
return defaultValue.getFactory().getFactFactory().createValue(intersection);
}
}
if (newValue instanceof DfaVariableValue) {
defaultFacts.facts(Pair::create).forEach(fact -> memState.applyFact(newValue, fact.getFirst(), fact.getSecond()));
}
if (defaultValue == null || DfaTypeValue.isUnknown(defaultValue)) return newValue;
if (newValue == null || DfaTypeValue.isUnknown(newValue)) return defaultValue;
DfType defaultType = memState.getDfType(defaultValue);
DfType newType = memState.getDfType(newValue);
DfType result = defaultType.meet(newType);
if (result == DfTypes.BOTTOM) return newValue;
if (newValue instanceof DfaVariableValue) {
memState.meetDfType(newValue, result);
return newValue;
}
return newValue;
return defaultValue.getFactory().fromDfType(result);
}
/**
@@ -352,7 +352,7 @@ public abstract class ContractReturnValue {
@Override
public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) {
return factory.getConstFactory().getContractFail();
return factory.getContractFail();
}
@Override
@@ -369,7 +369,7 @@ public abstract class ContractReturnValue {
@Override
public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) {
return factory.getConstFactory().getNull();
return factory.getNull();
}
@Override
@@ -391,11 +391,7 @@ public abstract class ContractReturnValue {
@Override
public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) {
if (defaultValue instanceof DfaVariableValue) {
callState.myMemoryState.forceVariableFact((DfaVariableValue)defaultValue, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
return defaultValue;
}
return factory.withFact(defaultValue, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState);
}
@Override
@@ -412,20 +408,17 @@ public abstract class ContractReturnValue {
@Override
public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) {
if (defaultValue instanceof DfaVariableValue) {
defaultValue = factory.getFactFactory().createValue(callState.myMemoryState.getFacts((DfaVariableValue)defaultValue));
}
DfaValue value = factory.withFact(defaultValue, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
DfType dfType = callState.myMemoryState.getDfType(defaultValue);
dfType = dfType.meet(DfTypes.NOT_NULL_OBJECT);
if (callState.myCallArguments.myPure) {
boolean unmodifiableView =
value instanceof DfaFactMapValue && ((DfaFactMapValue)value).get(DfaFactType.MUTABILITY) == Mutability.UNMODIFIABLE_VIEW;
boolean unmodifiableView = Mutability.fromDfType(dfType) == Mutability.UNMODIFIABLE_VIEW;
// Unmodifiable view methods like Collections.unmodifiableList create new object, but their special field "size" is
// actually a delegate, so we cannot trust it if the original value is not local
if (!unmodifiableView) {
value = factory.withFact(value, DfaFactType.LOCALITY, true);
dfType = dfType.meet(DfTypes.LOCAL_OBJECT);
}
}
return value;
return merge(defaultValue, factory.fromDfType(dfType), callState.myMemoryState);
}
@Override
@@ -462,10 +455,10 @@ public abstract class ContractReturnValue {
@Override
public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) {
DfaValue qualifier = callState.myCallArguments.myQualifier;
if (qualifier != null && qualifier != DfaUnknownValue.getInstance()) {
if (qualifier != null && !DfaTypeValue.isUnknown(qualifier)) {
return merge(defaultValue, qualifier, callState.myMemoryState);
}
return factory.withFact(defaultValue, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState);
}
@Override
@@ -510,8 +503,8 @@ public abstract class ContractReturnValue {
@Override
public boolean isValueCompatible(DfaMemoryState state, DfaValue value) {
DfaConstValue dfaConst = state.getConstantValue(value);
return dfaConst == null || Boolean.valueOf(myValue).equals(dfaConst.getValue());
DfType type = state.getUnboxedDfType(value);
return type.isSuperType(DfTypes.booleanValue(myValue));
}
}
@@ -15,8 +15,12 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.types.DfPrimitiveType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.psi.*;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Function;
import com.siyeh.ig.psiutils.MethodCallUtils;
import org.jetbrains.annotations.NotNull;
@@ -47,7 +51,7 @@ public abstract class ContractValue {
qualifierValue = factory.createValue(qualifier);
}
if (qualifierValue == null) {
qualifierValue = DfaUnknownValue.getInstance();
qualifierValue = factory.getUnknown();
}
boolean varArgCall = MethodCallUtils.isVarArgCall(call);
PsiExpression[] args = argumentList.getExpressions();
@@ -59,7 +63,7 @@ public abstract class ContractValue {
argValue = factory.createValue(args[i]);
}
if (argValue == null) {
argValue = DfaUnknownValue.getInstance();
argValue = factory.getUnknown();
}
argValues[i] = argValue;
}
@@ -122,7 +126,8 @@ public abstract class ContractValue {
}
public static ContractValue constant(Object value, @NotNull PsiType type) {
return new IndependentValue(factory -> factory.getConstFactory().createFromValue(value, type), String.valueOf(value));
return new IndependentValue(factory -> factory.getConstant(TypeConversionUtil.computeCastTo(value, type), type),
String.valueOf(value));
}
public static ContractValue booleanValue(boolean value) {
@@ -165,7 +170,7 @@ public abstract class ContractValue {
@Override
DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) {
if (arguments.myArguments.length <= myIndex) {
return DfaUnknownValue.getInstance();
return factory.getUnknown();
}
return arguments.myArguments[myIndex];
}
@@ -194,14 +199,14 @@ public abstract class ContractValue {
}
private static class IndependentValue extends ContractValue {
static final IndependentValue NULL = new IndependentValue(factory -> factory.getConstFactory().getNull(), "null");
static final IndependentValue TRUE = new IndependentValue(factory -> factory.getConstFactory().getTrue(), "true") {
static final IndependentValue NULL = new IndependentValue(factory -> factory.getNull(), "null");
static final IndependentValue TRUE = new IndependentValue(factory -> factory.getBoolean(true), "true") {
@Override
public boolean isExclusive(ContractValue other) {
return other == FALSE;
}
};
static final IndependentValue FALSE = new IndependentValue(factory -> factory.getConstFactory().getFalse(), "false") {
static final IndependentValue FALSE = new IndependentValue(factory -> factory.getBoolean(false), "false") {
@Override
public boolean isExclusive(ContractValue other) {
return other == TRUE;
@@ -311,11 +316,12 @@ public abstract class ContractValue {
}
if (index >= 0 && index < arguments.myArguments.length) {
DfaValue arg = arguments.myArguments[index];
if (arg instanceof DfaFactMapValue) {
DfaValue newArg = ((DfaFactMapValue)arg).withFact(DfaFactType.NULLABILITY, targetNullability);
if (newArg != arg) {
if (arg instanceof DfaTypeValue) {
DfType dfType = arg.getDfType();
DfType target = dfType.meet(targetNullability.asDfType());
if (!target.equals(dfType) && target != DfTypes.BOTTOM) {
DfaValue[] newArguments = arguments.myArguments.clone();
newArguments[index] = newArg;
newArguments[index] = arg.getFactory().fromDfType(target);
return new DfaCallArguments(arguments.myQualifier, newArguments, arguments.myPure);
}
}
@@ -348,7 +354,7 @@ public abstract class ContractValue {
@Override
DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) {
return DfaUnknownValue.getInstance();
return factory.getUnknown();
}
@NotNull
@@ -356,10 +362,10 @@ public abstract class ContractValue {
DfaCondition makeCondition(DfaValueFactory factory, DfaCallArguments arguments) {
DfaValue left = myLeft.makeDfaValue(factory, arguments);
DfaValue right = myRight.makeDfaValue(factory, arguments);
if (left instanceof DfaConstValue && left.getType() instanceof PsiPrimitiveType) {
if (left.getDfType() instanceof DfPrimitiveType) {
right = DfaUtil.boxUnbox(right, left.getType());
}
if (right instanceof DfaConstValue && right.getType() instanceof PsiPrimitiveType) {
if (right.getDfType() instanceof DfPrimitiveType) {
left = DfaUtil.boxUnbox(left, right.getType());
}
return left.cond(myRelationType, right);
@@ -13,6 +13,8 @@ import com.intellij.codeInspection.dataFlow.Trap.TwrFinally;
import com.intellij.codeInspection.dataFlow.inliner.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -222,7 +224,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
checkZeroDivisor();
}
addInstruction(new BinopInstruction(sign, expression.isPhysical() ? expression : null, resType));
generateBoxingUnboxingInstructionFor(rExpr, resType, type);
generateBoxingUnboxingInstructionFor(rExpr, resType, type, false);
}
addInstruction(new AssignInstruction(rExpr, myFactory.createValue(lExpr)));
@@ -282,7 +284,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
else if (!field.hasModifierProperty(PsiModifier.FINAL) && !UnusedSymbolUtil.isImplicitWrite(field)) {
// initialize with default value
DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(field);
DfaConstValue value = myFactory.getConstFactory().createDefault(field.getType());
DfaValue value = myFactory.getDefaultValue(field.getType());
new CFGBuilder(this).assignAndPop(dfaVariable, value);
}
}
@@ -295,7 +297,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private void initializeVariable(PsiVariable variable, PsiExpression initializer) {
if (DfaUtil.ignoreInitializer(variable)) return;
DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(variable);
addInstruction(new PushInstruction(dfaVariable, initializer, true));
addInstruction(new PushInstruction(dfaVariable, null, true));
initializer.accept(this);
generateBoxingUnboxingInstructionFor(initializer, variable.getType());
addInstruction(new AssignInstruction(initializer, dfaVariable));
@@ -387,11 +389,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(statement);
}
void addNullCheck(PsiExpression expression) {
void addNullCheck(@NotNull PsiExpression expression) {
addNullCheck(NullabilityProblemKind.fromContext(expression, myCustomNullabilityProblems));
}
void addNullCheck(NullabilityProblemKind.NullabilityProblem<?> problem) {
void addNullCheck(@Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
if (problem != null) {
DfaControlTransferValue transfer = shouldHandleException() && problem.thrownException() != null
? myFactory.controlTransfer(myExceptionCache.get(problem.thrownException()), myTrapStack) : null;
@@ -495,7 +497,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (iteratedValue instanceof PsiMethodCallExpression && LIST_INITIALIZER.test((PsiMethodCallExpression)iteratedValue)) {
expressions = ((PsiMethodCallExpression)iteratedValue).getArgumentList().getExpressions();
}
return expressions == null ? DfaUnknownValue.getInstance() : getFactory().createCommonValue(expressions, type);
return expressions == null ? getFactory().getUnknown() : getFactory().createCommonValue(expressions, type);
}
@Override public void visitForeachStatement(PsiForeachStatement statement) {
@@ -584,7 +586,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN);
}
else {
addInstruction(new PushInstruction(statement.getRParenth() == null ? null : myFactory.getConstFactory().getTrue(), null));
addInstruction(new PushInstruction(myFactory.fromDfType(statement.getRParenth() == null ? DfTypes.BOOLEAN : DfTypes.TRUE), null));
}
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), true, condition));
@@ -646,7 +648,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
DfaValue origin = null;
Object initialValue = ExpressionUtils.computeConstantExpression(initializer);
if (initialValue instanceof Number) {
origin = myFactory.getConstFactory().createFromValue(initialValue, type);
origin = myFactory.getConstant(initialValue, type);
}
else if (initializer instanceof PsiReferenceExpression) {
PsiVariable initialVariable = ObjectUtils.tryCast(((PsiReferenceExpression)initializer).resolve(), PsiVariable.class);
@@ -662,28 +664,23 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
// Unroll small loops
addInstruction(new PushInstruction(loopVar, null, true));
addInstruction(new PushInstruction(loopVar, null));
addInstruction(new PushInstruction(myFactory.getConstFactory().createFromValue(1, PsiType.INT), null));
addInstruction(new PushInstruction(myFactory.getInt(1), null));
addInstruction(new BinopInstruction(JavaTokenType.PLUS, null, loopVar.getType(), -1, true));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
}
else if (start != null) {
long maxValue;
if (end != null) {
maxValue = loop.isIncluding() ? end + 1 : end;
}
else {
maxValue = type.equals(PsiType.LONG) ? Long.MAX_VALUE : Integer.MAX_VALUE;
}
long maxValue = end == null ? Long.MAX_VALUE : loop.isIncluding() ? end + 1 : end;
if (start >= maxValue) {
addInstruction(new GotoInstruction(getEndOffset(statement)));
}
else {
DfaValue range = myFactory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(start + 1L, maxValue));
LongRangeSet rangeSet = LongRangeSet.range(start + 1L, maxValue);
DfaValue range = myFactory.fromDfType(DfTypes.rangeClamped(rangeSet, type.equals(PsiType.LONG)));
new CFGBuilder(this).assignAndPop(loopVar, range);
}
} else {
new CFGBuilder(this).assign(loopVar, DfaUnknownValue.getInstance())
new CFGBuilder(this).assign(loopVar, myFactory.getUnknown())
.push(origin)
.compare(JavaTokenType.LE);
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null));
@@ -740,7 +737,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
public void visitLambdaExpression(PsiLambdaExpression expression) {
startElement(expression);
DfaValue dfaValue = myFactory.createValue(expression);
addInstruction(new PushInstruction(dfaValue, expression));
addInstruction(new PushInstruction(dfaValue == null ? myFactory.getUnknown() : dfaValue, expression));
handleClosure(expression);
finishElement(expression);
}
@@ -757,9 +754,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
variables.add((PsiVariable)target);
}
if (target instanceof PsiMember && !((PsiMember)target).hasModifierProperty(PsiModifier.STATIC)) {
DfaVariableValue qualifier = getFactory().getExpressionFactory().getQualifierOrThisVariable(expression);
if (qualifier != null) {
escapedVars.add(qualifier);
DfaValue qualifier = getFactory().getExpressionFactory().getQualifierOrThisValue(expression);
if (qualifier instanceof DfaVariableValue) {
escapedVars.add((DfaVariableValue)qualifier);
}
}
}
@@ -1190,7 +1187,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitExpression(PsiExpression expression) {
startElement(expression);
DfaValue dfaValue = myFactory.createValue(expression);
addInstruction(new PushInstruction(dfaValue, expression));
addInstruction(new PushInstruction(dfaValue == null ? myFactory.getUnknown() : dfaValue, expression));
finishElement(expression);
}
@@ -1205,12 +1202,12 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
indexExpression.accept(this);
generateBoxingUnboxingInstructionFor(indexExpression, PsiType.INT);
} else {
addInstruction(new PushInstruction(DfaUnknownValue.getInstance(), null));
addInstruction(new PushInstruction(myFactory.getUnknown(), null));
}
DfaValue toPush = myFactory.createValue(expression);
if (toPush == null) {
toPush = myFactory.createTypeValue(expression.getType(), Nullability.UNKNOWN);
toPush = myFactory.getObjectType(expression.getType(), Nullability.UNKNOWN);
}
addInstruction(new ArrayAccessInstruction(toPush, expression));
addNullCheck(expression);
@@ -1267,12 +1264,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
arrayWriteTarget = null;
}
}
DfaFactMap arrayFacts = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, type == null ? null : TypeConstraint.exact(myFactory.createDfaType(type)))
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.LOCALITY, true)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.ARRAY_LENGTH.withValue(myFactory.getInt(expression.getInitializers().length)));
DfaValue arrayValue = myFactory.getFactFactory().createValue(arrayFacts);
TypeConstraint constraint = type == null ? TypeConstraint.empty() : TypeConstraint.exact(myFactory.createDfaType(type));
DfType arrayType = constraint.asDfType()
.meet(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(expression.getInitializers().length)))
.meet(DfTypes.LOCAL_OBJECT);
DfaValue arrayValue = myFactory.fromDfType(arrayType);
if (arrayWriteTarget != null) {
addInstruction(new PushInstruction(arrayWriteTarget, null, true));
addInstruction(new PushInstruction(arrayValue, expression));
@@ -1284,7 +1280,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
target = Objects.requireNonNull(expressionFactory.getArrayElementValue(arrayWriteTarget, index));
}
index++;
addInstruction(new PushInstruction(target, null, true));
addInstruction(new PushInstruction(target == null ? myFactory.getUnknown() : target, null, true));
initializer.accept(this);
if (componentType != null) {
generateBoxingUnboxingInstructionFor(initializer, componentType);
@@ -1295,7 +1291,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
else {
for (PsiExpression initializer : initializers) {
addInstruction(new PushInstruction(null, null, true));
addInstruction(new PushInstruction(myFactory.getUnknown(), null, true));
initializer.accept(this);
if (componentType != null) {
generateBoxingUnboxingInstructionFor(initializer, componentType);
@@ -1350,7 +1346,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private void checkZeroDivisor() {
addInstruction(new DupInstruction());
addInstruction(new PushInstruction(myFactory.getConstFactory().createFromValue(0, PsiType.LONG), null));
addInstruction(new PushInstruction(myFactory.getConstant(0, PsiType.LONG), null));
addInstruction(new BinopInstruction(JavaTokenType.NE, null, PsiType.BOOLEAN));
ConditionalGotoInstruction ifNonZero = new ConditionalGotoInstruction(null, false, null);
addInstruction(ifNonZero);
@@ -1389,16 +1385,29 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
&& !TypeConversionUtil.isPrimitiveAndNotNull(lType)
&& !TypeConversionUtil.isPrimitiveAndNotNull(rType);
boolean comparingPrimitiveNumeric = comparing &&
TypeConversionUtil.isPrimitiveAndNotNull(lType) &&
TypeConversionUtil.isPrimitiveAndNotNull(rType) &&
boolean comparingPrimitiveNumeric = !comparingRef && ComparisonUtils.isComparisonOperation(op) &&
TypeConversionUtil.isNumericType(lType) &&
TypeConversionUtil.isNumericType(rType);
// comparing object and primitive is not compilable code but we try to balance types to avoid noise warnings
boolean comparingObjectAndPrimitive = comparing && !comparingRef && !comparingPrimitiveNumeric &&
(TypeConversionUtil.isNumericType(lType) || TypeConversionUtil.isNumericType(rType));
boolean shift = op == JavaTokenType.GTGT || op == JavaTokenType.LTLT || op == JavaTokenType.GTGTGT;
PsiType castType = comparingPrimitiveNumeric ? TypeConversionUtil.unboxAndBalanceTypes(lType, rType) :
shift && PsiType.LONG.equals(rType) ? rType : type;
PsiType castType;
if (comparingPrimitiveNumeric) {
castType = TypeConversionUtil.unboxAndBalanceTypes(lType, rType);
}
else if (comparingObjectAndPrimitive) {
castType = TypeConversionUtil.isNumericType(lType) ? rType : lType;
}
else if (shift && PsiType.LONG.equals(rType)) {
castType = rType;
}
else {
castType = type;
}
if (!comparingRef) {
generateBoxingUnboxingInstructionFor(lExpr,castType);
@@ -1411,13 +1420,13 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression expression, PsiType expectedType) {
generateBoxingUnboxingInstructionFor(expression, expression.getType(), expectedType);
generateBoxingUnboxingInstructionFor(expression, expression.getType(), expectedType, false);
}
void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression context, PsiType actualType, PsiType expectedType) {
void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression context, PsiType actualType, PsiType expectedType, boolean explicit) {
if (PsiType.VOID.equals(expectedType)) return;
if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(actualType)) {
if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isAssignableFromPrimitiveWrapper(actualType)) {
addInstruction(new UnwrapSpecialFieldInstruction(SpecialField.UNBOX));
actualType = PsiPrimitiveType.getUnboxedType(actualType);
}
@@ -1431,7 +1440,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
TypeConversionUtil.isPrimitiveAndNotNull(expectedType) &&
TypeConversionUtil.isNumericType(actualType) &&
TypeConversionUtil.isNumericType(expectedType)) {
addInstruction(new PrimitiveConversionInstruction((PsiPrimitiveType)expectedType, context));
addInstruction(new PrimitiveConversionInstruction((PsiPrimitiveType)expectedType, explicit ? context : null));
}
}
@@ -1459,7 +1468,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) {
startElement(expression);
PsiTypeElement operand = expression.getOperand();
DfaConstValue classConstant = myFactory.getConstFactory().createFromValue(operand.getType(), expression.getType());
DfaValue classConstant = myFactory.getConstant(operand.getType(), expression.getType());
addInstruction(new PushInstruction(classConstant, expression));
finishElement(expression);
}
@@ -1502,7 +1511,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
void pushUnknown() {
addInstruction(new PushInstruction(DfaUnknownValue.getInstance(), null));
addInstruction(new PushInstruction(myFactory.getUnknown(), null));
}
@Override public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
@@ -1512,7 +1521,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (checkType != null) {
operand.accept(this);
PsiType type = checkType.getType();
addInstruction(new PushInstruction(myFactory.createTypeValue(type, Nullability.NOT_NULL), null));
addInstruction(new PushInstruction(myFactory.getObjectType(type, Nullability.NOT_NULL), null));
addInstruction(new InstanceofInstruction(expression, operand, type));
}
else {
@@ -1568,7 +1577,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (qualifierExpression == null) {
DfaValue thisVariable = myFactory.getExpressionFactory().getQualifierOrThisVariable(call.getMethodExpression());
DfaValue thisVariable = myFactory.getExpressionFactory().getQualifierOrThisValue(call.getMethodExpression());
if (thisVariable != null) {
addInstruction(new PushInstruction(thisVariable, null));
}
@@ -1635,13 +1644,12 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
else {
addInstruction(new MethodCallInstruction(expression, myFactory.createValue(expression), contracts));
addNullCheck(expression);
anchor = expression;
}
if (contracts.stream().anyMatch(c -> c.getReturnValue().isFail())) {
// if a contract resulted in 'fail', handle it
addInstruction(new DupInstruction());
addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null));
addInstruction(new PushInstruction(myFactory.getContractFail(), null));
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, PsiType.BOOLEAN));
ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null);
addInstruction(ifNotFail);
@@ -1651,6 +1659,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
addMethodThrows(method, anchor);
if (expression != null) {
addNullCheck(expression);
}
}
/**
@@ -1705,7 +1716,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
// stack: ... var.length actual_size
addInstruction(new PushInstruction(var, null, true));
DfaValue arrayValue = myFactory.withFact(myFactory.createExactTypeValue(type), DfaFactType.LOCALITY, true);
DfaValue arrayValue = myFactory.fromDfType(TypeConstraint.exact(myFactory.createDfaType(type)).asDfType().meet(DfTypes.LOCAL_OBJECT));
addInstruction(new PushInstruction(arrayValue, expression));
addInstruction(new AssignInstruction(expression, var));
// stack: ... var.length actual_size var
@@ -1719,7 +1730,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
else {
PsiExpression qualifier = expression.getQualifier();
DfaValue qualifierValue = DfaUnknownValue.getInstance();
DfaValue qualifierValue = myFactory.getUnknown();
if (qualifier != null) {
qualifier.accept(this);
} else {
@@ -1753,12 +1764,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private DfaValue getPrecalculatedNewValue(PsiNewExpression expression) {
PsiType type = expression.getType();
if (type != null && ConstructionUtils.isEmptyCollectionInitializer(expression)) {
DfaFactMap facts = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, TypeConstraint.exact(myFactory.createDfaType(type)))
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.LOCALITY, true)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.COLLECTION_SIZE.withValue(myFactory.getInt(0)));
return myFactory.getFactFactory().createValue(facts);
DfType dfType = TypeConstraint.exact(myFactory.createDfaType(type)).asDfType()
.meet(SpecialField.COLLECTION_SIZE.asDfType(DfTypes.intValue(0)))
.meet(DfTypes.LOCAL_OBJECT);
return myFactory.fromDfType(dfType);
}
return null;
}
@@ -1774,9 +1783,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (lengthValue > 0 && lengthValue <= MAX_UNROLL_SIZE) {
for (int i = 0; i < lengthValue; i++) {
DfaValue value = getFactory().getExpressionFactory().getArrayElementValue(var, i);
addInstruction(new PushInstruction(value, null, true));
addInstruction(new PushInstruction(value == null ? getFactory().getUnknown() : value, null, true));
}
addInstruction(new PushInstruction(getFactory().getConstFactory().createDefault(componentType), null));
addInstruction(new PushInstruction(getFactory().getDefaultValue(componentType), null));
for (int i = lengthValue - 1; i >= 0; i--) {
DfaValue value = getFactory().getExpressionFactory().getArrayElementValue(var, i);
addInstruction(new AssignInstruction(null, value));
@@ -1848,7 +1857,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new DupInstruction());
generateBoxingUnboxingInstructionFor(operand, unboxedType);
PsiType resultType = TypeConversionUtil.binaryNumericPromotion(unboxedType, PsiType.INT);
addInstruction(new PushInstruction(myFactory.getConstFactory().createFromValue(1, PsiType.INT), null));
addInstruction(new PushInstruction(myFactory.getInt(1), null));
addInstruction(new BinopInstruction(token, null, resultType));
if (!unboxedType.equals(resultType)) {
addInstruction(new PrimitiveConversionInstruction(unboxedType, null));
@@ -1889,7 +1898,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
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));
addInstruction(new PushInstruction(myFactory.getDefaultValue(type), null));
addInstruction(new SwapInstruction());
addInstruction(new BinopInstruction(expression.getOperationTokenType(), expression, type));
}
@@ -1917,7 +1926,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
// complex assignments (e.g. "|=") are both reading and writing
boolean writing = PsiUtil.isAccessedForWriting(expression) && !PsiUtil.isAccessedForReading(expression);
addInstruction(new PushInstruction(myFactory.createValue(expression), expression, writing));
DfaValue value = myFactory.createValue(expression);
addInstruction(new PushInstruction(value == null ? myFactory.getUnknown() : value, expression, writing));
addNullCheck(expression);
finishElement(expression);
@@ -1926,9 +1936,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitLiteralExpression(PsiLiteralExpression expression) {
startElement(expression);
DfaValue dfaValue = myFactory.createLiteralValue(expression);
addInstruction(new PushInstruction(dfaValue, expression));
if (DfaConstValue.isConstant(dfaValue, null)) {
DfaValue dfaValue = myFactory.getConstantFromLiteral(expression);
addInstruction(new PushInstruction(dfaValue == null ? myFactory.getUnknown() : dfaValue, expression));
if (dfaValue != null && dfaValue.getDfType() == DfTypes.NULL) {
addNullCheck(expression);
}
@@ -1941,10 +1951,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (operand != null) {
operand.accept(this);
generateBoxingUnboxingInstructionFor(castExpression, operand.getType(), castExpression.getType());
generateBoxingUnboxingInstructionFor(castExpression, operand.getType(), castExpression.getType(), true);
}
else {
addInstruction(new PushInstruction(myFactory.createTypeValue(castExpression.getType(), Nullability.UNKNOWN), null));
addInstruction(new PushInstruction(myFactory.getObjectType(castExpression.getType(), Nullability.UNKNOWN), null));
}
final PsiTypeElement typeElement = castExpression.getCastType();
@@ -1,8 +1,13 @@
// Copyright 2000-2019 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.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.CachedValueProvider;
@@ -23,6 +28,8 @@ import java.util.Calendar;
import java.util.List;
import static com.intellij.codeInspection.dataFlow.SpecialField.*;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.LOCAL_OBJECT;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.TOP;
import static com.intellij.psi.CommonClassNames.*;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
@@ -43,17 +50,17 @@ class CustomMethodHandlers {
interface CustomMethodHandler {
@Nullable
DfaValue getMethodResult(DfaCallArguments callArguments,
DfaMemoryState memState,
DfaValueFactory factory,
PsiMethod method);
@NotNull
DfType getMethodResult(DfaCallArguments callArguments,
DfaMemoryState memState,
DfaValueFactory factory,
PsiMethod method);
default CustomMethodHandler compose(CustomMethodHandler other) {
if (other == null) return this;
return (args, memState, factory, method) -> {
DfaValue result = this.getMethodResult(args, memState, factory, method);
return result == null ? other.getMethodResult(args, memState, factory, method) : result;
DfType result = this.getMethodResult(args, memState, factory, method);
return result == TOP ? other.getMethodResult(args, memState, factory, method) : result;
};
}
}
@@ -64,30 +71,30 @@ class CustomMethodHandlers {
.register(instanceCall(JAVA_UTIL_LIST, "indexOf", "lastIndexOf"),
(args, memState, factory, method) -> indexOf(args.myQualifier, memState, factory, COLLECTION_SIZE))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("int"),
(args, memState, factory, method) -> mathAbs(args.myArguments, memState, factory, false))
(args, memState, factory, method) -> mathAbs(args.myArguments, memState, false))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
(args, memState, factory, method) -> mathAbs(args.myArguments, memState, factory, true))
(args, memState, factory, method) -> mathAbs(args.myArguments, memState, true))
.register(exactInstanceCall(JAVA_LANG_STRING, "substring"),
(args, memState, factory, method) -> substring(args, memState, factory, method.getReturnType()))
.register(OptionalUtil.OPTIONAL_OF_NULLABLE,
(args, memState, factory, method) -> ofNullable(args.myArguments[0], memState, factory))
(args, memState, factory, method) -> ofNullable(args.myArguments[0], memState))
.register(instanceCall(JAVA_UTIL_CALENDAR, "get").parameterTypes("int"),
(args, memState, factory, method) -> calendarGet(args.myArguments, memState, factory))
(args, memState, factory, method) -> calendarGet(args.myArguments, memState))
.register(anyOf(instanceCall("java.io.InputStream", "skip").parameterTypes("long"),
instanceCall("java.io.Reader", "skip").parameterTypes("long")),
(args, memState, factory, method) -> skip(args.myArguments, memState, factory))
(args, memState, factory, method) -> skip(args.myArguments, memState))
.register(staticCall(JAVA_LANG_INTEGER, "toHexString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 4, Integer.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 4, Integer.SIZE))
.register(staticCall(JAVA_LANG_INTEGER, "toOctalString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 3, Integer.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 3, Integer.SIZE))
.register(staticCall(JAVA_LANG_INTEGER, "toBinaryString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 1, Integer.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 1, Integer.SIZE))
.register(staticCall(JAVA_LANG_LONG, "toHexString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 4, Long.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 4, Long.SIZE))
.register(staticCall(JAVA_LANG_LONG, "toOctalString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 3, Long.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 3, Long.SIZE))
.register(staticCall(JAVA_LANG_LONG, "toBinaryString").parameterCount(1),
(args, memState, factory, method) -> numberAsString(args, memState, factory, 1, Long.SIZE))
(args, memState, factory, method) -> numberAsString(args, memState, 1, Long.SIZE))
.register(instanceCall(JAVA_LANG_ENUM, "name").parameterCount(0),
(args, memState, factory, method) -> enumName(args.myQualifier, memState, factory, method.getReturnType()))
.register(anyOf(
@@ -111,20 +118,20 @@ class CustomMethodHandlers {
return CONSTANT_CALLS.methodMatches(method);
}
@Nullable
private static DfaValue handleConstantCall(DfaCallArguments arguments, DfaMemoryState state,
DfaValueFactory factory, PsiMethod method) {
@NotNull
private static DfType handleConstantCall(DfaCallArguments arguments, DfaMemoryState state,
DfaValueFactory factory, PsiMethod method) {
PsiType returnType = method.getReturnType();
if (returnType == null) return null;
if (returnType == null) return TOP;
List<Object> args = new ArrayList<>();
Object qualifierValue = null;
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
qualifierValue = getConstantValue(state, arguments.myQualifier);
if (qualifierValue == null) return null;
if (qualifierValue == null) return TOP;
}
for (DfaValue argument : arguments.myArguments) {
Object argumentValue = getConstantValue(state, argument);
if (argumentValue == null) return null;
if (argumentValue == null) return TOP;
if (argumentValue instanceof Long) {
long longValue = ((Long)argumentValue).longValue();
if (longValue >= Integer.MIN_VALUE && longValue <= Integer.MAX_VALUE) {
@@ -134,15 +141,15 @@ class CustomMethodHandlers {
args.add(argumentValue);
}
Method jvmMethod = toJvmMethod(method);
if (jvmMethod == null) return null;
if (jvmMethod == null) return TOP;
Object result;
try {
result = jvmMethod.invoke(qualifierValue, args.toArray());
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
return null;
return TOP;
}
return factory.getConstFactory().createFromValue(result, returnType);
return DfTypes.constant(result, factory.createDfaType(returnType));
}
private static Method toJvmMethod(PsiMethod method) {
@@ -210,104 +217,86 @@ class CustomMethodHandlers {
});
}
private static DfaValue indexOf(DfaValue qualifier,
DfaMemoryState memState,
DfaValueFactory factory,
SpecialField specialField) {
@NotNull
private static DfType 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 factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1));
LongRangeSet range = DfIntType.extractRange(memState.getDfType(length));
return DfTypes.intRange(LongRangeSet.range(-1, range.max() - 1));
}
private static DfaValue collectionFactory(DfaCallArguments args,
DfaMemoryState memState, DfaValueFactory factory,
PsiMethod method) {
@NotNull
private static DfType collectionFactory(DfaCallArguments args,
DfaMemoryState memState, DfaValueFactory factory,
PsiMethod method) {
PsiType type = method.getReturnType();
if (type == null) return null;
if (type == null) return TOP;
DfaPsiType dfaType = factory.createDfaType(type);
int factor = dfaType.getPsiType().equalsToText(JAVA_UTIL_MAP) ? 2 : 1;
DfaValue size;
DfType size;
if (method.isVarArgs()) {
LongRangeSet range = memState.getValueFact(ARRAY_LENGTH.createValue(factory, args.myArguments[0]), DfaFactType.RANGE);
size = factory.getFactValue(DfaFactType.RANGE, range);
size = memState.getDfType(ARRAY_LENGTH.createValue(factory, args.myArguments[0]));
}
else {
size = factory.getInt(args.myArguments.length / factor);
size = DfTypes.intValue(args.myArguments.length / factor);
}
SpecialFieldValue sizeConstraint = size == DfaUnknownValue.getInstance() ? null : COLLECTION_SIZE.withValue(size);
DfaFactMap facts = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, dfaType.asConstraint())
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, sizeConstraint);
if (method.getName().equals("asList")) {
facts = facts.with(DfaFactType.LOCALITY, Boolean.TRUE);
} else {
facts = facts.with(DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE);
}
return factory.getFactFactory().createValue(facts);
boolean asList = method.getName().equals("asList");
Mutability mutability = asList ? Mutability.MUTABLE : Mutability.UNMODIFIABLE;
DfType result = DfTypes.typedObject(dfaType, Nullability.NOT_NULL)
.meet(COLLECTION_SIZE.asDfType(size))
.meet(mutability.asDfType());
return asList ? result.meet(LOCAL_OBJECT) : result;
}
private static DfaValue substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, PsiType stringType) {
if (stringType == null || !stringType.equalsToText(JAVA_LANG_STRING)) return null;
@NotNull
private static DfType substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, PsiType stringType) {
if (stringType == null || !stringType.equalsToText(JAVA_LANG_STRING)) return TOP;
DfaValue qualifier = args.myQualifier;
DfaValue[] arguments = args.myArguments;
if (arguments.length < 1 || arguments.length > 2 || arguments[0] == null) return null;
if (arguments.length < 1 || arguments.length > 2 || arguments[0] == null) return TOP;
DfaValue from = arguments[0];
DfaValue to = arguments.length == 1 ? STRING_LENGTH.createValue(factory, qualifier) : arguments[1];
DfaValue lengthVal = factory.getBinOpFactory().create(to, from, state, false, JavaTokenType.MINUS);
LongRangeSet resultLen = state.getValueFact(lengthVal, DfaFactType.RANGE);
if (resultLen == null) {
resultLen = LongRangeSet.point(0).fromRelation(RelationType.GE);
} else {
resultLen = resultLen.intersect(LongRangeSet.point(0).fromRelation(RelationType.GE));
}
LongRangeSet length = state.getValueFact(STRING_LENGTH.createValue(factory, qualifier), DfaFactType.RANGE);
if (length != null) {
resultLen = resultLen.intersect(length.fromRelation(RelationType.LE));
}
LongRangeSet resultLen = DfIntType.extractRange(state.getDfType(lengthVal));
LongRangeSet length = DfIntType.extractRange(state.getDfType(STRING_LENGTH.createValue(factory, qualifier)));
resultLen = resultLen
.intersect(LongRangeSet.point(0).fromRelation(RelationType.GE))
.intersect(length.fromRelation(RelationType.LE));
return getStringValue(factory, stringType, resultLen);
}
@NotNull
private static DfaValue getStringValue(@NotNull DfaValueFactory factory, @NotNull PsiType stringType, @NotNull LongRangeSet stringLength) {
private static DfType getStringValue(@NotNull DfaValueFactory factory, @NotNull PsiType stringType, @NotNull LongRangeSet stringLength) {
DfaPsiType dfaType = factory.createDfaType(stringType);
if (Long.valueOf(0).equals(stringLength.getConstantValue())) {
return factory.getConstFactory().createFromValue("", stringType);
return DfTypes.constant("", dfaType);
}
if (stringLength.isEmpty()) {
return factory.getConstFactory().getContractFail();
}
return factory.getFactFactory().createValue(
DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, factory.createDfaType(stringType).asConstraint())
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, STRING_LENGTH.withValue(factory.getFactValue(DfaFactType.RANGE, stringLength))));
return DfTypes.typedObject(dfaType, Nullability.NOT_NULL).meet(STRING_LENGTH.asDfType(DfTypes.intRange(stringLength)));
}
private static DfaValue ofNullable(DfaValue argument, DfaMemoryState state, DfaValueFactory factory) {
if (state.isNull(argument)) {
return DfaOptionalSupport.getOptionalValue(factory, false);
}
if (state.isNotNull(argument)) {
return DfaOptionalSupport.getOptionalValue(factory, true);
}
return null;
@NotNull
private static DfType ofNullable(DfaValue argument, DfaMemoryState state) {
return OPTIONAL_VALUE.asDfType(state.getDfType(argument));
}
private static DfaValue mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
@NotNull
private static DfType mathAbs(DfaValue[] args, DfaMemoryState memState, boolean isLong) {
DfaValue arg = ArrayUtil.getFirstElement(args);
if (arg == null) return null;
LongRangeSet range = memState.getValueFact(arg, DfaFactType.RANGE);
if (range == null) return null;
return factory.getFactValue(DfaFactType.RANGE, range.abs(isLong));
if (arg == null) return TOP;
DfType type = memState.getDfType(arg);
LongRangeSet range = isLong ? DfLongType.extractRange(type) : DfIntType.extractRange(type);
return isLong ? DfTypes.longRange(range.abs(true)) : DfTypes.intRange(range.abs(false));
}
private static DfaValue calendarGet(DfaValue[] arguments, DfaMemoryState state, DfaValueFactory factory) {
if (arguments.length != 1) return null;
DfaConstValue arg = state.getConstantValue(arguments[0]);
if (arg == null || !(arg.getValue() instanceof Long)) return null;
@NotNull
private static DfType calendarGet(DfaValue[] arguments, DfaMemoryState state) {
if (arguments.length != 1) return TOP;
Integer val = DfConstantType.getConstantOfType(state.getDfType(arguments[0]), Integer.class);
if (val == null) return TOP;
LongRangeSet range = null;
switch (((Long)arg.getValue()).intValue()) {
switch (val) {
case Calendar.DATE: range = LongRangeSet.range(1, 31); break;
case Calendar.MONTH: range = LongRangeSet.range(0, 12); break;
case Calendar.AM_PM: range = LongRangeSet.range(0, 1); break;
@@ -318,56 +307,41 @@ class CustomMethodHandlers {
case Calendar.SECOND: range = LongRangeSet.range(0, 59); break;
case Calendar.MILLISECOND: range = LongRangeSet.range(0, 999); break;
}
return range == null ? null : factory.getFactValue(DfaFactType.RANGE, range);
return range == null ? TOP : DfTypes.intRange(range);
}
private static DfaValue skip(DfaValue[] arguments, DfaMemoryState state, DfaValueFactory factory) {
if (arguments.length != 1) return null;
LongRangeSet range = state.getValueFact(arguments[0], DfaFactType.RANGE);
if (range == null || range.isEmpty()) return null;
return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(0, Math.max(0, range.max())));
@NotNull
private static DfType skip(DfaValue[] arguments, DfaMemoryState state) {
if (arguments.length != 1) return TOP;
LongRangeSet range = DfLongType.extractRange(state.getDfType(arguments[0]));
return DfTypes.longRange(LongRangeSet.range(0, Math.max(0, range.max())));
}
private static DfaValue numberAsString(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, int bitsPerChar,
int maxBits) {
@NotNull
private static DfType numberAsString(DfaCallArguments args, DfaMemoryState state, int bitsPerChar, int maxBits) {
DfaValue arg = args.myArguments[0];
if (arg == null) return null;
LongRangeSet range = state.getValueFact(arg, DfaFactType.RANGE);
if (range == null || range.isEmpty()) return null;
if (arg == null) return TOP;
LongRangeSet range = DfLongType.extractRange(state.getDfType(arg));
int usedBits = range.min() >= 0 ? Long.SIZE - Long.numberOfLeadingZeros(range.max()) : maxBits;
int max = Math.max(1, (usedBits - 1) / bitsPerChar + 1);
DfaValue lengthRange = factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(1, max));
DfaFactMap map = DfaFactMap.EMPTY.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, STRING_LENGTH.withValue(lengthRange));
return factory.getFactFactory().createValue(map);
return STRING_LENGTH.asDfType(DfTypes.intRange(LongRangeSet.range(1, max)));
}
private static DfaValue enumName(DfaValue qualifier, DfaMemoryState state, DfaValueFactory factory, PsiType type) {
DfaConstValue constant = state.getConstantValue(qualifier);
if (constant != null) {
Object value = constant.getValue();
if (value instanceof PsiEnumConstant) {
return factory.getConstFactory().createFromValue(((PsiEnumConstant)value).getName(), type);
}
@NotNull
private static DfType enumName(DfaValue qualifier, DfaMemoryState state, DfaValueFactory factory, PsiType type) {
DfType dfType = state.getDfType(qualifier);
PsiEnumConstant value = DfConstantType.getConstantOfType(dfType, PsiEnumConstant.class);
if (value != null) {
return DfTypes.constant(value.getName(), factory.createDfaType(type));
}
return null;
return TOP;
}
private static Object getConstantValue(DfaMemoryState memoryState, DfaValue value) {
if (value != null) {
LongRangeSet fact = memoryState.getValueFact(value, DfaFactType.RANGE);
Long constantValue = fact == null ? null : fact.getConstantValue();
if (constantValue != null) {
return constantValue;
}
}
DfaConstValue dfaConst = memoryState.getConstantValue(value);
if (dfaConst != null) {
Object constant = dfaConst.getValue();
if (constant instanceof String && ((String)constant).length() > MAX_STRING_CONSTANT_LENGTH_TO_TRACK) return null;
return constant;
}
return null;
DfType type = memoryState.getDfType(value);
Object constant = DfConstantType.getConstantOfType(type, Object.class);
if (constant instanceof String && ((String)constant).length() > MAX_STRING_CONSTANT_LENGTH_TO_TRACK) return null;
return constant;
}
}
@@ -2,6 +2,9 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.openapi.application.Application;
@@ -69,8 +72,9 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
} else {
DfaValue value = memState.peek();
DfaValue target = memState.getStackValue(1);
DfType dfType = memState.getDfType(value);
if (target != null && memState.areEqual(value, target) &&
!(value instanceof DfaConstValue && isFloatingZero(((DfaConstValue)value).getValue())) &&
!(dfType instanceof DfConstantType && isFloatingZero(((DfConstantType<?>)dfType).getValue())) &&
// Reporting strings is skipped because string reassignment might be intentionally used to deduplicate the heap objects
// (we compare strings by contents)
!(TypeUtils.isJavaLangString(left.getType()) && !memState.isNull(value)) &&
@@ -107,14 +111,13 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
((PsiAssignmentExpression)rExpression).getOperationTokenType().equals(JavaTokenType.EQ)) {
rExpression = ((PsiAssignmentExpression)rExpression).getRExpression();
}
if (rExpression == null) return false;
DfaValue dest = runner.getFactory().createValue(rExpression);
if (!(dest instanceof DfaConstValue)) return false;
Object value = ((DfaConstValue)dest).getValue();
if (dest == null) return false;
DfType dfType = dest.getDfType();
PsiType type = var.getType();
boolean isDefaultValue = Objects.equals(PsiTypesUtil.getDefaultValue(type), value) ||
Long.valueOf(0L).equals(value) && TypeConversionUtil.isIntegralNumberType(type);
boolean isDefaultValue = DfConstantType.isConst(dfType, PsiTypesUtil.getDefaultValue(type)) ||
DfConstantType.isConst(dfType, 0) && TypeConversionUtil.isIntegralNumberType(type);
if (!isDefaultValue) return false;
PsiMethod method = PsiTreeUtil.getParentOfType(rExpression, PsiMethod.class);
return method != null && method.isConstructor();
@@ -222,8 +225,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
expression.accept(new ExpressionVisitor(value, memState));
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiTypeCastExpression) {
TypeConstraint fact = memState.getValueFact(value, DfaFactType.TYPE_CONSTRAINT);
if (fact == null) fact = TypeConstraint.empty();
TypeConstraint fact = TypeConstraint.fromDfType(memState.getDfType(value));
myRealOperandTypes.merge((PsiTypeCastExpression)parent, fact, TypeConstraint::unite);
}
reportConstantExpressionValue(value, memState, expression, range);
@@ -257,7 +259,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private void processOfNullableResult(@NotNull DfaValue value, @NotNull DfaMemoryState memState, PsiElement anchor) {
DfaValueFactory factory = value.getFactory();
DfaValue optionalValue = factory == null ? DfaUnknownValue.getInstance() : SpecialField.OPTIONAL_VALUE.createValue(factory, value);
DfaValue optionalValue = SpecialField.OPTIONAL_VALUE.createValue(factory, value);
ThreeState present;
if (memState.isNull(optionalValue)) {
present = ThreeState.NO;
@@ -380,7 +382,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
super.visitCallExpression(call);
Boolean isFailing = myFailingCalls.get(call);
if (isFailing != null || hasNonTrivialFailingContracts(call)) {
myFailingCalls.put(call, DfaConstValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing));
myFailingCalls.put(call, DfaTypeValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing));
}
}
}
@@ -408,20 +410,17 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
@NotNull
static ConstantResult fromConstValue(@NotNull DfaConstValue constant) {
Object value = constant.getValue();
if (value == null) return NULL;
if (Boolean.TRUE.equals(value)) return TRUE;
if (Boolean.FALSE.equals(value)) return FALSE;
static ConstantResult fromDfType(@NotNull DfType dfType) {
if (dfType == DfTypes.NULL) return NULL;
if (dfType == DfTypes.TRUE) return TRUE;
if (dfType == DfTypes.FALSE) return FALSE;
return UNKNOWN;
}
@NotNull
static ConstantResult mergeValue(@Nullable ConstantResult state, @NotNull DfaMemoryState memState, @Nullable DfaValue value) {
if (state == UNKNOWN) return UNKNOWN;
DfaConstValue dfaConst = memState.getConstantValue(value);
if (dfaConst == null) return UNKNOWN;
ConstantResult nextState = fromConstValue(dfaConst);
if (state == UNKNOWN || value == null) return UNKNOWN;
ConstantResult nextState = fromDfType(memState.getUnboxedDfType(value));
return state == null || state == nextState ? nextState : UNKNOWN;
}
}
@@ -4,6 +4,8 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaExpressionFactory;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
@@ -448,7 +450,7 @@ public class DataFlowRunner {
DfaVariableValue thisValue = getFactory().getVarFactory().createThisValue((PsiClass)psiBlock);
// In class initializer this variable is local until escaped
for (DfaMemoryState state : initialStates) {
state.applyFact(thisValue, DfaFactType.LOCALITY, true);
state.meetDfType(thisValue, DfTypes.LOCAL_OBJECT);
}
return;
}
@@ -465,13 +467,14 @@ public class DataFlowRunner {
@Nullable
private static DfaValue makeInitialValue(DfaVariableValue var, @NotNull PsiMethod method) {
DfaValueFactory factory = var.getFactory();
if (var.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor) {
if (var.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor && var.getType() != null) {
PsiClass aClass = ((DfaExpressionFactory.ThisDescriptor)var.getDescriptor()).getPsiElement();
DfaValue value = factory.createTypeValue(var.getType(), Nullability.NOT_NULL);
if (method.getContainingClass() == aClass && MutationSignature.fromMethod(method).preservesThis()) {
// Unmodifiable view, because we cannot call mutating methods, but it's not guaranteed that all fields are stable
// as fields may not contribute to the visible state
return factory.withFact(value, DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE_VIEW);
DfType dfType = DfTypes.typedObject(factory.createDfaType(var.getType()), Nullability.NOT_NULL)
.meet(Mutability.UNMODIFIABLE_VIEW.asDfType());
return factory.fromDfType(dfType);
}
return null;
}
@@ -15,8 +15,10 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.util.keyFMap.KeyFMap;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -32,8 +34,10 @@ import java.util.List;
* To create a new {@code DfaFactMap}, use {@link #EMPTY} and call {@link #with(DfaFactType, Object)} method.
*
* @author Tagir Valeev
* @deprecated Will be removed once {@link TrackingRunner} is adapted to avoid it
*/
public final class DfaFactMap {
@Deprecated
final class DfaFactMap {
public static final DfaFactMap EMPTY = new DfaFactMap(KeyFMap.EMPTY_MAP);
// Contains DfaFactType as keys only
@@ -180,34 +184,24 @@ public final class DfaFactMap {
});
}
/**
* Calculate facts from variable itself (not knowing its state). This method should not be used directly.
* Instead use {@link DfaVariableValue#getInherentFacts()} which caches the calculated value.
*
* @param value variable value to calculate facts from
* @return a calculated fact map.
*/
public static DfaFactMap calcFromVariable(@NotNull DfaVariableValue value) {
return StreamEx.of(DfaFactType.getTypes()).foldLeft(EMPTY, (factMap, type) -> updateMap(factMap, type, value));
}
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaVariableValue value) {
return map.with(factType, factType.calcFromVariable(value));
}
/**
* Derives facts which might be known from given DfaValue without knowing the particular memory state
*
* @param value a value to derive facts from
* @return map of facts derived from the value
*/
@NotNull
public static DfaFactMap fromDfaValue(DfaValue value) {
return StreamEx.of(DfaFactType.getTypes()).foldLeft(EMPTY, (map, type) -> updateMap(map, type, value));
}
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaValue value) {
return map.with(factType, factType.fromDfaValue(value));
public static DfaFactMap fromDfType(DfType dfType) {
DfaFactMap map = EMPTY;
if (dfType instanceof DfIntegralType) {
map = map.with(DfaFactType.RANGE, ((DfIntegralType)dfType).getRange());
}
else if (dfType instanceof DfReferenceType) {
DfReferenceType refType = (DfReferenceType)dfType;
SpecialField field = refType.getSpecialField();
DfType type = refType.getSpecialFieldType();
map = map
.with(DfaFactType.TYPE_CONSTRAINT, refType.getConstraint())
.with(DfaFactType.NULLABILITY, refType.getNullability())
.with(DfaFactType.MUTABILITY, refType.getMutability())
.with(DfaFactType.LOCALITY, refType.isLocal())
.with(DfaFactType.SPECIAL_FIELD_VALUE, field == null || type == DfTypes.TOP ? null : new SpecialFieldValue(field, type));
}
return map;
}
@FunctionalInterface
@@ -15,13 +15,16 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
import com.intellij.codeInspection.dataFlow.types.DfLongType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaBinOpValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -35,8 +38,10 @@ import java.util.Objects;
* A type of the fact which restricts some value.
*
* @author Tagir Valeev
* @deprecated Will be removed once {@link TrackingRunner} is adapted to avoid it
*/
public abstract class DfaFactType<T> extends Key<T> {
@Deprecated
abstract class DfaFactType<T> extends Key<T> {
private static final List<DfaFactType<?>> ourFactTypes = new ArrayList<>();
/**
@@ -50,17 +55,10 @@ public abstract class DfaFactType<T> extends Key<T> {
}
@Override
boolean isUnknown(@NotNull DfaNullability fact) {
public boolean isUnknown(@NotNull DfaNullability fact) {
return fact == DfaNullability.UNKNOWN;
}
@NotNull
@Override
public String getPresentationText(@NotNull DfaNullability fact, @Nullable PsiType type) {
if (type instanceof PsiPrimitiveType) return "";
return fact.getPresentationName();
}
@Override
boolean isSuper(@Nullable DfaNullability superFact, @Nullable DfaNullability subFact) {
return (superFact == null && (subFact == DfaNullability.NOT_NULL || subFact == DfaNullability.FLUSHED)) ||
@@ -70,53 +68,26 @@ public abstract class DfaFactType<T> extends Key<T> {
@NotNull
@Override
DfaNullability uniteFacts(@NotNull DfaNullability left, @NotNull DfaNullability right) {
if (left == right) {
return left;
}
if (left == DfaNullability.NULL || right == DfaNullability.NULL ||
left == DfaNullability.NULLABLE || right == DfaNullability.NULLABLE) {
return DfaNullability.NULLABLE;
}
if (left == DfaNullability.FLUSHED || right == DfaNullability.FLUSHED) {
return DfaNullability.FLUSHED;
}
return DfaNullability.UNKNOWN;
return left.unite(right);
}
@Nullable
@Override
DfaNullability intersectFacts(@NotNull DfaNullability left, @NotNull DfaNullability right) {
if (left == DfaNullability.NOT_NULL || right == DfaNullability.NOT_NULL) {
return DfaNullability.NOT_NULL;
}
if (left == DfaNullability.FLUSHED && DfaNullability.toNullability(right) == Nullability.NULLABLE ||
right == DfaNullability.FLUSHED && DfaNullability.toNullability(left) == Nullability.NULLABLE) {
return DfaNullability.NULLABLE;
}
return super.intersectFacts(left, right);
return left.intersect(right);
}
@Nullable
@Override
public DfaNullability fromDfaValue(DfaValue value) {
if (value instanceof DfaConstValue) {
return ((DfaConstValue)value).getValue() == null ? DfaNullability.NULL : DfaNullability.NOT_NULL;
}
if (value instanceof DfaBoxedValue) return DfaNullability.NOT_NULL;
if (value instanceof DfaFactMapValue && ((DfaFactMapValue)value).get(RANGE) != null) return DfaNullability.NOT_NULL;
return super.fromDfaValue(value);
}
@Nullable
@Override
DfaNullability calcFromVariable(@NotNull DfaVariableValue value) {
return NullabilityUtil.calcCanBeNull(value);
DfaNullability nullability = DfaNullability.fromDfType(value.getDfType());
return nullability == DfaNullability.UNKNOWN ? null : nullability;
}
};
public static final DfaFactType<Mutability> MUTABILITY = new DfaFactType<Mutability>("Mutability") {
@Override
boolean isUnknown(@NotNull Mutability fact) {
public boolean isUnknown(@NotNull Mutability fact) {
return fact == Mutability.UNKNOWN;
}
@@ -125,13 +96,6 @@ public abstract class DfaFactType<T> extends Key<T> {
Mutability uniteFacts(@NotNull Mutability left, @NotNull Mutability right) {
return left.unite(right);
}
@NotNull
@Override
Mutability calcFromVariable(@NotNull DfaVariableValue value) {
PsiModifierListOwner variable = value.getPsiVariable();
return variable == null ? Mutability.UNKNOWN : Mutability.getMutability(variable);
}
};
/**
@@ -145,7 +109,7 @@ public abstract class DfaFactType<T> extends Key<T> {
}
@Override
boolean isUnknown(@NotNull LongRangeSet fact) {
public boolean isUnknown(@NotNull LongRangeSet fact) {
return LongRangeSet.all().equals(fact);
}
@@ -163,18 +127,16 @@ public abstract class DfaFactType<T> extends Key<T> {
return left.binOpFromToken(binOp.getTokenType(), right, PsiType.LONG.equals(binOp.getType()));
}
}
return LongRangeSet.fromDfaValue(value);
return DfLongType.extractRange(value.getDfType());
}
@Nullable
@Override
LongRangeSet calcFromVariable(@NotNull DfaVariableValue var) {
private LongRangeSet calcFromVariable(@NotNull DfaVariableValue var) {
VariableDescriptor descriptor = var.getDescriptor();
if(descriptor instanceof SpecialField) {
DfaValue defaultValue = ((SpecialField)descriptor).getDefaultValue(var.getFactory(), false);
LongRangeSet fromSpecialField = LongRangeSet.fromDfaValue(defaultValue);
if (fromSpecialField != null) {
return fromSpecialField;
DfType dfType = ((SpecialField)descriptor).getDefaultValue(false);
if (dfType instanceof DfIntegralType) {
return ((DfIntegralType)dfType).getRange();
}
}
LongRangeSet fromType = LongRangeSet.fromType(var.getType());
@@ -193,14 +155,6 @@ public abstract class DfaFactType<T> extends Key<T> {
LongRangeSet intersection = left.intersect(right);
return intersection.isEmpty() ? null : intersection;
}
@NotNull
@Override
public String getPresentationText(@NotNull LongRangeSet fact, @Nullable PsiType type) {
LongRangeSet fromType = LongRangeSet.fromType(type);
if(fact.equals(fromType)) return "";
return fact.getPresentationText(type);
}
};
/**
* This fact represents a set of possible types of this value
@@ -212,16 +166,8 @@ public abstract class DfaFactType<T> extends Key<T> {
return superFact == null || (subFact != null && superFact.isSuperStateOf(subFact));
}
@Nullable
@Override
TypeConstraint calcFromVariable(@NotNull DfaVariableValue value) {
PsiType psiType = value.getType();
DfaPsiType type = psiType == null ? null : value.getFactory().createDfaType(psiType);
return type == null ? null : TypeConstraint.empty().withInstanceofValue(type);
}
@Override
boolean isUnknown(@NotNull TypeConstraint fact) {
public boolean isUnknown(@NotNull TypeConstraint fact) {
return fact.isEmpty();
}
@@ -231,22 +177,16 @@ public abstract class DfaFactType<T> extends Key<T> {
return left.intersect(right);
}
@Nullable
@NotNull
@Override
TypeConstraint uniteFacts(@NotNull TypeConstraint left, @NotNull TypeConstraint right) {
return left.unite(right);
}
@NotNull
@Override
public String getPresentationText(@NotNull TypeConstraint fact, @Nullable PsiType type) {
return fact.getPresentationText(type);
}
};
public static final DfaFactType<Boolean> LOCALITY = new DfaFactType<Boolean>("Locality") {
@Override
boolean isUnknown(@NotNull Boolean fact) {
public boolean isUnknown(@NotNull Boolean fact) {
return !fact;
}
@@ -269,12 +209,6 @@ public abstract class DfaFactType<T> extends Key<T> {
SpecialFieldValue uniteFacts(@NotNull SpecialFieldValue left, @NotNull SpecialFieldValue right) {
return left.unite(right);
}
@NotNull
@Override
public String getPresentationText(@NotNull SpecialFieldValue fact, @Nullable PsiType type) {
return fact.getPresentationText(type);
}
};
@NotNull
@@ -294,20 +228,14 @@ public abstract class DfaFactType<T> extends Key<T> {
@Nullable
public T fromDfaValue(DfaValue value) {
return value instanceof DfaFactMapValue ? ((DfaFactMapValue)value).get(this) : null;
}
// Could be expensive
@Nullable
T calcFromVariable(@NotNull DfaVariableValue value) {
return null;
return value != null ? DfaFactMap.fromDfType(value.getDfType()).get(this) : null;
}
boolean isSuper(@Nullable T superFact, @Nullable T subFact) {
return Objects.equals(superFact, subFact);
}
boolean isUnknown(@NotNull T fact) {
public boolean isUnknown(@NotNull T fact) {
return false;
}
@@ -345,18 +273,6 @@ public abstract class DfaFactType<T> extends Key<T> {
return fact.toString();
}
/**
* Produces a user-friendly presentation of the fact based on the fact itself and the type of the expression
* @param fact a fact to represent
* @param type an expression type, if known
* @return a user-friendly string representation of the fact; empty string if the fact adds nothing to the expression type
* (e.g. fact is Range {0..65535} and type is 'char').
*/
@NotNull
public String getPresentationText(@NotNull T fact, @Nullable PsiType type) {
return toString(fact);
}
static List<DfaFactType<?>> getTypes() {
return Collections.unmodifiableList(ourFactTypes);
}
@@ -15,8 +15,8 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -91,60 +91,38 @@ public interface DfaMemoryState {
boolean applyContractCondition(DfaCondition dfaCond);
/**
* Returns a value fact about supplied value within the context of current memory state.
* Returns null if the fact of given type is not known or not applicable to a given value.
*
* @param <T> a type of the fact value
* @param value a value to get the fact about
* @param factType a type of the fact to get
* @return a fact about value, if known
* Updates value dfType if it's compatible with current value state.
* Depending on value passed and memory state implementation the new fact may or may not be memoized.
*
* @param value value to update
* @param dfType wanted type
* @return true if update was successful. If false was returned the memory state may be in inconsistent state.
*/
@Nullable
<T> T getValueFact(@NotNull DfaValue value, @NotNull DfaFactType<T> factType);
boolean meetDfType(@NotNull DfaValue value, @NotNull DfType dfType);
/**
* Forgets given fact if it was known for the supplied value
* @param value a value to drop fact for
* @param factType a type of the fact to drop
* Forcibly sets the supplied dfType to given value if given value state can be memoized.
* This is necessary to override some knowledge about the variable state. In most of the cases
* {@link #meetDfType(DfaValue, DfType)} should be used as it narrows existing type.
*
* @param value value to update.
* @param dfType type to assign to value. Note that type might be adjusted, e.g. to be compatible with value declared PsiType.
*/
void dropFact(@NotNull DfaValue value, @NotNull DfaFactType<?> factType);
void setDfType(@NotNull DfaValue value, @NotNull DfType dfType);
/**
* Updates value fact if it's compatible with current value state. Depending on value passed and memory state implementation
* the new fact may or may not be memoized.
*
* @param <T> a type of the fact value
* @param var a value to update its state
* @param factType a type of the fact to set
* @param value a new fact value
* @return true if update was successful; false if current state contradicts with the wanted fact value
*/
<T> boolean applyFact(@NotNull DfaValue var, @NotNull DfaFactType<T> factType, @Nullable T value);
/**
* Forces variable to have given fact (ignoring current value of this fact and flushing existing relations with this variable).
* This might be useful if state is proven to be invalid, but we want to continue analysis to discover subsequent
* problems under assumption that the state is still valid.
* <p>
* E.g. if it's proven that nullable variable is dereferenced, for the sake of subsequent analysis one might call
* {@code forceVariableFact(var, NULLABILITY, NOT_NULL)}
* </p>
*
* @param var the variable to modify
* @param factType the type of the fact
* @param value the new variable value
* @param <T> type of fact value
*/
<T> void forceVariableFact(@NotNull DfaVariableValue var, @NotNull DfaFactType<T> factType, @Nullable T value);
/**
* Returns a map of known facts associated with given variable
*
* @param variable a variable to query the facts from
* @return facts map
* @param value value to get the type of
* @return the DfType of the value within this memory state
*/
@NotNull
DfaFactMap getFacts(@NotNull DfaVariableValue variable);
DfType getDfType(@NotNull DfaValue value);
/**
* @param value value to get the type of; if value is a primitive wrapper, it will be unboxed before fetching the DfType
* @return the DfType of the value within this memory state
*/
@NotNull
DfType getUnboxedDfType(@NotNull DfaValue value);
void flushFields();
@@ -156,16 +134,6 @@ public interface DfaMemoryState {
boolean isNotNull(DfaValue dfaVar);
/**
* Returns a constant value which equals to given value, if such.
*
* @param value a value to find a corresponding constant
* @return found constant or null
*/
@Nullable
@Contract("null -> null")
DfaConstValue getConstantValue(@Nullable DfaValue value);
/**
* Ephemeral means a state that was created when considering a method contract and checking if one of its arguments is null.
* With explicit null check, that would result in any non-annotated variable being treated as nullable and producing possible NPE warnings later.
@@ -2,6 +2,9 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -10,8 +13,6 @@ import org.jetbrains.annotations.Nullable;
* values useful during the DFA. If you have a DfaNullability value (e.g. from {@link CommonDataflow}),
* and want to check if it's nullable, or not, it's advised to convert it to {@link Nullability} first,
* as more values could be introduced to this enum in future.
*
* @see DfaFactType#NULLABILITY
*/
public enum DfaNullability {
/**
@@ -47,12 +48,36 @@ public enum DfaNullability {
return myPresentationalName;
}
public static boolean isNullable(DfaFactMap map) {
return toNullability(map.get(DfaFactType.NULLABILITY)) == Nullability.NULLABLE;
@NotNull
public DfaNullability unite(@NotNull DfaNullability other) {
if (this == other) {
return this;
}
if (this == NULL || other == NULL ||
this == NULLABLE || other == NULLABLE) {
return NULLABLE;
}
if (this == FLUSHED || other == FLUSHED) {
return FLUSHED;
}
return UNKNOWN;
}
public static boolean isNotNull(DfaFactMap map) {
return map.get(DfaFactType.NULLABILITY) == NOT_NULL;
@Nullable
public DfaNullability intersect(@NotNull DfaNullability right) {
if (this == NOT_NULL) {
return right == NULL ? null : NOT_NULL;
}
if (right == NOT_NULL) {
return this == NULL ? null : NOT_NULL;
}
if (this == UNKNOWN) return right;
if (right == UNKNOWN) return this;
if (this == FLUSHED && toNullability(right) == Nullability.NULLABLE ||
right == FLUSHED && toNullability(this) == Nullability.NULLABLE) {
return NULLABLE;
}
return equals(right) ? this : null;
}
@NotNull
@@ -72,4 +97,23 @@ public enum DfaNullability {
}
throw new IllegalStateException("Unknown nullability: "+nullability);
}
@NotNull
public DfReferenceType asDfType() {
switch (this) {
case NULL:
return DfTypes.NULL;
case NOT_NULL:
return DfTypes.NOT_NULL_OBJECT;
case UNKNOWN:
return DfTypes.OBJECT_OR_NULL;
default:
return DfTypes.customObject(TypeConstraint.empty(), this, Mutability.UNKNOWN, null, DfTypes.BOTTOM);
}
}
@NotNull
public static DfaNullability fromDfType(@NotNull DfType type) {
return type instanceof DfReferenceType ? ((DfReferenceType)type).getNullability() : UNKNOWN;
}
}
@@ -17,6 +17,8 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.util.OptionalUtil;
@@ -84,10 +86,8 @@ public class DfaOptionalSupport {
*/
@NotNull
public static DfaValue getOptionalValue(DfaValueFactory factory, boolean present) {
DfaValue value = present ? factory.getFactValue(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL) : factory.getConstFactory().getNull();
DfaFactMap facts = DfaFactMap.EMPTY.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.OPTIONAL_VALUE.withValue(value));
return factory.getFactFactory().createValue(facts);
DfType valueType = present ? DfTypes.NOT_NULL_OBJECT : DfTypes.NULL;
return factory.fromDfType(SpecialField.OPTIONAL_VALUE.asDfType(valueType));
}
private static class ReplaceOptionalCallFix implements LocalQuickFix {
@@ -9,7 +9,6 @@ import com.intellij.codeInspection.dataFlow.instructions.PushInstruction;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.util.MultiValuesMap;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
@@ -191,7 +190,7 @@ public class DfaUtil {
if (!placeMethod.hasModifierProperty(PsiModifier.STATIC) && target.hasModifierProperty(PsiModifier.STATIC)) return null;
if (getAccessOffset(placeMethod) >= getWriteOffset(target)) return null;
return factory.createTypeValue(target.getType(), Nullability.NULLABLE);
return factory.getObjectType(target.getType(), Nullability.NULLABLE);
}
private static int getWriteOffset(PsiField target) {
@@ -323,15 +322,13 @@ public class DfaUtil {
public static DfaValue boxUnbox(DfaValue value, @Nullable PsiType type) {
if (TypeConversionUtil.isPrimitiveWrapper(type)) {
if (value instanceof DfaConstValue ||
(value instanceof DfaVariableValue && TypeConversionUtil.isPrimitiveAndNotNull(value.getType()))) {
if (TypeConversionUtil.isPrimitiveAndNotNull(value.getType())) {
DfaValue boxed = value.getFactory().getBoxedFactory().createBoxed(value, type);
return boxed == null ? DfaUnknownValue.getInstance() : boxed;
return boxed == null ? value.getFactory().getUnknown() : boxed;
}
}
if (TypeConversionUtil.isPrimitiveAndNotNull(type)) {
if (value instanceof DfaBoxedValue ||
(value instanceof DfaVariableValue && TypeConversionUtil.isPrimitiveWrapper(value.getType()))) {
if (value instanceof DfaBoxedValue || TypeConversionUtil.isPrimitiveWrapper(value.getType())) {
return SpecialField.UNBOX.createValue(value.getFactory(), value);
}
}
@@ -350,38 +347,9 @@ public class DfaUtil {
if (expressionToAnalyze == null) return null;
Object computed = ExpressionUtils.computeConstantExpression(expression);
if (computed != null) return computed;
DataFlowRunner runner = new DataFlowRunner(expression);
class Visitor extends StandardInstructionVisitor {
Object exprValue;
@Override
protected void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expr,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
super.beforeExpressionPush(value, expr, range, state);
if (expr != expressionToAnalyze) return;
Object newValue;
if (value instanceof DfaConstValue) {
newValue = ((DfaConstValue)value).getValue();
} else {
newValue = UNKNOWN_VALUE;
}
if (exprValue == null) {
exprValue = newValue;
} else if (exprValue != newValue) {
exprValue = UNKNOWN_VALUE;
}
if (exprValue == UNKNOWN_VALUE) {
runner.cancel();
}
}
}
Visitor visitor = new Visitor();
RunnerResult result = runner.analyzeMethod(expressionToAnalyze, visitor);
if (result == RunnerResult.OK && visitor.exprValue != UNKNOWN_VALUE) {
return visitor.exprValue;
CommonDataflow.DataflowResult dataflowResult = CommonDataflow.getDataflowResult(expression);
if (dataflowResult != null) {
return ContainerUtil.getOnlyItem(dataflowResult.getExpressionValues(expression));
}
return null;
}
@@ -399,13 +367,13 @@ public class DfaUtil {
LongRangeSet fromAnnotation = LongRangeSet.fromPsiElement(parameter);
if (fromAnnotation.min() > fromType.min()) {
MethodContract contract = MethodContract.singleConditionContract(
ContractValue.argument(i), RelationType.LT, ContractValue.constant(fromAnnotation.min(), PsiType.LONG),
ContractValue.argument(i), RelationType.LT, ContractValue.constant(fromAnnotation.min(), parameter.getType()),
ContractReturnValue.fail());
rangeContracts.add(contract);
}
if (fromAnnotation.max() < fromType.max()) {
MethodContract contract = MethodContract.singleConditionContract(
ContractValue.argument(i), RelationType.GT, ContractValue.constant(fromAnnotation.max(), PsiType.LONG),
ContractValue.argument(i), RelationType.GT, ContractValue.constant(fromAnnotation.max(), parameter.getType()),
ContractReturnValue.fail());
rangeContracts.add(contract);
}
@@ -17,49 +17,39 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
class DfaVariableState {
@NotNull final DfaFactMap myFactMap;
@NotNull final DfType myDfType;
private final int myHash;
DfaVariableState(@NotNull DfaVariableValue dfaVar) {
this(dfaVar.getInherentFacts());
this(dfaVar.getInherentType());
}
public boolean isSuperStateOf(DfaVariableState that) {
return myFactMap.isSuperStateOf(that.myFactMap);
DfaVariableState(@NotNull DfType dfType) {
assert dfType != DfTypes.BOTTOM;
myDfType = dfType instanceof DfReferenceType ? ((DfReferenceType)dfType).dropSpecialField() : dfType;
myHash = myDfType.hashCode();
}
DfaVariableState(@NotNull DfaFactMap factMap) {
myFactMap = factMap.with(DfaFactType.SPECIAL_FIELD_VALUE, null);
myHash = myFactMap.hashCode();
}
@Nullable
DfaVariableState withInstanceofValue(@NotNull DfaPsiType dfaType) {
if (dfaType.getPsiType() instanceof PsiPrimitiveType) return this;
return withFacts(TypeConstraint.withInstanceOf(myFactMap, dfaType));
}
@Nullable
DfaVariableState withNotInstanceofValue(@NotNull DfaPsiType dfaType) {
TypeConstraint typeConstraint = getTypeConstraint();
TypeConstraint newTypeConstraint = typeConstraint.withNotInstanceofValue(dfaType);
return newTypeConstraint == null ? null : withFact(DfaFactType.TYPE_CONSTRAINT, newTypeConstraint);
public boolean isSuperStateOf(DfaVariableState other) {
return this.myDfType.isMergeable(other.myDfType);
}
@NotNull
DfaVariableState withoutType(@NotNull DfaPsiType type) {
return withFact(DfaFactType.TYPE_CONSTRAINT, getTypeConstraint().withoutType(type));
if (myDfType instanceof DfReferenceType) {
return createCopy(((DfReferenceType)myDfType).dropTypeConstraint().meet(getTypeConstraint().withoutType(type).asDfType()));
}
return this;
}
public int hashCode() {
@@ -70,55 +60,44 @@ class DfaVariableState {
if (obj == this) return true;
if (!(obj instanceof DfaVariableState)) return false;
DfaVariableState aState = (DfaVariableState) obj;
return myHash == aState.myHash && Objects.equals(myFactMap, aState.myFactMap);
return myHash == aState.myHash && myDfType.equals(aState.myDfType);
}
@NotNull
protected DfaVariableState createCopy(@NotNull DfaFactMap factMap) {
return new DfaVariableState(factMap);
protected DfaVariableState createCopy(@NotNull DfType dfType) {
return dfType.equals(myDfType) ? this : new DfaVariableState(dfType);
}
public String toString() {
return "State: " + myFactMap;
return "State: " + myDfType;
}
@NotNull
Nullability getNullability() {
return DfaNullability.toNullability(myFactMap.get(DfaFactType.NULLABILITY));
return DfaNullability.toNullability(DfaNullability.fromDfType(myDfType));
}
public boolean isNotNull() {
return DfaNullability.isNotNull(myFactMap);
return !myDfType.isSuperType(DfTypes.NULL);
}
@NotNull
DfaVariableState withNotNull() {
return getNullability() == Nullability.NOT_NULL ? this : withoutFact(DfaFactType.NULLABILITY);
return getNullability() == Nullability.NOT_NULL ? this : withNullability(DfaNullability.UNKNOWN);
}
@NotNull
<T> DfaVariableState withFact(DfaFactType<T> type, T value) {
return withFacts(myFactMap.with(type, value));
}
@NotNull
<T> DfaVariableState withoutFact(DfaFactType<T> type) {
return withFact(type, null);
DfaVariableState withNullability(@NotNull DfaNullability nullability) {
if (myDfType instanceof DfReferenceType && ((DfReferenceType)myDfType).getNullability() != nullability) {
return createCopy(((DfReferenceType)myDfType).dropNullability().meet(nullability.asDfType()));
}
return this;
}
@Nullable
<T> DfaVariableState intersectFact(DfaFactType<T> type, T value) {
return withFacts(myFactMap.intersect(type, value));
}
@Nullable
DfaVariableState intersectMap(DfaFactMap map) {
return withFacts(myFactMap.intersect(map));
}
@Contract("null -> null;!null -> !null")
public DfaVariableState withFacts(@Nullable DfaFactMap facts) {
return facts == null ? null : facts.equals(myFactMap) ? this : createCopy(facts);
DfaVariableState meet(DfType dfType) {
DfType result = myDfType.meet(dfType);
return result == DfTypes.BOTTOM ? null : createCopy(result);
}
@NotNull
@@ -128,12 +107,6 @@ class DfaVariableState {
@NotNull
public TypeConstraint getTypeConstraint() {
TypeConstraint fact = getFact(DfaFactType.TYPE_CONSTRAINT);
return fact == null ? TypeConstraint.empty() : fact;
}
@Nullable
public <T> T getFact(@NotNull DfaFactType<T> factType) {
return myFactMap.get(factType);
return TypeConstraint.fromDfType(myDfType);
}
}
@@ -1,21 +1,19 @@
// Copyright 2000-2019 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.value.*;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ObjectUtils;
import one.util.streamex.IntStreamEx;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.*;
/**
* @author peter
*/
class EqClass extends SortedIntSet {
class EqClass extends SortedIntSet implements Iterable<DfaVariableValue> {
private final DfaValueFactory myFactory;
/**
@@ -51,17 +49,19 @@ class EqClass extends SortedIntSet {
buf.append(")");
return buf.toString();
}
DfaVariableValue getVariable(int index) {
return (DfaVariableValue)myFactory.getValue(get(index));
}
List<DfaVariableValue> getVariables(boolean unwrap) {
List<DfaVariableValue> vars = new ArrayList<>();
/**
* @return copy of variables from this class as a list. Use this method if you expect
* class updates during the iteration.
*/
List<DfaVariableValue> asList() {
List<DfaVariableValue> vars = new ArrayList<>(size());
forEach(id -> {
DfaValue value = myFactory.getValue(id);
if (value instanceof DfaVariableValue) {
vars.add((DfaVariableValue)value);
}
else if (unwrap && value instanceof DfaBoxedValue) {
vars.add(((DfaBoxedValue)value).getWrappedValue());
}
vars.add((DfaVariableValue)myFactory.getValue(id));
return true;
});
return vars;
@@ -74,39 +74,27 @@ class EqClass extends SortedIntSet {
@Nullable
DfaVariableValue getCanonicalVariable() {
if (size() == 1) {
return ObjectUtils.tryCast(myFactory.getValue(get(0)), DfaVariableValue.class);
return getVariable(0);
}
return IntStreamEx.range(size()).mapToObj(idx -> myFactory.getValue(get(idx)))
.select(DfaVariableValue.class).min(CANONICAL_VARIABLE_COMPARATOR).orElse(null);
return StreamEx.of(iterator()).min(CANONICAL_VARIABLE_COMPARATOR).orElse(null);
}
List<DfaValue> getMemberValues() {
final List<DfaValue> result = new ArrayList<>(size());
forEach(id -> {
DfaValue value = myFactory.getValue(id);
result.add(value);
return true;
});
return result;
}
@Nullable
DfaConstValue findConstant() {
Ref<DfaConstValue> result = new Ref<>();
forEach(id -> {
DfaValue value = myFactory.getValue(id);
if (value instanceof DfaConstValue) {
result.set((DfaConstValue)value);
return false;
@NotNull
@Override
public Iterator<DfaVariableValue> iterator() {
return new Iterator<DfaVariableValue>() {
int pos;
@Override
public boolean hasNext() {
return pos < size();
}
return true;
});
return result.get();
}
boolean containsConstantsOnly() {
int size = size();
return size <= 1 && (size == 0 || myFactory.getValue(get(0)) instanceof DfaConstValue);
@Override
public DfaVariableValue next() {
if (pos >= size()) throw new NoSuchElementException();
return (DfaVariableValue)myFactory.getValue(get(pos++));
}
};
}
}
@@ -15,10 +15,7 @@ import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
import static com.intellij.codeInspection.dataFlow.ContractReturnValue.*;
import static com.intellij.codeInspection.dataFlow.MethodContract.singleConditionContract;
@@ -219,7 +216,7 @@ public class HardcodedContracts {
className.startsWith("com.google.common.truth.") ||
className.startsWith("org.assertj.core.api.") ||
className.equals("org.hamcrest.MatcherAssert")) {
return handleTestFrameworks(paramCount, className, methodName, call);
return handleTestFrameworks(method, paramCount, className, methodName, call);
}
else if (TypeUtils.isOptional(owner)) {
if (OptionalUtil.OPTIONAL_GET.methodMatches(method) || "orElseThrow".equals(methodName)) {
@@ -298,7 +295,10 @@ public class HardcodedContracts {
return className.startsWith("org.testng.") && !className.equals("org.testng.AssertJUnit");
}
private static List<MethodContract> handleTestFrameworks(int paramCount, String className, String methodName,
private static List<MethodContract> handleTestFrameworks(PsiMethod method,
int paramCount,
String className,
String methodName,
@Nullable PsiMethodCallExpression call) {
if (("assertThat".equals(methodName) || "assumeThat".equals(methodName) || "that".equals(methodName)) && call != null) {
return handleAssertThat(paramCount, call);
@@ -316,20 +316,21 @@ public class HardcodedContracts {
if (paramCount == 0) return Collections.emptyList();
int checkedParam = testng || isJunit5(className) ? 0 : paramCount - 1;
PsiType type = Objects.requireNonNull(method.getParameterList().getParameter(checkedParam)).getType();
ValueConstraint[] constraints = createConstraintArray(paramCount);
if ("assertTrue".equals(methodName) || "assumeTrue".equals(methodName)) {
if (("assertTrue".equals(methodName) || "assumeTrue".equals(methodName)) && PsiType.BOOLEAN.equals(type)) {
constraints[checkedParam] = FALSE_VALUE;
return Collections.singletonList(new StandardMethodContract(constraints, fail()));
}
if ("assertFalse".equals(methodName) || "assumeFalse".equals(methodName)) {
if (("assertFalse".equals(methodName) || "assumeFalse".equals(methodName)) && PsiType.BOOLEAN.equals(type)) {
constraints[checkedParam] = TRUE_VALUE;
return Collections.singletonList(new StandardMethodContract(constraints, fail()));
}
if ("assertNull".equals(methodName)) {
if ("assertNull".equals(methodName) && TypeUtils.isJavaLangObject(type)) {
constraints[checkedParam] = NOT_NULL_VALUE;
return Collections.singletonList(new StandardMethodContract(constraints, fail()));
}
if ("assertNotNull".equals(methodName)) {
if ("assertNotNull".equals(methodName) && TypeUtils.isJavaLangObject(type)) {
return failIfNull(checkedParam, paramCount, false);
}
return Collections.emptyList();
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
@@ -158,10 +159,10 @@ public abstract class InstructionVisitor {
DfaValue value = state.pop();
DfaValueFactory factory = runner.getFactory();
if (value instanceof DfaBinOpValue) {
value = factory.getFactValue(DfaFactType.RANGE, state.getValueFact(value, DfaFactType.RANGE));
value = factory.fromDfType(state.getDfType(value));
}
DfaValue boxed = factory.getBoxedFactory().createBoxed(value, instruction.getTargetType());
state.push(boxed == null ? factory.createTypeValue(instruction.getTargetType(), Nullability.NOT_NULL) : boxed);
state.push(boxed == null ? factory.getObjectType(instruction.getTargetType(), Nullability.NOT_NULL) : boxed);
return nextInstruction(instruction, runner, state);
}
@@ -176,7 +177,7 @@ public abstract class InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState state) {
state.pop();
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, state);
pushExpressionResult(runner.getFactory().getUnknown(), instruction, state);
return nextInstruction(instruction, runner, state);
}
@@ -234,15 +235,14 @@ public abstract class InstructionVisitor {
public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop();
memState.pop();
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
pushExpressionResult(runner.getFactory().getUnknown(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
public DfaInstructionState[] visitObjectOfInstruction(ObjectOfInstruction instruction, DataFlowRunner runner, DfaMemoryState state) {
DfaValue value = state.pop();
DfaConstValue constant = state.getConstantValue(value);
PsiType type = constant == null ? null : ObjectUtils.tryCast(constant.getValue(), PsiType.class);
state.push(runner.getFactory().createTypeValue(type, Nullability.NOT_NULL));
PsiType type = DfConstantType.getConstantOfType(state.getDfType(value), PsiType.class);
state.push(runner.getFactory().getObjectType(type, Nullability.NOT_NULL));
return nextInstruction(instruction, runner, state);
}
@@ -279,7 +279,7 @@ public abstract class InstructionVisitor {
public DfaInstructionState[] visitMethodReference(MethodReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop();
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
pushExpressionResult(runner.getFactory().getUnknown(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -299,13 +299,13 @@ public abstract class InstructionVisitor {
}
memState.pop(); //qualifier
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
pushExpressionResult(runner.getFactory().getUnknown(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
public DfaInstructionState[] visitNot(NotInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.pop();
pushExpressionResult(DfaUnknownValue.getInstance(), instruction, memState);
pushExpressionResult(runner.getFactory().getUnknown(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -5,6 +5,9 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.inference.JavaSourceInference;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ModificationTracker;
@@ -33,17 +36,17 @@ public enum Mutability {
* A value is known to be mutable (e.g. elements are sometimes added to the collection)
*/
MUTABLE("modifiable", null),
/**
* A value is known to be immutable. For collection no elements could be added, removed or altered (though if collection
* contains mutable elements, they still could be mutated).
*/
UNMODIFIABLE("unmodifiable", "org.jetbrains.annotations.Unmodifiable"),
/**
* A value is known to be an immutable view over a possibly mutable value: it cannot be mutated directly using this
* reference; however subsequent reads (e.g. {@link java.util.Collection#size}) may return different results if the
* underlying value is mutated by somebody else.
*/
UNMODIFIABLE_VIEW("unmodifiable view", "org.jetbrains.annotations.UnmodifiableView");
UNMODIFIABLE_VIEW("unmodifiable view", "org.jetbrains.annotations.UnmodifiableView"),
/**
* A value is known to be immutable. For collection no elements could be added, removed or altered (though if collection
* contains mutable elements, they still could be mutated).
*/
UNMODIFIABLE("unmodifiable", "org.jetbrains.annotations.Unmodifiable");
public static final @NotNull String UNMODIFIABLE_ANNOTATION = UNMODIFIABLE.myAnnotation;
public static final @NotNull String UNMODIFIABLE_VIEW_ANNOTATION = UNMODIFIABLE_VIEW.myAnnotation;
@@ -56,6 +59,10 @@ public enum Mutability {
myAnnotation = annotation;
myKey = annotation == null ? null : Key.create(annotation);
}
public DfReferenceType asDfType() {
return DfTypes.customObject(TypeConstraint.empty(), DfaNullability.UNKNOWN, this, null, DfTypes.BOTTOM);
}
@Override
public String toString() {
@@ -69,12 +76,21 @@ public enum Mutability {
@NotNull
public Mutability unite(Mutability other) {
if (this == other) return this;
if (this == MUTABLE || other == MUTABLE) return MUTABLE;
if (this == UNKNOWN || other == UNKNOWN) return UNKNOWN;
if (this == MUTABLE || other == MUTABLE) return MUTABLE;
if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) return UNMODIFIABLE_VIEW;
return UNMODIFIABLE;
}
@NotNull
public Mutability intersect(Mutability other) {
if (this == other) return this;
if (this == UNMODIFIABLE || other == UNMODIFIABLE) return UNMODIFIABLE;
if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) return UNMODIFIABLE_VIEW;
if (this == MUTABLE || other == MUTABLE) return MUTABLE;
return UNKNOWN;
}
@Nullable
public PsiAnnotation asAnnotation(Project project) {
if (myAnnotation == null) return null;
@@ -157,4 +173,7 @@ public enum Mutability {
return owner instanceof PsiMethodImpl ? JavaSourceInference.inferMutability((PsiMethodImpl)owner) : UNKNOWN;
}
public static Mutability fromDfType(DfType dfType) {
return dfType instanceof DfReferenceType ? ((DfReferenceType)dfType).getMutability() : UNKNOWN;
}
}
@@ -122,7 +122,7 @@ class NullParameterConstraintChecker extends DataFlowRunner {
super(factory);
for (PsiParameter parameter : myPossiblyViolatedParameters) {
setVariableState(getFactory().getVarFactory().createVariableValue(parameter),
new DfaVariableState(DfaFactMap.EMPTY.with(DfaFactType.NULLABILITY, DfaNullability.NULLABLE)));
new DfaVariableState(DfaNullability.NULLABLE.asDfType()));
}
}
@@ -20,13 +20,15 @@ import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class NullabilityUtil {
static DfaNullability calcCanBeNull(DfaVariableValue value) {
@NotNull
public static DfaNullability calcCanBeNull(DfaVariableValue value) {
if (value.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor) {
return DfaNullability.NOT_NULL;
}
@@ -35,13 +37,13 @@ public class NullabilityUtil {
}
PsiModifierListOwner var = value.getPsiVariable();
if (value.getType() instanceof PsiPrimitiveType) {
return null;
return DfaNullability.UNKNOWN;
}
Nullability nullability = DfaPsiUtil.getElementNullabilityIgnoringParameterInference(value.getType(), var);
if (nullability != Nullability.UNKNOWN) {
return DfaNullability.fromNullability(nullability);
}
if (var == null) return null;
if (var == null) return DfaNullability.UNKNOWN;
Nullability defaultNullability = value.getFactory().suggestNullabilityForNonAnnotatedMember(var);
@@ -160,7 +162,7 @@ public class NullabilityUtil {
return getExpressionNullability(elseExpression, useDataflow);
}
if (useDataflow) {
return DfaNullability.toNullability(CommonDataflow.getExpressionFact(expression, DfaFactType.NULLABILITY));
return DfaNullability.toNullability(DfaNullability.fromDfType(CommonDataflow.getDfType(expression)));
}
Nullability left = getExpressionNullability(thenExpression, false);
if (left == Nullability.UNKNOWN) return Nullability.UNKNOWN;
@@ -178,7 +180,7 @@ public class NullabilityUtil {
return Nullability.NOT_NULL;
}
if (useDataflow) {
return DfaNullability.toNullability(CommonDataflow.getExpressionFact(expression, DfaFactType.NULLABILITY));
return DfaNullability.toNullability(DfaNullability.fromDfType(CommonDataflow.getDfType(expression)));
}
if (expression instanceof PsiReferenceExpression) {
PsiReferenceExpression ref = (PsiReferenceExpression)expression;
@@ -2,12 +2,16 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.TypeUtils;
@@ -15,6 +19,8 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import static com.intellij.codeInspection.dataFlow.ContractReturnValue.returnFalse;
import static com.intellij.codeInspection.dataFlow.ContractReturnValue.returnTrue;
import static com.intellij.codeInspection.dataFlow.StandardMethodContract.ValueConstraint.NULL_VALUE;
@@ -37,31 +43,33 @@ public enum SpecialField implements VariableDescriptor {
return accessor instanceof PsiField && "length".equals(accessor.getName()) && PsiUtil.isArrayClass(accessor.getContainingClass());
}
@NotNull
@Override
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
DfType fromInitializer(PsiExpression initializer) {
if (initializer instanceof PsiArrayInitializerExpression) {
return factory.getInt(((PsiArrayInitializerExpression)initializer).getInitializers().length);
return DfTypes.intValue(((PsiArrayInitializerExpression)initializer).getInitializers().length);
}
if (initializer instanceof PsiNewExpression) {
PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)initializer).getArrayInitializer();
if (arrayInitializer != null) {
return factory.getInt(arrayInitializer.getInitializers().length);
return DfTypes.intValue(arrayInitializer.getInitializers().length);
}
PsiExpression[] dimensions = ((PsiNewExpression)initializer).getArrayDimensions();
if (dimensions.length > 0) {
Object length = ExpressionUtils.computeConstantExpression(dimensions[0]);
if (length instanceof Integer) {
return factory.getInt(((Integer)length).intValue());
return DfTypes.intValue(((Integer)length).intValue());
}
}
}
return null;
return DfTypes.TOP;
}
},
STRING_LENGTH("length", true) {
@NotNull
@Override
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
return fromConstant(factory, ExpressionUtils.computeConstantExpression(initializer));
DfType fromInitializer(PsiExpression initializer) {
return fromConstant(ExpressionUtils.computeConstantExpression(initializer));
}
@Override
@@ -78,9 +86,10 @@ public enum SpecialField implements VariableDescriptor {
return containingClass != null && JAVA_LANG_STRING.equals(containingClass.getQualifiedName());
}
@NotNull
@Override
public DfaValue fromConstant(DfaValueFactory factory, @Nullable Object obj) {
return obj instanceof String ? factory.getInt(((String)obj).length()) : null;
public DfType fromConstant(@Nullable Object obj) {
return obj instanceof String ? DfTypes.intValue(((String)obj).length()) : DfTypes.TOP;
}
},
COLLECTION_SIZE("size", false) {
@@ -131,8 +140,8 @@ public enum SpecialField implements VariableDescriptor {
@NotNull
@Override
public DfaValue getDefaultValue(DfaValueFactory factory, boolean forAccessor) {
return DfaUnknownValue.getInstance();
public DfType getDefaultValue(boolean forAccessor) {
return DfTypes.TOP;
}
@NotNull
@@ -157,13 +166,18 @@ public enum SpecialField implements VariableDescriptor {
OPTIONAL_VALUE("value", true) {
@Override
public PsiType getType(DfaVariableValue variableValue) {
return OptionalUtil.getOptionalElementType(variableValue.getType());
PsiType optionalType = variableValue.getType();
PsiType type = OptionalUtil.getOptionalElementType(optionalType);
if (type instanceof PsiPrimitiveType) {
return ((PsiPrimitiveType)type).getBoxedType(Objects.requireNonNull(((PsiClassType)optionalType).resolve()));
}
return type;
}
@NotNull
@Override
public DfaValue getDefaultValue(DfaValueFactory factory, boolean forAccessor) {
return factory.getFactValue(DfaFactType.NULLABILITY, forAccessor ? DfaNullability.NOT_NULL : DfaNullability.NULLABLE);
public DfType getDefaultValue(boolean forAccessor) {
return (forAccessor ? DfaNullability.NOT_NULL : DfaNullability.NULLABLE).asDfType();
}
@Override
@@ -172,18 +186,14 @@ public enum SpecialField implements VariableDescriptor {
}
@Override
public String getPresentationText(@NotNull DfaValue value, @Nullable PsiType type) {
if (value instanceof DfaConstValue && ((DfaConstValue)value).getValue() == null) {
public String getPresentationText(@NotNull DfType dfType, @Nullable PsiType type) {
if (dfType == DfTypes.NULL) {
return "empty Optional";
}
if (value instanceof DfaFactMapValue) {
DfaNullability nullability = ((DfaFactMapValue)value).get(DfaFactType.NULLABILITY);
if (nullability == DfaNullability.NOT_NULL) {
return "present Optional";
}
return "";
if ((!dfType.isSuperType(DfTypes.NULL))) {
return "present Optional";
}
return super.getPresentationText(value, type);
return "";
}
@Override
@@ -216,11 +226,11 @@ public enum SpecialField implements VariableDescriptor {
*/
abstract boolean isMyAccessor(PsiMember accessor);
public String getPresentationText(@NotNull DfaValue value, @Nullable PsiType type) {
if (value.getFactory() != null && getDefaultValue(value.getFactory(), false) == value) {
public String getPresentationText(@NotNull DfType dfType, @Nullable PsiType type) {
if (getDefaultValue(false).equals(dfType)) {
return "";
}
return value.toString();
return dfType.toString();
}
/**
@@ -266,43 +276,28 @@ public enum SpecialField implements VariableDescriptor {
psiVariable.hasModifierProperty(PsiModifier.FINAL)) {
PsiExpression initializer = ((PsiField)psiVariable).getInitializer();
if (initializer != null) {
DfaValue value = fromInitializer(factory, initializer);
if (value != null) {
return value;
DfType dfType = fromInitializer(initializer);
if (dfType != DfTypes.TOP) {
return factory.fromDfType(dfType);
}
}
}
return VariableDescriptor.super.createValue(factory, qualifier, forAccessor);
}
if(qualifier instanceof DfaFactMapValue) {
SpecialFieldValue sfValue = ((DfaFactMapValue)qualifier).get(DfaFactType.SPECIAL_FIELD_VALUE);
if (sfValue != null && sfValue.getField() == this) {
return sfValue.getValue();
}
}
if(qualifier instanceof DfaConstValue) {
Object obj = ((DfaConstValue)qualifier).getValue();
if(obj != null) {
DfaValue value = fromConstant(factory, obj);
if(value != null) {
return value;
}
}
}
return getDefaultValue(factory, forAccessor);
DfType dfType = qualifier == null ? DfTypes.TOP : getFromQualifier(qualifier.getDfType());
return factory.fromDfType(dfType.meet(getDefaultValue(forAccessor)));
}
/**
* Creates a DfaValue which describes any possible value this special field may have
*
* @param factory {@link DfaValueFactory} to use
* Returns a dfType that describes any possible value this special field may have
*
* @param forAccessor if true, the default value for accessor result should be returned
* (may differ from internal representation of value)
* @return a default value, could be unknown
* @return a dfType for the default value
*/
@NotNull
public DfaValue getDefaultValue(DfaValueFactory factory, boolean forAccessor) {
return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.indexRange());
public DfType getDefaultValue(boolean forAccessor) {
return DfTypes.intRange(LongRangeSet.indexRange());
}
@Override
@@ -310,12 +305,14 @@ public enum SpecialField implements VariableDescriptor {
return PsiType.INT;
}
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
return null;
@NotNull
DfType fromInitializer(PsiExpression initializer) {
return DfTypes.TOP;
}
DfaValue fromConstant(DfaValueFactory factory, @Nullable Object obj) {
return null;
@NotNull
public DfType fromConstant(@Nullable Object obj) {
return DfTypes.TOP;
}
/**
@@ -335,19 +332,32 @@ public enum SpecialField implements VariableDescriptor {
ContractValue.argument(0).specialField(this), returnFalse())};
}
public SpecialFieldValue withValue(DfaValue value) {
return new SpecialFieldValue(this, value);
/**
* @param fieldValue dfType of the special field value
* @return a dfType that represents a value having this special field restricted to the supplied dfType
*/
@NotNull
public DfType asDfType(@NotNull DfType fieldValue) {
DfType defaultType = this == OPTIONAL_VALUE ? DfTypes.OBJECT_OR_NULL : getDefaultValue(false);
DfType clamped = fieldValue.meet(defaultType);
if (clamped.equals(defaultType)) return DfTypes.NOT_NULL_OBJECT;
if (clamped.equals(DfTypes.BOTTOM)) return DfTypes.BOTTOM;
return DfTypes.customObject(TypeConstraint.empty(), DfaNullability.NOT_NULL, Mutability.UNKNOWN, this, clamped);
}
/**
* Returns a value from given SpecialFieldValue if it's bound to this special field
* @param sfValue {@link SpecialFieldValue} to extract the value from
* @return en extracted value, or null if argument is null or it's bound to different special field
* Returns a DfType from given DfType qualifier if it's bound to this special field
* @param dfType of the qualifier
* @return en extracted DfType
*/
@Contract("null -> null")
@Nullable
public DfaValue extract(@Nullable SpecialFieldValue sfValue) {
return sfValue != null && sfValue.getField() == this ? sfValue.getValue() : null;
@NotNull
public DfType getFromQualifier(@NotNull DfType dfType) {
if (dfType == DfTypes.TOP) return DfTypes.TOP;
if (!(dfType instanceof DfReferenceType)) return DfTypes.BOTTOM;
SpecialField sf = ((DfReferenceType)dfType).getSpecialField();
if (sf == null) return DfTypes.TOP;
if (sf != this) return DfTypes.BOTTOM;
return ((DfReferenceType)dfType).getSpecialFieldType();
}
/**
@@ -377,16 +387,9 @@ public enum SpecialField implements VariableDescriptor {
*/
@Nullable
public static SpecialField fromQualifier(@NotNull DfaValue value) {
if (value instanceof DfaFactMapValue) {
DfaFactMap facts = ((DfaFactMapValue)value).getFacts();
SpecialFieldValue sfValue = facts.get(DfaFactType.SPECIAL_FIELD_VALUE);
if (sfValue != null) {
return sfValue.getField();
}
TypeConstraint constraint = facts.get(DfaFactType.TYPE_CONSTRAINT);
if (constraint != null) {
return fromQualifierType(constraint.getPsiType());
}
DfReferenceType dfType = ObjectUtils.tryCast(value.getDfType(), DfReferenceType.class);
if (dfType != null && dfType.getSpecialField() != null) {
return dfType.getSpecialField();
}
return fromQualifierType(value.getType());
}
@@ -1,10 +1,9 @@
// 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.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaFactMapValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.PsiType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -12,21 +11,15 @@ import java.util.Objects;
/**
* A special field with associated information about its value
* @deprecated could be removed in future; used only in DfaFactMap and TrackingRunner
*/
public final class SpecialFieldValue {
@Deprecated
final class SpecialFieldValue {
private final @NotNull SpecialField myField;
private final @NotNull DfaValue myValue;
private final @NotNull DfType myType;
public SpecialFieldValue(@NotNull SpecialField field, @NotNull DfaValue value) {
if (value instanceof DfaFactMapValue) {
myValue = ((DfaFactMapValue)value).withFact(DfaFactType.SPECIAL_FIELD_VALUE, null);
}
else if (value instanceof DfaConstValue) {
myValue = value;
}
else {
throw new IllegalArgumentException("Unexpected value: " + value);
}
SpecialFieldValue(@NotNull SpecialField field, @NotNull DfType type) {
myType = type instanceof DfReferenceType ? ((DfReferenceType)type).dropSpecialField() : type;
myField = field;
}
@@ -36,8 +29,8 @@ public final class SpecialFieldValue {
}
@NotNull
public DfaValue getValue() {
return myValue;
public DfType getDfType() {
return myType;
}
@Override
@@ -45,31 +38,24 @@ public final class SpecialFieldValue {
if (this == o) return true;
if (!(o instanceof SpecialFieldValue)) return false;
SpecialFieldValue value = (SpecialFieldValue)o;
return myField == value.myField && myValue == value.myValue;
return myField == value.myField && myType == value.myType;
}
@Nullable
public SpecialFieldValue unite(SpecialFieldValue other) {
if (other == this) return this;
if (myField != other.myField) return null;
DfaValue newValue = myValue.unite(other.myValue);
if (newValue instanceof DfaConstValue || newValue instanceof DfaFactMapValue) {
return new SpecialFieldValue(myField, newValue);
}
return null;
DfType type = myType.join(other.myType);
return type == DfTypes.TOP ? null : new SpecialFieldValue(myField, type);
}
@Override
public int hashCode() {
return myField.hashCode() * 31 + Objects.hashCode(myValue);
return myField.hashCode() * 31 + Objects.hashCode(myType);
}
@Override
public String toString() {
return myField + " = " + myValue;
}
public String getPresentationText(PsiType type) {
return myField.getPresentationText(myValue, type);
return myField + " = " + myType;
}
}
@@ -4,6 +4,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
@@ -21,6 +22,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.NOT_NULL_OBJECT;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.TOP;
import static com.intellij.util.ObjectUtils.tryCast;
/**
@@ -73,17 +76,20 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaVariableValue var = (DfaVariableValue) dfaDest;
PsiModifierListOwner psi = var.getPsiVariable();
if (dfaSource instanceof DfaFactMapValue &&
if (dfaSource instanceof DfaTypeValue &&
((psi instanceof PsiField && psi.hasModifierProperty(PsiModifier.STATIC)) ||
(var.getQualifier() != null && !Boolean.TRUE.equals(memState.getValueFact(var.getQualifier(), DfaFactType.LOCALITY))))) {
dfaSource = ((DfaFactMapValue)dfaSource).withFact(DfaFactType.LOCALITY, null);
(var.getQualifier() != null && !DfReferenceType.isLocal(memState.getDfType(var.getQualifier()))))) {
DfType dfType = dfaSource.getDfType();
if (dfType instanceof DfReferenceType) {
dfaSource = dfaSource.getFactory().fromDfType(((DfReferenceType)dfType).dropLocality());
}
}
if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) {
memState.setVarValue(var, dfaSource);
}
if (var.getInherentNullability() == Nullability.NULLABLE && !memState.isNotNull(dfaSource) && instruction.isVariableInitializer()) {
DfaMemoryStateImpl stateImpl = (DfaMemoryStateImpl)memState;
stateImpl.setVariableState(var, stateImpl.getVariableState(var).withFact(DfaFactType.NULLABILITY, DfaNullability.NULLABLE));
if (var.getInherentNullability() == Nullability.NULLABLE &&
DfaNullability.fromDfType(memState.getDfType(var)) == DfaNullability.UNKNOWN && instruction.isVariableInitializer()) {
memState.meetDfType(var, DfaNullability.NULLABLE.asDfType());
}
}
@@ -117,8 +123,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@Nullable
private static PsiType getType(@Nullable PsiExpression expression, @Nullable DfaValue value, @NotNull DfaMemoryState memState) {
TypeConstraint fact = value == null ? null : memState.getValueFact(value, DfaFactType.TYPE_CONSTRAINT);
PsiType type = fact == null ? null : fact.getPsiType();
PsiType type = value == null ? null : DfaTypeValue.toPsiType(memState.getDfType(value));
if (type != null) return type;
return expression == null ? null : expression.getType();
}
@@ -132,13 +137,25 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return super.visitEscapeInstruction(instruction, runner, state);
}
private static void dropLocality(DfaValue value, DfaMemoryState state) {
if (!(value instanceof DfaVariableValue)) return;
DfaVariableValue var = (DfaVariableValue)value;
state.dropFact(var, DfaFactType.LOCALITY);
for (DfaVariableValue v : new ArrayList<>(var.getDependentVariables())) {
state.dropFact(v, DfaFactType.LOCALITY);
private static DfaValue dropLocality(DfaValue value, DfaMemoryState state) {
if (!(value instanceof DfaVariableValue)) {
if (DfReferenceType.isLocal(value.getDfType())) {
return value.getFactory().fromDfType(((DfReferenceType)value.getDfType()).dropLocality());
}
return value;
}
DfaVariableValue var = (DfaVariableValue)value;
DfType dfType = state.getDfType(var);
if (dfType instanceof DfReferenceType) {
state.setDfType(var, ((DfReferenceType)dfType).dropLocality());
}
for (DfaVariableValue v : new ArrayList<>(var.getDependentVariables())) {
dfType = state.getDfType(v);
if (dfType instanceof DfReferenceType) {
state.setDfType(v, ((DfReferenceType)dfType).dropLocality());
}
}
return value;
}
@Override
@@ -148,7 +165,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue array = memState.pop();
boolean alwaysOutOfBounds = false;
DfaValueFactory factory = runner.getFactory();
if (index != DfaUnknownValue.getInstance()) {
if (!DfaTypeValue.isUnknown(index)) {
DfaCondition indexNonNegative = index.cond(RelationType.GE, factory.getInt(0));
if (!memState.applyCondition(indexNonNegative)) {
alwaysOutOfBounds = true;
@@ -165,10 +182,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
DfaValue result = instruction.getValue();
LongRangeSet rangeSet = memState.getValueFact(index, DfaFactType.RANGE);
DfaValue arrayElementValue =
runner.getFactory().getExpressionFactory().getArrayElementValue(array, rangeSet == null ? LongRangeSet.all() : rangeSet);
if (arrayElementValue != DfaUnknownValue.getInstance()) {
LongRangeSet rangeSet = DfIntType.extractRange(memState.getDfType(index));
DfaValue arrayElementValue = runner.getFactory().getExpressionFactory().getArrayElementValue(array, rangeSet);
if (!DfaTypeValue.isUnknown(arrayElementValue)) {
result = arrayElementValue;
}
if (!(result instanceof DfaVariableValue) && array instanceof DfaVariableValue) {
@@ -192,7 +208,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
final DfaValue qualifier = memState.pop();
dropLocality(qualifier, memState);
handleMethodReference(qualifier, expression, runner, memState);
pushExpressionResult(runner.getFactory().createTypeValue(expression.getFunctionalInterfaceType(), Nullability.NOT_NULL), instruction, memState);
pushExpressionResult(runner.getFactory().getObjectType(expression.getFunctionalInterfaceType(), Nullability.NOT_NULL), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -214,7 +230,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
dereference(state, callArguments.myQualifier, NullabilityProblemKind.callMethodRefNPE.problem(methodRef, null));
if (contracts.isEmpty()) return;
PsiType returnType = substitutor.substitute(method.getReturnType());
DfaValue defaultResult = runner.getFactory().createTypeValue(returnType, DfaPsiUtil.getElementNullability(returnType, method));
DfaValue defaultResult = runner.getFactory().getObjectType(returnType, DfaPsiUtil.getElementNullability(returnType, method));
Set<DfaCallState> currentStates = Collections.singleton(new DfaCallState(state.createClosureState(), callArguments));
for (MethodContract contract : contracts) {
currentStates = addContractResults(contract, currentStates, runner.getFactory(), new HashSet<>(), defaultResult, methodRef);
@@ -236,10 +252,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean instanceBound = !isStatic && !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef);
PsiParameter[] parameters = method.getParameterList().getParameters();
DfaValue[] arguments = new DfaValue[parameters.length];
Arrays.fill(arguments, DfaUnknownValue.getInstance());
Arrays.fill(arguments, runner.getFactory().getUnknown());
for (int i = 0; i < samParameters.length; i++) {
DfaValue value = runner.getFactory()
.createTypeValue(substitutor.substitute(samParameters[i].getType()), DfaPsiUtil.getFunctionalParameterNullability(methodRef, i));
.getObjectType(substitutor.substitute(samParameters[i].getType()), DfaPsiUtil.getFunctionalParameterNullability(methodRef, i));
if (i == 0 && !isStatic && !instanceBound) {
qualifier = value;
}
@@ -275,8 +291,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
DfaValue value = castFail.peek();
DfaCondition notNullCondition = value.cond(RelationType.NE, factory.getConstFactory().getNull());
DfaCondition notTypeCondition = value.cond(RelationType.IS_NOT, factory.createTypeValue(type, Nullability.NOT_NULL));
DfaCondition notNullCondition = value.cond(RelationType.NE, factory.getNull());
DfaCondition notTypeCondition = value.cond(RelationType.IS_NOT, factory.getObjectType(type, Nullability.NOT_NULL));
if (castFail.applyCondition(notNullCondition) && castFail.applyCondition(notTypeCondition)) {
List<DfaInstructionState> states = transfer.dispatch(castFail, runner);
for (DfaInstructionState cceState : states) {
@@ -320,7 +336,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
Set<DfaCallState> currentStates = Collections.singleton(new DfaCallState(memState, callArguments));
DfaValue defaultResult = getMethodResultValue(instruction, callArguments, memState, factory);
if (callArguments.myArguments != null && !(defaultResult instanceof DfaConstValue)) {
if (callArguments.myArguments != null && !(defaultResult.getDfType() instanceof DfConstantType)) {
for (MethodContract contract : instruction.getContracts()) {
currentStates = addContractResults(contract, currentStates, factory, finalStates, defaultResult, instruction.getExpression());
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
@@ -374,12 +390,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (paramCount == argCount || method.isVarArgs() && argCount >= paramCount - 1) {
argValues = new DfaValue[paramCount];
if (varargCall) {
DfaFactMap facts = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT,
factory.createDfaType(Objects.requireNonNull(paramList.getParameter(paramCount - 1)).getType()).asConstraint())
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.ARRAY_LENGTH.withValue(factory.getInt(argCount - paramCount + 1)));
argValues[paramCount - 1] = factory.getFactFactory().createValue(facts);
DfType dfType =
factory.createDfType(Objects.requireNonNull(paramList.getParameter(paramCount - 1)).getType(), Nullability.NOT_NULL)
.meet(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(argCount - paramCount + 1)));
argValues[paramCount - 1] = factory.fromDfType(dfType);
}
}
}
@@ -388,7 +402,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue arg = memState.pop();
int paramIndex = argCount - i - 1;
dropLocality(arg, memState);
arg = dropLocality(arg, memState);
PsiElement anchor = instruction.getArgumentAnchor(paramIndex);
if (instruction.getContext() instanceof PsiMethodReferenceExpression) {
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)instruction.getContext();
@@ -399,10 +413,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
checkNotNullable(memState, arg, NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.problem(methodRef, null));
}
}
if (sig.mutatesArg(paramIndex) && !memState.applyFact(arg, DfaFactType.MUTABILITY, Mutability.MUTABLE)) {
if (sig.mutatesArg(paramIndex) && Mutability.fromDfType(memState.getDfType(arg)).isUnmodifiable()) {
reportMutabilityViolation(false, anchor);
if (arg instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)arg, DfaFactType.MUTABILITY, Mutability.MUTABLE);
DfType dfType = memState.getDfType(arg);
if (dfType instanceof DfReferenceType) {
memState.setDfType(arg, ((DfReferenceType)dfType).dropMutability().meet(Mutability.MUTABLE.asDfType()));
}
}
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
@@ -423,18 +438,19 @@ public class StandardInstructionVisitor extends InstructionVisitor {
PsiMethodReferenceExpression context = (PsiMethodReferenceExpression)instruction.getContext();
value = dereference(memState, value, NullabilityProblemKind.callMethodRefNPE.problem(context, null));
}
if (sig.mutatesThis() && !memState.applyFact(value, DfaFactType.MUTABILITY, Mutability.MUTABLE)) {
if (sig.mutatesThis() && Mutability.fromDfType(memState.getDfType(value)).isUnmodifiable()) {
reportMutabilityViolation(true, instruction.getContext());
if (value instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)value, DfaFactType.MUTABILITY, Mutability.MUTABLE);
DfType dfType = memState.getDfType(value);
if (dfType instanceof DfReferenceType) {
memState.setDfType(value, ((DfReferenceType)dfType).dropMutability().meet(Mutability.MUTABLE.asDfType()));
}
}
if (value instanceof DfaVariableValue && !(value.getType() instanceof PsiArrayType)) {
if (instruction.shouldFlushFields() || !(instruction.getResultType() instanceof PsiPrimitiveType)) {
// For now drop locality on every qualified call except primitive returning pure calls
// as value might escape through the return value
dropLocality(value, memState);
}
if (!(value.getType() instanceof PsiArrayType) &&
(DfaUtil.isComparedByEquals(value.getType()) ||
instruction.shouldFlushFields() || !(instruction.getResultType() instanceof PsiPrimitiveType))) {
// For now drop locality on every qualified call except primitive returning pure calls
// as value might escape through the return value
value = dropLocality(value, memState);
}
return value;
}
@@ -489,15 +505,21 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<T> problem) {
boolean ok = checkNotNullable(memState, value, problem);
if (value instanceof DfaFactMapValue) {
return ((DfaFactMapValue)value).withFact(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
if (value instanceof DfaTypeValue) {
DfType dfType = value.getDfType().meet(NOT_NULL_OBJECT);
return value.getFactory().fromDfType(dfType == DfTypes.BOTTOM ? NOT_NULL_OBJECT : dfType);
}
if (ok) return value;
if (memState.isNull(value) && problem != null && problem.getKind() == NullabilityProblemKind.nullableFunctionReturn) {
return value.getFactory().getFactValue(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
return value.getFactory().fromDfType(NOT_NULL_OBJECT);
}
if (value instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)value, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
DfType dfType = memState.getDfType(value);
if (dfType == DfTypes.NULL) {
memState.setDfType(value, NOT_NULL_OBJECT);
} else {
memState.meetDfType(value, NOT_NULL_OBJECT);
}
}
return value;
}
@@ -515,8 +537,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
qualifierExpression = ((PsiMethodReferenceExpression)context).getQualifierExpression();
}
if (qualifierExpression instanceof PsiSuperExpression) return method; // non-virtual call
TypeConstraint constraint = state.getValueFact(qualifier, DfaFactType.TYPE_CONSTRAINT);
PsiType type = constraint == null ? null : constraint.getPsiType();
PsiType type = DfaTypeValue.toPsiType(state.getDfType(qualifier));
return MethodUtils.findSpecificMethod(method, type);
}
@@ -529,9 +550,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (method != null) {
CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(method);
if (handler != null) {
DfaValue result = handler.getMethodResult(callArguments, state, factory, method);
if (result != null) {
return result;
DfType dfType = handler.getMethodResult(callArguments, state, factory, method);
if (dfType != TOP) {
return factory.fromDfType(dfType);
}
}
}
@@ -572,13 +593,14 @@ public class StandardInstructionVisitor extends InstructionVisitor {
nullability = factory.suggestNullabilityForNonAnnotatedMember(targetMethod);
}
}
DfaValue value = instruction.getContext() instanceof PsiNewExpression ?
factory.createExactTypeValue(type) :
factory.createTypeValue(type, nullability);
DfaPsiType dfaType = factory.createDfaType(type);
DfType dfType = instruction.getContext() instanceof PsiNewExpression ?
TypeConstraint.exact(dfaType).asDfType().meet(NOT_NULL_OBJECT) :
DfTypes.typedObject(dfaType, nullability);
if (!instruction.shouldFlushFields() && instruction.getContext() instanceof PsiNewExpression) {
value = factory.withFact(value, DfaFactType.LOCALITY, true);
dfType = dfType.meet(DfTypes.LOCAL_OBJECT);
}
return factory.withFact(value, DfaFactType.MUTABILITY, mutable);
return factory.fromDfType(dfType.meet(mutable.asDfType()));
}
LongRangeSet range = LongRangeSet.fromType(type);
if (range != null) {
@@ -586,9 +608,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (call instanceof PsiMethodCallExpression) {
range = range.intersect(LongRangeSet.fromPsiElement(call.resolveMethod()));
}
return factory.getFactValue(DfaFactType.RANGE, range);
return factory.fromDfType(DfTypes.rangeClamped(range, PsiType.LONG.equals(type)));
}
return DfaUnknownValue.getInstance();
return factory.getUnknown();
}
private static DfaValue getPrecalculatedResult(@Nullable DfaValue qualifierValue,
@@ -598,11 +620,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
PsiModifierListOwner psi = ((DfaVariableValue)precalculated).getPsiVariable();
// Perform constant folding for getClass() call.
if (psi instanceof PsiMethod && PsiTypesUtil.isGetClass((PsiMethod)psi)) {
TypeConstraint fact = state.getValueFact(qualifierValue, DfaFactType.TYPE_CONSTRAINT);
if (fact != null && fact.isExact()) {
TypeConstraint fact = TypeConstraint.fromDfType(state.getDfType(qualifierValue));
if (fact.isExact()) {
PsiType javaLangClass = precalculated.getType();
if (javaLangClass != null) {
return factory.getConstFactory().createFromValue(fact.getPsiType(), javaLangClass);
return factory.getConstant(fact.getPsiType(), javaLangClass);
}
}
}
@@ -614,7 +636,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean notNullable = state.checkNotNullable(value);
if (notNullable && problem != null && problem.thrownException() != null) {
DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory();
state.applyCondition(value.cond(RelationType.NE, factory.getConstFactory().getNull()));
state.applyCondition(value.cond(RelationType.NE, factory.getNull()));
}
return notNullable;
}
@@ -630,20 +652,21 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
private static DfaValue getConversionResult(DfaValue value, PsiPrimitiveType type, DfaValueFactory factory, DfaMemoryState state) {
if (value instanceof DfaVariableValue && TypeConversionUtil.isSafeConversion(type, value.getType())) {
if (value instanceof DfaVariableValue && type != null &&
(type.equals(value.getType()) ||
TypeConversionUtil.isSafeConversion(type, value.getType()) && TypeConversionUtil.isSafeConversion(PsiType.INT, type))) {
return value;
}
DfaConstValue constValue = state.getConstantValue(value);
if (constValue != null && type != null) {
Object casted = TypeConversionUtil.computeCastTo(constValue.getValue(), type);
return factory.getConstFactory().createFromValue(casted, type);
DfType dfType = state.getDfType(value);
if (dfType instanceof DfConstantType && type != null) {
Object casted = TypeConversionUtil.computeCastTo(((DfConstantType<?>)dfType).getValue(), type);
return factory.getConstant(casted, type);
}
if (TypeConversionUtil.isIntegralNumberType(type)) {
LongRangeSet range = state.getValueFact(value, DfaFactType.RANGE);
if (range == null) range = LongRangeSet.all();
return factory.getFactValue(DfaFactType.RANGE, range.castTo(type));
LongRangeSet range = DfLongType.extractRange(dfType);
return factory.fromDfType(DfTypes.rangeClamped(range.castTo(type), PsiType.LONG.equals(type)));
}
return DfaUnknownValue.getInstance();
return factory.getUnknown();
}
@Override
@@ -668,7 +691,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
result.add(new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState));
}
DfaValueFactory factory = runner.getFactory();
if (nullState.applyCondition(value.eq(factory.getConstFactory().getNull()))) {
if (nullState.applyCondition(value.eq(factory.getNull()))) {
List<DfaInstructionState> dispatched = transfer.dispatch(nullState, runner);
for (DfaInstructionState npeState : dispatched) {
npeState.getMemoryState().markEphemeral();
@@ -688,11 +711,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaMemoryState falseState = memState.createCopy();
DfaValueFactory factory = runner.getFactory();
List<DfaInstructionState> result = new ArrayList<>(2);
if (memState.applyCondition(dfaValue.eq(factory.getConstFactory().getFalse()))) {
if (memState.applyCondition(dfaValue.eq(factory.getBoolean(false)))) {
pushExpressionResult(factory.getBoolean(true), instruction, memState);
result.add(new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState));
}
if (falseState.applyCondition(dfaValue.eq(factory.getConstFactory().getTrue()))) {
if (falseState.applyCondition(dfaValue.eq(factory.getBoolean(true)))) {
pushExpressionResult(factory.getBoolean(false), instruction, falseState);
result.add(new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), falseState));
}
@@ -709,35 +732,31 @@ public class StandardInstructionVisitor extends InstructionVisitor {
RelationType relationType =
RelationType.fromElementType(opSign == BinopInstruction.STRING_EQUALITY_BY_CONTENT ? JavaTokenType.EQEQ : opSign);
if (relationType != null) {
DfaInstructionState[] states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft, relationType);
if (states != null) {
return states;
}
return handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft, relationType);
}
PsiType type = instruction.getResultType();
if (PsiType.BOOLEAN.equals(type)) {
return handleAndOrBinop(instruction, runner, memState, dfaRight, dfaLeft);
}
DfaValue result = DfaUnknownValue.getInstance();
DfaValue result = runner.getFactory().getUnknown();
if (PsiType.INT.equals(type) || PsiType.LONG.equals(type)) {
boolean isLong = PsiType.LONG.equals(type);
if (instruction.isWidened()) {
LongRangeSet leftRange = memState.getValueFact(dfaLeft, DfaFactType.RANGE);
LongRangeSet rightRange = memState.getValueFact(dfaRight, DfaFactType.RANGE);
if (leftRange != null && rightRange != null) {
LongRangeSet range = leftRange.wideBinOpFromToken(opSign, rightRange, isLong);
if (range != null) {
result = runner.getFactory().getFactValue(DfaFactType.RANGE, range);
}
LongRangeSet leftRange = DfLongType.extractRange(memState.getDfType(dfaLeft));
LongRangeSet rightRange = DfLongType.extractRange(memState.getDfType(dfaRight));
LongRangeSet range = leftRange.wideBinOpFromToken(opSign, rightRange, isLong);
if (range == null) {
range = LongRangeSet.all();
}
result = runner.getFactory().fromDfType(DfTypes.rangeClamped(range, isLong));
}
else {
result = runner.getFactory().getBinOpFactory().create(dfaLeft, dfaRight, memState, isLong, opSign);
}
}
if (result == DfaUnknownValue.getInstance() && JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
if (DfaTypeValue.isUnknown(result) && JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
result = instruction.isWidened()
? runner.getFactory().createTypeValue(type, Nullability.NOT_NULL)
? runner.getFactory().getObjectType(type, Nullability.NOT_NULL)
: concatStrings(dfaLeft, dfaRight, memState, type, runner.getFactory());
}
pushExpressionResult(result, instruction, memState);
@@ -775,34 +794,22 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaMemoryState memState,
PsiType stringType,
DfaValueFactory factory) {
DfaConstValue leftConst = memState.getConstantValue(left);
DfaConstValue rightConst = memState.getConstantValue(right);
if (leftConst != null && rightConst != null) {
String leftString = tryCast(leftConst.getValue(), String.class);
String rightString = tryCast(rightConst.getValue(), String.class);
if (leftString != null && rightString != null &&
leftString.length() + rightString.length() <= CustomMethodHandlers.MAX_STRING_CONSTANT_LENGTH_TO_TRACK) {
return factory.getConstFactory().createFromValue(leftString + rightString, stringType);
}
String leftString = DfConstantType.getConstantOfType(memState.getDfType(left), String.class);
String rightString = DfConstantType.getConstantOfType(memState.getDfType(right), String.class);
if (leftString != null && rightString != null &&
leftString.length() + rightString.length() <= CustomMethodHandlers.MAX_STRING_CONSTANT_LENGTH_TO_TRACK) {
return factory.getConstant(leftString + rightString, stringType);
}
DfaValue leftLength = SpecialField.STRING_LENGTH.createValue(factory, left);
DfaValue rightLength = SpecialField.STRING_LENGTH.createValue(factory, right);
LongRangeSet leftRange = memState.getValueFact(leftLength, DfaFactType.RANGE);
LongRangeSet rightRange = memState.getValueFact(rightLength, DfaFactType.RANGE);
DfaFactMap map = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, factory.createDfaType(stringType).asConstraint())
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
if (leftRange != null && rightRange != null) {
LongRangeSet resultRange = leftRange.plus(rightRange, false).intersect(LongRangeSet.indexRange());
if (!resultRange.equals(LongRangeSet.indexRange())) {
map = map.with(DfaFactType.SPECIAL_FIELD_VALUE,
SpecialField.STRING_LENGTH.withValue(factory.getFactValue(DfaFactType.RANGE, resultRange)));
}
}
return factory.getFactFactory().createValue(map);
LongRangeSet leftRange = DfIntType.extractRange(memState.getDfType(leftLength));
LongRangeSet rightRange = DfIntType.extractRange(memState.getDfType(rightLength));
DfReferenceType dfType = DfTypes.typedObject(factory.createDfaType(stringType), Nullability.NOT_NULL);
LongRangeSet resultRange = leftRange.plus(rightRange, false);
return factory.fromDfType(dfType.meet(SpecialField.STRING_LENGTH.asDfType(DfTypes.intRange(resultRange))));
}
@Nullable
@NotNull
private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
@@ -831,7 +838,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
for (int i = 0; i < relations.length; i++) {
RelationType relation = relations[i];
DfaCondition condition = dfaLeft.cond(relation, dfaRight);
if (condition == DfaCondition.getUnknown()) return null;
if (condition == DfaCondition.getFalse()) continue;
if (condition == DfaCondition.getTrue()) {
DfaInstructionState state =
@@ -846,7 +852,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
if (states.isEmpty()) {
// Neither of relations could be applied: likely comparison with NaN; do not split the state in this case, just push false
pushExpressionResult(factory.getConstFactory().getFalse(), instruction, memState);
pushExpressionResult(factory.getBoolean(false), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
@@ -876,15 +882,13 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean unknownTargetType = false;
DfaCondition condition = null;
if (instruction.isClassObjectCheck()) {
DfaConstValue constant = memState.getConstantValue(dfaRight);
PsiType type = constant == null ? null : tryCast(constant.getValue(), PsiType.class);
PsiType type = DfConstantType.getConstantOfType(memState.getDfType(dfaRight), PsiType.class);
if (type == null || type instanceof PsiPrimitiveType) {
// Unknown/primitive class: just execute contract "null -> false"
DfaConstValue aNull = factory.getConstFactory().getNull();
condition = dfaLeft.cond(RelationType.NE, aNull);
condition = dfaLeft.cond(RelationType.NE, factory.getNull());
unknownTargetType = true;
} else {
dfaRight = factory.createTypeValue(type, Nullability.NOT_NULL);
dfaRight = factory.getObjectType(type, Nullability.NOT_NULL);
}
}
if (condition == null) {
@@ -893,11 +897,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean useful;
ArrayList<DfaInstructionState> states = new ArrayList<>(2);
DfType leftType = memState.getDfType(dfaLeft);
if (condition == DfaCondition.getUnknown()) {
if (dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) {
DfaFactMapValue left = (DfaFactMapValue)dfaLeft;
DfaFactMapValue right = (DfaFactMapValue)dfaRight;
useful = !right.getFacts().with(DfaFactType.NULLABILITY, null).isSuperStateOf(left.getFacts());
if (dfaLeft instanceof DfaTypeValue && dfaRight instanceof DfaTypeValue) {
TypeConstraint left = TypeConstraint.fromDfType(leftType);
TypeConstraint right = TypeConstraint.fromDfType(dfaRight.getDfType());
useful = !right.isSuperStateOf(left);
} else {
useful = true;
}
@@ -912,7 +917,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaCondition negated = condition.negate();
if (unknownTargetType ? memState.applyContractCondition(negated) : memState.applyCondition(negated)) {
states.add(makeBooleanResult(instruction, runner, memState, ThreeState.NO));
useful |= !memState.isNull(dfaLeft);
if (!memState.isNull(dfaLeft)) {
useful = true;
} else if (DfaNullability.fromDfType(leftType) == DfaNullability.UNKNOWN) {
// Not-instanceof check leaves only "null" possible value in some state: likely the state is ephemeral
memState.markEphemeral();
}
}
}
if (useful) {
@@ -925,7 +935,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState memState,
@NotNull ThreeState result) {
DfaValue value = result == ThreeState.UNSURE ? DfaUnknownValue.getInstance() : runner.getFactory().getBoolean(result.toBoolean());
DfaValue value = result == ThreeState.UNSURE ? runner.getFactory().getUnknown() : runner.getFactory().getBoolean(result.toBoolean());
pushExpressionResult(value, instruction, memState);
return new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState);
}
@@ -1,7 +1,7 @@
// Copyright 2000-2019 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.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.openapi.util.TextRange;
@@ -285,9 +285,9 @@ public final class StandardMethodContract extends MethodContract {
}
@Nullable
DfaConstValue getComparisonValue(DfaValueFactory factory) {
if (this == NULL_VALUE || this == NOT_NULL_VALUE) return factory.getConstFactory().getNull();
if (this == TRUE_VALUE || this == FALSE_VALUE) return factory.getConstFactory().getTrue();
DfaValue getComparisonValue(DfaValueFactory factory) {
if (this == NULL_VALUE || this == NOT_NULL_VALUE) return factory.getNull();
if (this == TRUE_VALUE || this == FALSE_VALUE) return factory.getBoolean(true);
return null;
}
@@ -2,6 +2,7 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.Function;
@@ -14,8 +15,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.codeInspection.dataFlow.DfaFactType.RANGE;
/**
* @author peter
*/
@@ -70,12 +69,12 @@ class StateMerger {
@NotNull
private MultiMap<Fact, DfaMemoryStateImpl> createFactToStateMap(@NotNull List<DfaMemoryStateImpl> states) {
MultiMap<Fact, DfaMemoryStateImpl> statesByFact = MultiMap.createLinked();
Map<DfaConstValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars = new HashMap<>();
Map<DfaTypeValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars = new HashMap<>();
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
for (Fact fact : getFacts(state)) {
statesByFact.putValue(fact, state);
DfaConstValue value = fact.comparedToConstant();
DfaTypeValue value = fact.comparedToConstant();
if (value != null) {
constantVars.computeIfAbsent(value, k -> new HashMap<>())
.computeIfAbsent(fact.myVar, k -> ContainerUtil.newIdentityTroveSet()).add(state);
@@ -107,7 +106,7 @@ class StateMerger {
* @return true if fact is {@link EqualityFact} which compares two variables which were compared with some constant
*/
private static boolean isComparisonOfVariablesComparedWithConstant(Fact fact,
Map<DfaConstValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars,
Map<DfaTypeValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars,
Collection<DfaMemoryStateImpl> positiveStates,
Collection<DfaMemoryStateImpl> negativeStates) {
if (!(fact instanceof EqualityFact) || !(((EqualityFact)fact).myArg instanceof DfaVariableValue)) return false;
@@ -172,9 +171,9 @@ class StateMerger {
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
state.forVariableStates((varValue, varState) -> {
LongRangeSet range = varState.getFact(RANGE);
if (range != null) {
ranges.computeIfAbsent(varValue, k -> new HashSet<>()).add(range);
DfType dfType = varState.myDfType;
if (dfType instanceof DfIntegralType) {
ranges.computeIfAbsent(varValue, k -> new HashSet<>()).add(((DfIntegralType)dfType).getRange());
}
});
}
@@ -187,11 +186,7 @@ class StateMerger {
Map<DfaMemoryStateImpl, List<DfaMemoryStateImpl>> merged = new LinkedHashMap<>();
for (DfaMemoryStateImpl state : states) {
DfaVariableState variableState = state.getVariableState(var);
LongRangeSet range = variableState.getFact(RANGE);
if (range == null) {
range = LongRangeSet.fromType(var.getType());
if (range == null) return null;
}
if (!(variableState.myDfType instanceof DfIntegralType)) return null;
merged.computeIfAbsent(copyWithoutVar(state, var), k -> new ArrayList<>()).add(state);
}
if (merged.size() == states.size()) return null;
@@ -224,55 +219,49 @@ class StateMerger {
private static Set<Fact> doGetFacts(DfaMemoryStateImpl state) {
Set<Fact> result = new LinkedHashSet<>();
IdentityHashMap<EqClass, EqClassInfo> classInfo = new IdentityHashMap<>();
for (EqClass eqClass : state.getNonTrivialEqClasses()) {
EqClassInfo info = classInfo.computeIfAbsent(eqClass, EqClassInfo::new);
DfaValue constant = info.constant;
List<DfaVariableValue> vars = info.vars;
int size = vars.size();
int size = eqClass.size();
for (int i = 0; i < size; i++) {
DfaVariableValue var = vars.get(i);
if (constant != null) {
result.add(Fact.createEqualityFact(var, constant));
}
DfaVariableValue var = eqClass.getVariable(i);
for (int j = i + 1; j < size; j++) {
DfaVariableValue eqVar = vars.get(j);
DfaVariableValue eqVar = eqClass.getVariable(j);
result.add(Fact.createEqualityFact(var, eqVar));
}
}
}
for (DistinctPairSet.DistinctPair classPair : state.getDistinctClassPairs()) {
EqClassInfo info1 = classInfo.computeIfAbsent(classPair.getFirst(), EqClassInfo::new);
EqClassInfo info2 = classInfo.computeIfAbsent(classPair.getSecond(), EqClassInfo::new);
for (DfaVariableValue var1 : info1.vars) {
for (DfaVariableValue var2 : info2.vars) {
EqClass class1 = classPair.getFirst();
EqClass class2 = classPair.getSecond();
for (DfaVariableValue var1 : class1) {
for (DfaVariableValue var2 : class2) {
result.add(new EqualityFact(var1, false, var2));
result.add(new EqualityFact(var2, false, var1));
}
}
if(info1.constant != null) {
for (DfaVariableValue var2 : info2.vars) {
result.add(new EqualityFact(var2, false, info1.constant));
}
}
if(info2.constant != null) {
for (DfaVariableValue var1 : info1.vars) {
result.add(new EqualityFact(var1, false, info2.constant));
}
}
}
state.forVariableStates((var, variableState) -> {
TypeConstraint typeConstraint = variableState.getTypeConstraint();
state.forVariableStates((var, varState) -> {
TypeConstraint typeConstraint = varState.getTypeConstraint();
for (DfaPsiType type : typeConstraint.getInstanceofValues()) {
result.add(new InstanceofFact(var, true, type));
}
for (DfaPsiType type : typeConstraint.getNotInstanceofValues()) {
result.add(new InstanceofFact(var, false, type));
}
DfType dfType = state.getDfType(var);
if (dfType instanceof DfConstantType) {
result.add(new EqualityFact(var, true, var.getFactory().fromDfType(dfType)));
}
if (dfType instanceof DfAntiConstantType) {
Set<?> notValues = ((DfAntiConstantType<?>)dfType).getNotValues();
if (!notValues.isEmpty() && var.getType() != null) {
DfaPsiType dfaPsiType = var.getFactory().createDfaType(var.getType());
for (Object notValue : notValues) {
result.add(new EqualityFact(var, false, var.getFactory().fromDfType(DfTypes.constant(notValue, dfaPsiType))));
}
}
}
});
return result;
}
@@ -343,7 +332,7 @@ class StateMerger {
@NotNull
abstract Fact getPositiveCounterpart();
DfaConstValue comparedToConstant() {
DfaTypeValue comparedToConstant() {
return null;
}
@@ -400,8 +389,8 @@ class StateMerger {
}
@Override
DfaConstValue comparedToConstant() {
return myArg instanceof DfaConstValue ? (DfaConstValue)myArg : null;
DfaTypeValue comparedToConstant() {
return myArg instanceof DfaTypeValue ? (DfaTypeValue)myArg : null;
}
@Override
@@ -418,7 +407,16 @@ class StateMerger {
@Override
void removeFromState(@NotNull DfaMemoryStateImpl state) {
state.removeEquivalenceForVariableAndWrappers(myVar);
DfType dfType = state.getDfType(myVar);
if (dfType instanceof DfConstantType ||
dfType instanceof DfAntiConstantType && ((DfAntiConstantType<?>)dfType).getNotValues().size() == 1) {
state.flushVariable(myVar);
if (myArg.getDfType() == DfTypes.NULL) {
state.meetDfType(myVar, DfaNullability.NULLABLE.asDfType());
}
} else {
state.removeEquivalence(myVar);
}
}
}
@@ -514,14 +512,4 @@ class StateMerger {
}
}
}
static final class EqClassInfo {
final List<DfaVariableValue> vars;
final DfaConstValue constant;
EqClassInfo(EqClass eqClass) {
vars = eqClass.getVariables(false);
constant = eqClass.findConstant();
}
}
}
@@ -5,6 +5,9 @@ import com.intellij.codeInspection.dataFlow.instructions.AssignInstruction;
import com.intellij.codeInspection.dataFlow.instructions.ConditionalGotoInstruction;
import com.intellij.codeInspection.dataFlow.instructions.ExpressionPushingInstruction;
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import com.intellij.codeInspection.dataFlow.types.DfAntiConstantType;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
@@ -49,15 +52,26 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
private Map<DfaVariableValue, Set<Relation>> getRelations() {
Map<DfaVariableValue, Set<Relation>> result = new HashMap<>();
for (EqClass eqClass : getNonTrivialEqClasses()) {
DfaConstValue constant = eqClass.findConstant();
List<DfaVariableValue> vars = eqClass.getVariables(false);
for (DfaVariableValue var : vars) {
Set<Relation> set = result.computeIfAbsent(var, k -> new HashSet<>());
if (constant != null) {
set.add(new Relation(RelationType.EQ, constant));
forVariableStates((var, state) -> {
if (state.myDfType instanceof DfConstantType) {
result.computeIfAbsent(var, k -> new HashSet<>()).add(new Relation(RelationType.EQ, getFactory().fromDfType(state.myDfType)));
}
if (state.myDfType instanceof DfAntiConstantType) {
Set<?> notValues = ((DfAntiConstantType<?>)state.myDfType).getNotValues();
if (!notValues.isEmpty() && var.getType() != null) {
DfaPsiType dfaPsiType = getFactory().createDfaType(var.getType());
for (Object notValue : notValues) {
result.computeIfAbsent(var, k -> new HashSet<>()).add(
new Relation(RelationType.NE, getFactory().fromDfType(DfTypes.constant(notValue, dfaPsiType))));
}
}
for (DfaVariableValue eqVar : vars) {
}
});
for (EqClass eqClass : getNonTrivialEqClasses()) {
for (DfaVariableValue var : eqClass) {
Set<Relation> set = result.computeIfAbsent(var, k -> new HashSet<>());
for (DfaVariableValue eqVar : eqClass) {
if (eqVar != var) {
set.add(new Relation(RelationType.EQ, eqVar));
}
@@ -71,26 +85,12 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
RelationType plain = classPair.isOrdered() ? RelationType.LT : RelationType.NE;
RelationType flipped = Objects.requireNonNull(plain.getFlipped());
List<DfaVariableValue> firstVars = first.getVariables(false);
List<DfaVariableValue> secondVars = second.getVariables(false);
for (DfaVariableValue var1 : firstVars) {
for (DfaVariableValue var2 : secondVars) {
for (DfaVariableValue var1 : first) {
for (DfaVariableValue var2 : second) {
result.computeIfAbsent(var1, k -> new HashSet<>()).add(new Relation(plain, var2));
result.computeIfAbsent(var2, k -> new HashSet<>()).add(new Relation(flipped, var1));
}
}
DfaConstValue firstConst = first.findConstant();
if (firstConst != null) {
for (DfaVariableValue var2 : secondVars) {
result.computeIfAbsent(var2, k -> new HashSet<>()).add(new Relation(flipped, firstConst));
}
}
DfaConstValue secondConst = second.findConstant();
if (secondConst != null) {
for (DfaVariableValue var1 : firstVars) {
result.computeIfAbsent(var1, k -> new HashSet<>()).add(new Relation(plain, secondConst));
}
}
}
return result;
@@ -99,7 +99,7 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
void recordChange(Instruction instruction, TrackingDfaMemoryState previous) {
Map<DfaVariableValue, Change> result = getChangeMap(previous);
DfaValue value = isEmptyStack() ? DfaUnknownValue.getInstance() : peek();
DfaValue value = isEmptyStack() ? getFactory().getUnknown() : peek();
myHistory = MemoryStateChange.create(myHistory, instruction, result, value);
}
@@ -110,8 +110,8 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
previous.forVariableStates((value, state) -> varsToCheck.add(value));
forVariableStates((value, state) -> varsToCheck.add(value));
for (DfaVariableValue value : varsToCheck) {
DfaFactMap newMap = getVariableState(value).myFactMap;
DfaFactMap oldMap = previous.getVariableState(value).myFactMap;
DfaFactMap newMap = DfaFactMap.fromDfType(getVariableState(value).myDfType);
DfaFactMap oldMap = DfaFactMap.fromDfType(previous.getVariableState(value).myDfType);
if (!newMap.equals(oldMap)) {
DfaFactMap added = DfaFactMap.EMPTY;
DfaFactMap removed = DfaFactMap.EMPTY;
@@ -345,14 +345,16 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
for (MemoryStateChange change = this; change != null; change = change.getPrevious()) {
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;
if (!(change.myInstruction instanceof ConditionalGotoInstruction)) {
factPair = factFromChange(type, change, change.myBridgeChanges.get(value));
if (factPair != null) return factPair;
}
if (change.myInstruction instanceof AssignInstruction && change.myTopOfStack == value && change.getPrevious() != null) {
FactDefinition<T> fact = change.getPrevious().findFact(value, type);
return new FactDefinition<>(change, fact.myFact);
}
}
return new FactDefinition<>(null, ((DfaVariableValue)value).getInherentFacts().get(type));
return new FactDefinition<>(null, DfaFactMap.fromDfType(((DfaVariableValue)value).getInherentType()).get(type));
}
return new FactDefinition<>(null, type.fromDfaValue(value));
}
@@ -421,7 +423,7 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
if (previous.size() == 1) {
return previous.iterator().next();
}
return new MemoryStateChange(new ArrayList<>(previous), new MergeInstruction(), Collections.emptyMap(), DfaUnknownValue.getInstance(),
return new MemoryStateChange(new ArrayList<>(previous), new MergeInstruction(), Collections.emptyMap(), myTopOfStack.getFactory().getUnknown(),
Collections.emptyMap());
}
@@ -432,7 +434,7 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
instruction = myInstruction;
} else {
return new MemoryStateChange(
Collections.singletonList(this), instruction, Collections.emptyMap(), DfaUnknownValue.getInstance(), bridge);
Collections.singletonList(this), instruction, Collections.emptyMap(), myTopOfStack.getFactory().getUnknown(), bridge);
}
}
assert myBridgeChanges.isEmpty();
@@ -444,7 +446,7 @@ public class TrackingDfaMemoryState extends DfaMemoryStateImpl {
@NotNull Instruction instruction,
@NotNull Map<DfaVariableValue, Change> result,
@NotNull DfaValue value) {
if (result.isEmpty() && value == DfaUnknownValue.getInstance()) {
if (result.isEmpty() && DfaTypeValue.isUnknown(value)) {
return previous;
}
return new MemoryStateChange(ContainerUtil.createMaybeSingletonList(previous), instruction, result, value, Collections.emptyMap());
@@ -9,6 +9,7 @@ import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.MemoryStateCh
import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.Relation;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
@@ -517,16 +518,16 @@ public class TrackingRunner extends DataFlowRunner {
if (constantExpressionValue != null && constantExpressionValue.equals(expectedValue)) {
return new CauseItem[]{new CauseItem("it's compile-time constant which evaluates to '" + value + "'", expression)};
}
if (value instanceof DfaConstValue) {
Object constValue = ((DfaConstValue)value).getValue();
if (value.getDfType() instanceof DfConstantType) {
Object constValue = ((DfConstantType<?>)value.getDfType()).getValue();
if (Objects.equals(constValue, expectedValue) && constValue instanceof Boolean) {
return findBooleanResultCauses(expression, history, ((Boolean)constValue).booleanValue());
}
}
if (value instanceof DfaVariableValue) {
MemoryStateChange change = history.findRelation(
(DfaVariableValue)value, rel -> rel.myRelationType == RelationType.EQ && rel.myCounterpart instanceof DfaConstValue &&
Objects.equals(expectedValue, ((DfaConstValue)rel.myCounterpart).getValue()), false);
(DfaVariableValue)value, rel -> rel.myRelationType == RelationType.EQ &&
DfConstantType.isConst(rel.myCounterpart.getDfType(), expectedValue), false);
if (change != null) {
PsiExpression varSourceExpression = change.getExpression();
Instruction instruction = change.myInstruction;
@@ -605,8 +606,7 @@ public class TrackingRunner extends DataFlowRunner {
if (push != null &&
((push.myInstruction instanceof ConditionalGotoInstruction &&
((ConditionalGotoInstruction)push.myInstruction).isTarget(value, history.myInstruction)) ||
(push.myTopOfStack instanceof DfaConstValue &&
Boolean.valueOf(value).equals(((DfaConstValue)push.myTopOfStack).getValue())))) {
DfConstantType.isConst(push.myTopOfStack.getDfType(), value))) {
CauseItem cause = new CauseItem("operand #" + (i + 1) + " of " + (and ? "&&" : "||") + "-chain is " + value, operand);
cause.addChildren(findBooleanResultCauses(operand, push, value));
operandCauses.add(cause);
@@ -640,7 +640,7 @@ public class TrackingRunner extends DataFlowRunner {
return causes;
}
if (leftValue == rightValue &&
(leftValue instanceof DfaVariableValue || leftValue instanceof DfaConstValue)) {
(leftValue instanceof DfaVariableValue || leftValue.getDfType() instanceof DfConstantType)) {
List<CauseItem> constCauses = new ArrayList<>();
CauseItem leftCause = constantInitializerCause(leftValue, leftChange.getExpression());
CauseItem rightCause = constantInitializerCause(rightValue, rightChange.getExpression());
@@ -651,7 +651,7 @@ public class TrackingRunner extends DataFlowRunner {
return constCauses.toArray(new CauseItem[0]);
}
if (leftValue != rightValue && relationType.isInequality() &&
leftValue instanceof DfaConstValue && rightValue instanceof DfaConstValue) {
leftValue.getDfType() instanceof DfConstantType && rightValue.getDfType() instanceof DfConstantType) {
CauseItem causeItem = new CauseItem("comparison arguments are different constants", binOp.getOperationSign());
causeItem.addChildren(constantInitializerCause(leftValue, leftChange.getExpression()),
constantInitializerCause(rightValue, rightChange.getExpression()));
@@ -689,7 +689,7 @@ public class TrackingRunner extends DataFlowRunner {
}
private static CauseItem constantInitializerCause(DfaValue value, PsiExpression ref) {
if (!(value instanceof DfaConstValue)) return null;
if (!(value.getDfType() instanceof DfConstantType)) return null;
if (ref instanceof PsiReferenceExpression) {
PsiElement target = ((PsiReferenceExpression)ref).resolve();
if (target instanceof PsiVariable && ((PsiVariable)target).hasModifierProperty(PsiModifier.FINAL)) {
@@ -1403,9 +1403,10 @@ public class TrackingRunner extends DataFlowRunner {
List<RelationType> subRelations;
switch (relation.myRelationType) {
case NE:
if (relation.myCounterpart instanceof DfaConstValue) {
if (relation.myCounterpart.getDfType() instanceof DfConstantType) {
return history.findRelation(var, rel -> rel.equals(relation) ||
rel.myRelationType == RelationType.EQ && rel.myCounterpart instanceof DfaConstValue,
rel.myRelationType == RelationType.EQ &&
rel.myCounterpart.getDfType() instanceof DfConstantType,
true);
}
subRelations = Arrays.asList(RelationType.NE, RelationType.GT, RelationType.LT);
@@ -1,6 +1,9 @@
// Copyright 2000-2019 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.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
@@ -15,8 +18,7 @@ import java.util.*;
/**
* Immutable class representing a number of non-primitive type constraints applied to some value.
* There are two types of constrains: value is instance of some type and value is not an instance of some type.
* Unlike usual Java semantics, the {@code null} value is considered to be instanceof any type (non-null instanceof can be expressed
* via additional restriction {@link DfaFactType#NULLABILITY} {@code = NOT_NULL}).
* Null or primitive types are not handled here.
*/
public abstract class TypeConstraint {
@@ -35,13 +37,13 @@ public abstract class TypeConstraint {
@Nullable
public abstract PsiType getPsiType();
abstract boolean isSuperStateOf(@NotNull TypeConstraint other);
public abstract boolean isSuperStateOf(@NotNull TypeConstraint other);
@Nullable
@NotNull
public abstract TypeConstraint unite(@NotNull TypeConstraint other);
@Nullable
abstract TypeConstraint intersect(@NotNull TypeConstraint right);
public abstract TypeConstraint intersect(@NotNull TypeConstraint right);
@NotNull
public abstract Set<DfaPsiType> getInstanceofValues();
@@ -57,6 +59,15 @@ public abstract class TypeConstraint {
public abstract String getAssignabilityExplanation(DfaPsiType otherType, boolean expectedAssignable);
public DfReferenceType asDfType() {
return DfTypes.customObject(this, DfaNullability.UNKNOWN, Mutability.UNKNOWN, null, DfTypes.BOTTOM);
}
@NotNull
public static TypeConstraint fromDfType(@NotNull DfType type) {
return type instanceof DfReferenceType ? ((DfReferenceType)type).getConstraint() : empty();
}
static final class Exact extends TypeConstraint {
final @NotNull DfaPsiType myType;
@@ -110,11 +121,11 @@ public abstract class TypeConstraint {
}
@Override
boolean isSuperStateOf(@NotNull TypeConstraint other) {
public boolean isSuperStateOf(@NotNull TypeConstraint other) {
return this.equals(other);
}
@Nullable
@NotNull
@Override
public TypeConstraint unite(@NotNull TypeConstraint other) {
if(isSuperStateOf(other)) return this;
@@ -124,7 +135,7 @@ public abstract class TypeConstraint {
@Override
@Nullable
TypeConstraint intersect(@NotNull TypeConstraint right) {
public TypeConstraint intersect(@NotNull TypeConstraint right) {
if (right instanceof Exact) {
return right.equals(this) ? this : null;
}
@@ -230,7 +241,10 @@ public abstract class TypeConstraint {
@Nullable
public TypeConstraint withInstanceofValue(@NotNull DfaPsiType type) {
PsiType psiType = type.getPsiType();
if (psiType instanceof PsiPrimitiveType || LambdaUtil.notInferredType(psiType)) return this;
if (psiType instanceof PsiPrimitiveType || LambdaUtil.notInferredType(psiType) ||
psiType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
return this;
}
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(psiType);
if (psiClass != null && psiClass.hasModifierProperty(PsiModifier.FINAL)) {
@@ -305,26 +319,32 @@ public abstract class TypeConstraint {
}
@Override
boolean isSuperStateOf(@NotNull TypeConstraint other) {
public boolean isSuperStateOf(@NotNull TypeConstraint other) {
if (other instanceof Constrained) {
Constrained that = (Constrained)other;
if (that.myNotInstanceofValues.containsAll(myNotInstanceofValues) && that.myInstanceofValues.containsAll(myInstanceofValues)) {
return true;
}
if (this.myNotInstanceofValues.isEmpty() && that.myNotInstanceofValues.isEmpty()) {
return that.myInstanceofValues.stream().allMatch(
thatType -> this.myInstanceofValues.stream().allMatch(thisType -> thisType.isAssignableFrom(thatType)));
if (!that.myNotInstanceofValues.containsAll(myNotInstanceofValues)) {
if (that.myInstanceofValues.isEmpty()) return false;
for (DfaPsiType thisNotType : this.myNotInstanceofValues) {
if (!that.myNotInstanceofValues.contains(thisNotType) &&
that.myInstanceofValues.stream().anyMatch(thatType -> thisNotType.isConvertibleFrom(thatType))) {
return false;
}
}
}
if (that.myInstanceofValues.containsAll(myInstanceofValues)) return true;
if (that.myInstanceofValues.isEmpty()) return myInstanceofValues.isEmpty();
return that.myInstanceofValues.stream().allMatch(
thatType -> this.myInstanceofValues.stream().allMatch(thisType -> thisType.isAssignableFrom(thatType)));
} else if (other instanceof Exact) {
DfaPsiType otherType = ((Exact)other).myType;
return this.myInstanceofValues.stream().allMatch(otherType::isAssignableFrom) &&
this.myNotInstanceofValues.stream().noneMatch(otherType::isAssignableFrom);
return this.myInstanceofValues.stream().allMatch(my -> my.isAssignableFrom(otherType)) &&
this.myNotInstanceofValues.stream().noneMatch(my -> my.isAssignableFrom(otherType));
}
return false;
}
@Override
@Nullable
@NotNull
public TypeConstraint unite(@NotNull TypeConstraint other) {
if(isSuperStateOf(other)) return this;
if(other.isSuperStateOf(this)) return other;
@@ -339,7 +359,7 @@ public abstract class TypeConstraint {
@Override
@Nullable
TypeConstraint intersect(@NotNull TypeConstraint right) {
public TypeConstraint intersect(@NotNull TypeConstraint right) {
if (right instanceof Exact) {
return right.intersect(this);
}
@@ -355,6 +375,7 @@ public abstract class TypeConstraint {
return result;
}
@NotNull
private TypeConstraint unite(@NotNull Constrained other) {
Set<DfaPsiType> notTypes = new THashSet<>(this.myNotInstanceofValues);
notTypes.retainAll(other.myNotInstanceofValues);
@@ -482,14 +503,6 @@ public abstract class TypeConstraint {
return new TypeConstraint.Constrained(instanceofValues, notInstanceofValues);
}
@Nullable
public static DfaFactMap withInstanceOf(@NotNull DfaFactMap map, @NotNull DfaPsiType type) {
TypeConstraint constraint = map.get(DfaFactType.TYPE_CONSTRAINT);
if (constraint == null) constraint = Constrained.EMPTY;
constraint = constraint.withInstanceofValue(type);
return constraint == null ? null : map.with(DfaFactType.TYPE_CONSTRAINT, constraint);
}
public static TypeConstraint exact(@NotNull DfaPsiType type) {
return new Exact(type);
}
@@ -16,6 +16,7 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
@@ -75,28 +76,34 @@ class ValuableDataFlowRunner extends DataFlowRunner {
private ValuableDfaVariableState(DfaValue value,
@NotNull FList<PsiExpression> concatenation,
@NotNull DfaFactMap factMap) {
super(factMap);
@NotNull DfType dfType) {
super(dfType);
myValue = value;
myConcatenation = concatenation;
}
@NotNull
@Override
protected DfaVariableState createCopy(@NotNull DfaFactMap factMap) {
return new ValuableDfaVariableState(myValue, myConcatenation, factMap);
protected DfaVariableState createCopy(@NotNull DfType dfType) {
return dfType.equals(myDfType) ? this : new ValuableDfaVariableState(myValue, myConcatenation, dfType);
}
@NotNull
@Override
public DfaVariableState withValue(@Nullable final DfaValue value) {
if (value == myValue) return this;
return new ValuableDfaVariableState(value, myConcatenation, myFactMap);
return new ValuableDfaVariableState(value, myConcatenation, myDfType);
}
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
if (concatenation == myConcatenation) return this;
return new ValuableDfaVariableState(myValue, concatenation, myFactMap);
return new ValuableDfaVariableState(myValue, concatenation, myDfType);
}
@Override
public boolean isSuperStateOf(DfaVariableState other) {
return other instanceof ValuableDfaVariableState && myConcatenation.equals(((ValuableDfaVariableState)other).myConcatenation) &&
Objects.equals(myValue, ((ValuableDfaVariableState)other).myValue) && super.isSuperStateOf(other);
}
@Override
@@ -172,8 +172,7 @@ internal class ControlTransferHandler(val state: DfaMemoryState, val runner: Dat
private fun stateForCatchClause(param: PsiParameter, constraint: TypeConstraint): DfaMemoryState {
val catchingCopy = state.createCopy()
val value = runner.factory.varFactory.createVariableValue(param)
catchingCopy.applyFact(value, DfaFactType.TYPE_CONSTRAINT, constraint)
catchingCopy.applyFact(value, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
catchingCopy.meetDfType(value, constraint.asDfType().meet(DfaNullability.NOT_NULL.asDfType()))
return catchingCopy
}
@@ -6,7 +6,6 @@ package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.CFGBuilder;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.psi.CommonClassNames;
@@ -74,7 +73,7 @@ public class MapUpdateInliner implements CallInliner {
.evaluateFunction(function)
.pushUnknown()
.ifNotNull()
.push(builder.getFactory().createTypeValue(type, Nullability.NOT_NULL))
.push(builder.getFactory().getObjectType(type, Nullability.NOT_NULL))
.swap()
.invokeFunction(2, function)
.end()
@@ -89,7 +88,7 @@ public class MapUpdateInliner implements CallInliner {
DfaValueFactory factory = builder.getFactory();
DfaValue value = factory.createValue(qualifier);
DfaValue size = SpecialField.COLLECTION_SIZE.createValue(factory, value);
builder.assignAndPop(size, DfaUnknownValue.getInstance());
builder.assignAndPop(size, factory.getUnknown());
}
private static void inlineComputeIfAbsent(@NotNull CFGBuilder builder,
@@ -106,7 +105,7 @@ public class MapUpdateInliner implements CallInliner {
.chain(b -> flushSize(qualifier, b))
.elseBranch()
.pop()
.push(builder.getFactory().createTypeValue(type, Nullability.NOT_NULL))
.push(builder.getFactory().getObjectType(type, Nullability.NOT_NULL))
.end();
}
@@ -119,7 +118,7 @@ public class MapUpdateInliner implements CallInliner {
.evaluateFunction(function)
.pushUnknown() // stack: .. key; get() result
.ifNotNull() // stack: .. key
.push(builder.getFactory().createTypeValue(type, Nullability.NOT_NULL))
.push(builder.getFactory().getObjectType(type, Nullability.NOT_NULL))
.invokeFunction(2, function) // stack: .. mapping result
.chain(b -> flushSize(qualifier, b))
.elseBranch()
@@ -135,7 +134,7 @@ public class MapUpdateInliner implements CallInliner {
builder
.pushExpression(key) // stack: .. key
.evaluateFunction(function)
.push(builder.getFactory().createTypeValue(type, Nullability.NULLABLE))
.push(builder.getFactory().getObjectType(type, Nullability.NULLABLE))
.invokeFunction(2, function) // stack: .. mapping result
.chain(b -> flushSize(qualifier, b));
}
@@ -174,7 +174,7 @@ public class OptionalChainInliner implements CallInliner {
if (pushIntermediateOperationValue(builder, call)) {
DfaVariableValue result = builder.createTempVariable(call.getType());
builder
.assign(result, builder.getFactory().createTypeValue(call.getType(), Nullability.NOT_NULL)) // stack: ...value opt
.assign(result, builder.getFactory().getObjectType(call.getType(), Nullability.NOT_NULL)) // stack: ...value opt
.push(SpecialField.OPTIONAL_VALUE.createValue(builder.getFactory(), result)) // stack: ...value opt opt.value
.splice(3, 1, 0, 2)
.assign()
@@ -19,7 +19,12 @@ import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
@@ -173,9 +178,7 @@ public class StreamChainInliner implements CallInliner {
myNext.pushResult(builder);
}
else {
DfaValue resultValue =
builder.getFactory().createTypeValue(myCall.getType(),
getNullability());
DfaValue resultValue = builder.getFactory().getObjectType(myCall.getType(), getNullability());
builder.push(resultValue, myCall);
}
}
@@ -256,7 +259,7 @@ public class StreamChainInliner implements CallInliner {
}
static class SumTerminalStep extends TerminalStep {
private @NotNull DfaValue myResultRange = DfaUnknownValue.getInstance();
private DfaValue myResultRange;
SumTerminalStep(@NotNull PsiMethodCallExpression call) {
super(call, null);
@@ -265,7 +268,9 @@ public class StreamChainInliner implements CallInliner {
@Override
protected void pushInitialValue(CFGBuilder builder) {
if ("count".equals(myCall.getMethodExpression().getReferenceName())) {
myResultRange = builder.getFactory().getFactValue(DfaFactType.RANGE, narrowCountResult(myCall));
myResultRange = builder.getFactory().fromDfType(DfTypes.longRange(narrowCountResult(myCall)));
} else {
myResultRange = builder.getFactory().getUnknown();
}
PsiType type = myCall.getType();
if (!(type instanceof PsiPrimitiveType)) {
@@ -275,7 +280,7 @@ public class StreamChainInliner implements CallInliner {
// Invalid standard library or custom sum() method?
builder.pushUnknown();
} else {
builder.push(builder.getFactory().getConstFactory().createDefault(type))
builder.push(builder.getFactory().getDefaultValue(type))
.boxUnbox(myCall, type, myCall.getType());
}
}
@@ -329,7 +334,7 @@ public class StreamChainInliner implements CallInliner {
DfaVariableValue optValue = (DfaVariableValue)SpecialField.OPTIONAL_VALUE.createValue(builder.getFactory(), myResult);
builder.push(optValue)
.ifNotNull()
.push(builder.getFactory().getFactValue(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL))
.push(builder.getFactory().fromDfType(DfTypes.NOT_NULL_OBJECT))
.swap()
.invokeFunction(2, myFunction, Nullability.NOT_NULL)
.end();
@@ -381,7 +386,7 @@ public class StreamChainInliner implements CallInliner {
@Override
void iteration(CFGBuilder builder) {
DfaConstValue result = builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName()));
DfaTypeValue result = builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName()));
builder.invokeFunction(1, myFunction)
.ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName()))
.assignAndPop(myResult, result)
@@ -490,7 +495,7 @@ public class StreamChainInliner implements CallInliner {
.pushUnknown()
.ifConditionIs(true)
.doWhileUnknown()
.push(builder.getFactory().createTypeValue(outType, Nullability.UNKNOWN))
.push(builder.getFactory().getObjectType(outType, Nullability.UNKNOWN))
.chain(myNext::iteration)
.end()
.end()
@@ -602,13 +607,13 @@ public class StreamChainInliner implements CallInliner {
}
else {
DfaValueFactory factory = builder.getFactory();
DfaValue value = factory.createTypeValue(myCall.getType(), Nullability.NOT_NULL);
DfType dfType = factory.createDfType(myCall.getType(), Nullability.NOT_NULL);
if (myImmutable) {
value = factory.withFact(value, DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE);
dfType = dfType.meet(Mutability.UNMODIFIABLE.asDfType());
} else {
value = factory.withFact(value, DfaFactType.LOCALITY, true);
dfType = dfType.meet(DfTypes.LOCAL_OBJECT);
}
builder.push(value);
builder.push(factory.fromDfType(dfType));
}
}
}
@@ -688,7 +693,7 @@ public class StreamChainInliner implements CallInliner {
builder.pushUnknown()
.ifConditionIs(true)
.pop()
.push(builder.getFactory().getFactValue(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL))
.push(builder.getFactory().fromDfType(DfTypes.NOT_NULL_OBJECT))
.dup()
.invokeFunction(2, myMerger)
.end();
@@ -719,7 +724,7 @@ public class StreamChainInliner implements CallInliner {
.ifConditionIs(true)
.chain(b -> buildStreamCFG(b, firstStep, originalQualifier))
.end()
.push(builder.getFactory().createTypeValue(call.getType(), Nullability.NOT_NULL), call);
.push(builder.getFactory().getObjectType(call.getType(), Nullability.NOT_NULL), call);
return true;
}
@@ -817,7 +822,7 @@ public class StreamChainInliner implements CallInliner {
private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) {
builder.doWhileUnknown()
.assign(builder.createTempVariable(inType), builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
.assign(builder.createTempVariable(inType), builder.getFactory().getObjectType(inType, DfaPsiUtil.getTypeNullability(inType)))
.chain(firstStep::iteration).end();
}
@@ -20,23 +20,21 @@ 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.codeInspection.dataFlow.value.DfaUnknownValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PushInstruction extends ExpressionPushingInstruction<PsiExpression> {
private final DfaValue myValue;
private final boolean myReferenceWrite;
public PushInstruction(@Nullable DfaValue value, PsiExpression place) {
public PushInstruction(@NotNull DfaValue value, PsiExpression place) {
this(value, place, false);
}
public PushInstruction(@Nullable DfaValue value, PsiExpression place, final boolean isReferenceWrite) {
public PushInstruction(@NotNull DfaValue value, PsiExpression place, final boolean isReferenceWrite) {
super(place);
myValue = value != null ? value : DfaUnknownValue.getInstance();
myValue = value;
myReferenceWrite = isReferenceWrite;
}
@@ -2,8 +2,7 @@
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.TypeConversionUtil;
@@ -686,6 +685,7 @@ public abstract class LongRangeSet {
/**
* @return a set containing all possible long values
*/
@NotNull
public static LongRangeSet all() {
return Range.LONG_RANGE;
}
@@ -717,20 +717,6 @@ public abstract class LongRangeSet {
return null;
}
@Nullable
public static LongRangeSet fromDfaValue(DfaValue value) {
if (value instanceof DfaFactMapValue) {
return ((DfaFactMapValue)value).get(DfaFactType.RANGE);
}
if (value instanceof DfaConstValue) {
return fromConstant(((DfaConstValue)value).getValue());
}
if (value instanceof DfaVariableValue) {
return fromType(value.getType());
}
return null;
}
/**
* Creates a new set which contains all the numbers between from (inclusive) and to (inclusive)
*
@@ -0,0 +1,43 @@
// Copyright 2000-2019 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.types;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/**
* Represents a type that maintains a set of constants that excluded from this type
*/
public abstract class DfAntiConstantType<T> implements DfType {
final @NotNull Set<T> myNotValues;
DfAntiConstantType(@NotNull Set<T> notValues) {
myNotValues = notValues;
}
/**
* @return set of excluded constants
*/
@NotNull
public Set<T> getNotValues() {
return Collections.unmodifiableSet(myNotValues);
}
@Override
public int hashCode() {
return myNotValues.hashCode()+1234;
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof DfAntiConstantType && Objects.equals(((DfAntiConstantType<?>)obj).myNotValues, myNotValues);
}
@Override
public String toString() {
return "!= " + StreamEx.of(myNotValues).map(DfConstantType::renderValue).joining(", ");
}
}
@@ -0,0 +1,31 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
class DfBooleanConstantType extends DfConstantType<Boolean> implements DfBooleanType {
DfBooleanConstantType(boolean value) {
super(value);
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other.equals(this)) return this;
if (other instanceof DfBooleanType) return DfTypes.BOOLEAN;
return DfTypes.TOP;
}
@NotNull
@Override
public PsiPrimitiveType getPsiType() {
return DfBooleanType.super.getPsiType();
}
@NotNull
@Override
public DfType tryNegate() {
return getValue() ? DfTypes.FALSE : DfTypes.TRUE;
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
public interface DfBooleanType extends DfPrimitiveType {
@NotNull
@Override
default PsiPrimitiveType getPsiType() {
return PsiType.BOOLEAN;
}
}
@@ -0,0 +1,96 @@
// Copyright 2000-2019 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.types;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiType;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* Represents a type that contains only one value
*/
public abstract class DfConstantType<T> implements DfType {
private final T myValue;
DfConstantType(T value) {
myValue = value;
}
@Override
public boolean isSuperType(@NotNull DfType other) {
return other.equals(this) || other == DfTypes.BOTTOM;
}
@NotNull
public abstract PsiType getPsiType();
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
return other.isSuperType(this) ? this : DfTypes.BOTTOM;
}
/**
* @return a value representation (different constants have different representation)
*/
public T getValue() {
return myValue;
}
@Override
public int hashCode() {
return Objects.hashCode(myValue);
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof DfConstantType && Objects.equals(((DfConstantType<?>)obj).myValue, myValue);
}
@Override
public String toString() {
return renderValue(myValue);
}
/**
* @param dfType dfType to check
* @param value constant value
* @return true if given dfType represents a constant that is equal to given value
*/
public static boolean isConst(@NotNull DfType dfType, @Nullable Object value) {
return dfType instanceof DfConstantType && Objects.equals(((DfConstantType<?>)dfType).getValue(), value);
}
/**
* @param dfType dfType to extract the constant value from
* @param clazz desired constant class
* @param <T> type of the constant
* @return the constant of given type; null if the supplied dfType is not a constant or its type class differs from the supplied one.
*/
@Nullable
public static <T> T getConstantOfType(@NotNull DfType dfType, @NotNull Class<T> clazz) {
return dfType instanceof DfConstantType ? ObjectUtils.tryCast(((DfConstantType<?>)dfType).getValue(), clazz) : null;
}
/**
* @param value constant value
* @return human readable representation of the value
*/
public static String renderValue(Object value) {
if (value == null) return "null";
if (value instanceof String) return '"' + StringUtil.escapeStringCharacters((String)value) + '"';
if (value instanceof Float) return value + "f";
if (value instanceof Long) return value + "L";
if (value instanceof PsiField) {
PsiField field = (PsiField)value;
PsiClass containingClass = field.getContainingClass();
return containingClass == null ? field.getName() : containingClass.getName() + "." + field.getName();
}
return value.toString();
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
class DfDoubleConstantType extends DfConstantType<Double> implements DfDoubleType {
DfDoubleConstantType(double value) {
super(value);
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other.isSuperType(this)) return other;
if (other instanceof DfDoubleType) return DfTypes.DOUBLE;
return DfTypes.TOP;
}
@NotNull
@Override
public PsiPrimitiveType getPsiType() {
return DfDoubleType.super.getPsiType();
}
@NotNull
@Override
public DfType tryNegate() {
return new DfDoubleNotValueType(Collections.singleton(getValue()));
}
}
@@ -0,0 +1,53 @@
// Copyright 2000-2019 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.types;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
class DfDoubleNotValueType extends DfAntiConstantType<Double> implements DfDoubleType {
DfDoubleNotValueType(Set<Double> values) {
super(values);
}
@Override
public boolean isSuperType(@NotNull DfType other) {
if (other == DfTypes.BOTTOM || other.equals(this)) return true;
if (other instanceof DfDoubleNotValueType) return ((DfDoubleNotValueType)other).myNotValues.containsAll(myNotValues);
if (other instanceof DfDoubleConstantType) return !myNotValues.contains(((DfDoubleConstantType)other).getValue());
return false;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (isSuperType(other)) return this;
if (other.isSuperType(this)) return other;
if (other instanceof DfDoubleNotValueType) {
THashSet<Double> notValues = new THashSet<>(myNotValues);
notValues.retainAll(((DfDoubleNotValueType)other).myNotValues);
return notValues.isEmpty() ? DfTypes.DOUBLE : new DfDoubleNotValueType(notValues);
}
return DfTypes.TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (isSuperType(other)) return other;
if (other.isSuperType(this)) return this;
if (other instanceof DfDoubleConstantType && myNotValues.contains(((DfDoubleConstantType)other).getValue())) return DfTypes.BOTTOM;
if (other instanceof DfDoubleNotValueType) {
THashSet<Double> notValues = new THashSet<>(myNotValues);
notValues.addAll(((DfDoubleNotValueType)other).myNotValues);
return new DfDoubleNotValueType(notValues);
}
return DfTypes.BOTTOM;
}
@Override
public String toString() {
return "double, not in " + myNotValues;
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
public interface DfDoubleType extends DfFloatingPointType {
@NotNull
@Override
default PsiPrimitiveType getPsiType() {
return PsiType.DOUBLE;
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
class DfFloatConstantType extends DfConstantType<Float> implements DfFloatType {
DfFloatConstantType(float value) {
super(value);
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other.isSuperType(this)) return other;
if (other instanceof DfFloatType) return DfTypes.FLOAT;
return DfTypes.TOP;
}
@NotNull
@Override
public PsiPrimitiveType getPsiType() {
return DfFloatType.super.getPsiType();
}
@NotNull
@Override
public DfType tryNegate() {
return new DfFloatNotValueType(Collections.singleton(getValue()));
}
}
@@ -0,0 +1,53 @@
// Copyright 2000-2019 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.types;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
class DfFloatNotValueType extends DfAntiConstantType<Float> implements DfFloatType {
DfFloatNotValueType(Set<Float> values) {
super(values);
}
@Override
public boolean isSuperType(@NotNull DfType other) {
if (other == DfTypes.BOTTOM || other.equals(this)) return true;
if (other instanceof DfFloatNotValueType) return ((DfFloatNotValueType)other).myNotValues.containsAll(myNotValues);
if (other instanceof DfFloatConstantType) return !myNotValues.contains(((DfFloatConstantType)other).getValue());
return false;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (isSuperType(other)) return this;
if (other.isSuperType(this)) return other;
if (other instanceof DfFloatNotValueType) {
THashSet<Float> notValues = new THashSet<>(myNotValues);
notValues.retainAll(((DfFloatNotValueType)other).myNotValues);
return notValues.isEmpty() ? DfTypes.FLOAT : new DfFloatNotValueType(notValues);
}
return DfTypes.TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (isSuperType(other)) return other;
if (other.isSuperType(this)) return this;
if (other instanceof DfFloatConstantType && myNotValues.contains(((DfFloatConstantType)other).getValue())) return DfTypes.BOTTOM;
if (other instanceof DfFloatNotValueType) {
THashSet<Float> notValues = new THashSet<>(myNotValues);
notValues.addAll(((DfFloatNotValueType)other).myNotValues);
return new DfFloatNotValueType(notValues);
}
return DfTypes.BOTTOM;
}
@Override
public String toString() {
return "float, not in " + myNotValues;
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
public interface DfFloatType extends DfFloatingPointType {
@NotNull
@Override
default PsiPrimitiveType getPsiType() {
return PsiType.FLOAT;
}
}
@@ -0,0 +1,5 @@
// Copyright 2000-2019 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.types;
public interface DfFloatingPointType extends DfPrimitiveType {
}
@@ -0,0 +1,302 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.BOTTOM;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.TOP;
class DfGenericObjectType extends DfAntiConstantType<Object> implements DfReferenceType {
private final @NotNull TypeConstraint myConstraint;
private final @NotNull DfaNullability myNullability;
private final @NotNull Mutability myMutability;
private final @Nullable SpecialField mySpecialField;
private final @NotNull DfType mySpecialFieldType;
private final boolean myLocal;
DfGenericObjectType(@NotNull Set<Object> notValues,
@NotNull TypeConstraint constraint,
@NotNull DfaNullability nullability,
@NotNull Mutability mutability,
@Nullable SpecialField field,
@NotNull DfType type,
boolean local) {
super(notValues);
myConstraint = constraint;
myNullability = nullability;
myMutability = mutability;
mySpecialField = field;
mySpecialFieldType = type instanceof DfReferenceType ? ((DfReferenceType)type).dropSpecialField() : type;
myLocal = local;
}
@NotNull
@Override
public DfaNullability getNullability() {
return myNullability;
}
@NotNull
@Override
public TypeConstraint getConstraint() {
return myConstraint;
}
@NotNull
@Override
public Mutability getMutability() {
return myMutability;
}
@Override
public boolean isLocal() {
return myLocal;
}
@Nullable
@Override
public SpecialField getSpecialField() {
return mySpecialField;
}
@NotNull
@Override
public DfType getSpecialFieldType() {
return mySpecialFieldType;
}
@Override
public DfType tryNegate() {
if (myMutability != Mutability.UNKNOWN || myLocal || mySpecialField != null || !myConstraint.getNotInstanceofValues().isEmpty()) {
return null;
}
Set<DfaPsiType> types = myConstraint.getInstanceofValues();
DfaPsiType type = ContainerUtil.getOnlyItem(types);
DfType result = BOTTOM;
if (type != null) {
result = Objects.requireNonNull(TypeConstraint.empty().withNotInstanceofValue(type)).asDfType();
}
else if (!types.isEmpty()) {
return null;
}
return myNullability == DfaNullability.NOT_NULL ? result.join(DfTypes.NULL) : result;
}
@NotNull
@Override
public Set<Object> getNotValues() {
if (myNullability == DfaNullability.NOT_NULL) {
Set<Object> values = new HashSet<>(myNotValues);
values.add(null);
return Collections.unmodifiableSet(values);
}
return super.getNotValues();
}
@NotNull
@Override
public DfReferenceType dropTypeConstraint() {
return myConstraint.isEmpty() ? this :
new DfGenericObjectType(myNotValues, TypeConstraint.empty(), myNullability, myMutability, mySpecialField, mySpecialFieldType,
myLocal);
}
@NotNull
@Override
public DfReferenceType dropMutability() {
return myMutability == Mutability.UNKNOWN ? this :
new DfGenericObjectType(myNotValues, myConstraint, myNullability, Mutability.UNKNOWN, mySpecialField, mySpecialFieldType,
myLocal);
}
@NotNull
@Override
public DfReferenceType dropLocality() {
return myLocal ? new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType,
false) : this;
}
@NotNull
@Override
public DfReferenceType dropNullability() {
return myNullability == DfaNullability.UNKNOWN ? this :
new DfGenericObjectType(myNotValues, myConstraint, DfaNullability.UNKNOWN, myMutability, mySpecialField, mySpecialFieldType,
myLocal);
}
@NotNull
@Override
public DfReferenceType dropSpecialField() {
return mySpecialField == null ? this :
new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, null, BOTTOM, myLocal);
}
@Override
public boolean isSuperType(@NotNull DfType other) {
if (other == BOTTOM) return true;
if (other instanceof DfNullConstantType) {
return getNullability() != DfaNullability.NOT_NULL;
}
if (!(other instanceof DfReferenceType)) return false;
if (!myNotValues.isEmpty()) {
if (other instanceof DfReferenceConstantType) {
if (myNotValues.contains(((DfReferenceConstantType)other).getValue())) {
return false;
}
}
else if (other instanceof DfGenericObjectType) {
if (!((DfGenericObjectType)other).myNotValues.containsAll(myNotValues)) {
return false;
}
}
else return false;
}
DfReferenceType type = (DfReferenceType)other;
if (isLocal() && !type.isLocal()) return false;
if (type.getNullability() != getNullability() && getNullability() != DfaNullability.UNKNOWN &&
type.getNullability() != DfaNullability.NOT_NULL) return false;
if (!getConstraint().isSuperStateOf(type.getConstraint())) return false;
if (getMutability().ordinal() > type.getMutability().ordinal()) return false;
SpecialField sf = getSpecialField();
if (sf != null) {
if (sf != type.getSpecialField()) return false;
if (!getSpecialFieldType().isSuperType(type.getSpecialFieldType())) return false;
}
return true;
}
@Override
public boolean isMergeable(@NotNull DfType other) {
if (!isSuperType(other)) return false;
if (getNullability() == DfaNullability.UNKNOWN) {
DfaNullability otherNullability = DfaNullability.fromDfType(other);
return otherNullability != DfaNullability.NULL && otherNullability != DfaNullability.NULLABLE;
}
return true;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (isSuperType(other)) return this;
if (other.isSuperType(this)) return other;
if (!(other instanceof DfReferenceType)) return TOP;
DfReferenceType type = (DfReferenceType)other;
TypeConstraint constraint = getConstraint().unite(type.getConstraint());
DfaNullability nullability = getNullability().unite(type.getNullability());
Mutability mutability = getMutability().unite(type.getMutability());
boolean locality = isLocal() && type.isLocal();
SpecialField sf = Objects.equals(getSpecialField(), type.getSpecialField()) ? getSpecialField() : null;
DfType sfType = sf == null ? BOTTOM : getSpecialFieldType().join(type.getSpecialFieldType());
Set<Object> notValues = myNotValues;
if (type instanceof DfGenericObjectType) {
notValues = new THashSet<>(myNotValues);
notValues.retainAll(((DfGenericObjectType)other).myNotValues);
}
if (type instanceof DfReferenceConstantType) {
notValues = new THashSet<>(myNotValues);
notValues.remove(((DfReferenceConstantType)type).getValue());
}
return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality);
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (other instanceof DfConstantType) {
return other.meet(this);
}
if (isSuperType(other)) return other;
if (other.isSuperType(this)) return this;
if (!(other instanceof DfReferenceType)) return BOTTOM;
DfReferenceType type = (DfReferenceType)other;
TypeConstraint constraint = getConstraint().intersect(type.getConstraint());
if (constraint == null) {
return isSuperType(DfTypes.NULL) && other.isSuperType(DfTypes.NULL) ? DfTypes.NULL : BOTTOM;
}
DfaNullability nullability = getNullability().intersect(type.getNullability());
if (nullability == null) return BOTTOM;
Mutability mutability = getMutability().intersect(type.getMutability());
boolean locality = isLocal() || type.isLocal();
SpecialField sf;
DfType sfType;
if (getSpecialField() == null) {
sf = type.getSpecialField();
sfType = type.getSpecialFieldType();
}
else if (type.getSpecialField() == null) {
sf = getSpecialField();
sfType = getSpecialFieldType();
} else {
sf = getSpecialField();
if (sf != type.getSpecialField()) return BOTTOM;
sfType = sf == null ? BOTTOM : getSpecialFieldType().meet(type.getSpecialFieldType());
}
if (sf != null && sfType == BOTTOM) return BOTTOM;
Set<Object> notValues = myNotValues;
if (type instanceof DfGenericObjectType) {
Set<Object> otherNotValues = ((DfGenericObjectType)other).myNotValues;
if (otherNotValues.containsAll(myNotValues)) {
notValues = otherNotValues;
} else if (!myNotValues.containsAll(otherNotValues)) {
notValues = new THashSet<>(myNotValues);
notValues.addAll(otherNotValues);
}
}
return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DfGenericObjectType type = (DfGenericObjectType)o;
return myLocal == type.myLocal &&
myNullability == type.myNullability &&
myMutability == type.myMutability &&
mySpecialField == type.mySpecialField &&
myConstraint.equals(type.myConstraint) &&
mySpecialFieldType.equals(type.mySpecialFieldType) &&
myNotValues.equals(type.myNotValues);
}
@Override
public int hashCode() {
return Objects.hash(myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType, myLocal, myNotValues);
}
@Override
public String toString() {
List<String> components = new ArrayList<>();
if (!myConstraint.isEmpty()) {
components.add(myConstraint.toString());
}
if (myNullability != DfaNullability.UNKNOWN) {
components.add(myNullability.toString());
}
if (myMutability != Mutability.UNKNOWN) {
components.add(myMutability.name());
}
if (myLocal) {
components.add("local object");
}
if (mySpecialField != null) {
components.add(mySpecialField + "=" + mySpecialFieldType);
}
if (!myNotValues.isEmpty()) {
components.add("!= " + StreamEx.of(myNotValues).map(DfConstantType::renderValue).joining(", "));
}
return String.join(" ", components);
}
}
@@ -0,0 +1,30 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
class DfIntConstantType extends DfConstantType<Integer> implements DfIntType {
DfIntConstantType(int value) {
super(value);
}
@NotNull
@Override
public PsiPrimitiveType getPsiType() {
return DfIntType.super.getPsiType();
}
@NotNull
@Override
public DfType tryNegate() {
return DfTypes.intRange(DfIntRangeType.FULL_RANGE.subtract(getRange()));
}
@NotNull
@Override
public LongRangeSet getRange() {
return LongRangeSet.point(getValue());
}
}
@@ -0,0 +1,49 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
class DfIntRangeType implements DfIntType {
static final @NotNull LongRangeSet FULL_RANGE = Objects.requireNonNull(LongRangeSet.fromType(PsiType.INT));
private final LongRangeSet myRange;
DfIntRangeType(LongRangeSet range) {
if (!FULL_RANGE.contains(range)) {
throw new IllegalArgumentException("Illegal range supplied for int type: " + range);
}
myRange = FULL_RANGE.equals(range) ? FULL_RANGE : range;
}
@NotNull
@Override
public LongRangeSet getRange() {
return myRange;
}
@Override
public boolean isSuperType(@NotNull DfType other) {
if (other == DfTypes.BOTTOM) return true;
if (!(other instanceof DfIntType)) return false;
return myRange.contains(((DfIntType)other).getRange());
}
@Override
public int hashCode() {
return myRange.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof DfIntRangeType && ((DfIntRangeType)obj).myRange.equals(myRange);
}
@Override
public String toString() {
if (myRange == FULL_RANGE) return "int";
return "int " + myRange.getPresentationText(PsiType.INT);
}
}
@@ -0,0 +1,46 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
public interface DfIntType extends DfIntegralType {
@Override
@NotNull
LongRangeSet getRange();
@NotNull
@Override
default DfType join(@NotNull DfType other) {
if (!(other instanceof DfIntType)) return DfTypes.TOP;
return DfTypes.intRange(((DfIntType)other).getRange().unite(getRange()));
}
@NotNull
@Override
default DfType meet(@NotNull DfType other) {
if (other == DfTypes.TOP) return this;
if (!(other instanceof DfIntType)) return DfTypes.BOTTOM;
return DfTypes.intRange(((DfIntType)other).getRange().intersect(getRange()));
}
@NotNull
@Override
default DfType meetRange(@NotNull LongRangeSet range) {
return meet(DfTypes.intRangeClamped(range));
}
@NotNull
@Override
default PsiPrimitiveType getPsiType() {
return PsiType.INT;
}
@NotNull
static LongRangeSet extractRange(@NotNull DfType type) {
return type instanceof DfIntegralType ? ((DfIntegralType)type).getRange().intersect(DfIntRangeType.FULL_RANGE) :
DfIntRangeType.FULL_RANGE;
}
}
@@ -0,0 +1,25 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import org.jetbrains.annotations.NotNull;
/**
* Represents an integral primitive (int or long)
*/
public interface DfIntegralType extends DfPrimitiveType {
@NotNull
LongRangeSet getRange();
@NotNull
default DfType meetRelation(@NotNull RelationType relation, @NotNull DfType other) {
if (other == DfTypes.TOP) return this;
if (other instanceof DfIntegralType) {
return meetRange(((DfIntegralType)other).getRange().fromRelation(relation));
}
return DfTypes.BOTTOM;
}
@NotNull DfType meetRange(@NotNull LongRangeSet range);
}
@@ -0,0 +1,30 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
class DfLongConstantType extends DfConstantType<Long> implements DfLongType {
DfLongConstantType(long value) {
super(value);
}
@NotNull
@Override
public PsiPrimitiveType getPsiType() {
return DfLongType.super.getPsiType();
}
@NotNull
@Override
public DfType tryNegate() {
return DfTypes.longRange(LongRangeSet.all().subtract(getRange()));
}
@NotNull
@Override
public LongRangeSet getRange() {
return LongRangeSet.point(getValue());
}
}
@@ -0,0 +1,43 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
class DfLongRangeType implements DfLongType {
private final LongRangeSet myRange;
DfLongRangeType(LongRangeSet range) {
myRange = range;
}
@NotNull
@Override
public LongRangeSet getRange() {
return myRange;
}
@Override
public boolean isSuperType(@NotNull DfType other) {
if (other == DfTypes.BOTTOM) return true;
if (!(other instanceof DfLongType)) return false;
return myRange.contains(((DfLongType)other).getRange());
}
@Override
public int hashCode() {
return myRange.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof DfLongRangeType && ((DfLongRangeType)obj).myRange.equals(myRange);
}
@Override
public String toString() {
if (myRange == LongRangeSet.all()) return "long";
return "long " + myRange.getPresentationText(PsiType.LONG);
}
}
@@ -0,0 +1,45 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
public interface DfLongType extends DfIntegralType {
@Override
@NotNull
LongRangeSet getRange();
@NotNull
@Override
default DfType join(@NotNull DfType other) {
if (!(other instanceof DfLongType)) return DfTypes.TOP;
return DfTypes.longRange(((DfLongType)other).getRange().unite(getRange()));
}
@NotNull
@Override
default DfType meet(@NotNull DfType other) {
if (other == DfTypes.TOP) return this;
if (!(other instanceof DfLongType)) return DfTypes.BOTTOM;
return DfTypes.longRange(((DfLongType)other).getRange().intersect(getRange()));
}
@NotNull
@Override
default DfType meetRange(@NotNull LongRangeSet range) {
return meet(DfTypes.longRange(range));
}
@NotNull
@Override
default PsiPrimitiveType getPsiType() {
return PsiType.LONG;
}
@NotNull
static LongRangeSet extractRange(@NotNull DfType type) {
return type instanceof DfIntegralType ? ((DfIntegralType)type).getRange() : LongRangeSet.all();
}
}
@@ -0,0 +1,58 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.BOTTOM;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.TOP;
public class DfNullConstantType extends DfConstantType<Object> implements DfReferenceType {
DfNullConstantType() {
super(null);
}
@NotNull
@Override
public DfaNullability getNullability() {
return DfaNullability.NULL;
}
@NotNull
@Override
public TypeConstraint getConstraint() {
return TypeConstraint.empty();
}
@Override
public DfType tryNegate() {
return DfTypes.NOT_NULL_OBJECT;
}
@NotNull
@Override
public PsiType getPsiType() {
return PsiType.NULL;
}
@NotNull
@Override
public DfReferenceType dropNullability() {
return DfTypes.OBJECT_OR_NULL;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (isSuperType(other)) return this;
if (other.isSuperType(this)) return other;
if (!(other instanceof DfReferenceType)) return TOP;
DfReferenceType type = (DfReferenceType)other;
return new DfGenericObjectType(Collections.emptySet(), type.getConstraint(), DfaNullability.NULL.unite(type.getNullability()),
type.getMutability(), null, BOTTOM, false);
}
}
@@ -0,0 +1,13 @@
// Copyright 2000-2019 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.types;
import com.intellij.psi.PsiPrimitiveType;
import org.jetbrains.annotations.NotNull;
/**
* A type that represents concrete JVM primitive type or subset of values of given type
*/
public interface DfPrimitiveType extends DfType {
@NotNull
PsiPrimitiveType getPsiType();
}
@@ -0,0 +1,118 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Objects;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.BOTTOM;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.TOP;
public class DfReferenceConstantType extends DfConstantType<Object> implements DfReferenceType {
private final @NotNull PsiType myPsiType;
private final @NotNull TypeConstraint myConstraint;
private final @NotNull Mutability myMutability;
private final @Nullable SpecialField mySpecialField;
private final @NotNull DfType mySpecialFieldType;
DfReferenceConstantType(@NotNull Object constant, @NotNull PsiType psiType, @NotNull TypeConstraint type) {
super(constant);
myPsiType = psiType;
myConstraint = type;
myMutability = constant instanceof PsiModifierListOwner ? Mutability.getMutability((PsiModifierListOwner)constant) : Mutability.UNKNOWN;
mySpecialField = SpecialField.fromQualifierType(type.getPsiType());
mySpecialFieldType = mySpecialField == null ? BOTTOM : mySpecialField.fromConstant(constant);
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (other.isSuperType(this)) return this;
if (other instanceof DfGenericObjectType) {
DfReferenceType type = ((DfReferenceType)other).dropMutability();
if (type.isSuperType(this)) return this;
TypeConstraint constraint = type.getConstraint().intersect(myConstraint);
if (constraint != null) {
PsiType psiType = constraint.getPsiType();
DfReferenceConstantType subConstant = new DfReferenceConstantType(getValue(), psiType == null ? myPsiType : psiType, constraint);
if (other.isSuperType(subConstant)) return subConstant;
}
}
return BOTTOM;
}
@NotNull
@Override
public PsiType getPsiType() {
return Objects.requireNonNull(myConstraint.getPsiType());
}
@NotNull
@Override
public DfaNullability getNullability() {
return DfaNullability.NOT_NULL;
}
@NotNull
@Override
public TypeConstraint getConstraint() {
return myConstraint;
}
@NotNull
@Override
public Mutability getMutability() {
return myMutability;
}
@Nullable
@Override
public SpecialField getSpecialField() {
return mySpecialField;
}
@NotNull
@Override
public DfType getSpecialFieldType() {
return mySpecialFieldType;
}
@Override
public DfType tryNegate() {
return new DfGenericObjectType(Collections.singleton(getValue()), TypeConstraint.empty(), DfaNullability.UNKNOWN, Mutability.UNKNOWN,
null, BOTTOM, false);
}
@NotNull
@Override
public DfReferenceType dropNullability() {
return this;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other instanceof DfGenericObjectType) {
return other.join(this);
}
if (isSuperType(other)) return this;
if (other.isSuperType(this)) return other;
if (!(other instanceof DfReferenceType)) return TOP;
DfReferenceType type = (DfReferenceType)other;
TypeConstraint constraint = getConstraint().unite(type.getConstraint());
DfaNullability nullability = getNullability().unite(type.getNullability());
Mutability mutability = getMutability().unite(type.getMutability());
boolean locality = isLocal() && type.isLocal();
SpecialField sf = Objects.equals(getSpecialField(), type.getSpecialField()) ? getSpecialField() : null;
DfType sfType = sf == null ? BOTTOM : getSpecialFieldType().join(type.getSpecialFieldType());
return new DfGenericObjectType(Collections.emptySet(), constraint, nullability, mutability, sf, sfType, locality);
}
}
@@ -0,0 +1,105 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.BOTTOM;
/**
* Type that corresponds to JVM reference type; represents subset of possible reference values (may include null)
*/
public interface DfReferenceType extends DfType {
/**
* @return nullability of this type
*/
@NotNull
DfaNullability getNullability();
/**
* @return type constraint of this type
*/
@NotNull
TypeConstraint getConstraint();
/**
* @return mutability of all the objects referred by this type
*/
@NotNull
default Mutability getMutability() {
return Mutability.UNKNOWN;
}
/**
* @return true if this type contains references only to local objects (not leaked from the current context to unknown methods)
*/
default boolean isLocal() {
return false;
}
/**
* @return special field if additional information is known about the special field of all referenced objects
*/
@Nullable
default SpecialField getSpecialField() {
return null;
}
/**
* @return type of special field; {@link DfTypes#BOTTOM} if {@link #getSpecialField()} returns null
*/
@NotNull
default DfType getSpecialFieldType() {
return BOTTOM;
}
/**
* @return this type without type constraint, or simply this type if it's a constant
*/
@NotNull
default DfReferenceType dropTypeConstraint() {
return this;
}
/**
* @return this type without locality flag, or simply this type if it's a constant
*/
@NotNull
default DfReferenceType dropLocality() {
return this;
}
/**
* @return this type without nullability knowledge, or simply this type if it's a constant
*/
@NotNull
DfReferenceType dropNullability();
/**
* @return this type without mutability knowledge, or simply this type if it's a constant
*/
@NotNull
default DfReferenceType dropMutability() {
return this;
}
/**
* @return this type without special field knowledge, or simply this type if it's a constant
*/
default DfReferenceType dropSpecialField() {
return this;
}
/**
* @param type type to check
* @return true if the supplied type is a reference type that contains references only
* to local objects (not leaked from the current context to unknown methods)
*/
static boolean isLocal(DfType type) {
return type instanceof DfReferenceType && ((DfReferenceType)type).isLocal();
}
}
@@ -0,0 +1,49 @@
// Copyright 2000-2019 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.types;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a domain of possible values within data flow analysis
*/
public interface DfType {
/**
* Checks whether this type is the supertype of the supplied type, i.e. every value from the other type belongs to this type as well.
* if A.isSuperType(B) then A.join(B) is A and A.meet(B) is B.
*
* @param other other type
* @return true if this type is the supertype of other.
*/
boolean isSuperType(@NotNull DfType other);
default boolean isMergeable(@NotNull DfType other) {
return isSuperType(other);
}
/**
* Return the most specific type that contains all values from this type and from other type.
* @param other type to join
* @return the result of the join operation
*/
@NotNull
DfType join(@NotNull DfType other);
/**
* Returns the least specific type that contains all values that belong both to this type and to other type.
* @param other type to meet
* @return the result of the meet operation.
*/
@NotNull
DfType meet(@NotNull DfType other);
/**
* @return a type that contains all the values of the corresponding JVM type except the values of given type;
* may return null if the corresponding type is not supported by our type system.
*/
@Nullable
default DfType tryNegate() {
return null;
}
}
@@ -0,0 +1,466 @@
// Copyright 2000-2019 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.types;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.psi.PsiType;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Objects;
/**
* Commonly used types and factory methods
*/
public class DfTypes {
private DfTypes() {}
/**
* A type that contains every possible value supported by the type system
*/
public static final DfType TOP = new DfType() {
@Override
public boolean isSuperType(@NotNull DfType other) {
return true;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
return this;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
return other;
}
@NotNull
@Override
public DfType tryNegate() {
return BOTTOM;
}
@Override
public int hashCode() {
return 1254215;
}
@Override
public String toString() {
return "TOP";
}
};
/**
* A type that contains no values
*/
public static final DfType BOTTOM = new DfType() {
@Override
public boolean isSuperType(@NotNull DfType other) {
return other == this;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
return other;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
return this;
}
@NotNull
@Override
public DfType tryNegate() {
return TOP;
}
@Override
public int hashCode() {
return 67532141;
}
@Override
public String toString() {
return "BOTTOM";
}
};
/**
* A special value that represents a contract failure after method return (the control flow should immediately proceed
* with exception handling). This value is like a constant but it's type doesn't correspond to any JVM type.
*/
public static final DfType FAIL = new DfConstantType<Object>(ObjectUtils.sentinel("FAIL")) {
@NotNull
@Override
public PsiType getPsiType() {
return PsiType.VOID;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
return other == this ? this : TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
return other == this ? this : BOTTOM;
}
@Override
public int hashCode() {
return 5362412;
}
};
/**
* A type that corresponds to JVM boolean type. Contains two values: true and false
*/
public static final DfBooleanType BOOLEAN = new DfBooleanType() {
@Override
public boolean isSuperType(@NotNull DfType other) {
return other == BOTTOM || other instanceof DfBooleanType;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other instanceof DfBooleanType) return this;
return TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (other == TOP) return this;
if (other instanceof DfBooleanType) return other;
return BOTTOM;
}
@NotNull
@Override
public DfType tryNegate() {
return BOTTOM;
}
@Override
public int hashCode() {
return 345661;
}
@Override
public String toString() {
return "boolean";
}
};
/**
* A true boolean constant
*/
public static final DfBooleanConstantType TRUE = new DfBooleanConstantType(true);
/**
* A false boolean constant
*/
public static final DfBooleanConstantType FALSE = new DfBooleanConstantType(false);
/**
* @param value boolean value
* @return a boolean constant having given value
*/
public static DfBooleanConstantType booleanValue(boolean value) {
return value ? TRUE : FALSE;
}
/**
* A type that corresponds to JVM int type
*/
public static final DfIntType INT = new DfIntRangeType(LongRangeSet.fromType(PsiType.INT));
/**
* Creates a type that represents a subset of int values, clamping values not representable in the JVM int type.
*
* @param range range of values. Values that cannot be represented in JVM int type are removed from this range upon creation.
* @return resulting type. Might be {@link #BOTTOM} if range is empty or all its values are out of the int domain.
*/
@NotNull
public static DfType intRangeClamped(LongRangeSet range) {
return intRange(range.intersect(DfIntRangeType.FULL_RANGE));
}
/**
* Creates a type that represents a subset of int values.
*
* @param range range of values.
* @return resulting type. Might be {@link #BOTTOM} if range is empty.
* @throws IllegalArgumentException if range contains values not representable in the JVM int type.
*/
@NotNull
public static DfType intRange(LongRangeSet range) {
if (range.equals(DfIntRangeType.FULL_RANGE)) return INT;
if (range.isEmpty()) return BOTTOM;
Long value = range.getConstantValue();
if (value != null) {
return intValue(Math.toIntExact(value));
}
return new DfIntRangeType(range);
}
/**
* @param value int value
* @return a int constant type that contains a given value
*/
@NotNull
public static DfIntConstantType intValue(int value) {
return new DfIntConstantType(value);
}
/**
* A type that corresponds to JVM long type
*/
public static final DfLongType LONG = new DfLongRangeType(LongRangeSet.all());
/**
* Creates a type that represents a subset of long values.
*
* @param range range of values.
* @return resulting type. Might be {@link #BOTTOM} if range is empty.
*/
@NotNull
public static DfType longRange(LongRangeSet range) {
if (range.equals(LongRangeSet.all())) return LONG;
if (range.isEmpty()) return BOTTOM;
Long value = range.getConstantValue();
if (value != null) {
return longValue(value);
}
return new DfLongRangeType(range);
}
/**
* @param value long value
* @return a long constant type that contains a given value
*/
@NotNull
public static DfLongConstantType longValue(long value) {
return new DfLongConstantType(value);
}
/**
* A convenience selector method to call {@link #longRange(LongRangeSet)} or {@link #intRangeClamped(LongRangeSet)}
* @param range range
* @param isLong whether int or long type should be created
* @return resulting type.
*/
@NotNull
public static DfType rangeClamped(LongRangeSet range, boolean isLong) {
return isLong ? longRange(range) : intRangeClamped(range);
}
/**
* A type that corresponds to JVM float type
*/
public static final DfFloatType FLOAT = new DfFloatType() {
@Override
public boolean isSuperType(@NotNull DfType other) {
return other == BOTTOM || other instanceof DfFloatType;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other instanceof DfFloatType) return this;
return TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (other == TOP) return this;
if (other instanceof DfFloatType) return other;
return BOTTOM;
}
@NotNull
@Override
public DfType tryNegate() {
return BOTTOM;
}
@Override
public int hashCode() {
return 521441254;
}
@Override
public String toString() {
return "float";
}
};
/**
* @param value float value
* @return a float constant type that contains a given value
*/
public static DfFloatConstantType floatValue(float value) {
return new DfFloatConstantType(value);
}
/**
* A type that corresponds to JVM double type
*/
public static final DfDoubleType DOUBLE = new DfDoubleType() {
@Override
public boolean isSuperType(@NotNull DfType other) {
return other == BOTTOM || other instanceof DfDoubleType;
}
@NotNull
@Override
public DfType join(@NotNull DfType other) {
if (other instanceof DfDoubleType) return this;
return TOP;
}
@NotNull
@Override
public DfType meet(@NotNull DfType other) {
if (other == TOP) return this;
if (other instanceof DfDoubleType) return other;
return BOTTOM;
}
@NotNull
@Override
public DfType tryNegate() {
return BOTTOM;
}
@Override
public int hashCode() {
return 5645123;
}
@Override
public String toString() {
return "double";
}
};
/**
* @param value double value
* @return a double constant type that contains a given value
*/
public static DfDoubleConstantType doubleValue(double value) {
return new DfDoubleConstantType(value);
}
/**
* A reference type that contains only null reference
*/
public static final DfNullConstantType NULL = new DfNullConstantType();
/**
* A reference type that contains any reference except null
*/
public static final DfReferenceType NOT_NULL_OBJECT =
customObject(TypeConstraint.empty(), DfaNullability.NOT_NULL, Mutability.UNKNOWN, null, BOTTOM);
/**
* A reference type that contains any reference or null
*/
public static final DfReferenceType OBJECT_OR_NULL =
customObject(TypeConstraint.empty(), DfaNullability.UNKNOWN, Mutability.UNKNOWN, null, BOTTOM);
/**
* A reference type that contains any reference to a local object
*/
public static final DfReferenceType LOCAL_OBJECT =
new DfGenericObjectType(Collections.emptySet(), TypeConstraint.empty(), DfaNullability.NOT_NULL, Mutability.UNKNOWN,
null, BOTTOM, true);
/**
* Returns a custom constant type
*
* @param constant constant value
* @param type value type
* @return a constant type that contains only given constant
*/
public static DfConstantType<?> constant(@Nullable Object constant, @NotNull DfaPsiType type) {
if (constant == null) {
return NULL;
}
if (constant instanceof Boolean) {
return booleanValue((Boolean)constant);
}
if (constant instanceof Integer || constant instanceof Short || constant instanceof Byte) {
return intValue(((Number)constant).intValue());
}
if (constant instanceof Character) {
return intValue((Character)constant);
}
if (constant instanceof Long) {
return longValue((Long)constant);
}
if (constant instanceof Float) {
return floatValue((Float)constant);
}
if (constant instanceof Double) {
return doubleValue((Double)constant);
}
return new DfReferenceConstantType(constant, type.getPsiType(), type.asConstraint());
}
/**
* @param type type of the object
* @param nullability nullability
* @return a reference type that references given objects of given type (or it subtypes) and has given nullability
*/
public static DfReferenceType typedObject(@NotNull DfaPsiType type, @NotNull Nullability nullability) {
return new DfGenericObjectType(Collections.emptySet(), Objects.requireNonNull(TypeConstraint.empty().withInstanceofValue(type)),
DfaNullability.fromNullability(nullability), Mutability.UNKNOWN, null, BOTTOM, false);
}
/**
* A low-level method to construct a custom reference type. Should not be normally used. Instead prefer construct a type
* using a series of {@link DfType#meet(DfType)} calls like
* <pre>{@code
* constraint.asDfType()
* .meet(mutability.asDfType())
* .meet(LOCAL_OBJECT)
* .meet(specialField.asDfType(sfType))
* }</pre>
*
*
* @param constraint type constraint
* @param nullability nullability, must not be {@link DfaNullability#NULL}
* @param mutability mutability desired mutability
* @param specialField special field
* @param sfType type of special field
* @return a reference type object
*/
public static DfReferenceType customObject(@NotNull TypeConstraint constraint,
@NotNull DfaNullability nullability,
@NotNull Mutability mutability,
@Nullable SpecialField specialField,
@NotNull DfType sfType) {
if (nullability == DfaNullability.NULL) {
throw new IllegalArgumentException();
}
return new DfGenericObjectType(Collections.emptySet(), constraint, nullability, mutability, specialField, sfType, false);
}
}
@@ -1,19 +1,19 @@
// 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.value;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiType;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Represents a value like "variable+var/const", "variable-var/const".
@@ -27,7 +27,8 @@ public class DfaBinOpValue extends DfaValue {
private DfaBinOpValue(@NotNull DfaVariableValue left, @NotNull DfaValue right, boolean isLong, BinOp op) {
super(left.getFactory());
assert (right instanceof DfaConstValue && op != BinOp.MINUS) || (right instanceof DfaVariableValue && op != BinOp.REM);
assert (right.getDfType() instanceof DfConstantType && op != BinOp.MINUS) ||
(right instanceof DfaVariableValue && op != BinOp.REM);
myLeft = left;
myRight = right;
myLong = isLong;
@@ -50,6 +51,12 @@ public class DfaBinOpValue extends DfaValue {
return myLong ? PsiType.LONG : PsiType.INT;
}
@NotNull
@Override
public DfIntegralType getDfType() {
return myLong ? DfTypes.LONG : DfTypes.INT;
}
@Override
public boolean dependsOn(DfaVariableValue other) {
return myLeft.dependsOn(other) || myRight.dependsOn(other);
@@ -62,15 +69,19 @@ public class DfaBinOpValue extends DfaValue {
@Override
public String toString() {
String delimiter = myOp.toString();
if (myOp == BinOp.PLUS && myRight instanceof DfaConstValue) {
Object value = ((DfaConstValue)myRight).getValue();
if (value instanceof Long && (Long)value < 0) {
if (myOp == BinOp.PLUS && myRight instanceof DfaTypeValue) {
long value = extractLong((DfaTypeValue)myRight);
if (value < 0) {
delimiter = "";
}
}
return myLeft + delimiter + myRight;
}
private static long extractLong(DfaTypeValue right) {
return ((Number)((DfConstantType<?>)right.getDfType()).getValue()).longValue();
}
@NotNull
public IElementType getTokenType() {
return myOp.getTokenType();
@@ -85,7 +96,7 @@ public class DfaBinOpValue extends DfaValue {
}
public DfaValue create(DfaValue left, DfaValue right, DfaMemoryState state, boolean isLong, IElementType tokenType) {
if (tokenType == null) return DfaUnknownValue.getInstance();
if (tokenType == null) return myFactory.getUnknown();
BinOp op = BinOp.fromTokenType(tokenType);
if (op != null) {
DfaValue value = doCreate(left, right, state, isLong, op);
@@ -93,8 +104,8 @@ public class DfaBinOpValue extends DfaValue {
return value;
}
}
LongRangeSet leftRange = state.getValueFact(left, DfaFactType.RANGE);
LongRangeSet rightRange = state.getValueFact(right, DfaFactType.RANGE);
LongRangeSet leftRange = DfLongType.extractRange(state.getDfType(left));
LongRangeSet rightRange = DfLongType.extractRange(state.getDfType(right));
if (tokenType.equals(JavaTokenType.ASTERISK)) {
if (LongRangeSet.point(1).equals(leftRange)) return right;
if (LongRangeSet.point(1).equals(rightRange)) return left;
@@ -105,39 +116,35 @@ public class DfaBinOpValue extends DfaValue {
if (tokenType.equals(JavaTokenType.GTGT) || tokenType.equals(JavaTokenType.LTLT) || tokenType.equals(JavaTokenType.GTGTGT)) {
if (LongRangeSet.point(0).equals(rightRange)) return left;
}
if (leftRange != null && rightRange != null) {
LongRangeSet result = leftRange.binOpFromToken(tokenType, rightRange, isLong);
return myFactory.getFactValue(DfaFactType.RANGE, result);
}
return DfaUnknownValue.getInstance();
LongRangeSet result = Objects.requireNonNull(leftRange.binOpFromToken(tokenType, rightRange, isLong));
return myFactory.fromDfType(DfTypes.rangeClamped(result, isLong));
}
@Nullable
private DfaValue doCreate(DfaValue left, DfaValue right, DfaMemoryState state, boolean isLong, BinOp op) {
DfaConstValue leftConst = state.getConstantValue(left);
DfType leftDfType = state.getDfType(left);
Number leftConst = DfConstantType.getConstantOfType(leftDfType, Number.class);
if (leftConst != null) {
left = leftConst;
left = left.getFactory().fromDfType(leftDfType);
}
DfaConstValue rightConst = state.getConstantValue(right);
DfType rightDfType = state.getDfType(right);
Number rightConst = DfConstantType.getConstantOfType(rightDfType, Number.class);
if (rightConst != null) {
right = rightConst;
right = right.getFactory().fromDfType(rightDfType);
}
if (op == BinOp.MINUS && state.areEqual(left, right)) {
return myFactory.getInt(0);
}
if (op == BinOp.REM) {
if (left instanceof DfaVariableValue && right instanceof DfaConstValue) {
Object value = ((DfaConstValue)right).getValue();
if (value instanceof Long) {
long divisor = ((Long)value).longValue();
if (divisor > 1 && divisor <= Long.SIZE) {
return doCreate((DfaVariableValue)left, right, isLong, op);
}
if (left instanceof DfaVariableValue && rightConst != null) {
long divisor = rightConst.longValue();
if (divisor > 1 && divisor <= Long.SIZE) {
return doCreate((DfaVariableValue)left, right, isLong, op);
}
}
return null;
}
if (left instanceof DfaConstValue && (right instanceof DfaVariableValue || right instanceof DfaBinOpValue) && op == BinOp.PLUS) {
if (leftConst != null && (right instanceof DfaVariableValue || right instanceof DfaBinOpValue) && op == BinOp.PLUS) {
return doCreate(right, left, state, isLong, op);
}
if (left instanceof DfaVariableValue) {
@@ -147,31 +154,27 @@ public class DfaBinOpValue extends DfaValue {
}
return doCreate((DfaVariableValue)left, right, isLong, op);
}
if (right instanceof DfaConstValue) {
Long value = ObjectUtils.tryCast(((DfaConstValue)right).getValue(), Long.class);
if (value != null) {
if (value == 0) return left;
if (op == BinOp.MINUS && (isLong || value != Integer.MIN_VALUE)) {
right = myFactory.getConstFactory().createFromValue(-value, PsiType.LONG);
}
return doCreate((DfaVariableValue)left, right, isLong, BinOp.PLUS);
if (rightConst != null) {
long value = rightConst.longValue();
if (value == 0) return left;
if (op == BinOp.MINUS) {
right = myFactory.fromDfType(isLong ? DfTypes.longValue(-value) : DfTypes.intValue(-(int)value));
}
return doCreate((DfaVariableValue)left, right, isLong, BinOp.PLUS);
}
}
if (left instanceof DfaBinOpValue) {
DfaBinOpValue sumValue = (DfaBinOpValue)left;
if (right instanceof DfaConstValue) {
if (sumValue.getRight() instanceof DfaConstValue) {
Long value1 = ObjectUtils.tryCast(((DfaConstValue)sumValue.getRight()).getValue(), Long.class);
Long value2 = ObjectUtils.tryCast(((DfaConstValue)right).getValue(), Long.class);
if (value1 != null && value2 != null) {
if (op == BinOp.MINUS) {
value2 = -value2;
}
long res = value1 + value2;
right = myFactory.getConstFactory().createFromValue(isLong ? res : (int)res, PsiType.LONG);
return create(sumValue.getLeft(), right, state, isLong, JavaTokenType.PLUS);
if (rightConst != null) {
if (sumValue.getRight() instanceof DfaTypeValue) {
long value1 = extractLong((DfaTypeValue)sumValue.getRight());
long value2 = rightConst.longValue();
if (op == BinOp.MINUS) {
value2 = -value2;
}
long res = value1 + value2;
right = myFactory.fromDfType(isLong ? DfTypes.longValue(res) : DfTypes.intValue((int)res));
return create(sumValue.getLeft(), right, state, isLong, JavaTokenType.PLUS);
}
}
if (op == BinOp.MINUS && sumValue.getOperation() == BinOp.PLUS) {
@@ -15,7 +15,10 @@
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.psi.PsiType;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NonNls;
@@ -26,7 +29,7 @@ public class DfaBoxedValue extends DfaValue {
private final @NotNull DfaVariableValue myWrappedValue;
private final @Nullable PsiType myType;
private DfaBoxedValue(@NotNull DfaVariableValue valueToWrap, DfaValueFactory factory, @Nullable PsiType type) {
private DfaBoxedValue(@NotNull DfaVariableValue valueToWrap, @NotNull DfaValueFactory factory, @Nullable PsiType type) {
super(factory);
myWrappedValue = valueToWrap;
myType = type;
@@ -48,6 +51,12 @@ public class DfaBoxedValue extends DfaValue {
return myType;
}
@NotNull
@Override
public DfType getDfType() {
return myFactory.createDfType(myType, Nullability.NOT_NULL);
}
public static class Factory {
private final TIntObjectHashMap<DfaBoxedValue> cachedValues = new TIntObjectHashMap<>();
@@ -57,10 +66,6 @@ public class DfaBoxedValue extends DfaValue {
myFactory = factory;
}
public DfaBoxedValue getBoxedIfExists(DfaVariableValue variable) {
return cachedValues.get(variable.getID());
}
@Nullable
public DfaValue createBoxed(DfaValue valueToWrap, @Nullable PsiType type) {
if (valueToWrap instanceof DfaVariableValue && ((DfaVariableValue)valueToWrap).getDescriptor() == SpecialField.UNBOX) {
@@ -69,12 +74,10 @@ public class DfaBoxedValue extends DfaValue {
return qualifier;
}
}
if (valueToWrap instanceof DfaConstValue || valueToWrap instanceof DfaFactMapValue) {
DfaFactMap facts = DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, type == null ? null : TypeConstraint.exact(myFactory.createDfaType(type)))
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.UNBOX.withValue(valueToWrap));
return myFactory.getFactFactory().createValue(facts);
if (valueToWrap instanceof DfaTypeValue) {
TypeConstraint constraint = type == null ? TypeConstraint.empty() : TypeConstraint.exact(myFactory.createDfaType(type));
DfType dfType = SpecialField.UNBOX.asDfType(valueToWrap.getDfType()).meet(constraint.asDfType());
return myFactory.fromDfType(dfType);
}
if (valueToWrap instanceof DfaVariableValue) {
int id = valueToWrap.getID();
@@ -1,11 +1,11 @@
// Copyright 2000-2019 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.value;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -92,74 +92,53 @@ public abstract class DfaCondition {
@Nullable
static Exact tryEvaluate(DfaValue dfaLeft, RelationType relationType, DfaValue dfaRight) {
if (DfaConstValue.isSentinel(dfaLeft) != DfaConstValue.isSentinel(dfaRight)) {
return fromBoolean(relationType == RelationType.NE);
}
if (dfaRight instanceof DfaFactMapValue && DfaConstValue.isConstant(dfaLeft, null)) {
return tryEvaluate(dfaRight, relationType, dfaLeft);
}
if (dfaLeft instanceof DfaFactMapValue &&
DfaConstValue.isConstant(dfaRight, null) &&
DfaNullability.isNotNull(((DfaFactMapValue)dfaLeft).getFacts())) {
if (relationType == RelationType.EQ) {
return FALSE;
}
if (relationType == RelationType.NE) {
return TRUE;
}
}
if(dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) {
if(relationType == RelationType.IS || relationType == RelationType.IS_NOT) {
DfaFactMap leftFacts = ((DfaFactMapValue)dfaLeft).getFacts();
DfaFactMap rightFacts = ((DfaFactMapValue)dfaRight).getFacts();
boolean isSuperState = rightFacts.isSuperStateOf(leftFacts);
if (isSuperState) {
return fromBoolean(relationType == RelationType.IS);
}
boolean isDistinct = rightFacts.intersect(leftFacts) == null;
if (isDistinct) {
return fromBoolean(relationType == RelationType.IS_NOT);
}
}
DfaValue sentinel = dfaLeft.getFactory().getSentinel();
if ((dfaLeft == sentinel) || (dfaRight == sentinel)) {
return fromBoolean((dfaLeft == sentinel && dfaRight == sentinel) == (relationType == RelationType.EQ));
}
DfType leftType = dfaLeft.getDfType();
DfType rightType = dfaRight.getDfType();
if (relationType == RelationType.EQ || relationType == RelationType.NE) {
SpecialField leftSpecialField = SpecialField.fromQualifier(dfaLeft);
if (leftSpecialField != null) {
SpecialField rightSpecialField = SpecialField.fromQualifier(dfaRight);
DfaValueFactory factory = dfaLeft.getFactory();
if (rightSpecialField == leftSpecialField && factory != null) {
DfaValue leftValue = leftSpecialField.createValue(factory, dfaLeft);
DfaValue rightValue = leftSpecialField.createValue(factory, dfaRight);
Exact
specialFieldComparison = tryEvaluate(leftValue, RelationType.EQ, rightValue);
if (specialFieldComparison == FALSE) {
return fromBoolean(relationType == RelationType.NE);
}
if (leftType instanceof DfConstantType) {
if (rightType instanceof DfConstantType) {
return fromBoolean(leftType.equals(rightType) ^
!DfaUtil.isNaN(((DfConstantType<?>)leftType).getValue()) ^
relationType == RelationType.EQ);
}
if (!rightType.isSuperType(leftType)) {
return fromBoolean(relationType == RelationType.NE);
}
}
else if (rightType instanceof DfConstantType) {
if (!leftType.isSuperType(rightType)) {
return fromBoolean(relationType == RelationType.NE);
}
}
else if (leftType.meet(rightType) == DfTypes.BOTTOM) {
return fromBoolean(relationType == RelationType.NE);
}
}
LongRangeSet leftRange = LongRangeSet.fromDfaValue(dfaLeft);
LongRangeSet rightRange = LongRangeSet.fromDfaValue(dfaRight);
if (leftRange != null && rightRange != null) {
LongRangeSet constraint = rightRange.fromRelation(relationType);
if (constraint != null && !constraint.intersects(leftRange)) {
if(relationType == RelationType.IS || relationType == RelationType.IS_NOT) {
boolean isSuperState = rightType.isSuperType(leftType);
if (isSuperState) {
return fromBoolean(relationType == RelationType.IS);
}
boolean isDistinct = rightType.meet(leftType) == DfTypes.BOTTOM;
if (isDistinct) {
return fromBoolean(relationType == RelationType.IS_NOT);
}
}
if (leftType instanceof DfIntegralType && rightType instanceof DfIntegralType) {
if (((DfIntegralType)leftType).meetRelation(relationType, rightType) == DfTypes.BOTTOM) {
return FALSE;
}
LongRangeSet revConstraint = rightRange.fromRelation(relationType.getNegated());
if (revConstraint != null && !revConstraint.intersects(leftRange)) {
if (((DfIntegralType)leftType).meetRelation(relationType.getNegated(), rightType) == DfTypes.BOTTOM) {
return TRUE;
}
}
if(dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue &&
(relationType == RelationType.EQ || relationType == RelationType.NE)) {
return fromBoolean(dfaLeft == dfaRight ^
!DfaUtil.isNaN(((DfaConstValue)dfaLeft).getValue()) ^
relationType == RelationType.EQ);
}
return null;
}
}
@@ -1,201 +0,0 @@
// Copyright 2000-2019 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.value;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class DfaConstValue extends DfaValue {
private static final Throwable ourThrowable = new Throwable();
private static final Object SENTINEL = ObjectUtils.sentinel("SENTINEL");
public static class Factory {
private final DfaConstValue dfaNull;
private final DfaConstValue dfaFalse;
private final DfaConstValue dfaTrue;
private final DfaConstValue dfaFail;
private final DfaConstValue dfaSentinel;
private final DfaValueFactory myFactory;
private final Map<Object, DfaConstValue> myValues = new HashMap<>();
Factory(DfaValueFactory factory) {
myFactory = factory;
dfaNull = new DfaConstValue(null, PsiType.NULL, factory);
dfaFalse = new DfaConstValue(Boolean.FALSE, PsiType.BOOLEAN, factory);
dfaTrue = new DfaConstValue(Boolean.TRUE, PsiType.BOOLEAN, factory);
dfaFail = new DfaConstValue(ourThrowable, PsiType.VOID, factory);
dfaSentinel = new DfaConstValue(SENTINEL, PsiType.VOID, factory);
}
@Nullable
public DfaValue create(PsiLiteralExpression expr) {
PsiType type = expr.getType();
if (type == null) return null;
if (PsiType.NULL.equals(type)) return dfaNull;
Object value = expr.getValue();
if (value == null) return null;
return createFromValue(value, type);
}
@Nullable
public DfaValue create(PsiVariable variable) {
if (DfaUtil.ignoreInitializer(variable)) return null;
Object value = variable.computeConstantValue();
PsiType type = variable.getType();
if (value == null) {
Boolean boo = computeJavaLangBooleanFieldReference(variable);
if (boo != null) {
DfaConstValue unboxed = createFromValue(boo, PsiType.BOOLEAN);
return myFactory.getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable));
}
PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(variable.getInitializer());
if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) {
return dfaNull;
}
if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) {
return createFromValue(variable, type);
}
return null;
}
return createFromValue(value, type);
}
@Nullable
private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) {
if (!(variable instanceof PsiField)) return null;
PsiClass psiClass = ((PsiField)variable).getContainingClass();
if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) return null;
@NonNls String name = variable.getName();
return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null;
}
/**
* Creates a constant which corresponds to the default value of given type
*
* @param type type to get the default value for
* @return a constant (e.g. 0 from int, false for boolean, null for reference type).
*/
@NotNull
public DfaConstValue createDefault(@NotNull PsiType type) {
return createFromValue(PsiTypesUtil.getDefaultValue(type), type);
}
@NotNull
public DfaConstValue createFromValue(Object value, @NotNull PsiType type) {
if (Boolean.TRUE.equals(value)) return dfaTrue;
if (Boolean.FALSE.equals(value)) return dfaFalse;
if (value == null) return dfaNull;
if (TypeConversionUtil.isNumericType(type) && !TypeConversionUtil.isFloatOrDoubleType(type)) {
type = PsiType.LONG;
Object numeric = TypeConversionUtil.computeCastTo(value, type);
if (numeric != null) {
value = numeric;
}
}
if (value instanceof Float) {
value = ((Float)value).doubleValue();
}
DfaConstValue instance = myValues.get(value);
if (instance == null) {
instance = new DfaConstValue(value, type, myFactory);
myValues.put(value, instance);
}
return instance;
}
public DfaConstValue getContractFail() {
return dfaFail;
}
public DfaConstValue getFalse() {
return dfaFalse;
}
public DfaConstValue getTrue() {
return dfaTrue;
}
public DfaConstValue getNull() {
return dfaNull;
}
/**
* Sentinel value is special value used internally by dataflow. It cannot be stored to any variable, and equals to itself only
* @return sentinel value
*/
public DfaConstValue getSentinel() {
return dfaSentinel;
}
}
private final Object myValue;
@NotNull private final PsiType myType;
private DfaConstValue(Object value, @NotNull PsiType type, DfaValueFactory factory) {
super(factory);
myValue = value;
myType = type;
}
public String toString() {
return renderValue(myValue);
}
public static String renderValue(Object value) {
if (value == null) return "null";
if (value instanceof String) return '"' + StringUtil.escapeStringCharacters((String)value) + '"';
if (value instanceof PsiField) {
PsiField field = (PsiField)value;
PsiClass containingClass = field.getContainingClass();
return containingClass == null ? field.getName() : containingClass.getName() + "." + field.getName();
}
return value.toString();
}
@Override
@NotNull
public PsiType getType() {
return myType;
}
@Nullable
public Object getValue() {
return myValue;
}
/**
* Checks whether given value is a special value representing method failure, according to its contract
*
* @param value value to check
* @return true if specified value represents method failure
*/
@Contract("null -> false")
public static boolean isContractFail(DfaValue value) {
return isConstant(value, ourThrowable);
}
@Contract("null -> false")
static boolean isSentinel(DfaValue value) {
return isConstant(value, SENTINEL);
}
@Contract("null, _ -> false")
public static boolean isConstant(DfaValue value, Object constVal) {
return value instanceof DfaConstValue && Objects.equals(((DfaConstValue)value).getValue(), constVal);
}
}
@@ -6,6 +6,9 @@ import com.intellij.codeInsight.ConcurrencyAnnotationsManager;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.source.PsiImmediateClassType;
@@ -46,7 +49,7 @@ public class DfaExpressionFactory {
if (expression instanceof PsiArrayAccessExpression) {
PsiExpression arrayExpression = ((PsiArrayAccessExpression)expression).getArrayExpression();
DfaVariableValue qualifier = getQualifierVariable(arrayExpression);
DfaValue qualifier = getQualifierValue(arrayExpression);
if (qualifier != null) {
Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression)expression).getIndexExpression());
if (index instanceof Integer) {
@@ -58,7 +61,7 @@ public class DfaExpressionFactory {
}
PsiType type = expression.getType();
if (type != null) {
return myFactory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, null));
return myFactory.getObjectType(type, DfaPsiUtil.getElementNullability(type, null));
}
}
@@ -71,18 +74,18 @@ public class DfaExpressionFactory {
}
if (expression instanceof PsiLiteralExpression) {
return myFactory.createLiteralValue((PsiLiteralExpression)expression);
return myFactory.getConstantFromLiteral((PsiLiteralExpression)expression);
}
if (expression instanceof PsiNewExpression || expression instanceof PsiLambdaExpression) {
return myFactory.createTypeValue(expression.getType(), Nullability.NOT_NULL);
return myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL);
}
final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false);
if (value != null) {
PsiType type = expression.getType();
if (type != null) {
return myFactory.getConstFactory().createFromValue(value, type);
return myFactory.getConstant(value, type);
}
}
@@ -96,7 +99,7 @@ public class DfaExpressionFactory {
target = ClassUtils.getContainingClass(expression);
}
return target == null
? myFactory.createTypeValue(expression.getType(), Nullability.NOT_NULL)
? myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL)
: myFactory.getVarFactory().createThisValue(target);
}
return null;
@@ -106,8 +109,8 @@ public class DfaExpressionFactory {
PsiElement target = refExpr.resolve();
if (target instanceof PsiVariable) {
PsiVariable variable = (PsiVariable)target;
if (variable.hasModifierProperty(PsiModifier.FINAL) && !PsiUtil.isAccessedForWriting(refExpr)) {
DfaValue constValue = myFactory.getConstFactory().create(variable);
if (!PsiUtil.isAccessedForWriting(refExpr)) {
DfaValue constValue = myFactory.getConstantFromVariable(variable);
if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) return constValue;
}
}
@@ -116,7 +119,7 @@ public class DfaExpressionFactory {
return null;
}
DfaVariableValue qualifier = getQualifierOrThisVariable(refExpr);
DfaValue qualifier = getQualifierOrThisValue(refExpr);
return var.createValue(myFactory, qualifier, true);
}
@@ -128,7 +131,7 @@ public class DfaExpressionFactory {
* @return a qualifier variable or null if qualifier is unnecessary or cannot be represented as a variable
*/
@Nullable
public DfaVariableValue getQualifierOrThisVariable(PsiReferenceExpression refExpr) {
public DfaValue getQualifierOrThisValue(PsiReferenceExpression refExpr) {
PsiExpression qualifierExpression = refExpr.getQualifierExpression();
if (qualifierExpression == null) {
PsiElement element = refExpr.resolve();
@@ -148,23 +151,18 @@ public class DfaExpressionFactory {
}
}
}
return getQualifierVariable(qualifierExpression);
return getQualifierValue(qualifierExpression);
}
@Nullable
private DfaVariableValue getQualifierVariable(PsiExpression qualifierExpression) {
private DfaValue getQualifierValue(PsiExpression qualifierExpression) {
DfaValue qualifierValue = getExpressionDfaValue(qualifierExpression);
DfaVariableValue qualifier = null;
if (qualifierValue instanceof DfaVariableValue) {
qualifier = (DfaVariableValue)qualifierValue;
if (qualifierValue == null) return null;
PsiVariable constVar = DfConstantType.getConstantOfType(qualifierValue.getDfType(), PsiVariable.class);
if (constVar != null) {
return myFactory.getVarFactory().createVariableValue(constVar);
}
else if (qualifierValue instanceof DfaConstValue) {
Object constValue = ((DfaConstValue)qualifierValue).getValue();
if (constValue instanceof PsiVariable) {
qualifier = myFactory.getVarFactory().createVariableValue((PsiVariable)constValue);
}
}
return qualifier;
return qualifierValue;
}
private static boolean maybeUninitializedConstant(DfaValue constValue,
@@ -173,8 +171,8 @@ public class DfaExpressionFactory {
// If static final field is referred from the same or inner/nested class,
// we consider that it might be uninitialized yet as some class initializers may call its methods or
// even instantiate objects of this class and call their methods
if(!(constValue instanceof DfaConstValue) || ((DfaConstValue)constValue).getValue() != var) return false;
if(!(var instanceof PsiField) || var instanceof PsiEnumConstant) return false;
if (!DfConstantType.isConst(constValue.getDfType(), var)) return false;
if (!(var instanceof PsiField) || var instanceof PsiEnumConstant) return false;
return PsiTreeUtil.getTopmostParentOfType(refExpr, PsiClass.class) == PsiTreeUtil.getTopmostParentOfType(var, PsiClass.class);
}
@@ -215,7 +213,7 @@ public class DfaExpressionFactory {
@NotNull
private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression, @Nullable PsiType targetType) {
if (expression == null) return DfaUnknownValue.getInstance();
if (expression == null) return myFactory.getUnknown();
DfaValue value = getExpressionDfaValue(expression);
if (value != null) {
return DfaUtil.boxUnbox(value, targetType);
@@ -225,40 +223,39 @@ public class DfaExpressionFactory {
getAdvancedExpressionDfaValue(((PsiConditionalExpression)expression).getElseExpression(), targetType));
}
PsiType type = expression.getType();
if (type instanceof PsiPrimitiveType) return DfaUnknownValue.getInstance();
DfaValue typeValue = myFactory.createTypeValue(type, NullabilityUtil.getExpressionNullability(expression));
DfType dfType = myFactory.createDfType(type, NullabilityUtil.getExpressionNullability(expression));
if (expression instanceof PsiArrayInitializerExpression) {
int length = ((PsiArrayInitializerExpression)expression).getInitializers().length;
return myFactory.withFact(typeValue, DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.ARRAY_LENGTH.withValue(myFactory.getInt(length)));
dfType = dfType.meet(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length)));
}
return DfaUtil.boxUnbox(typeValue, targetType);
return DfaUtil.boxUnbox(myFactory.fromDfType(dfType), targetType);
}
@NotNull
public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) {
if (!(array instanceof DfaVariableValue)) return DfaUnknownValue.getInstance();
if (indexSet.isEmpty()) return DfaUnknownValue.getInstance();
if (!(array instanceof DfaVariableValue)) return myFactory.getUnknown();
if (indexSet.isEmpty()) return myFactory.getUnknown();
long min = indexSet.min();
long max = indexSet.max();
if (min == max && min >= 0 && min < Integer.MAX_VALUE) {
DfaValue value = getArrayElementValue(array, (int)min);
return value == null ? DfaUnknownValue.getInstance() : value;
return value == null ? myFactory.getUnknown() : value;
}
DfaVariableValue arrayDfaVar = (DfaVariableValue)array;
PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable();
if (!(arrayPsiVar instanceof PsiVariable)) return DfaUnknownValue.getInstance();
if (!(arrayPsiVar instanceof PsiVariable)) return myFactory.getUnknown();
PsiType arrayType = ((PsiVariable)arrayPsiVar).getType();
PsiType targetType = arrayType instanceof PsiArrayType ? ((PsiArrayType)arrayType).getComponentType() : null;
PsiExpression[] elements = ExpressionUtils.getConstantArrayElements((PsiVariable)arrayPsiVar);
if (elements == null || elements.length == 0) return DfaUnknownValue.getInstance();
if (elements == null || elements.length == 0) return myFactory.getUnknown();
indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1));
if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) return DfaUnknownValue.getInstance();
if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) return myFactory.getUnknown();
return LongStreamEx.of(indexSet.stream())
.mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int)idx], targetType))
.prefix(DfaValue::unite)
.takeWhileInclusive(value -> value != DfaUnknownValue.getInstance())
.takeWhileInclusive(value -> !DfaTypeValue.isUnknown(value))
.reduce((a, b) -> b)
.orElse(DfaUnknownValue.getInstance());
.orElseGet(myFactory::getUnknown);
}
@Contract("null, _ -> null")
@@ -364,11 +361,11 @@ public class DfaExpressionFactory {
@Override
public DfaValue createValue(@NotNull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) {
if (myVariable.getType().equalsToText(CommonClassNames.JAVA_LANG_VOID)) {
return factory.getConstFactory().getNull();
return factory.getNull();
}
if (myVariable.hasModifierProperty(PsiModifier.VOLATILE)) {
PsiType type = getType(ObjectUtils.tryCast(qualifier, DfaVariableValue.class));
return factory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, myVariable));
return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable));
}
if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter ||
(myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC) &&
@@ -1,73 +0,0 @@
// Copyright 2000-2017 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.value;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class DfaFactMapValue extends DfaValue {
private final DfaFactMap myFacts;
DfaFactMapValue(DfaValueFactory factory, DfaFactMap facts) {
super(factory);
myFacts = facts;
}
public <T> DfaValue withFact(@NotNull DfaFactType<T> factType, @Nullable T value) {
DfaFactMap newFacts = myFacts.with(factType, value);
return newFacts == myFacts ? this : getFactory().getFactFactory().createValue(newFacts);
}
public DfaFactMap getFacts() {
return myFacts;
}
@Nullable
public <T> T get(@NotNull DfaFactType<T> factType) {
return myFacts.get(factType);
}
@Override
public String toString() {
return myFacts.toString();
}
public static class Factory {
private final DfaValueFactory myFactory;
private final Map<DfaFactMap, DfaFactMapValue> myValues = new HashMap<>();
Factory(DfaValueFactory factory) {
myFactory = factory;
}
public <T> DfaValue createValue(@NotNull DfaFactType<T> factType, @Nullable T value) {
if (factType == DfaFactType.RANGE && value instanceof LongRangeSet) {
if (((LongRangeSet)value).isEmpty()) {
throw new IllegalArgumentException("Empty range is disallowed (a bottom value)");
}
Long constantValue = ((LongRangeSet)value).getConstantValue();
if (constantValue != null) {
return myFactory.getConstFactory().createFromValue(constantValue, PsiType.LONG);
}
}
return createValue(DfaFactMap.EMPTY.with(factType, value));
}
public DfaValue createValue(DfaFactMap facts) {
if (facts == DfaFactMap.EMPTY) {
return DfaUnknownValue.getInstance();
}
if (facts.get(DfaFactType.NULLABILITY) == DfaNullability.NULL) {
return myFactory.getConstFactory().getNull();
}
return myValues.computeIfAbsent(facts, f -> new DfaFactMapValue(myFactory, f));
}
}
}
@@ -27,7 +27,7 @@ public class DfaInstanceofValue extends DfaValue {
private final @NotNull PsiType myCastType;
private final @NotNull DfaCondition myRelation;
public DfaInstanceofValue(DfaValueFactory factory,
public DfaInstanceofValue(@NotNull DfaValueFactory factory,
@NotNull PsiExpression expression,
@NotNull PsiType castType,
@NotNull DfaCondition relation) {
@@ -16,8 +16,9 @@
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -53,7 +54,7 @@ public final class DfaRelation extends DfaCondition {
public static DfaRelation createRelation(@NotNull DfaValue dfaLeft, @NotNull RelationType relationType, @NotNull DfaValue dfaRight) {
if ((relationType == RelationType.IS || relationType == RelationType.IS_NOT) &&
dfaRight instanceof DfaFactMapValue && !(dfaLeft instanceof DfaFactMapValue)) {
dfaRight instanceof DfaTypeValue && !(dfaLeft instanceof DfaTypeValue)) {
return new DfaRelation(dfaLeft, dfaRight, relationType);
}
if (dfaLeft instanceof DfaVariableValue || dfaLeft instanceof DfaBoxedValue || dfaLeft instanceof DfaBinOpValue
@@ -65,27 +66,27 @@ public final class DfaRelation extends DfaCondition {
}
return new DfaRelation(dfaLeft, dfaRight, relationType);
}
if (dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaConstValue) {
return createConstBasedRelation((DfaFactMapValue)dfaLeft, relationType, (DfaConstValue)dfaRight);
if (dfaLeft instanceof DfaTypeValue && dfaRight.getDfType() instanceof DfConstantType) {
return createConstBasedRelation((DfaTypeValue)dfaLeft, relationType, dfaRight);
}
else if (dfaRight instanceof DfaFactMapValue && dfaLeft instanceof DfaConstValue) {
return createConstBasedRelation((DfaFactMapValue)dfaRight, relationType, (DfaConstValue)dfaLeft);
else if (dfaRight instanceof DfaTypeValue && dfaLeft.getDfType() instanceof DfConstantType) {
return createConstBasedRelation((DfaTypeValue)dfaRight, relationType, dfaLeft);
}
if (dfaLeft instanceof DfaInstanceofValue && dfaRight instanceof DfaConstValue) {
if (dfaLeft instanceof DfaInstanceofValue && dfaRight.getDfType() instanceof DfConstantType) {
return new DfaRelation(dfaLeft, dfaRight, relationType);
}
if (dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaInstanceofValue) {
if (dfaLeft.getDfType() instanceof DfConstantType && dfaRight instanceof DfaInstanceofValue) {
return new DfaRelation(dfaRight, dfaLeft, relationType);
}
return null;
}
@NotNull
private static DfaRelation createConstBasedRelation(DfaFactMapValue dfaLeft, RelationType relationType, DfaConstValue dfaRight) {
if (dfaRight.getValue() == null && DfaNullability.isNullable(dfaLeft.getFacts())) {
return new DfaRelation(dfaLeft.getFactory().getFactValue(DfaFactType.NULLABILITY, DfaNullability.NULLABLE), dfaRight, relationType);
private static DfaRelation createConstBasedRelation(DfaTypeValue dfaLeft, RelationType relationType, DfaValue dfaRight) {
if (dfaRight.getDfType() == DfTypes.NULL && DfaNullability.fromDfType(dfaLeft.getDfType()) == DfaNullability.NULLABLE) {
return new DfaRelation(dfaLeft.getFactory().fromDfType(DfaNullability.NULLABLE.asDfType()), dfaRight, relationType);
}
return new DfaRelation(DfaUnknownValue.getInstance(), dfaRight, relationType);
return new DfaRelation(dfaLeft.getFactory().getUnknown(), dfaRight, relationType);
}
public boolean isEquality() {
@@ -0,0 +1,79 @@
// Copyright 2000-2019 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.value;
import com.intellij.codeInspection.dataFlow.types.DfPrimitiveType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class DfaTypeValue extends DfaValue {
private final @NotNull DfType myType;
DfaTypeValue(@NotNull DfaValueFactory factory, @NotNull DfType type) {
super(factory);
myType = type;
}
@NotNull
@Override
public DfType getDfType() {
return myType;
}
@Nullable
@Override
public PsiType getType() {
return toPsiType(myType);
}
public static boolean isUnknown(DfaValue value) {
return value instanceof DfaTypeValue && value.getDfType() == DfTypes.TOP;
}
@Override
public String toString() {
return myType.toString();
}
/**
* Checks whether given value is a special value representing method failure, according to its contract
*
* @param value value to check
* @return true if specified value represents method failure
*/
@Contract("null -> false")
public static boolean isContractFail(DfaValue value) {
return value instanceof DfaTypeValue && value.getDfType() == DfTypes.FAIL;
}
static class Factory {
private final DfaValueFactory myFactory;
private final Map<DfType, DfaTypeValue> myValues = new HashMap<>();
Factory(DfaValueFactory factory) {
myFactory = factory;
}
@NotNull DfaTypeValue create(@NotNull DfType type) {
return myValues.computeIfAbsent(type, t -> new DfaTypeValue(myFactory, t));
}
}
@Nullable
public static PsiType toPsiType(DfType dfType) {
if (dfType instanceof DfPrimitiveType) {
return ((DfPrimitiveType)dfType).getPsiType();
}
if (dfType instanceof DfReferenceType) {
return ((DfReferenceType)dfType).getConstraint().getPsiType();
}
return null;
}
}
@@ -1,53 +0,0 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.dataFlow.value;
import org.jetbrains.annotations.NotNull;
public class DfaUnknownValue extends DfaValue {
private static class DfaUnknownValueHolder {
private static final DfaUnknownValue myInstance = new DfaUnknownValue();
}
@NotNull
public static DfaUnknownValue getInstance() {
return DfaUnknownValueHolder.myInstance;
}
private DfaUnknownValue() {
super(null);
}
@SuppressWarnings({"HardCodedStringLiteral"})
public String toString() {
return "<unknown>";
}
public boolean equals(Object obj) {
return obj == this;
}
public int hashCode() {
return 0;
}
@Override
public int getID() {
return 0;
}
}
@@ -15,21 +15,24 @@
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class DfaValue {
private final int myID;
@NotNull
protected final DfaValueFactory myFactory;
protected DfaValue(final DfaValueFactory factory) {
protected DfaValue(@NotNull final DfaValueFactory factory) {
myFactory = factory;
myID = factory == null ? 0 : factory.registerValue(this);
myID = factory.registerValue(this);
}
@NotNull
public DfaValueFactory getFactory() {
return myFactory;
}
@@ -46,6 +49,14 @@ public abstract class DfaValue {
return null;
}
/**
* @return a DfType this value belongs under any possible memory state
*/
@NotNull
public DfType getDfType() {
return DfTypes.TOP;
}
/**
* Produces a value which describes a union of this value and other value
*
@@ -54,8 +65,7 @@ public abstract class DfaValue {
*/
public DfaValue unite(DfaValue other) {
if (this == other) return this;
if (this == DfaUnknownValue.getInstance() || other == DfaUnknownValue.getInstance()) return DfaUnknownValue.getInstance();
return myFactory.getFactFactory().createValue(DfaFactMap.fromDfaValue(this).unite(DfaFactMap.fromDfaValue(other)));
return myFactory.fromDfType(getDfType().join(other.getDfType()));
}
/**
@@ -6,15 +6,20 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.util.Pair;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFieldImpl;
import com.intellij.psi.util.*;
import com.intellij.util.containers.FList;
import com.intellij.util.containers.FactoryMap;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,11 +46,10 @@ public class DfaValueFactory {
myUnknownMembersAreNullable = unknownMembersAreNullable;
myValues.add(null);
myVarFactory = new DfaVariableValue.Factory(this);
myConstFactory = new DfaConstValue.Factory(this);
myBoxedFactory = new DfaBoxedValue.Factory(this);
myExpressionFactory = new DfaExpressionFactory(this);
myFactFactory = new DfaFactMapValue.Factory(this);
myBinOpFactory = new DfaBinOpValue.Factory(this);
myTypeValueFactory = new DfaTypeValue.Factory(this);
}
public boolean canTrustFieldInitializer(PsiField field) {
@@ -68,36 +72,26 @@ public class DfaValueFactory {
}
@NotNull
public DfaValue createTypeValue(@Nullable PsiType type, @NotNull Nullability nullability) {
if (type == null) return DfaUnknownValue.getInstance();
public DfaTypeValue getObjectType(@Nullable PsiType type, @NotNull Nullability nullability) {
return fromDfType(createDfType(type, nullability));
}
@NotNull
public DfType createDfType(@Nullable PsiType type, @NotNull Nullability nullability) {
if (type == null) return DfTypes.TOP;
if (type instanceof PsiPrimitiveType) {
LongRangeSet range = LongRangeSet.fromType(type);
if (range != null) {
return getFactFactory().createValue(DfaFactType.RANGE, range);
if (type.equals(PsiType.VOID)) return DfTypes.TOP;
if (type.equals(PsiType.BOOLEAN)) return DfTypes.BOOLEAN;
if (type.equals(PsiType.INT)) return DfTypes.INT;
if (type.equals(PsiType.CHAR) || type.equals(PsiType.SHORT) || type.equals(PsiType.BYTE)){
return DfTypes.intRange(Objects.requireNonNull(LongRangeSet.fromType(type)));
}
if (type.equals(PsiType.LONG)) return DfTypes.LONG;
if (type.equals(PsiType.DOUBLE)) return DfTypes.DOUBLE;
if (type.equals(PsiType.FLOAT)) return DfTypes.FLOAT;
if (type.equals(PsiType.NULL)) return DfTypes.NULL;
}
DfaFactMap facts = DfaFactMap.EMPTY.with(DfaFactType.TYPE_CONSTRAINT, createDfaType(type).asConstraint())
.with(DfaFactType.NULLABILITY, DfaNullability.fromNullability(nullability));
return getFactFactory().createValue(facts);
}
@NotNull
public DfaValue createExactTypeValue(@Nullable PsiType type) {
if (type == null) return DfaUnknownValue.getInstance();
DfaFactMap facts = DfaFactMap.EMPTY.with(DfaFactType.TYPE_CONSTRAINT, TypeConstraint.exact(createDfaType(type)))
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
return getFactFactory().createValue(facts);
}
@NotNull
public <T> DfaValue withFact(@NotNull DfaValue value, @NotNull DfaFactType<T> factType, @Nullable T factValue) {
if(value instanceof DfaUnknownValue) {
return getFactFactory().createValue(DfaFactMap.EMPTY.with(factType, factValue));
}
if(value instanceof DfaFactMapValue) {
return ((DfaFactMapValue)value).withFact(factType, factValue);
}
return DfaUnknownValue.getInstance();
return DfTypes.typedObject(createDfaType(type), nullability);
}
@NotNull
@@ -131,21 +125,133 @@ public class DfaValueFactory {
}
@NotNull
public DfaConstValue getInt(int value) {
return getConstFactory().createFromValue(value, PsiType.INT);
public DfaTypeValue getInt(int value) {
return fromDfType(DfTypes.intValue(value));
}
@NotNull
public DfaTypeValue getUnknown() {
return fromDfType(DfTypes.TOP);
}
/**
* @return a special sentinel value that never equals to anything else (even unknown value) and
* sometimes pushed on the stack as control flow implementation detail.
* It's never assigned to the variable or merged with any other value.
*/
@NotNull
public DfaValue getSentinel() {
return mySentinelValue;
}
@NotNull
public DfaTypeValue getBoolean(boolean value) {
return fromDfType(DfTypes.booleanValue(value));
}
/**
* @return a null value
*/
@NotNull
public DfaTypeValue getNull() {
return fromDfType(DfTypes.NULL);
}
/**
* @return a special value that indicates a failing contract.
* @see DfTypes#FAIL
*/
@NotNull
public DfaTypeValue getContractFail() {
return fromDfType(DfTypes.FAIL);
}
/**
* Creates a constant of given type and given value. Constants are always unique
* (two different constants are not equal to each other).
*
* The following types of the objects are supported:
* <ul>
* <li>Integer/Long/Double/Float/Boolean (will be unboxed)</li>
* <li>Character/Byte/Short (will be unboxed and widened to int)</li>
* <li>String</li>
* <li>{@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)</li>
* <li>{@link PsiField} (a final field that contains a unique value, type must be a type of that field)</li>
* <li>{@link PsiType} (java.lang.Class object value, type must be java.lang.Class)</li>
* </ul>
*
* @param type type of the constant
* @return a DfaTypeValue whose type is DfConstantType that corresponds to given constant.
*/
public DfaTypeValue getConstant(Object value, @NotNull PsiType type) {
return fromDfType(DfTypes.constant(value, createDfaType(type)));
}
/**
* @param expr literal to create a constant type value from
* @return a DfaTypeValue; null if the literal is malformed
*/
@Nullable
public DfaTypeValue getConstantFromLiteral(PsiLiteralExpression expr) {
PsiType type = expr.getType();
if (type == null) return null;
if (PsiType.NULL.equals(type)) return getNull();
Object value = expr.getValue();
if (value == null) return null;
return getConstant(value, type);
}
/**
* @param variable variable to create a constant based on its value
* @return a value that represents a constant created from variable; null if variable cannot be represented as a constant
*/
@Nullable
public DfaValue getConstantFromVariable(PsiVariable variable) {
if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) return null;
Object value = variable.computeConstantValue();
PsiType type = variable.getType();
if (value == null) {
Boolean boo = computeJavaLangBooleanFieldReference(variable);
if (boo != null) {
DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN);
return getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable));
}
PsiExpression initializer =
variable instanceof PsiFieldImpl ? ((PsiFieldImpl)variable).getDetachedInitializer() : variable.getInitializer();
initializer = PsiUtil.skipParenthesizedExprDown(initializer);
if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) {
return getNull();
}
if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) {
return getConstant(variable, type);
}
return null;
}
return getConstant(value, type);
}
@Nullable
public DfaValue createLiteralValue(PsiLiteralExpression literal) {
return getConstFactory().create(literal);
private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) {
if (!(variable instanceof PsiField)) return null;
PsiClass psiClass = ((PsiField)variable).getContainingClass();
if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) return null;
@NonNls String name = variable.getName();
return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null;
}
/**
* Creates a constant that corresponds to the default value of given type
*
* @param type type to get the default value for
* @return a constant (e.g. 0 from int, false for boolean, null for reference type).
*/
@NotNull
public DfaTypeValue getDefaultValue(@NotNull PsiType type) {
return fromDfType(DfTypes.constant(PsiTypesUtil.getDefaultValue(type), createDfaType(type)));
}
public DfaConstValue getBoolean(boolean value) {
return value ? getConstFactory().getTrue() : getConstFactory().getFalse();
}
public <T> DfaValue getFactValue(@NotNull DfaFactType<T> factType, @Nullable T value) {
return getFactFactory().createValue(factType, value);
@NotNull
public DfaTypeValue fromDfType(@NotNull DfType dfType) {
return myTypeValueFactory.create(dfType);
}
public Collection<DfaValue> getValues() {
@@ -161,31 +267,27 @@ public class DfaValueFactory {
FactoryMap.create(p -> new DfaControlTransferValue(this, p.first, p.second));
private final DfaVariableValue.Factory myVarFactory;
private final DfaConstValue.Factory myConstFactory;
private final DfaBoxedValue.Factory myBoxedFactory;
private final DfaBinOpValue.Factory myBinOpFactory;
private final DfaExpressionFactory myExpressionFactory;
private final DfaFactMapValue.Factory myFactFactory;
private final DfaTypeValue.Factory myTypeValueFactory;
private final DfaValue mySentinelValue = new DfaValue(this) {
@Override
public String toString() {
return "SENTINEL";
}
};
@NotNull
public DfaVariableValue.Factory getVarFactory() {
return myVarFactory;
}
@NotNull
public DfaConstValue.Factory getConstFactory() {
return myConstFactory;
}
@NotNull
public DfaBoxedValue.Factory getBoxedFactory() {
return myBoxedFactory;
}
@NotNull
public DfaFactMapValue.Factory getFactFactory() {
return myFactFactory;
}
@NotNull
public DfaExpressionFactory getExpressionFactory() { return myExpressionFactory;}
@@ -200,12 +302,12 @@ public class DfaValueFactory {
for (PsiExpression expression : expressions) {
DfaValue expressionValue = createValue(expression);
if (expressionValue == null) {
expressionValue = createTypeValue(expression.getType(), NullabilityUtil.getExpressionNullability(expression));
expressionValue = getObjectType(expression.getType(), NullabilityUtil.getExpressionNullability(expression));
}
loopElement = loopElement == null ? expressionValue : loopElement.unite(expressionValue);
if (loopElement == DfaUnknownValue.getInstance()) break;
if (DfaTypeValue.isUnknown(loopElement)) break;
}
return loopElement == null ? DfaUnknownValue.getInstance() : DfaUtil.boxUnbox(loopElement, targetType);
return loopElement == null ? getUnknown() : DfaUtil.boxUnbox(loopElement, targetType);
}
private static class ClassInitializationInfo {
@@ -17,11 +17,17 @@
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.NullabilityUtil;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -85,10 +91,10 @@ public final class DfaVariableValue extends DfaValue {
@NotNull private final VariableDescriptor myDescriptor;
private final PsiType myVarType;
@Nullable private final DfaVariableValue myQualifier;
private DfaFactMap myInherentFacts;
private DfType myInherentType;
private final List<DfaVariableValue> myDependents = new SmartList<>();
private DfaVariableValue(@NotNull VariableDescriptor descriptor, DfaValueFactory factory, @Nullable DfaVariableValue qualifier) {
private DfaVariableValue(@NotNull VariableDescriptor descriptor, @NotNull DfaValueFactory factory, @Nullable DfaVariableValue qualifier) {
super(factory);
myDescriptor = descriptor;
myQualifier = qualifier;
@@ -149,17 +155,41 @@ public final class DfaVariableValue extends DfaValue {
return myQualifier;
}
public DfaFactMap getInherentFacts() {
if(myInherentFacts == null) {
myInherentFacts = DfaFactMap.calcFromVariable(this);
public DfType getInherentType() {
if(myInherentType == null) {
myInherentType = calcInherentType();
}
return myInherentType;
}
return myInherentFacts;
private DfType calcInherentType() {
PsiType type = getType();
DfType dfType = getFactory().createDfType(type, Nullability.UNKNOWN);
if(myDescriptor instanceof SpecialField) {
return dfType.meet(((SpecialField)myDescriptor).getDefaultValue(false));
}
PsiModifierListOwner psi = getPsiVariable();
if (type instanceof PsiPrimitiveType) {
if (TypeConversionUtil.isIntegralNumberType(type)) {
LongRangeSet fromType = LongRangeSet.fromType(type);
if (fromType != null) {
LongRangeSet range = LongRangeSet.fromPsiElement(psi).intersect(fromType);
return type.equals(PsiType.LONG) ? DfTypes.longRange(range) : DfTypes.intRange(range);
}
}
}
if (dfType instanceof DfReferenceType) {
if (psi != null) {
dfType = dfType.meet(Mutability.getMutability(psi).asDfType());
}
dfType = dfType.meet(NullabilityUtil.calcCanBeNull(this).asDfType());
}
return dfType;
}
@NotNull
public Nullability getInherentNullability() {
return DfaNullability.toNullability(getInherentFacts().get(DfaFactType.NULLABILITY));
return DfaNullability.toNullability(DfaNullability.fromDfType(getInherentType()));
}
public boolean isFlushableByCalls() {
@@ -1,9 +1,10 @@
// 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.value;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
@@ -62,8 +63,11 @@ public interface VariableDescriptor {
}
PsiType type = getType(null);
LongRangeSet range = LongRangeSet.fromPsiElement(getPsiElement());
return factory.withFact(factory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, getPsiElement())),
DfaFactType.RANGE, range);
DfType dfType = factory.createDfType(type, DfaPsiUtil.getElementNullability(type, getPsiElement()));
if (dfType instanceof DfIntegralType) {
dfType = ((DfIntegralType)dfType).meetRange(range);
}
return factory.fromDfType(dfType);
}
/**
@@ -6,7 +6,6 @@ import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.psi.*;
@@ -93,12 +92,10 @@ public class SuspiciousCollectionsMethodCallsInspection extends AbstractBaseJava
final String plainMessage = SuspiciousMethodCallUtil
.getSuspiciousMethodCallMessage(methodCall, arg, argType, exactType || reportConvertibleMethodCalls, patternMethods, i);
if (plainMessage != null && !exactType) {
TypeConstraint constraint = CommonDataflow.getExpressionFact(arg, DfaFactType.TYPE_CONSTRAINT);
if (constraint != null) {
PsiType type = constraint.getPsiType();
if (type != null && SuspiciousMethodCallUtil.getSuspiciousMethodCallMessage(methodCall, arg, type, reportConvertibleMethodCalls, patternMethods, i) == null) {
return null;
}
TypeConstraint constraint = TypeConstraint.fromDfType(CommonDataflow.getDfType(arg));
PsiType type = constraint.getPsiType();
if (type != null && SuspiciousMethodCallUtil.getSuspiciousMethodCallMessage(methodCall, arg, type, reportConvertibleMethodCalls, patternMethods, i) == null) {
return null;
}
}
@@ -17,16 +17,17 @@ package com.intellij.codeInsight.hint;
import com.intellij.codeInsight.documentation.DocumentationComponent;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactMap;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.lang.ExpressionTypeProvider;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.ColorUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -85,41 +86,54 @@ public class JavaTypeProvider extends ExpressionTypeProvider<PsiExpression> {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression == null) return "<unknown>";
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression);
String advancedTypeInfo = "";
List<Pair<String, String>> infoLines = new ArrayList<>();
String basicTypeEscaped = getInformationHint(expression);
if (result != null) {
DfaFactMap map = result.getAllFacts(expression);
DfType dfType = result.getDfType(expression);
PsiType type = expression.getType();
if (map != null) {
advancedTypeInfo = map.facts(new DfaFactMap.FactMapper<String>() {
@Override
public <T> String apply(DfaFactType<T> factType, T value) {
return formatFact(factType, value, type);
}
}).joining();
}
List<Object> nonValues = new ArrayList<>(result.getValuesNotEqualToExpression(expression));
nonValues.remove(null); // Nullability: not-null will be displayed, so this just duplicates nullability info
if (!nonValues.isEmpty()) {
advancedTypeInfo = makeHtmlRow("Not equal to", StringUtil.join(nonValues, DfaConstValue::renderValue, ", ")) + advancedTypeInfo;
}
Set<Object> values = result.getExpressionValues(expression);
if (!values.isEmpty()) {
if (values.size() == 1) {
advancedTypeInfo = makeHtmlRow("Value", DfaConstValue.renderValue(values.iterator().next())) + advancedTypeInfo;
infoLines.add(Pair.create("Value", DfConstantType.renderValue(values.iterator().next())));
} else {
advancedTypeInfo = makeHtmlRow("Value (one of)", StringUtil.join(values, DfaConstValue::renderValue, ", ")) + advancedTypeInfo;
infoLines.add(Pair.create("Value (one of)", StreamEx.of(values).map(DfConstantType::renderValue).sorted().joining(", ")));
}
} else {
if (dfType instanceof DfAntiConstantType) {
List<Object> nonValues = new ArrayList<>(((DfAntiConstantType<?>)dfType).getNotValues());
nonValues.remove(null); // Nullability: not-null will be displayed, so this just duplicates nullability info
if (!nonValues.isEmpty()) {
infoLines.add(Pair.create("Not equal to", StreamEx.of(nonValues).map(DfConstantType::renderValue).sorted().joining(", ")));
}
}
if (dfType instanceof DfIntegralType) {
String rangeText = ((DfIntegralType)dfType).getRange().getPresentationText(type);
if (!rangeText.equals("any value")) {
infoLines.add(Pair.create("Range", rangeText));
}
}
else if (dfType instanceof DfReferenceType) {
DfReferenceType refType = (DfReferenceType)dfType;
infoLines.add(Pair.create("Nullability", refType.getNullability().getPresentationName()));
infoLines.add(Pair.create("Constraints", refType.getConstraint().getPresentationText(type)));
if (refType.getMutability() != Mutability.UNKNOWN) {
infoLines.add(Pair.create("Mutability", refType.getMutability().toString()));
}
infoLines.add(Pair.create("Locality", refType.isLocal() ? "local object" : ""));
SpecialField field = refType.getSpecialField();
if (field != null) {
infoLines.add(Pair.create(StringUtil.wordsToBeginFromUpperCase(field.toString()),
field.getPresentationText(refType.getSpecialFieldType(), type)));
}
}
}
}
return advancedTypeInfo.isEmpty()
? basicTypeEscaped
: "<table>" + makeHtmlRow("Type", basicTypeEscaped) + advancedTypeInfo + "</table>";
}
private static <T> String formatFact(@NotNull DfaFactType<T> factType, @NotNull T value, @Nullable PsiType type) {
String presentationText = factType.getPresentationText(value, type);
return presentationText.isEmpty() ? "" : makeHtmlRow(factType.getName(value), StringUtil.escapeXmlEntities(presentationText));
infoLines.removeIf(pair -> pair.getSecond().isEmpty());
if (!infoLines.isEmpty()) {
infoLines.add(0, Pair.create("Type", basicTypeEscaped));
return StreamEx.of(infoLines).map(pair -> makeHtmlRow(pair.getFirst(), pair.getSecond())).joining("", "<table>", "</table>");
}
return basicTypeEscaped;
}
private static String makeHtmlRow(@NotNull String titleText, String contentHtml) {
@@ -3,8 +3,8 @@ package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
@@ -146,8 +146,8 @@ public class WrapWithUnmodifiableAction extends BaseIntentionAction {
}
private static boolean isUnmodifiable(@NotNull PsiExpression expression) {
Mutability fact = CommonDataflow.getExpressionFact(expression, DfaFactType.MUTABILITY);
if (fact != null && fact.isUnmodifiable()) {
DfType dfType = CommonDataflow.getDfType(expression);
if (Mutability.fromDfType(dfType).isUnmodifiable()) {
return true;
}
PsiMethodCallExpression methodCall = tryCast(expression, PsiMethodCallExpression.class);
@@ -1,10 +1,10 @@
// 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;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaFactMapValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.codeInspection.util.OptionalRefactoringUtil;
import com.intellij.openapi.project.Project;
@@ -469,8 +469,8 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI
}
String name = call.getMethodExpression().getReferenceName();
if ("get".equals(name)) {
SpecialFieldValue fact = CommonDataflow.getExpressionFact(qualifier, DfaFactType.SPECIAL_FIELD_VALUE);
if (DfaFactType.NULLABILITY.fromDfaValue(SpecialField.OPTIONAL_VALUE.extract(fact)) != DfaNullability.NOT_NULL) return null;
DfType dfType = SpecialField.OPTIONAL_VALUE.getFromQualifier(CommonDataflow.getDfType(qualifier));
if (dfType.isSuperType(DfTypes.NULL)) return null;
} else if ("orElse".equals(name)) {
if (!ExpressionUtils.isNullLiteral(call.getArgumentList().getExpressions()[0])) return null;
}
@@ -808,10 +808,7 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI
}
private static boolean isPresentOptional(PsiExpression optionalExpression) {
SpecialFieldValue fact = CommonDataflow.getExpressionFact(optionalExpression, DfaFactType.SPECIAL_FIELD_VALUE);
DfaValue value = SpecialField.OPTIONAL_VALUE.extract(fact);
if (!(value instanceof DfaFactMapValue)) return false;
return DfaNullability.toNullability(DfaFactType.NULLABILITY.fromDfaValue(value)) == Nullability.NOT_NULL;
return !SpecialField.OPTIONAL_VALUE.getFromQualifier(CommonDataflow.getDfType(optionalExpression)).isSuperType(DfTypes.NULL);
}
private static class Context {
@@ -2,7 +2,7 @@
package com.intellij.codeInspection.duplicateExpressions;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -94,7 +94,7 @@ class SideEffectCalculator {
PsiExpression array = access.getArrayExpression();
return mayHaveSideEffect(array) ||
mayHaveSideEffect(access.getIndexExpression()) ||
!Boolean.TRUE.equals(CommonDataflow.getExpressionFact(array, DfaFactType.LOCALITY));
!DfReferenceType.isLocal(CommonDataflow.getDfType(array));
}
if (e instanceof PsiLambdaExpression) {
return false; // lambda itself (unless called) has no side effect
@@ -3,9 +3,12 @@ package com.intellij.codeInspection.java18api;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaFactMapValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.openapi.project.Project;
@@ -38,12 +41,10 @@ public class OptionalGetWithoutIsPresentInspection extends AbstractBaseJavaLocal
if (optionalClass == null) return;
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(qualifier);
if (result == null || !result.expressionWasAnalyzed(qualifier)) return;
SpecialFieldValue fact = result.getExpressionFact(qualifier, DfaFactType.SPECIAL_FIELD_VALUE);
DfaValue value = SpecialField.OPTIONAL_VALUE.extract(fact);
if (value != null && !(value instanceof DfaFactMapValue)) return;
DfaNullability nullability = value != null ? ((DfaFactMapValue)value).get(DfaFactType.NULLABILITY) : null;
if (nullability != DfaNullability.NOT_NULL &&
nullability != DfaNullability.FLUSHED &&
DfType dfType = SpecialField.OPTIONAL_VALUE.getFromQualifier(result.getDfType(qualifier));
if (dfType != DfTypes.TOP && !(dfType instanceof DfReferenceType)) return;
DfaNullability nullability = DfaNullability.fromDfType(dfType);
if ((nullability == DfaNullability.UNKNOWN || nullability == DfaNullability.NULLABLE) &&
!isPresentCallWithSameQualifierExists(qualifier)) {
holder.registerProblem(nameElement,
InspectionsBundle.message("inspection.optional.get.without.is.present.message", optionalClass.getName()),
@@ -3,6 +3,11 @@ import java.util.Calendar;
class Test {
public void foo(Object c) {
if (c instanceof Calendar) return;
if (c == Calendar.getInstance()) {}
if (c == getInstance()) {
if (<warning descr="Condition 'c == null' is always 'true'">c == null</warning>) {}
}
if (<warning descr="Condition 'c == Calendar.getInstance()' is always 'false'">c == Calendar.getInstance()</warning>) {}
}
native Calendar getInstance();
}
@@ -1,6 +1,11 @@
import java.util.Arrays;
public final class ArrayLength {
public static void diff(String[] args, String[] args2) {
String[] arr = new String[args2.length - args.length];
if (arr.length > 0) {}
}
void testForSimple2(int[] arr, int[][] arr2) {
boolean b = arr2[0].length == arr.length;
for(int i=0; i<arr.length; i++) {
@@ -35,8 +35,13 @@ class Contracts {
if(<warning descr="Condition 'b' is always 'true'">b</warning>) {
System.out.println("always");
}
assertThat("1", getBooleanWrapper(1), is(true));
assertThat("2", getBooleanPrimitive(2), is(true));
<warning descr="The call to 'assertThat' always fails, according to its method contracts">assertThat</warning>("b is not true", <weak_warning descr="Value 'b' is always 'true'">b</weak_warning>, not(is(true)));
}
private native Boolean getBooleanWrapper(int x);
private native boolean getBooleanPrimitive(int x);
private void checkFalse(boolean b) {
assertThat("b is false", b, is(equalTo(false)));
@@ -0,0 +1,36 @@
class Test {
void test(Object obj) {
Object base = obj;
while (base != null) {
base = getObject(obj);
}
// On one hand people write such kind of code (assuming that obj is never null) and the warning looks noise to them
// On the other hand if precondition loop is used then indeed obj was compared to null, which is useless if we assume that
// it's never null. This code is completely equivalent to test2 where the problem is more explicit.
// If obj is not expected to be null, we should not check the condition before loop, thus do-while should be used
// (or, alternatively, @NotNull annotation should be added)
System.out.println(obj.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
}
void test2(Object obj) {
Object base = obj;
if (base != null) {
do {
base = getObject(obj);
}
while (base != null);
}
System.out.println(obj.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
}
void test3(Object obj) {
Object base = obj;
do {
base = getObject(obj);
}
while (base != null);
System.out.println(obj.hashCode());
}
native Object getObject(Object obj);
}
@@ -1,6 +1,34 @@
import java.util.*;
class LessThanRelations {
void foo1(long f1, long f2, long t1, long t2) {
if (t1 < f2 || f1 > f2) return;
if (f1 <= f2 && t1 >= t2) return;
if (f1 > f2 && t1 < t2) return;
if (f1 <= f2) return;
if (t1 >= t2) return; // TODO: must be always true
}
void foo2(long f1, long f2, long t1, long t2) {
if (f1 <= f2 && t1 >= t2) return;
if (f1 > f2 && t1 < t2) return;
if (f1 <= f2) return;
if (<warning descr="Condition 't1 >= t2' is always 'true'">t1 >= t2</warning>) return;
}
void foo3(long f1, long f2, long t1, long t2) {
if (t1 < f2 || f1 > f2) return;
if (f1 > f2 && t1 < t2) return;
if (f1 <= f2) return;
if (t1 >= t2) return; // TODO: must be always true
}
void foo4(long f1, long f2, long t1, long t2) {
if (f1 > f2 && t1 < t2) return;
if (f1 <= f2) return;
if (<warning descr="Condition 't1 >= t2' is always 'true'">t1 >= t2</warning>) return;
}
// IDEA-184278
void m(int value) {
for (int i = 0; i < value; i++) {
@@ -58,7 +86,7 @@ final class Range {
if (from <= myFrom) {
return new Range(to + 1, myTo);
}
if (<warning descr="Condition 'to >= myTo' is always 'true'">to >= myTo</warning>) {
if (to >= myTo) {
return new Range(myFrom, from - 1);
}
throw new RuntimeException("Impossible: " + this + ":" + other);
@@ -37,4 +37,11 @@ class Testcase {
Integer boxed = x;
if (<warning descr="Condition 'boxed == 5' is always 'false'">boxed == 5</warning>) {}
}
public void testUnboxObject(Object obj, int val) {
if (obj instanceof Integer) {
int objVal = (int)obj;
if (objVal == val) {}
}
}
}
@@ -0,0 +1,20 @@
class Test {
class X {}
native Object getObject();
native X getX();
public void check(int val, boolean b) {
Object typ = b ? getObject() : getX();
if (val == 1) {
System.out.println((X)typ);
}
else if (val == 2) {
if (typ instanceof X) {
}
}
System.out.println(typ.hashCode());
}
}
@@ -5,8 +5,8 @@ import org.jetbrains.annotations.NotNull;
class NullableReturn {
@NotNull Object test(Object o, Object o2, Object o3) {
Object x = o == null ? o3 : o2;
// no nullable return from notnull method here
return x == null ? o3 : x;
// probably ephemeral (e.g. o3 could be never null; o2 only), but it's ok to warn in the absence of nullity annotations
return x == null ? <warning descr="Expression 'o3' might evaluate to null but is returned by the method declared as @NotNull">o3</warning> : x;
}
interface Context {}
@@ -0,0 +1,11 @@
class Test {
interface X{}
void test(X x1, X x2, boolean b) {
if (x1 == null && b) {
x1 = x2;
}
X x3 = x1 != null ? x1 : x2;
System.out.println(x3.hashCode());
}
}
@@ -0,0 +1,20 @@
class X {
void test1(X arg, boolean b) {
if (arg instanceof Z && b) return;
if (arg == Y.y) {
}
}
void test() {
X x = getX();
if (x instanceof Z || x == Y.y) {}
if (x == Y.y) {}
}
native X getX();
}
class Y extends X {
public static final Y y = new Y();
}
class Z extends X {}
@@ -275,7 +275,9 @@ public class StreamInlining {
if (count3 == 1) {}
long count4 = Stream.of(arr[0]).map(String::trim).count();
if (<warning descr="Condition 'count4 == 1' is always 'true'">count4 == 1</warning>) {}
long count5 = Stream.of("foo", "bar", "baz", "qux").filter(s -> s.length() > 1).count();
long count5 = Stream.of("foo", "bar", "baz", "qux").filter(s -> <warning descr="Condition 's.length() > 1' is always 'true'">s.length() > 1</warning>).count();
long count5a = Stream.of("foo", "bar", "baz", "q").filter(s -> s.length() > 1).count();
long count5b = Stream.of("foo", "bar", "bazzz", "qx").filter(s -> <warning descr="Condition 's.length() > 1' is always 'true'">s.length() > 1</warning>).count();
if (count5 == 4) {}
if (<warning descr="Condition 'count5 < 0' is always 'false'">count5 < 0</warning>) {}
if (<warning descr="Condition 'count5 > 4' is always 'false'">count5 > 4</warning>) {}
@@ -1,4 +1,8 @@
class StringEquality {
void testNewString(char[] c) {
if (new String(c).equals("FOO")) {}
}
void ifChain(String s) {
if (s.equals("foo")) {
@@ -98,7 +102,7 @@ class StringEquality {
}
void testIncorrect(String s) {
if(<error descr="Operator '==' cannot be applied to 'java.lang.String', 'int'">s == s.length()</error>) {}
if(<error descr="Operator '==' cannot be applied to 'java.lang.String', 'int'"><warning descr="Condition 's == s.length()' is always 'false'">s == s.length()</warning></error>) {}
}
void testTrim() {
@@ -4,7 +4,7 @@ public class SwitchExpressionsNullabilityJava12 {
void cons(@NotNull String str) {}
void test(@Nullable String a, @Nullable String b, int i, boolean f) {
cons(((String)(switch(i) {
cons(((<warning descr="Casting '(switch(i) { ...' to 'String' may produce 'ClassCastException'">String</warning>)(switch(i) {
case 1 -> <warning descr="Argument 'a' might be null">a</warning>;
case 2 -> "foo";
case 3 -> <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>;

Some files were not shown because too many files have changed in this diff Show More