mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Dataflow refactoring: DfaFactMap => DfType
GitOrigin-RevId: 3ef9e633dc92929191cc5724109b3575bf6d12a1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8899d33dbf
commit
c79e23a713
+21
-17
@@ -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) {
|
||||
|
||||
+11
-12
@@ -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);
|
||||
}
|
||||
|
||||
+10
-11
@@ -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) {
|
||||
|
||||
+5
-9
@@ -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);
|
||||
|
||||
+29
-108
@@ -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 {
|
||||
|
||||
+5
-2
@@ -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]);
|
||||
|
||||
+28
-35
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-14
@@ -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);
|
||||
|
||||
+77
-67
@@ -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();
|
||||
|
||||
+106
-132
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-19
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
+27
-111
@@ -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);
|
||||
}
|
||||
|
||||
+26
-58
@@ -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.
|
||||
|
||||
+316
-621
File diff suppressed because it is too large
Load Diff
+50
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
|
||||
+32
-59
@@ -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++));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-10
@@ -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();
|
||||
|
||||
+10
-10
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -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;
|
||||
|
||||
+77
-74
@@ -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());
|
||||
}
|
||||
|
||||
+16
-30
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+137
-127
@@ -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);
|
||||
}
|
||||
|
||||
+4
-4
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-33
@@ -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());
|
||||
|
||||
+12
-11
@@ -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);
|
||||
|
||||
+41
-28
@@ -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);
|
||||
}
|
||||
|
||||
+13
-6
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -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));
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+22
-17
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -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
-16
@@ -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)
|
||||
*
|
||||
|
||||
+43
@@ -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(", ");
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+96
@@ -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();
|
||||
}
|
||||
}
|
||||
+33
@@ -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()));
|
||||
}
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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()));
|
||||
}
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+5
@@ -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 {
|
||||
}
|
||||
+302
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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());
|
||||
}
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
+30
@@ -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());
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+45
@@ -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();
|
||||
}
|
||||
}
|
||||
+58
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
@@ -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();
|
||||
}
|
||||
+118
@@ -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);
|
||||
}
|
||||
}
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
+48
-45
@@ -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
-12
@@ -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();
|
||||
|
||||
+40
-61
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
-201
@@ -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);
|
||||
}
|
||||
}
|
||||
+36
-39
@@ -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) &&
|
||||
|
||||
-73
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+13
-12
@@ -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() {
|
||||
|
||||
+79
@@ -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;
|
||||
}
|
||||
}
|
||||
-53
@@ -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
-5
@@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+155
-53
@@ -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 {
|
||||
|
||||
+39
-9
@@ -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() {
|
||||
|
||||
+7
-3
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-7
@@ -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
-3
@@ -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);
|
||||
|
||||
+7
-10
@@ -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
-2
@@ -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
|
||||
|
||||
+10
-9
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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() {
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user