diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java index 721de56b5e15..9843dd694b13 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java @@ -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) { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java index 734ccfa99a1b..d735a0e374a5 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/ExpressionTypeMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/ExpressionTypeMemoryState.java index b53228d0d8fb..391c91db1fad 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/ExpressionTypeMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/ExpressionTypeMemoryState.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java index 3ed096d8c7b4..10cfe6ea5396 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java @@ -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; - } } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java index a8a60898d532..bce8dac18204 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java index cca4fd10f27d..d98cad323c3e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java @@ -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 myPossibleValues = Collections.emptySet(); - // null = top; empty = bottom - @Nullable Set 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 notValues = myNotValues; - if (notValues == null) { - Set 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 resulting type - * @return a fact value or null if fact of given type is not known for given expression - */ - @Nullable - public T getExpressionFact(PsiExpression expression, DfaFactType 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. - * - *

- * 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 getValuesNotEqualToExpression(@Nullable PsiExpression expression) { + public DfType getDfType(PsiExpression expression) { DataflowPoint point = myData.get(expression); - if (point == null) return Collections.emptySet(); - Set 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 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 getExpressionFact(PsiExpression expression, DfaFactType 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 { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java index 2cdbba4e3de0..27f876a31eea 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java @@ -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]); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java index 30558d7ac6c2..6dd7f822c99c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java @@ -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)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java index e686797702bd..d10ce1c23d09 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index b9d8838ddd3c..202b9c3d91a3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -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(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index 5ed5c7fe1cef..7a585edf237d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -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 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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java index 807868270be8..5495799dd7a2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java @@ -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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index 9f7234f9e68c..47c6a8b2d3c0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java index 288d9b8870aa..275ce04de17e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java @@ -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 DfaFactMap updateMap(DfaFactMap map, DfaFactType 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 DfaFactMap updateMap(DfaFactMap map, DfaFactType 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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java index c1b0c27d7f2e..fdd78a3f8445 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java @@ -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 extends Key { +@Deprecated +abstract class DfaFactType extends Key { private static final List> ourFactTypes = new ArrayList<>(); /** @@ -50,17 +55,10 @@ public abstract class DfaFactType extends Key { } @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 extends Key { @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 = new DfaFactType("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 extends Key { 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 extends Key { } @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 extends Key { 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 extends Key { 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 extends Key { 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 extends Key { 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 LOCALITY = new DfaFactType("Locality") { @Override - boolean isUnknown(@NotNull Boolean fact) { + public boolean isUnknown(@NotNull Boolean fact) { return !fact; } @@ -269,12 +209,6 @@ public abstract class DfaFactType extends Key { 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 extends Key { @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 extends Key { 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> getTypes() { return Collections.unmodifiableList(ourFactTypes); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index 0c0bfb3bf774..2b490bbbd5bd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -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 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 getValueFact(@NotNull DfaValue value, @NotNull DfaFactType 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 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 - */ - boolean applyFact(@NotNull DfaValue var, @NotNull DfaFactType 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. - *

- * 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)} - *

- * - * @param var the variable to modify - * @param factType the type of the fact - * @param value the new variable value - * @param type of fact value - */ - void forceVariableFact(@NotNull DfaVariableValue var, @NotNull DfaFactType 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. diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index a6a92c611fe2..bf941be7f9d2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -4,6 +4,7 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInsight.Nullability; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.*; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -25,7 +26,6 @@ import gnu.trove.TIntArrayList; import gnu.trove.TIntObjectHashMap; import gnu.trove.TIntObjectProcedure; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -93,7 +93,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { public DfaMemoryStateImpl createClosureState() { DfaMemoryStateImpl copy = createCopy(); for (DfaVariableValue value : new ArrayList<>(copy.myVariableStates.keySet())) { - copy.dropFact(value, DfaFactType.LOCALITY); + DfType dfType = getDfType(value); + if (dfType instanceof DfReferenceType) { + copy.setDfType(value, ((DfReferenceType)dfType).dropLocality()); + } } copy.flushFields(); copy.emptyStack(); @@ -213,70 +216,51 @@ public class DfaMemoryStateImpl implements DfaMemoryState { flushVariable(var, var.getInherentNullability() != Nullability.UNKNOWN); flushQualifiedMethods(var); - if (value instanceof DfaUnknownValue) { + if (DfaTypeValue.isUnknown(value)) { setVariableState(var, getVariableState(var).withNotNull()); return; } DfaVariableState state = getVariableState(var).withValue(value); - if (value instanceof DfaFactMapValue) { - DfaFactMap facts = filterFactsOnAssignment(var, ((DfaFactMapValue)value).getFacts()); - setVariableState(var, state.withFacts(facts)); - SpecialFieldValue specialFieldValue = facts.get(DfaFactType.SPECIAL_FIELD_VALUE); - if (specialFieldValue != null) { - DfaValue targetSpecialField = specialFieldValue.getField().createValue(myFactory, var); - if (targetSpecialField instanceof DfaVariableValue) { - setVarValue((DfaVariableValue)targetSpecialField, specialFieldValue.getValue()); - } - } + DfType dfType = filterDfTypeOnAssignment(var, getDfType(value)).meet(var.getDfType()); + if (dfType == DfTypes.BOTTOM) return; // likely uncompilable code or bad CFG + if (value instanceof DfaVariableValue && !ControlFlowAnalyzer.isTempVariable(var) && + !ControlFlowAnalyzer.isTempVariable((DfaVariableValue)value) && + (var.getQualifier() == null || !ControlFlowAnalyzer.isTempVariable(var.getQualifier()))) { + // assigning a = b when b is known to be null: could be ephemeral + checkEphemeral(var, value); } - else { - setVariableState(var, isNull(value) ? state.withFact(DfaFactType.NULLABILITY, DfaNullability.NULL) : state); - DfaRelation dfaEqual = DfaRelation.createRelation(var, RelationType.EQ, value); - if (dfaEqual == null) return; - applyCondition(dfaEqual); - - if (value instanceof DfaVariableValue) { - DfaVariableState targetState = getVariableState((DfaVariableValue)value); - setVariableState(var, targetState.withFacts(filterFactsOnAssignment(var, targetState.myFactMap))); - } + setVariableState(var, state.createCopy(dfType)); + applyRelation(var, value, false); + Couple specialFields = getSpecialEquivalencePair(var, value); + if (specialFields != null && specialFields.getFirst() instanceof DfaVariableValue) { + setVarValue((DfaVariableValue)specialFields.getFirst(), specialFields.getSecond()); } - - updateEqClassesByState(var); } - protected DfaFactMap filterFactsOnAssignment(DfaVariableValue var, @NotNull DfaFactMap facts) { - return facts; + protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @NotNull DfType dfType) { + return dfType; } private DfaValue handleStackValueOnVariableFlush(DfaValue value, DfaVariableValue flushed, DfaVariableValue replacement) { if (value.dependsOn(flushed)) { + DfType dfType = getDfType(value); if (value instanceof DfaVariableValue) { if (replacement != null) { DfaVariableValue target = replaceQualifier((DfaVariableValue)value, flushed, replacement); if (target != value) return target; } - if (value.getType() instanceof PsiPrimitiveType) { - return myFactory.getFactValue(DfaFactType.RANGE, getValueFact(value, DfaFactType.RANGE)); - } - DfaNullability dfaNullability = isNotNull(value) ? DfaNullability.NOT_NULL : getValueFact(value, DfaFactType.NULLABILITY); - if (dfaNullability == null) { - dfaNullability = DfaNullability.fromNullability(((DfaVariableValue)value).getInherentNullability()); - } - return myFactory.withFact(myFactory.createTypeValue(value.getType(), Nullability.UNKNOWN), DfaFactType.NULLABILITY, dfaNullability); } - return myFactory.getFactValue(DfaFactType.RANGE, getValueFact(value, DfaFactType.RANGE)); + return myFactory.fromDfType(dfType); } return value; } - @Nullable("for non-variables and non-constants which can't be compared by ==") - private Integer getOrCreateEqClassIndex(@NotNull DfaValue dfaValue) { + private int getOrCreateEqClassIndex(@NotNull DfaVariableValue dfaValue) { int i = getEqClassIndex(dfaValue); if (i != -1) return i; - if (!canBeInRelation(dfaValue)) return null; dfaValue = canonicalize(dfaValue); EqClass eqClass = new EqClass(myFactory); eqClass.add(dfaValue.getID()); @@ -375,7 +359,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public boolean shouldCompareByEquals(DfaValue dfaLeft, DfaValue dfaRight) { - if (dfaLeft == dfaRight && !(dfaLeft instanceof DfaBoxedValue) && !(dfaLeft instanceof DfaConstValue)) { + if (dfaLeft == dfaRight && !(dfaLeft instanceof DfaBoxedValue) && !(dfaLeft.getDfType() instanceof DfConstantType)) { return false; } return !isNull(dfaLeft) && !isNull(dfaRight) && @@ -383,33 +367,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } private static boolean isSuperValue(DfaValue superValue, DfaValue subValue) { - if (superValue == DfaUnknownValue.getInstance() || superValue == subValue) return true; - if (superValue instanceof DfaFactMapValue && subValue instanceof DfaFactMapValue) { - return ((DfaFactMapValue)superValue).getFacts().isSuperStateOf(((DfaFactMapValue)subValue).getFacts()); + if (DfaTypeValue.isUnknown(superValue) || superValue == subValue) return true; + if (superValue instanceof DfaTypeValue && subValue instanceof DfaTypeValue) { + return superValue.getDfType().isMergeable(subValue.getDfType()); } return false; } - private static boolean canBeInRelation(@NotNull DfaValue value) { - return value instanceof DfaBoxedValue || value instanceof DfaVariableValue || value instanceof DfaConstValue; - } - - private boolean canBeNaN(@NotNull DfaValue dfaValue) { - if (!(dfaValue instanceof DfaVariableValue) || - (!PsiType.FLOAT.equals(dfaValue.getType()) && !PsiType.DOUBLE.equals(dfaValue.getType()))) { - return false; - } - EqClass eqClass = getEqClass(dfaValue); - DfaConstValue constant = eqClass == null ? null : eqClass.findConstant(); - return constant == null || isNaN(constant); - } - - - private boolean isEffectivelyNaN(@NotNull DfaValue dfaValue) { - EqClass eqClass = getEqClass(dfaValue); - return eqClass != null && isNaN(eqClass.findConstant()); - } - List getEqClasses() { return myEqClasses; } @@ -451,20 +415,18 @@ public class DfaMemoryStateImpl implements DfaMemoryState { * @param val2 the second value * @return true if classes were successfully united. */ - private boolean uniteClasses(DfaValue val1, DfaValue val2) { + private boolean uniteClasses(DfaVariableValue val1, DfaVariableValue val2) { DfaVariableValue var1 = getCanonicalVariable(val1); DfaVariableValue var2 = getCanonicalVariable(val2); - Integer c1Index = getOrCreateEqClassIndex(val1); - Integer c2Index = getOrCreateEqClassIndex(val2); - if (c1Index == null || c2Index == null || c1Index.equals(c2Index)) return true; + int c1Index = getOrCreateEqClassIndex(val1); + int c2Index = getOrCreateEqClassIndex(val2); + if (c1Index == c2Index) return true; if (!myDistinctClasses.unite(c1Index, c2Index)) return false; EqClass c1 = myEqClasses.get(c1Index); EqClass c2 = myEqClasses.get(c2Index); - if (c1.findConstant() != null && c2.findConstant() != null) return false; - EqClass newClass = new EqClass(c1); myEqClasses.set(c1Index, newClass); @@ -504,7 +466,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { toState = fromState; } else { - toState = fromState.intersectMap(toState.myFactMap); + toState = fromState.meet(toState.myDfType); if (toState == null) return false; } setVariableState(target, toState); @@ -517,7 +479,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (var == null || var.getQualifier() != from) continue; DfaVariableValue target = var.withQualifier(to); if (!uniteClasses(var, target)) return false; - removeEquivalenceForVariableAndWrappers(var); + removeEquivalence(var); } return true; } @@ -536,82 +498,12 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public boolean isNull(DfaValue dfaValue) { - if (dfaValue instanceof DfaConstValue) return ((DfaConstValue)dfaValue).getValue() == null; - - if (dfaValue instanceof DfaVariableValue) { - int c1Index = getEqClassIndex(dfaValue); - return c1Index >= 0 && c1Index == getEqClassIndex(myFactory.getConstFactory().getNull()); - } - - return false; + return getDfType(dfaValue) == DfTypes.NULL; } @Override public boolean isNotNull(DfaValue dfaVar) { - if (dfaVar instanceof DfaConstValue) return ((DfaConstValue)dfaVar).getValue() != null; - if (dfaVar instanceof DfaBoxedValue) return true; - if (dfaVar instanceof DfaFactMapValue) return DfaNullability.isNotNull(((DfaFactMapValue)dfaVar).getFacts()); - if (dfaVar instanceof DfaVariableValue) { - if (getVariableState((DfaVariableValue)dfaVar).isNotNull()) return true; - - DfaConstValue constantValue = getConstantValue(dfaVar); - if (constantValue != null && constantValue.getValue() != null) return true; - } - - DfaConstValue dfaNull = myFactory.getConstFactory().getNull(); - Integer c1Index = getOrCreateEqClassIndex(dfaVar); - int c2Index = getEqClassIndex(dfaNull); - if (c1Index == null || c2Index < 0) { - return false; - } - - return myDistinctClasses.areDistinctUnordered(c1Index, c2Index); - } - - /** - * Returns constants which are known to be not equal to given value - * - * @param value a value to test - * @return set of non-equal constant values - */ - public Set getNonEqualConstants(DfaVariableValue value) { - int index = getEqClassIndex(value); - if (index == -1 || myEqClasses.get(index).findConstant() != null) return Collections.emptySet(); - return StreamEx.of(getDistinctClassPairs()) - .map(pair -> pair.getOtherClass(index)) - .nonNull() - .map(EqClass::findConstant) - .nonNull() - .map(DfaConstValue::getValue) - .toSet(); - } - - @Override - @Nullable - @Contract("null -> null") - public DfaConstValue getConstantValue(@Nullable DfaValue value) { - return getConstantValue(value, true); - } - - private DfaConstValue getConstantValue(@Nullable DfaValue value, boolean unbox) { - if (value instanceof DfaConstValue) { - return (DfaConstValue)value; - } - if (value instanceof DfaVariableValue) { - if (unbox && TypeConversionUtil.isPrimitiveWrapper(value.getType())) { - value = SpecialField.UNBOX.createValue(myFactory, value); - } - EqClass ec = getEqClass(value); - DfaConstValue constValue = ec == null ? null : ec.findConstant(); - if (constValue != null) return constValue; - DfaVariableState state = getExistingVariableState((DfaVariableValue)value); - LongRangeSet range = state != null ? state.getFact(DfaFactType.RANGE) : null; - Long constantValue = range == null ? null : range.getConstantValue(); - if (constantValue != null) { - return myFactory.getConstFactory().createFromValue(constantValue, PsiType.LONG); - } - } - return null; + return !getDfType(dfaVar).isSuperType(DfTypes.NULL); } @Override @@ -632,48 +524,14 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public boolean castTopOfStack(@NotNull DfaPsiType type) { DfaValue value = peek(); - - DfaFactMap facts = null; - if (value instanceof DfaVariableValue) { - DfaVariableValue dfaVar = (DfaVariableValue)value; - - if (isNull(dfaVar)) return true; - DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(type); - if (newState == null) return false; - setVariableState(dfaVar, newState); - } else if (value instanceof DfaFactMapValue) { - facts = ((DfaFactMapValue)value).getFacts(); - } - if (facts != null) { - DfaFactMap newFacts = TypeConstraint.withInstanceOf(facts, type); - if (newFacts == null) return false; - pop(); - push(myFactory.getFactFactory().createValue(newFacts)); - } - return true; - } - - private boolean applyFacts(DfaValue value, DfaFactMap facts) { - if (value instanceof DfaVariableValue) { - DfaVariableState oldState = getVariableState((DfaVariableValue)value); - DfaVariableState newState = oldState.intersectMap(facts); - if (newState == null) { - newState = oldState.withoutFact(DfaFactType.TYPE_CONSTRAINT); - if (newState.intersectMap(facts) != null && DfaNullability.isNotNull(facts)) { - setVariableState((DfaVariableValue)value, newState); - return applyRelation(value, getFactory().getConstFactory().getNull(), false); - } - return false; + DfType dfType = getDfType(value); + DfType result = dfType.meet(type.asConstraint().asDfType()); + if (!result.equals(dfType)) { + if (result == DfTypes.NULL || !meetDfType(value, result)) return false; + if (!(value instanceof DfaVariableValue)) { + pop(); + push(myFactory.fromDfType(result)); } - setVariableState((DfaVariableValue)value, newState); - if (DfaUtil.isComparedByEquals(newState.getTypeConstraint().getPsiType()) && - !newState.getTypeConstraint().equals(oldState.getTypeConstraint())) { - // Type is narrowed to java.lang.String, java.lang.Integer, etc.: we consider String & boxed types - // equivalence by content, but other object types by reference, so we need to remove distinct pairs, if any. - convertReferenceEqualityToValueEquality(value); - } - updateEquivalentVariables((DfaVariableValue)value, newState); - return updateEqClassesByState((DfaVariableValue)value); } return true; } @@ -681,55 +539,74 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private void convertReferenceEqualityToValueEquality(DfaValue value) { int id = canonicalize(value).getID(); Integer index = myIdToEqClassesIndices.get(id); - assert index != null; + if (index == null) return; for (Iterator iterator = myDistinctClasses.iterator(); iterator.hasNext(); ) { DistinctPairSet.DistinctPair pair = iterator.next(); EqClass otherClass = pair.getOtherClass(index); - if (otherClass != null && otherClass.findConstant() != getFactory().getConstFactory().getNull()) { + if (otherClass != null && !isNull(otherClass.getVariable(0))) { iterator.remove(); } } } - private boolean updateEqClassesByState(DfaVariableValue value) { - if (DfaNullability.isNotNull(getVariableState(value).myFactMap)) { - return applyRelation(value, getFactory().getConstFactory().getNull(), true); + @Override + public void setDfType(@NotNull DfaValue value, @NotNull DfType dfType) { + if (value instanceof DfaVariableValue) { + DfaVariableValue var = (DfaVariableValue)value; + DfaVariableState state = getVariableState(var); + if (DfaNullability.fromDfType(state.myDfType) != DfaNullability.fromDfType(dfType)) { + removeEquivalence(var); + } + setVariableState(var, state.createCopy(dfType)); } - return true; + } + + @NotNull + private static DfType sanitizeNullability(@NotNull DfType dfType) { + if (!(dfType instanceof DfReferenceType)) return dfType; + DfaNullability nullability = ((DfReferenceType)dfType).getNullability(); + if (nullability == DfaNullability.NULLABLE) return ((DfReferenceType)dfType).dropNullability(); + return dfType; } @Override - public void dropFact(@NotNull DfaValue value, @NotNull DfaFactType factType) { - if (value instanceof DfaVariableValue) { - DfaVariableValue var = (DfaVariableValue)value; - DfaVariableState state = getExistingVariableState(var); - if (state != null) { - state = state.withoutFact(factType); - setVariableState(var, state); - } - } - } - - @Override - public boolean applyFact(@NotNull DfaValue value, @NotNull DfaFactType factType, @Nullable T factValue) { - if (value instanceof DfaFactMapValue) { - return ((DfaFactMapValue)value).getFacts().intersect(factType, factValue) != null; - } - if (value instanceof DfaBinOpValue && factType == DfaFactType.RANGE && factValue != null) { - return propagateRangeBack((LongRangeSet)factValue, (DfaBinOpValue)value); + public boolean meetDfType(@NotNull DfaValue value, @NotNull DfType dfType) { + if (dfType == DfTypes.TOP) return true; + if (dfType == DfTypes.BOTTOM) return false; + if (value instanceof DfaBinOpValue) { + return propagateRangeBack(DfLongType.extractRange(dfType), (DfaBinOpValue)value); } if (value instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue)value; - if (factValue != null) { - DfaVariableState state = getVariableState(var); - DfaVariableState newState = state.intersectFact(factType, factValue); - if (newState == null) return false; - setVariableState(var, newState); - updateEquivalentVariables(var, newState); - return updateEqClassesByState(var); + DfaVariableState state = getVariableState(var); + DfType result = state.myDfType.meet(dfType); + if (result.equals(state.myDfType)) return true; + if (result == DfTypes.BOTTOM) return false; + DfaVariableState newState = state.createCopy(result); + setVariableState(var, newState); + if (DfaUtil.isComparedByEquals(newState.getTypeConstraint().getPsiType()) && + !newState.getTypeConstraint().equals(state.getTypeConstraint())) { + // Type is narrowed to java.lang.String, java.lang.Integer, etc.: we consider String & boxed types + // equivalence by content, but other object types by reference, so we need to remove distinct pairs, if any. + convertReferenceEqualityToValueEquality(value); } + updateEquivalentVariables(var, newState); + if (result instanceof DfConstantType) { + if (!propagateConstant(var, (DfConstantType)result)) return false; + } + if (result instanceof DfAntiConstantType) { + for (Object notValue : ((DfAntiConstantType)result).getNotValues()) { + if (notValue instanceof PsiType) { + if (processGetClass(var, (PsiType)notValue, true) == ThreeState.NO) return false; + } + } + } + if (result instanceof DfIntegralType) { + if (!applyRangeToRelatedValues(var, ((DfIntegralType)result).getRange())) return false; + } + return true; } - return true; + return value.getDfType().meet(dfType) != DfTypes.BOTTOM; } private boolean propagateRangeBack(@NotNull LongRangeSet factValue, @NotNull DfaBinOpValue binOp) { @@ -737,9 +614,11 @@ public class DfaMemoryStateImpl implements DfaMemoryState { LongRangeSet appliedRange = isLong ? factValue : factValue.intersect(Objects.requireNonNull(LongRangeSet.fromType(PsiType.INT))); DfaVariableValue left = binOp.getLeft(); DfaValue right = binOp.getRight(); - LongRangeSet leftRange = getValueFact(left, DfaFactType.RANGE); - LongRangeSet rightRange = getValueFact(right, DfaFactType.RANGE); - if (leftRange == null || rightRange == null) return true; + DfIntegralType leftDfType = ObjectUtils.tryCast(getDfType(left), DfIntegralType.class); + DfIntegralType rightDfType = ObjectUtils.tryCast(getDfType(right), DfIntegralType.class); + if(leftDfType == null || rightDfType == null) return true; + LongRangeSet leftRange = leftDfType.getRange(); + LongRangeSet rightRange = rightDfType.getRange(); LongRangeSet result = getBinOpRange(binOp); assert result != null; if (!result.intersects(appliedRange)) return false; @@ -761,19 +640,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } break; } - return applyFact(left, DfaFactType.RANGE, leftConstraint) && applyFact(right, DfaFactType.RANGE, rightConstraint); + return meetDfType(left, leftDfType.meetRange(leftConstraint)) && meetDfType(right, rightDfType.meetRange(rightConstraint)); } @Override public boolean applyContractCondition(DfaCondition condition) { if (condition instanceof DfaRelation) { DfaRelation relation = (DfaRelation)condition; - if (relation.isEquality() && - DfaConstValue.isConstant(relation.getRightOperand(), null) && - (relation.getLeftOperand() instanceof DfaUnknownValue || - (relation.getLeftOperand() instanceof DfaVariableValue && - getVariableState((DfaVariableValue)relation.getLeftOperand()).getNullability() == Nullability.UNKNOWN))) { - markEphemeral(); + if (relation.isEquality()) { + checkEphemeral(relation.getLeftOperand(), relation.getRightOperand()); } } return applyCondition(condition); @@ -788,11 +663,12 @@ public class DfaMemoryStateImpl implements DfaMemoryState { areEqual(binOp1.getLeft(), binOp2.getLeft()) && areEqual(binOp1.getRight(), binOp2.getRight()); } - if (!(value1 instanceof DfaConstValue) && !(value1 instanceof DfaVariableValue)) return false; - if (!(value2 instanceof DfaConstValue) && !(value2 instanceof DfaVariableValue)) return false; + DfType dfType1 = getDfType(value1); + DfType dfType2 = getDfType(value2); + if (dfType1 instanceof DfConstantType && dfType2 instanceof DfConstantType && dfType1.equals(dfType2)) return true; + if (!(value1 instanceof DfaVariableValue)) return false; + if (!(value2 instanceof DfaVariableValue)) return false; if (value1 == value2) return true; - DfaConstValue const1 = getConstantValue(value1, false); - if (const1 != null && const1 == getConstantValue(value2, false)) return true; int index1 = getEqClassIndex(value1); int index2 = getEqClassIndex(value2); return index1 != -1 && index1 == index2; @@ -821,31 +697,41 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaValue dfaRight = dfaRelation.getRightOperand(); RelationType relationType = dfaRelation.getRelation(); - DfaConstValue sentinel = getFactory().getConstFactory().getSentinel(); - if (dfaLeft == sentinel || dfaRight == sentinel) { - assert relationType == RelationType.EQ || relationType == RelationType.NE; - return (dfaLeft == dfaRight) == (relationType == RelationType.EQ); - } - if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true; + if (DfaTypeValue.isUnknown(dfaLeft) || DfaTypeValue.isUnknown(dfaRight)) return true; - LongRangeSet left = getValueFact(dfaLeft, DfaFactType.RANGE); - LongRangeSet right = getValueFact(dfaRight, DfaFactType.RANGE); + DfType leftType = getDfType(dfaLeft); + DfType rightType = getDfType(dfaRight); - if (left != null && right != null) { - if (!applyFact(dfaLeft, DfaFactType.RANGE, right.fromRelation(relationType)) || - !applyFact(dfaRight, DfaFactType.RANGE, left.fromRelation(relationType.getFlipped()))) { + if (leftType instanceof DfIntegralType && rightType instanceof DfIntegralType && relationType.getFlipped() != null) { + if (!meetDfType(dfaLeft, ((DfIntegralType)leftType).meetRelation(relationType, rightType)) || + !meetDfType(dfaRight, ((DfIntegralType)rightType).meetRelation(relationType.getFlipped(), leftType))) { return false; } if (!applyBinOpRelations(dfaLeft, relationType, dfaRight)) return false; + if (!(dfaRight instanceof DfaVariableValue)) return true; + return applyEquivalenceRelation(relationType, dfaLeft, dfaRight); } - if (dfaRight instanceof DfaFactMapValue) { - DfaFactMapValue factValue = (DfaFactMapValue)dfaRight; - if ((relationType == RelationType.IS || relationType == RelationType.EQ) && - DfaNullability.isNotNull(factValue.getFacts()) && - !applyRelation(dfaLeft, getFactory().getConstFactory().getNull(), true)) { - return false; + if (leftType instanceof DfFloatingPointType && rightType instanceof DfFloatingPointType && relationType.getFlipped() != null) { + if (isNaN(leftType) || isNaN(rightType)) { + applyEquivalenceRelation(relationType, dfaLeft, dfaRight); + return relationType == RelationType.NE; } + RelationType constantRelation = getFloatingConstantRelation(leftType, rightType); + if (constantRelation != null) { + return constantRelation == relationType; + } + if (canBeNaN(leftType) || canBeNaN(rightType)) { + if (dfaLeft == dfaRight && dfaLeft instanceof DfaVariableValue && !(dfaLeft.getType() instanceof PsiPrimitiveType)) { + return !dfaRelation.isNonEquality(); + } + applyEquivalenceRelation(relationType, dfaLeft, dfaRight); + return true; + } + return applyEquivalenceRelation(relationType, dfaLeft, dfaRight); + } + + if (dfaRight instanceof DfaTypeValue) { if ((relationType == RelationType.EQ || relationType.isInequality()) && !applyUnboxedRelation(dfaLeft, dfaRight, relationType.isInequality())) { return false; @@ -853,48 +739,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (relationType == RelationType.EQ && !applySpecialFieldEquivalence(dfaLeft, dfaRight)) { return false; } - if (dfaLeft instanceof DfaVariableValue) { - DfaVariableValue dfaVar = (DfaVariableValue)dfaLeft; - - switch (relationType) { - case IS: - return applyFacts(dfaVar, factValue.getFacts()); - case IS_NOT: { - boolean isNotNull = DfaNullability.isNotNull(factValue.getFacts()); - TypeConstraint constraint = factValue.get(DfaFactType.TYPE_CONSTRAINT); - if (constraint != null && constraint.getNotInstanceofValues().isEmpty()) { - DfaVariableState state = getVariableState(dfaVar); - for (DfaPsiType type : constraint.getInstanceofValues()) { - state = state.withNotInstanceofValue(type); - if (state == null) { - return isNotNull && !getVariableState(dfaVar).isNotNull() && - applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false); - } - setVariableState(dfaVar, state); - } - } - return true; - } - default: - return true; + if (relationType == RelationType.IS || relationType == RelationType.EQ) { + return meetDfType(dfaLeft, sanitizeNullability(rightType)); + } + else if (relationType == RelationType.IS_NOT || (relationType == RelationType.NE && rightType instanceof DfConstantType)) { + DfType antiType = rightType.tryNegate(); + if (antiType != null && !meetDfType(dfaLeft, antiType)) { + return leftType.meet(rightType) == DfTypes.BOTTOM; } } - if (relationType == RelationType.IS || relationType == RelationType.EQ) { - return getFactMap(dfaLeft).intersect(factValue.getFacts()) != null; - } - return true; - } - - if (isEffectivelyNaN(dfaLeft) || isEffectivelyNaN(dfaRight)) { - applyEquivalenceRelation(relationType, dfaLeft, dfaRight); - return relationType == RelationType.NE; - } - if ((canBeNaN(dfaLeft) && !isNull(dfaRight)) || (canBeNaN(dfaRight) && !isNull(dfaLeft))) { - if (dfaLeft == dfaRight && dfaLeft instanceof DfaVariableValue && !(dfaLeft.getType() instanceof PsiPrimitiveType)) { - return !dfaRelation.isNonEquality(); - } - - applyEquivalenceRelation(relationType, dfaLeft, dfaRight); return true; } @@ -907,38 +760,35 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaBinOpValue sum = (DfaBinOpValue)left; DfaBinOpValue.BinOp op = sum.getOperation(); if (op != DfaBinOpValue.BinOp.PLUS && op != DfaBinOpValue.BinOp.MINUS) return true; - LongRangeSet leftRange = getValueFact(sum.getLeft(), DfaFactType.RANGE); - LongRangeSet rightRange = getValueFact(sum.getRight(), DfaFactType.RANGE); - if (leftRange == null || rightRange == null) return true; + LongRangeSet leftRange = DfLongType.extractRange(getDfType(sum.getLeft())); + LongRangeSet rightRange = DfLongType.extractRange(getDfType(sum.getRight())); boolean isLong = PsiType.LONG.equals(sum.getType()); LongRangeSet rightNegated = rightRange.negate(isLong); LongRangeSet rightCorrected = op == DfaBinOpValue.BinOp.MINUS ? rightNegated : rightRange; - LongRangeSet resultRange = getValueFact(right, DfaFactType.RANGE); + LongRangeSet resultRange = DfLongType.extractRange(getDfType(right)); RelationType correctedRelation = correctRelation(type, leftRange, rightCorrected, resultRange, isLong); if (op == DfaBinOpValue.BinOp.MINUS) { - if (resultRange != null) { - long min = resultRange.min(); - long max = resultRange.max(); - if (min == 0 && max == 0) { - // a-b (rel) 0 => a (rel) b - if (!applyCondition(sum.getLeft().cond(correctedRelation, sum.getRight()))) return false; - } - else if (min >= 0 && RelationType.GE.isSubRelation(type)) { - RelationType correctedGt = correctRelation(RelationType.GT, leftRange, rightCorrected, resultRange, isLong); - if (!applyCondition(sum.getLeft().cond(correctedGt, sum.getRight()))) return false; - } - else if (max <= 0 && RelationType.LE.isSubRelation(type)) { - RelationType correctedLt = correctRelation(RelationType.LT, leftRange, rightCorrected, resultRange, isLong); - if (!applyCondition(sum.getLeft().cond(correctedLt, sum.getRight()))) return false; - } - if (RelationType.EQ.equals(type) && !resultRange.contains(0)) { - // a-b == non-zero => a != b - if (!applyRelation(sum.getLeft(), sum.getRight(), true)) return false; - } + long min = resultRange.min(); + long max = resultRange.max(); + if (min == 0 && max == 0) { + // a-b (rel) 0 => a (rel) b + if (!applyCondition(sum.getLeft().cond(correctedRelation, sum.getRight()))) return false; + } + else if (min == 0 && type == RelationType.GT || min >= 1 && RelationType.GE.isSubRelation(type)) { + RelationType correctedGt = correctRelation(RelationType.GT, leftRange, rightCorrected, resultRange, isLong); + if (!applyCondition(sum.getLeft().cond(correctedGt, sum.getRight()))) return false; + } + else if (max == 0 && type == RelationType.LT || max <= -1 && RelationType.LE.isSubRelation(type)) { + RelationType correctedLt = correctRelation(RelationType.LT, leftRange, rightCorrected, resultRange, isLong); + if (!applyCondition(sum.getLeft().cond(correctedLt, sum.getRight()))) return false; + } + if (RelationType.EQ.equals(type) && !resultRange.contains(0)) { + // a-b == non-zero => a != b + if (!applyRelation(sum.getLeft(), sum.getRight(), true)) return false; } } - if (op == DfaBinOpValue.BinOp.PLUS && RelationType.EQ == type && resultRange != null && + if (op == DfaBinOpValue.BinOp.PLUS && RelationType.EQ == type && !resultRange.intersects(LongRangeSet.all().mul(LongRangeSet.point(2), true))) { // a+b == odd => a != b if (!applyRelation(sum.getLeft(), sum.getRight(), true)) return false; @@ -1000,45 +850,36 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return result; } - private void updateVarStateOnComparison(@NotNull DfaVariableValue dfaVar, DfaValue value, boolean isNegated) { - if (isNegated) { - if (isNull(value)) { - setVariableState(dfaVar, getVariableState(dfaVar).withFact(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)); - } - } else { - if (value instanceof DfaConstValue) { - Object constValue = ((DfaConstValue)value).getValue(); - if (constValue == null) { - setVariableState(dfaVar, getVariableState(dfaVar).withFact(DfaFactType.NULLABILITY, DfaNullability.NULL)); - return; - } - DfaPsiType dfaType = myFactory.createDfaType(((DfaConstValue)value).getType()); - DfaVariableState state = getVariableState(dfaVar).withInstanceofValue(dfaType); - if (state != null) { - setVariableState(dfaVar, state); - } - } - if (isNotNull(value) && !isNotNull(dfaVar)) { - setVariableState(dfaVar, getVariableState(dfaVar).withoutFact(DfaFactType.NULLABILITY)); - applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true); - } - } - } - private boolean applyEquivalenceRelation(RelationType type, DfaValue dfaLeft, DfaValue dfaRight) { boolean isNegated = type == RelationType.NE || type == RelationType.GT || type == RelationType.LT; if (!isNegated && type != RelationType.EQ) { return true; } - if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaConstValue) { - LongRangeSet leftRange = getValueFact(dfaLeft, DfaFactType.RANGE); - LongRangeSet rightRange = getValueFact(dfaRight, DfaFactType.RANGE); - if (leftRange != null && rightRange != null) { - LongRangeSet appliedRange = rightRange.fromRelation(type); - if (appliedRange != null) { - if (!applyRangeToRelatedValues(dfaLeft, appliedRange)) return false; - if (appliedRange.contains(leftRange)) return true; - } + + DfType leftType = getDfType(dfaLeft); + DfType rightType = getDfType(dfaRight); + + if (type == RelationType.EQ) { + if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaVariableValue) { + checkEphemeral(dfaLeft, dfaRight); + checkEphemeral(dfaRight, dfaLeft); + } + if (!meetDfType(dfaLeft, sanitizeNullability(rightType))) return false; + if (!meetDfType(dfaRight, sanitizeNullability(leftType))) return false; + if (!applySpecialFieldEquivalence(dfaLeft, dfaRight)) return false; + } else { + if (leftType instanceof DfConstantType) { + DfType antiType = leftType.tryNegate(); + if (antiType != null && !meetDfType(dfaRight, antiType)) return false; + } + if (rightType instanceof DfConstantType) { + DfType antiType = rightType.tryNegate(); + if (antiType != null && !meetDfType(dfaLeft, antiType)) return false; + } + } + if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaVariableValue && !isNegated) { + if (!equalizeTypesOnGetClass((DfaVariableValue)dfaLeft, (DfaVariableValue)dfaRight)) { + return false; } } @@ -1046,53 +887,27 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return !isNegated || (dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls()); } - if (dfaLeft instanceof DfaVariableValue && (dfaRight instanceof DfaVariableValue || dfaRight instanceof DfaConstValue) && - (type == RelationType.NE || type == RelationType.EQ)) { - DfaConstValue leftConstant = getConstantValue(dfaLeft, false); - DfaConstValue rightConstant = getConstantValue(dfaRight, false); - if (leftConstant != null && leftConstant.getValue() instanceof PsiType && rightConstant == null) { - assert dfaRight instanceof DfaVariableValue; // otherwise rightConstant is not-null - ThreeState result = processGetClass((DfaVariableValue)dfaRight, (PsiType)leftConstant.getValue(), isNegated); - if (result != ThreeState.UNSURE) return result.toBoolean(); + if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaVariableValue) { + if (type == RelationType.LT) { + if (!applyLessThanRelation(dfaLeft, dfaRight)) return false; + } else if (type == RelationType.GT) { + if (!applyLessThanRelation(dfaRight, dfaLeft)) return false; + } else { + if (!applyRelation(dfaLeft, dfaRight, isNegated)) return false; } - if (rightConstant != null && rightConstant.getValue() instanceof PsiType && leftConstant == null) { - ThreeState result = processGetClass((DfaVariableValue)dfaLeft, (PsiType)rightConstant.getValue(), isNegated); - if (result != ThreeState.UNSURE) return result.toBoolean(); - } - if (dfaRight instanceof DfaVariableValue && !isNegated) { - if (!equalizeTypesOnGetClass((DfaVariableValue)dfaLeft, (DfaVariableValue)dfaRight)) { - return false; - } - } - } - - if (isNull(dfaLeft) && isNotNull(dfaRight) || isNull(dfaRight) && isNotNull(dfaLeft)) { - return isNegated; - } - - if (dfaLeft instanceof DfaVariableValue) { - updateVarStateOnComparison((DfaVariableValue)dfaLeft, dfaRight, isNegated); - } - if (dfaRight instanceof DfaVariableValue) { - updateVarStateOnComparison((DfaVariableValue)dfaRight, dfaLeft, isNegated); - } - - if (type == RelationType.LT) { - if (!applyLessThanRelation(dfaLeft, dfaRight)) return false; - } else if (type == RelationType.GT) { - if (!applyLessThanRelation(dfaRight, dfaLeft)) return false; - } else { - if (!isNegated && !applySpecialFieldEquivalence(dfaLeft, dfaRight)) { - return false; - } - if (!applyRelation(dfaLeft, dfaRight, isNegated)) return false; - } - if (!checkCompareWithBooleanLiteral(dfaLeft, dfaRight, isNegated)) { - return false; } return applyUnboxedRelation(dfaLeft, dfaRight, isNegated); } + private void checkEphemeral(DfaValue left, DfaValue right) { + if (getDfType(right) == DfTypes.NULL) { + DfaNullability nullability = DfaNullability.fromDfType(getDfType(left)); + if (nullability == DfaNullability.UNKNOWN || nullability == DfaNullability.FLUSHED) { + markEphemeral(); + } + } + } + private boolean equalizeTypesOnGetClass(DfaVariableValue dfaLeft, DfaVariableValue dfaRight) { PsiModifierListOwner leftPsi = dfaLeft.getPsiVariable(); PsiModifierListOwner rightPsi = dfaRight.getPsiVariable(); @@ -1100,17 +915,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaVariableValue leftQualifier = dfaLeft.getQualifier(); DfaVariableValue rightQualifier = dfaRight.getQualifier(); if (leftQualifier == null || rightQualifier == null || leftQualifier == rightQualifier) return true; - TypeConstraint leftType = getValueFact(leftQualifier, DfaFactType.TYPE_CONSTRAINT); - TypeConstraint rightType = getValueFact(rightQualifier, DfaFactType.TYPE_CONSTRAINT); - return applyFact(leftQualifier, DfaFactType.TYPE_CONSTRAINT, rightType) && - applyFact(rightQualifier, DfaFactType.TYPE_CONSTRAINT, leftType); + TypeConstraint leftType = TypeConstraint.fromDfType(getDfType(leftQualifier)); + TypeConstraint rightType = TypeConstraint.fromDfType(getDfType(rightQualifier)); + return meetDfType(leftQualifier, rightType.asDfType()) && meetDfType(rightQualifier, leftType.asDfType()); } @NotNull private ThreeState processGetClass(DfaVariableValue variable, PsiType value, boolean negated) { EqClass eqClass = getEqClass(variable); - List variables = eqClass == null ? Collections.singletonList(variable) - : eqClass.getVariables(false); + List variables = eqClass == null ? Collections.singletonList(variable) : eqClass.asList(); boolean hasUnprocessed = false; for (DfaVariableValue var : variables) { PsiModifierListOwner psi = var.getPsiVariable(); @@ -1140,35 +953,49 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return ThreeState.fromBoolean(negated); } if (!negated) { - return ThreeState.fromBoolean(applyFact(qualifier, DfaFactType.TYPE_CONSTRAINT, constraint)); + return ThreeState.fromBoolean(meetDfType(qualifier, constraint.asDfType())); } - TypeConstraint existingConstraint = getValueFact(qualifier, DfaFactType.TYPE_CONSTRAINT); - if (existingConstraint != null && existingConstraint.isExact()) { + TypeConstraint existingConstraint = TypeConstraint.fromDfType(getDfType(qualifier)); + if (existingConstraint.isExact()) { return ThreeState.fromBoolean(!existingConstraint.equals(constraint)); } if (dfaType.asConstraint().isExact()) { // final class return ThreeState.fromBoolean( - applyFact(qualifier, DfaFactType.TYPE_CONSTRAINT, TypeConstraint.empty().withNotInstanceofValue(dfaType))); + meetDfType(qualifier, Objects.requireNonNull(TypeConstraint.empty().withNotInstanceofValue(dfaType)).asDfType())); } return ThreeState.UNSURE; } + private boolean propagateConstant(DfaVariableValue value, DfConstantType constant) { + if (constant.getValue() instanceof PsiType) { + if (processGetClass(value, (PsiType)constant.getValue(), false) == ThreeState.NO) { + return false; + } + } + DfType dfType = constant.tryNegate(); + if (dfType == null) return true; + EqClass eqClass = getEqClass(value); + if (eqClass == null) return true; + for (DistinctPairSet.DistinctPair pair : getDistinctClassPairs().toArray(new DistinctPairSet.DistinctPair[0])) { + EqClass other = pair.getFirst() == eqClass ? pair.getSecond() : pair.getSecond() == eqClass ? pair.getFirst() : null; + if (other != null) { + for (DfaVariableValue var : other.asList()) { + if (!meetDfType(var, dfType)) return false; + } + } + } + return true; + } + private boolean applyRangeToRelatedValues(DfaValue value, LongRangeSet appliedRange) { EqClass eqClass = getEqClass(value); - if (eqClass != null) { - if (!applyRelationRangeToClass(eqClass, appliedRange, RelationType.EQ)) return false; - for (DistinctPairSet.DistinctPair pair : getDistinctClassPairs().toArray(new DistinctPairSet.DistinctPair[0])) { - if (pair.isOrdered()) { - if (pair.getFirst() == eqClass) { - if (!applyRelationRangeToClass(pair.getSecond(), appliedRange, RelationType.GT)) return false; - } else if(pair.getSecond() == eqClass) { - if (!applyRelationRangeToClass(pair.getFirst(), appliedRange, RelationType.LT)) return false; - } - } else if(appliedRange.getConstantValue() != null) { - EqClass other = pair.getFirst() == eqClass ? pair.getSecond() : pair.getSecond() == eqClass ? pair.getFirst() : null; - if (other != null) { - if (!applyRelationRangeToClass(other, appliedRange, RelationType.NE)) return false; - } + if (eqClass == null) return true; + for (DistinctPairSet.DistinctPair pair : getDistinctClassPairs().toArray(new DistinctPairSet.DistinctPair[0])) { + if (pair.isOrdered()) { + if (pair.getFirst() == eqClass) { + if (!applyRelationRangeToClass(pair.getSecond(), appliedRange, RelationType.GT)) return false; + } else if(pair.getSecond() == eqClass) { + if (!applyRelationRangeToClass(pair.getFirst(), appliedRange, RelationType.LT)) return false; } } } @@ -1177,8 +1004,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private boolean applyRelationRangeToClass(EqClass eqClass, LongRangeSet range, RelationType relationType) { LongRangeSet appliedRange = range.fromRelation(relationType); - for (DfaVariableValue var : eqClass.getVariables(false)) { - if (!applyFact(var, DfaFactType.RANGE, appliedRange)) return false; + for (DfaVariableValue var : eqClass.asList()) { + DfType rangeType = DfTypes.rangeClamped(appliedRange, PsiType.LONG.equals(var.getType())); + if (!meetDfType(var, rangeType)) return false; } return true; } @@ -1189,14 +1017,14 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (field == null) return null; DfaValue leftValue = field.createValue(myFactory, left); DfaValue rightValue = field.createValue(myFactory, right); - if (isNaN(leftValue) || isNaN(rightValue)) return null; - return rightValue.equals(field.getDefaultValue(myFactory, false)) ? null : Couple.of(leftValue, rightValue); + return Couple.of(leftValue, rightValue); } private boolean applySpecialFieldEquivalence(@NotNull DfaValue left, @NotNull DfaValue right) { Couple pair = left instanceof DfaVariableValue ? getSpecialEquivalencePair((DfaVariableValue)left, right) : right instanceof DfaVariableValue ? getSpecialEquivalencePair((DfaVariableValue)right, left) : null; - return pair == null || applyCondition(pair.getFirst().eq(pair.getSecond())); + if (pair == null || isNaN(pair.getFirst()) || isNaN(pair.getSecond())) return true; + return applyCondition(pair.getFirst().eq(pair.getSecond())); } private boolean applyUnboxedRelation(@NotNull DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { @@ -1214,10 +1042,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaValue unboxedLeft = SpecialField.UNBOX.createValue(myFactory, dfaLeft); DfaValue unboxedRight = SpecialField.UNBOX.createValue(myFactory, dfaRight); - DfaConstValue leftConst = getConstantValue(unboxedLeft); - DfaConstValue rightConst = getConstantValue(unboxedRight); - if (leftConst != null && rightConst != null) { - return Objects.equals(leftConst.getValue(), rightConst.getValue()) != negated; + DfType leftDfType = getDfType(unboxedLeft); + DfType rightDfType = getDfType(unboxedRight); + if (leftDfType instanceof DfConstantType && rightDfType instanceof DfConstantType) { + return leftDfType.equals(rightDfType) != negated; } if (negated && (PsiType.FLOAT.equals(unboxedLeft.getType()) || PsiType.DOUBLE.equals(unboxedLeft.getType()))) { // If floating point wrappers are not equal, unboxed versions could still be equal if they are 0.0 and -0.0 @@ -1228,93 +1056,47 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Nullable private PsiType getPsiType(@NotNull DfaValue value) { - TypeConstraint constraint = getValueFact(value, DfaFactType.TYPE_CONSTRAINT); - if (constraint != null) { - PsiType type = constraint.getPsiType(); - if (type != null) { - return type; - } - } - return value.getType(); + PsiType type = DfaTypeValue.toPsiType(getDfType(value)); + return type == null ? value.getType() : type; } - private boolean checkCompareWithBooleanLiteral(DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { - if (dfaRight instanceof DfaConstValue) { - Object constVal = ((DfaConstValue)dfaRight).getValue(); - if (constVal instanceof Boolean) { - boolean boolValue = (Boolean)constVal; - return applyRelation(dfaLeft, myFactory.getBoolean(!boolValue), !negated) && - applyRelation(dfaLeft, myFactory.getBoolean(boolValue), negated); - } - } - return true; + private static boolean isNaN(final DfaValue dfa) { + return dfa != null && isNaN(dfa.getDfType()); } - static boolean isNaN(final DfaValue dfa) { - return dfa instanceof DfaConstValue && DfaUtil.isNaN(((DfaConstValue)dfa).getValue()); + private static boolean canBeNaN(@NotNull DfType dfType) { + return dfType.isSuperType(DfTypes.floatValue(Float.NaN)) || dfType.isSuperType(DfTypes.doubleValue(Double.NaN)); + } + + private static boolean isNaN(DfType type) { + return type instanceof DfConstantType && DfaUtil.isNaN(((DfConstantType)type).getValue()); } private boolean applyRelation(@NotNull final DfaValue dfaLeft, @NotNull final DfaValue dfaRight, boolean isNegated) { - // DfaConstValue || DfaVariableValue - Integer c1Index = getOrCreateEqClassIndex(dfaLeft); - Integer c2Index = getOrCreateEqClassIndex(dfaRight); - if (c1Index == null || c2Index == null) return true; - if (c1Index.equals(c2Index)) return !isNegated; + if (!(dfaLeft instanceof DfaVariableValue) || !(dfaRight instanceof DfaVariableValue)) return true; + int c1Index = getOrCreateEqClassIndex((DfaVariableValue)dfaLeft); + int c2Index = getOrCreateEqClassIndex((DfaVariableValue)dfaRight); + if (c1Index == c2Index) return !isNegated; - RelationType constantRelation = getConstantRelation(dfaLeft, dfaRight); - if (constantRelation != null) { - return (constantRelation == RelationType.EQ) != isNegated; - } if (!isNegated) { //Equals if (isUnstableValue(dfaLeft) || isUnstableValue(dfaRight)) return true; - if (!uniteClasses(dfaLeft, dfaRight)) return false; - - for (Iterator iterator = myDistinctClasses.iterator(); iterator.hasNext(); ) { - DistinctPairSet.DistinctPair pair = iterator.next(); - DfaConstValue const1 = pair.getFirst().findConstant(); - DfaConstValue const2 = pair.getSecond().findConstant(); - if (const1 != null && const2 != null && !preserveConstantDistinction(const1.getValue(), const2.getValue())) { - iterator.remove(); - } - } - myCachedNonTrivialEqClasses = null; + if (!uniteClasses((DfaVariableValue)dfaLeft, (DfaVariableValue)dfaRight)) return false; } else { // Not Equals - if (isNull(dfaLeft) && isPrimitive(dfaRight) || isNull(dfaRight) && isPrimitive(dfaLeft)) return true; - if (applyBooleanInequality(dfaLeft, dfaRight) || - applyBooleanInequality(dfaRight, dfaLeft)) { - return true; - } myDistinctClasses.addUnordered(c1Index, c2Index); } + myCachedNonTrivialEqClasses = null; myCachedHash = null; return true; } - private boolean applyBooleanInequality(DfaValue var, DfaValue value) { - if (!(var instanceof DfaVariableValue) || - !PsiType.BOOLEAN.equals(var.getType()) || - ((DfaVariableValue)var).getDescriptor() == SpecialField.UNBOX) { - return false; - } - if (!(value instanceof DfaConstValue)) return false; - Boolean constValue = ObjectUtils.tryCast(((DfaConstValue)value).getValue(), Boolean.class); - return constValue != null && applyRelation(var, myFactory.getBoolean(!constValue), false); - } - private boolean applyLessThanRelation(@NotNull final DfaValue dfaLeft, @NotNull final DfaValue dfaRight) { - // DfaConstValue || DfaVariableValue - Integer c1Index = getOrCreateEqClassIndex(dfaLeft); - Integer c2Index = getOrCreateEqClassIndex(dfaRight); - if (c1Index == null || c2Index == null) return true; - if (c1Index.equals(c2Index)) return false; + if (!(dfaLeft instanceof DfaVariableValue) || !(dfaRight instanceof DfaVariableValue)) return true; + int c1Index = getOrCreateEqClassIndex((DfaVariableValue)dfaLeft); + int c2Index = getOrCreateEqClassIndex((DfaVariableValue)dfaRight); + if (c1Index == c2Index) return false; - RelationType constantRelation = getConstantRelation(dfaLeft, dfaRight); - if (constantRelation != null) { - return constantRelation == RelationType.LT; - } - if (isNull(dfaLeft) && isPrimitive(dfaRight) || isNull(dfaRight) && isPrimitive(dfaLeft)) return true; myCachedHash = null; return myDistinctClasses.addOrdered(c1Index, c2Index); } @@ -1337,77 +1119,28 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return true; } - private static boolean isPrimitive(DfaValue value) { - return value instanceof DfaVariableValue && - value.getType() instanceof PsiPrimitiveType && - !PsiType.VOID.equals(value.getType()); // void is used for temporary variables sometimes - } - - private static boolean preserveConstantDistinction(final Object c1, final Object c2) { - return c1 == null && c2 instanceof PsiVariable || - c2 == null && c1 instanceof PsiVariable; - } - @Nullable - private RelationType getConstantRelation(DfaValue val1, DfaValue val2) { - DfaConstValue const1 = getConstantValue(val1, false); - DfaConstValue const2 = getConstantValue(val2, false); - if (const1 == null || const2 == null) return null; - Number value1 = ObjectUtils.tryCast(const1.getValue(), Number.class); - Number value2 = ObjectUtils.tryCast(const2.getValue(), Number.class); + private static RelationType getFloatingConstantRelation(DfType leftType, DfType rightType) { + Number value1 = DfConstantType.getConstantOfType(leftType, Number.class); + Number value2 = DfConstantType.getConstantOfType(rightType, Number.class); if (value1 == null || value2 == null) return null; - int cmp; - if (value1 instanceof Long && value2 instanceof Long) { - cmp = Long.compare((Long)value1, (Long)value2); - } else { - double double1 = value1.doubleValue(); - double double2 = value2.doubleValue(); - if (double1 == 0.0 && double2 == 0.0) return RelationType.EQ; - cmp = Double.compare(double1, double2); - } + double double1 = value1.doubleValue(); + double double2 = value2.doubleValue(); + if (double1 == 0.0 && double2 == 0.0) return RelationType.EQ; + int cmp = Double.compare(double1, double2); return cmp == 0 ? RelationType.EQ : cmp < 0 ? RelationType.LT : RelationType.GT; } @Override public boolean checkNotNullable(DfaValue value) { - if (value == myFactory.getConstFactory().getNull()) return false; - if (value instanceof DfaFactMapValue && DfaNullability.isNullable(((DfaFactMapValue)value).getFacts())) return false; - - if (value instanceof DfaVariableValue) { - DfaVariableValue varValue = (DfaVariableValue)value; - if (varValue.getType() instanceof PsiPrimitiveType) return true; - if (isNotNull(varValue)) return true; - return getVariableState(varValue).getNullability() != Nullability.NULLABLE; - } - return true; - } - - @Override - @Nullable - public T getValueFact(@NotNull DfaValue value, @NotNull DfaFactType factType) { - if (value instanceof DfaBinOpValue && factType == DfaFactType.RANGE) { - //noinspection unchecked - return (T)getBinOpRange((DfaBinOpValue)value); - } - if (value instanceof DfaVariableValue) { - DfaVariableValue var = (DfaVariableValue)value; - DfaVariableState state = getExistingVariableState(var); - if (state != null) { - T fact = state.getFact(factType); - if (fact != null) { - return fact; - } - } - value = resolveVariableValue(var); - } - return factType.fromDfaValue(value); + DfaNullability nullability = DfaNullability.fromDfType(getDfType(value)); + return nullability != DfaNullability.NULL && nullability != DfaNullability.NULLABLE; } @Nullable public LongRangeSet getBinOpRange(DfaBinOpValue binOp) { - LongRangeSet left = getValueFact(binOp.getLeft(), DfaFactType.RANGE); - LongRangeSet right = getValueFact(binOp.getRight(), DfaFactType.RANGE); - if (left == null || right == null) return null; + LongRangeSet left = DfLongType.extractRange(getDfType(binOp.getLeft())); + LongRangeSet right = DfLongType.extractRange(getDfType(binOp.getRight())); boolean isLong = PsiType.LONG.equals(binOp.getType()); LongRangeSet result = left.binOpFromToken(binOp.getTokenType(), right, isLong); if (result != null && binOp.getOperation() == DfaBinOpValue.BinOp.MINUS) { @@ -1430,49 +1163,30 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return result; } - @Override - public void forceVariableFact(@NotNull DfaVariableValue var, @NotNull DfaFactType factType, @Nullable T value) { - DfaVariableState state = getVariableState(var); - if (factType.equals(DfaFactType.NULLABILITY)) { - if (value == DfaNullability.NOT_NULL && isNull(var) || - value == DfaNullability.NULLABLE && isNotNull(var)) { - removeEquivalenceForVariableAndWrappers(var); - } - } - setVariableState(var, state.withFact(factType, value)); - updateEqClassesByState(var); - } - @NotNull - private DfaValue resolveVariableValue(DfaVariableValue var) { - DfaConstValue constValue = getConstantValue(var, false); - if (constValue != null) { - return constValue; + @Override + public DfType getUnboxedDfType(@NotNull DfaValue value) { + if (value instanceof DfaBoxedValue) { + return getDfType(((DfaBoxedValue)value).getWrappedValue()); } - return var; + if (value instanceof DfaVariableValue && TypeConversionUtil.isPrimitiveWrapper(value.getType())) { + return getDfType(SpecialField.UNBOX.createValue(myFactory, value)); + } + return getDfType(value); } @NotNull @Override - public DfaFactMap getFacts(@NotNull DfaVariableValue variable) { - return getVariableState(variable).myFactMap; - } - - DfaFactMap getFactMap(@NotNull DfaValue value) { - if (value instanceof DfaVariableValue) { - DfaVariableState state = getExistingVariableState((DfaVariableValue)value); - if (state != null) { - return state.myFactMap; - } - value = resolveVariableValue((DfaVariableValue)value); - if (value instanceof DfaVariableValue) { - return getDefaultState((DfaVariableValue)value).myFactMap; - } - } + public DfType getDfType(@NotNull DfaValue value) { if (value instanceof DfaBinOpValue) { - return DfaFactMap.EMPTY.with(DfaFactType.RANGE, getValueFact(value, DfaFactType.RANGE)); + LongRangeSet range = getBinOpRange((DfaBinOpValue)value); + if (range == null) range = LongRangeSet.all(); + return ((DfaBinOpValue)value).getDfType().meetRange(range); } - return DfaFactMap.fromDfaValue(value); + if (value instanceof DfaVariableValue) { + return getVariableState((DfaVariableValue)value).myDfType; + } + return value.getDfType(); } void setVariableState(@NotNull DfaVariableValue dfaVar, @NotNull DfaVariableState state) { @@ -1488,9 +1202,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { protected void updateEquivalentVariables(DfaVariableValue dfaVar, DfaVariableState state) { EqClass eqClass = getEqClass(dfaVar); if (eqClass != null) { - for (DfaValue value : eqClass.getMemberValues()) { - if (value != dfaVar && value instanceof DfaVariableValue) { - setVariableState((DfaVariableValue)value, state); + for (DfaVariableValue value : eqClass.asList()) { + if (value != dfaVar) { + setVariableState(value, state); } } } @@ -1561,26 +1275,24 @@ public class DfaMemoryStateImpl implements DfaMemoryState { Set vars = new LinkedHashSet<>(getChangedVariables()); for (EqClass aClass : myEqClasses) { if (aClass != null) { - vars.addAll(aClass.getVariables(true)); + ContainerUtil.addAll(vars, aClass); } } for (DfaVariableValue value : vars) { if (!value.isFlushableByCalls()) continue; DfaVariableValue qualifier = value.getQualifier(); if (qualifier != null) { - if (getValueFact(qualifier, DfaFactType.MUTABILITY) == Mutability.UNMODIFIABLE || - Boolean.TRUE.equals(getValueFact(qualifier, DfaFactType.LOCALITY))) { - continue; - } + DfReferenceType dfType = ObjectUtils.tryCast(getDfType(qualifier), DfReferenceType.class); + if (dfType != null && (dfType.getMutability() == Mutability.UNMODIFIABLE || dfType.isLocal())) continue; } doFlush(value, shouldMarkFlushed(value)); } myStack.replaceAll(val -> { - if (val instanceof DfaFactMapValue) { - DfaFactMapValue factMapValue = (DfaFactMapValue)val; - SpecialFieldValue sfValue = factMapValue.get(DfaFactType.SPECIAL_FIELD_VALUE); - if (sfValue != null && !sfValue.getField().isStable() && factMapValue.get(DfaFactType.MUTABILITY) != Mutability.UNMODIFIABLE) { - return factMapValue.withFact(DfaFactType.SPECIAL_FIELD_VALUE, null); + DfType type = val.getDfType(); + if (type instanceof DfReferenceType) { + SpecialField field = ((DfReferenceType)type).getSpecialField(); + if (field != null && !field.isStable() && ((DfReferenceType)type).getMutability() != Mutability.UNMODIFIABLE) { + return myFactory.fromDfType(((DfReferenceType)type).dropSpecialField()); } } return val; @@ -1589,7 +1301,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private boolean shouldMarkFlushed(@NotNull DfaVariableValue value) { if (value.getInherentNullability() != Nullability.NULLABLE) return false; - return getVariableState(value).getFact(DfaFactType.NULLABILITY) == DfaNullability.FLUSHED || isNull(value) || isNotNull(value); + return DfaNullability.fromDfType(getVariableState(value).myDfType) == DfaNullability.FLUSHED || isNull(value) || isNotNull(value); } @NotNull @@ -1605,7 +1317,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { protected void flushVariable(@NotNull final DfaVariableValue variable, boolean shouldMarkFlushed) { EqClass eqClass = variable.getDependentVariables().isEmpty() ? null : getEqClass(variable); DfaVariableValue newCanonical = - eqClass == null ? null : StreamEx.of(eqClass.getVariables(false)).without(variable).min(EqClass.CANONICAL_VARIABLE_COMPARATOR) + eqClass == null ? null : StreamEx.of(eqClass.iterator()).without(variable).min(EqClass.CANONICAL_VARIABLE_COMPARATOR) .filter(candidate -> !candidate.dependsOn(variable)) .orElse(null); myStack.replaceAll(value -> handleStackValueOnVariableFlush(value, variable, newCanonical)); @@ -1634,18 +1346,18 @@ public class DfaMemoryStateImpl implements DfaMemoryState { void doFlush(@NotNull DfaVariableValue var, boolean markFlushed) { if(isNull(var)) { - myStack.replaceAll(val -> val == var ? myFactory.getConstFactory().getNull() : val); + myStack.replaceAll(val -> val == var ? myFactory.getNull() : val); } - removeEquivalenceForVariableAndWrappers(var); + removeEquivalence(var); myVariableStates.remove(var); if (markFlushed) { - setVariableState(var, getVariableState(var).withFact(DfaFactType.NULLABILITY, DfaNullability.FLUSHED)); + setVariableState(var, getVariableState(var).withNullability(DfaNullability.FLUSHED)); } myCachedHash = null; } - private void removeEquivalence(DfaValue var) { + void removeEquivalence(DfaVariableValue var) { int varID = var.getID(); Integer varClassIndex = myIdToEqClassesIndices.get(varID); if (varClassIndex == null) { @@ -1674,15 +1386,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } } - else if (varClass.containsConstantsOnly()) { - for (Iterator iterator = myDistinctClasses.iterator(); iterator.hasNext(); ) { - DistinctPairSet.DistinctPair pair = iterator.next(); - EqClass other = pair.getOtherClass(varClassIndex); - if (other != null && other.containsConstantsOnly()) { - iterator.remove(); - } - } - } else { DfaVariableValue newCanonical = varClass.getCanonicalVariable(); if (newCanonical != null && previousCanonical != null && previousCanonical != newCanonical && @@ -1699,14 +1402,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState { myCachedHash = null; } - void removeEquivalenceForVariableAndWrappers(@NotNull DfaVariableValue var) { - removeEquivalence(var); - DfaValue wrapped = myFactory.getBoxedFactory().getBoxedIfExists(var); - if (wrapped != null) { - removeEquivalence(wrapped); - } - } - /** * @return a mergeability key. If two states return the same key, then states could be merged via {@link #merge(DfaMemoryStateImpl)}. */ @@ -1775,15 +1470,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { for (DfaVariableValue var : vars) { DfaVariableState state = getVariableState(var); DfaVariableState otherState = other.getVariableState(var); - DfaFactMap result = state.myFactMap.unite(otherState.myFactMap); + DfType result = state.myDfType.join(otherState.myDfType); Nullability nullability = state.getNullability(); Nullability otherNullability = otherState.getNullability(); if (nullability != otherNullability && (nullability == Nullability.NULLABLE || otherNullability == Nullability.NULLABLE)) { // When merging nullable with something we cannot warn about nullability violation anymore // because we lose the information about coherent state, thus noise warnings could be produced - result = result.with(DfaFactType.NULLABILITY, DfaNullability.FLUSHED); + result = ((DfReferenceType)result).dropNullability().meet(DfaNullability.FLUSHED.asDfType()); } - setVariableState(var, state.withFacts(result)); + setVariableState(var, state.createCopy(result)); } } @@ -1864,7 +1559,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private List splitEqClass(EqClass eqClass, DfaMemoryStateImpl other) { TIntObjectHashMap groupsInClasses = new TIntObjectHashMap<>(); List groups = new ArrayList<>(); - for (DfaValue value : eqClass.getMemberValues()) { + for (DfaVariableValue value : eqClass.asList()) { int otherClass = other.getEqClassIndex(value); EqClass list; if (otherClass == -1) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaNullability.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaNullability.java index 7ce6abd2078e..de95b95f28e2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaNullability.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaNullability.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java index cabc6c5603ec..67fc8a78ac57 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java @@ -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 { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index dbe2141bf694..e1f6281aa448 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java index a9b514c0a6d6..27f9d451be9f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java @@ -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 - DfaVariableState withFact(DfaFactType type, T value) { - return withFacts(myFactMap.with(type, value)); - } - - @NotNull - DfaVariableState withoutFact(DfaFactType 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 - DfaVariableState intersectFact(DfaFactType 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 getFact(@NotNull DfaFactType factType) { - return myFactMap.get(factType); + return TypeConstraint.fromDfType(myDfType); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java index 2328253bcf7e..2d05472a0c36 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java @@ -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 { 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 getVariables(boolean unwrap) { - List 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 asList() { + List 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 getMemberValues() { - final List result = new ArrayList<>(size()); - forEach(id -> { - DfaValue value = myFactory.getValue(id); - result.add(value); - return true; - }); - return result; - } - - @Nullable - DfaConstValue findConstant() { - Ref result = new Ref<>(); - forEach(id -> { - DfaValue value = myFactory.getValue(id); - if (value instanceof DfaConstValue) { - result.set((DfaConstValue)value); - return false; + @NotNull + @Override + public Iterator iterator() { + return new Iterator() { + 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++)); + } + }; } - } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index 0bf2cb84cc08..afab9a052cc4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -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 handleTestFrameworks(int paramCount, String className, String methodName, + private static List 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(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java index d585faefd624..6bcc766f7153 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Mutability.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Mutability.java index 2235b47b074b..68c8ba063a6a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Mutability.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Mutability.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java index 9229ddae4414..0625083a8d53 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java @@ -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())); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java index 1f52f2819057..6ca26566604c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java index be13b039119a..85aabeb15729 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java @@ -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()); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialFieldValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialFieldValue.java index 721919915fb8..e37ad1ef2a76 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialFieldValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialFieldValue.java @@ -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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index c51b58da48de..87fb495c4338 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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 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 states = transfer.dispatch(castFail, runner); for (DfaInstructionState cceState : states) { @@ -320,7 +336,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { Set 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 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 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 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 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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java index da619ba611b1..65a0b4329425 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java @@ -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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java index 53a86102420b..87c4d7fbdccd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java @@ -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 createFactToStateMap(@NotNull List states) { MultiMap statesByFact = MultiMap.createLinked(); - Map>> constantVars = new HashMap<>(); + Map>> 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>> constantVars, + Map>> constantVars, Collection positiveStates, Collection 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> 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 doGetFacts(DfaMemoryStateImpl state) { Set result = new LinkedHashSet<>(); - IdentityHashMap classInfo = new IdentityHashMap<>(); - for (EqClass eqClass : state.getNonTrivialEqClasses()) { - EqClassInfo info = classInfo.computeIfAbsent(eqClass, EqClassInfo::new); - DfaValue constant = info.constant; - List 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 vars; - final DfaConstValue constant; - - EqClassInfo(EqClass eqClass) { - vars = eqClass.getVariables(false); - constant = eqClass.findConstant(); - } - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingDfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingDfaMemoryState.java index 6b7ee7e5db3e..139546464e68 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingDfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingDfaMemoryState.java @@ -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> getRelations() { Map> result = new HashMap<>(); - for (EqClass eqClass : getNonTrivialEqClasses()) { - DfaConstValue constant = eqClass.findConstant(); - List vars = eqClass.getVariables(false); - for (DfaVariableValue var : vars) { - Set 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 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 firstVars = first.getVariables(false); - List 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 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 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 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 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()); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java index 26c36a766e27..e585f6fbad85 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java @@ -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 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 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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java index b2c1473ab37f..470103ba095e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java @@ -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 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 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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java index 10e4c1e434f5..28a4ef642576 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java @@ -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 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 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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt index 0e8833e1bf2a..b63d96703c5e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt @@ -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 } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java index 5ed4fbd7e1f8..a6849f848265 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java @@ -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)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java index cecba16a6c91..e0fe5110cd92 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -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() diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java index f783eaee79fb..a2679ed7af8a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java index 8587ab111c42..494268546af5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java @@ -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 { 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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java index b5fd36658852..8d4d73e3eb36 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -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) * diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfAntiConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfAntiConstantType.java new file mode 100644 index 000000000000..99332e11d27b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfAntiConstantType.java @@ -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 implements DfType { + final @NotNull Set myNotValues; + + DfAntiConstantType(@NotNull Set notValues) { + myNotValues = notValues; + } + + /** + * @return set of excluded constants + */ + @NotNull + public Set 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(", "); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanConstantType.java new file mode 100644 index 000000000000..712b5e8efb11 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanConstantType.java @@ -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 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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanType.java new file mode 100644 index 000000000000..65ef18f59578 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfBooleanType.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfConstantType.java new file mode 100644 index 000000000000..37b04b0334cd --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfConstantType.java @@ -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 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 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 getConstantOfType(@NotNull DfType dfType, @NotNull Class 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(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java new file mode 100644 index 000000000000..1a30547531fa --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java @@ -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 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())); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleNotValueType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleNotValueType.java new file mode 100644 index 000000000000..f43456574f82 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleNotValueType.java @@ -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 implements DfDoubleType { + DfDoubleNotValueType(Set 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 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 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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleType.java new file mode 100644 index 000000000000..0d83a1e19582 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleType.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java new file mode 100644 index 000000000000..4ceebc2f35c0 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java @@ -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 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())); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatNotValueType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatNotValueType.java new file mode 100644 index 000000000000..4039d72d3dbf --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatNotValueType.java @@ -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 implements DfFloatType { + DfFloatNotValueType(Set 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 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 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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatType.java new file mode 100644 index 000000000000..735f594bd9f0 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatType.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatingPointType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatingPointType.java new file mode 100644 index 000000000000..4bd768cb2773 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatingPointType.java @@ -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 { +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfGenericObjectType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfGenericObjectType.java new file mode 100644 index 000000000000..2d18e8236e64 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfGenericObjectType.java @@ -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 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 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 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 getNotValues() { + if (myNullability == DfaNullability.NOT_NULL) { + Set 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 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 notValues = myNotValues; + if (type instanceof DfGenericObjectType) { + Set 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 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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntConstantType.java new file mode 100644 index 000000000000..741f775a1535 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntConstantType.java @@ -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 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()); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntRangeType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntRangeType.java new file mode 100644 index 000000000000..1fe83e1e89b7 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntRangeType.java @@ -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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java new file mode 100644 index 000000000000..ce7745eeba62 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java new file mode 100644 index 000000000000..25efcdeaf207 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java @@ -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); +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongConstantType.java new file mode 100644 index 000000000000..cef79ed2c8d7 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongConstantType.java @@ -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 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()); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongRangeType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongRangeType.java new file mode 100644 index 000000000000..a89213c8f9e8 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongRangeType.java @@ -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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java new file mode 100644 index 000000000000..3fa774b955a9 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java @@ -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(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfNullConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfNullConstantType.java new file mode 100644 index 000000000000..9dadfe13b9f6 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfNullConstantType.java @@ -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 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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java new file mode 100644 index 000000000000..cfed1d2cbabf --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java @@ -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(); +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java new file mode 100644 index 000000000000..08f7b3f81fa8 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java @@ -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 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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java new file mode 100644 index 000000000000..2e200dc88a3c --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java @@ -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(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java new file mode 100644 index 000000000000..e32da6c24435 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java new file mode 100644 index 000000000000..18f5baa67315 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java @@ -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(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 + *
{@code
+   * constraint.asDfType()
+   *   .meet(mutability.asDfType())
+   *   .meet(LOCAL_OBJECT)
+   *   .meet(specialField.asDfType(sfType))
+   * }
+ * + * + * @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); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java index 5f5946cf1a59..d937662098bf 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java index 686e88aea9e2..a7eacfc466e3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java @@ -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 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(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java index 07c748f38289..b074db2c3433 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java @@ -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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java deleted file mode 100644 index 06eef3a76253..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - -package com.intellij.codeInspection.dataFlow.value; - -import com.intellij.codeInspection.dataFlow.DfaUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTypesUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.ObjectUtils; -import com.siyeh.ig.psiutils.ExpressionUtils; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class DfaConstValue extends DfaValue { - private static final Throwable ourThrowable = new Throwable(); - private static final Object SENTINEL = ObjectUtils.sentinel("SENTINEL"); - public static class Factory { - private final DfaConstValue dfaNull; - private final DfaConstValue dfaFalse; - private final DfaConstValue dfaTrue; - private final DfaConstValue dfaFail; - private final DfaConstValue dfaSentinel; - private final DfaValueFactory myFactory; - private final Map 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); - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java index a2a7bba05955..8783d3fee9b1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -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) && diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaFactMapValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaFactMapValue.java deleted file mode 100644 index 7684812ab60d..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaFactMapValue.java +++ /dev/null @@ -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 DfaValue withFact(@NotNull DfaFactType 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 get(@NotNull DfaFactType factType) { - return myFacts.get(factType); - } - - @Override - public String toString() { - return myFacts.toString(); - } - - public static class Factory { - private final DfaValueFactory myFactory; - private final Map myValues = new HashMap<>(); - - Factory(DfaValueFactory factory) { - myFactory = factory; - } - - public DfaValue createValue(@NotNull DfaFactType 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)); - } - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaInstanceofValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaInstanceofValue.java index 240f8bf06bcb..ba6c45fcfb43 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaInstanceofValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaInstanceofValue.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelation.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelation.java index 6b8f375cbc18..4f6249afa403 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelation.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelation.java @@ -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() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java new file mode 100644 index 000000000000..e4f4da530b93 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java @@ -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 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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaUnknownValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaUnknownValue.java deleted file mode 100644 index 6a1d4d5f96a5..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaUnknownValue.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.codeInspection.dataFlow.value; - -import org.jetbrains.annotations.NotNull; - -public class DfaUnknownValue extends DfaValue { - private static class DfaUnknownValueHolder { - private static final DfaUnknownValue myInstance = new DfaUnknownValue(); - } - - @NotNull - public static DfaUnknownValue getInstance() { - return DfaUnknownValueHolder.myInstance; - } - - private DfaUnknownValue() { - super(null); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - public String toString() { - return ""; - } - - public boolean equals(Object obj) { - return obj == this; - } - - public int hashCode() { - return 0; - } - - @Override - public int getID() { - return 0; - } - -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java index 5aa60bc3a741..75e7c6ce9882 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java @@ -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())); } /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index a3d9f4650335..c724639a8d7b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -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 DfaValue withFact(@NotNull DfaValue value, @NotNull DfaFactType 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: + *
    + *
  • Integer/Long/Double/Float/Boolean (will be unboxed)
  • + *
  • Character/Byte/Short (will be unboxed and widened to int)
  • + *
  • String
  • + *
  • {@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)
  • + *
  • {@link PsiField} (a final field that contains a unique value, type must be a type of that field)
  • + *
  • {@link PsiType} (java.lang.Class object value, type must be java.lang.Class)
  • + *
+ * + * @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 DfaValue getFactValue(@NotNull DfaFactType factType, @Nullable T value) { - return getFactFactory().createValue(factType, value); + @NotNull + public DfaTypeValue fromDfType(@NotNull DfType dfType) { + return myTypeValueFactory.create(dfType); } public Collection 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 { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index f3f9520f3e06..a2f7652948ad 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -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 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() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java index ddbfa442c50e..fcee46204d39 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java @@ -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); } /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/SuspiciousCollectionsMethodCallsInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/SuspiciousCollectionsMethodCallsInspection.java index f6aa67261bd3..91f6308907b9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/SuspiciousCollectionsMethodCallsInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/SuspiciousCollectionsMethodCallsInspection.java @@ -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; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/hint/JavaTypeProvider.java b/java/java-impl/src/com/intellij/codeInsight/hint/JavaTypeProvider.java index e7496846d33c..9338b2633d66 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hint/JavaTypeProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/hint/JavaTypeProvider.java @@ -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 { expression = PsiUtil.skipParenthesizedExprDown(expression); if (expression == null) return ""; CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression); - String advancedTypeInfo = ""; + List> 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() { - @Override - public String apply(DfaFactType factType, T value) { - return formatFact(factType, value, type); - } - }).joining(); - } - List 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 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 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 - : "" + makeHtmlRow("Type", basicTypeEscaped) + advancedTypeInfo + "
"; - } - - private static String formatFact(@NotNull DfaFactType 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("", "", "
"); + } + return basicTypeEscaped; } private static String makeHtmlRow(@NotNull String titleText, String contentHtml) { diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/WrapWithUnmodifiableAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/WrapWithUnmodifiableAction.java index 2f94ad691c27..3de6a21fdaaa 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/WrapWithUnmodifiableAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/WrapWithUnmodifiableAction.java @@ -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); diff --git a/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java index 76fb6a8e3813..ac35da84eaef 100644 --- a/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java @@ -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 { diff --git a/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/SideEffectCalculator.java b/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/SideEffectCalculator.java index 25fe32b8fd9c..f10bec9d7f7f 100644 --- a/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/SideEffectCalculator.java +++ b/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/SideEffectCalculator.java @@ -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 diff --git a/java/java-impl/src/com/intellij/codeInspection/java18api/OptionalGetWithoutIsPresentInspection.java b/java/java-impl/src/com/intellij/codeInspection/java18api/OptionalGetWithoutIsPresentInspection.java index cdcbb84991c8..5ccb5017418d 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java18api/OptionalGetWithoutIsPresentInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/java18api/OptionalGetWithoutIsPresentInspection.java @@ -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()), diff --git a/java/java-tests/testData/inspection/dataFlow/ancient/WrongEqualTypes.java b/java/java-tests/testData/inspection/dataFlow/ancient/WrongEqualTypes.java index 835a261aba96..79bdda6293db 100644 --- a/java/java-tests/testData/inspection/dataFlow/ancient/WrongEqualTypes.java +++ b/java/java-tests/testData/inspection/dataFlow/ancient/WrongEqualTypes.java @@ -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 (c == null) {} + } + if (c == Calendar.getInstance()) {} } + + native Calendar getInstance(); } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java index 9390ca34bf16..b95f39a8e716 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java @@ -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; ib) { System.out.println("always"); } + assertThat("1", getBooleanWrapper(1), is(true)); + assertThat("2", getBooleanPrimitive(2), is(true)); assertThat("b is not true", b, 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))); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CompareEqualObjectWithNull.java b/java/java-tests/testData/inspection/dataFlow/fixture/CompareEqualObjectWithNull.java new file mode 100644 index 000000000000..5166761b1b36 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CompareEqualObjectWithNull.java @@ -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.hashCode()); + } + + void test2(Object obj) { + Object base = obj; + if (base != null) { + do { + base = getObject(obj); + } + while (base != null); + } + System.out.println(obj.hashCode()); + } + + void test3(Object obj) { + Object base = obj; + do { + base = getObject(obj); + } + while (base != null); + System.out.println(obj.hashCode()); + } + + native Object getObject(Object obj); +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LessThanRelations.java b/java/java-tests/testData/inspection/dataFlow/fixture/LessThanRelations.java index e25e2fa60336..e39f5a722f6b 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LessThanRelations.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LessThanRelations.java @@ -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 (t1 >= t2) 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 (t1 >= t2) 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 (to >= myTo) { + if (to >= myTo) { return new Range(myFrom, from - 1); } throw new RuntimeException("Impossible: " + this + ":" + other); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NewBoxedNumberEquality.java b/java/java-tests/testData/inspection/dataFlow/fixture/NewBoxedNumberEquality.java index 4172599f30fe..3cd2586e7703 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/NewBoxedNumberEquality.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NewBoxedNumberEquality.java @@ -37,4 +37,11 @@ class Testcase { Integer boxed = x; if (boxed == 5) {} } + + public void testUnboxObject(Object obj, int val) { + if (obj instanceof Integer) { + int objVal = (int)obj; + if (objVal == val) {} + } + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NullabilityAfterCastAndInstanceOf.java b/java/java-tests/testData/inspection/dataFlow/fixture/NullabilityAfterCastAndInstanceOf.java new file mode 100644 index 000000000000..87680b796936 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NullabilityAfterCastAndInstanceOf.java @@ -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()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java b/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java index 58010c2bb7ab..b2d62263211b 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java @@ -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 ? o3 : x; } interface Context {} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ReassignedAfterNullCheck.java b/java/java-tests/testData/inspection/dataFlow/fixture/ReassignedAfterNullCheck.java new file mode 100644 index 000000000000..89944f9eccea --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ReassignedAfterNullCheck.java @@ -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()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StaticConstantType.java b/java/java-tests/testData/inspection/dataFlow/fixture/StaticConstantType.java new file mode 100644 index 000000000000..7ae6fc993f40 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StaticConstantType.java @@ -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 {} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java index 86cbda30dda1..02fc86068753 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java @@ -275,7 +275,9 @@ public class StreamInlining { if (count3 == 1) {} long count4 = Stream.of(arr[0]).map(String::trim).count(); if (count4 == 1) {} - long count5 = Stream.of("foo", "bar", "baz", "qux").filter(s -> s.length() > 1).count(); + long count5 = Stream.of("foo", "bar", "baz", "qux").filter(s -> s.length() > 1).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 -> s.length() > 1).count(); if (count5 == 4) {} if (count5 < 0) {} if (count5 > 4) {} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StringEquality.java b/java/java-tests/testData/inspection/dataFlow/fixture/StringEquality.java index 4ae12c12e813..76f15a33d7c6 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StringEquality.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StringEquality.java @@ -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(s == s.length()) {} + if(s == s.length()) {} } void testTrim() { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/SwitchExpressionsNullabilityJava12.java b/java/java-tests/testData/inspection/dataFlow/fixture/SwitchExpressionsNullabilityJava12.java index 5e83c673255d..bf2e42a302ef 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/SwitchExpressionsNullabilityJava12.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/SwitchExpressionsNullabilityJava12.java @@ -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(((String)(switch(i) { case 1 -> a; case 2 -> "foo"; case 3 -> null; diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/WidenMulInLoop.java b/java/java-tests/testData/inspection/dataFlow/fixture/WidenMulInLoop.java index efd021957eb6..3802d8860b8f 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/WidenMulInLoop.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/WidenMulInLoop.java @@ -18,4 +18,10 @@ public class WidenMulInLoop { System.out.println(i); } } + + void test4(int b, int c) { + for (int i = 1; i < 100; i++) { + b = i * b / c; + } + } } diff --git a/java/java-tests/testData/inspection/dataFlow/tracker/ConstantStrings.java b/java/java-tests/testData/inspection/dataFlow/tracker/ConstantStrings.java index cedb1151b8d6..07e8bec557cf 100644 --- a/java/java-tests/testData/inspection/dataFlow/tracker/ConstantStrings.java +++ b/java/java-tests/testData/inspection/dataFlow/tracker/ConstantStrings.java @@ -1,6 +1,6 @@ /* Value is always false (s == null; line#11) - It's known that 's != null' from line #10 (!"foo".equals(s) && !"bar".equals(s); line#10) + 's' is known to be 'non-null' from line #10 (!"foo".equals(s) && !"bar".equals(s); line#10) */ import java.util.List; diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/hint/JavaTypeProviderTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/hint/JavaTypeProviderTest.java index b39ef0feeb12..82e6c18e6dd2 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/hint/JavaTypeProviderTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/hint/JavaTypeProviderTest.java @@ -113,8 +113,6 @@ public class JavaTypeProviderTest extends LightJavaCodeInsightTestCase { "" + "" + "" + - "" + - "" + "
Type:String
Value (one of):\"bar\", \"foo\"
Nullability:non-null
Length:3
"); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 5e98c724e827..07b86e85dbb9 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -672,4 +672,8 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testArraysAsList() { doTest(); } public void testArraycopy() { doTest(); } public void testEnumName() { doTest(); } + public void testStaticConstantType() { doTest(); } + public void testReassignedAfterNullCheck() { doTest(); } + public void testCompareEqualObjectWithNull() { doTest(); } + public void testNullabilityAfterCastAndInstanceOf() { doTest(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullityInferenceFromSourceTestCase.groovy b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullityInferenceFromSourceTestCase.groovy index 5a4ac645ad9b..f021aa00f83d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullityInferenceFromSourceTestCase.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullityInferenceFromSourceTestCase.groovy @@ -209,7 +209,7 @@ Object foo(Object o) { if (o == null) return o.hashCode(); return 2; } } void "test reassigned in switch"() { - assert inferNullability(parse("""String foo() { + assert inferNullability(parse("""String foo(int foo) { String res = "bar"; switch(foo) { case 1:res = getSomething(); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/CastToIncompatibleInterfaceInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/CastToIncompatibleInterfaceInspection.java index e1cdb1c3dfaa..0c1b57e99b03 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/CastToIncompatibleInterfaceInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/CastToIncompatibleInterfaceInspection.java @@ -16,7 +16,6 @@ package com.siyeh.ig.bugs; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.TypeConstraint; import com.intellij.codeInspection.dataFlow.value.DfaPsiType; import com.intellij.psi.*; @@ -79,9 +78,8 @@ public class CastToIncompatibleInterfaceInspection extends BaseInspection { if (InheritanceUtil.existsMutualSubclass(operandClass, castClass, isOnTheFly())) { return; } - TypeConstraint constraint = CommonDataflow.getExpressionFact(operand, DfaFactType.TYPE_CONSTRAINT); - if (constraint != null && - constraint.getInstanceofValues().stream().map(DfaPsiType::getPsiType).anyMatch(type -> castClassType.isAssignableFrom(type))) { + TypeConstraint constraint = TypeConstraint.fromDfType(CommonDataflow.getDfType(operand)); + if (constraint.getInstanceofValues().stream().map(DfaPsiType::getPsiType).anyMatch(type -> castClassType.isAssignableFrom(type))) { return; } registerError(castTypeElement); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java index 953925d5d472..9e86606a9c6d 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java @@ -17,6 +17,7 @@ package com.siyeh.ig.bugs; import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfIntType; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.psi.*; @@ -164,8 +165,7 @@ public class SuspiciousComparatorCompareInspection extends BaseInspection { @NotNull DfaMemoryState state) { if (owner != myOwner) return; myContexts.add(expression); - LongRangeSet range = state.getValueFact(value, DfaFactType.RANGE); - myRange = range == null ? LongRangeSet.all() : myRange.unite(range); + myRange = myRange.unite(DfIntType.extractRange(state.getDfType(value))); } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousSystemArraycopyInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousSystemArraycopyInspection.java index c7b7b795c9be..b9133cf04ce1 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousSystemArraycopyInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousSystemArraycopyInspection.java @@ -16,10 +16,9 @@ package com.siyeh.ig.bugs; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.SpecialField; -import com.intellij.codeInspection.dataFlow.SpecialFieldValue; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfIntType; import com.intellij.psi.*; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; @@ -115,17 +114,12 @@ public class SuspiciousSystemArraycopyInspection extends BaseInspection { @NotNull PsiMethodCallExpression call) { CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(src); if (result == null) return; - SpecialFieldValue srcFact = result.getExpressionFact(src, DfaFactType.SPECIAL_FIELD_VALUE); - if (srcFact == null) return; - SpecialFieldValue destFact = result.getExpressionFact(dest, DfaFactType.SPECIAL_FIELD_VALUE); - if (destFact == null) return; - LongRangeSet srcLengthSet = DfaFactType.RANGE.fromDfaValue(SpecialField.ARRAY_LENGTH.extract(srcFact)); - LongRangeSet destLengthSet = DfaFactType.RANGE.fromDfaValue(SpecialField.ARRAY_LENGTH.extract(destFact)); - LongRangeSet srcPosSet = result.getExpressionFact(srcPos, DfaFactType.RANGE); - LongRangeSet destPosSet = result.getExpressionFact(destPos, DfaFactType.RANGE); - LongRangeSet lengthSet = result.getExpressionFact(length, DfaFactType.RANGE); - if (srcLengthSet == null || destLengthSet == null || srcPosSet == null || destPosSet == null || lengthSet == null) return; + LongRangeSet srcLengthSet = DfIntType.extractRange(SpecialField.ARRAY_LENGTH.getFromQualifier(result.getDfType(src))); + LongRangeSet destLengthSet = DfIntType.extractRange(SpecialField.ARRAY_LENGTH.getFromQualifier(result.getDfType(dest))); + LongRangeSet srcPosSet = DfIntType.extractRange(result.getDfType(srcPos)); + LongRangeSet destPosSet = DfIntType.extractRange(result.getDfType(destPos)); + LongRangeSet lengthSet = DfIntType.extractRange(result.getDfType(length)); LongRangeSet srcPossibleLengthToCopy = srcLengthSet.minus(srcPosSet, false); LongRangeSet destPossibleLengthToCopy = destLengthSet.minus(destPosSet, false); long lengthMin = lengthSet.min(); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/controlflow/SimplifiableEqualsExpressionInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/controlflow/SimplifiableEqualsExpressionInspection.java index 12dac5b4adc8..60af2b2fe3e3 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/controlflow/SimplifiableEqualsExpressionInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/controlflow/SimplifiableEqualsExpressionInspection.java @@ -19,8 +19,7 @@ import com.intellij.codeInspection.CleanupLocalInspectionTool; import com.intellij.codeInspection.CommonQuickFixBundle; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; -import com.intellij.codeInspection.dataFlow.DfaNullability; +import com.intellij.codeInspection.dataFlow.types.DfTypes; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -241,7 +240,7 @@ public class SimplifiableEqualsExpressionInspection extends BaseInspection imple return REPORT_NON_CONSTANT && !VariableAccessUtils.variableIsUsed(variable, argument) && !SideEffectChecker.mayHaveSideEffects(argument) && - CommonDataflow.getExpressionFact(argument, DfaFactType.NULLABILITY) == DfaNullability.NOT_NULL; + !CommonDataflow.getDfType(argument).isSuperType(DfTypes.NULL); } } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java index 1a6825d3a643..21bef05862c6 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java @@ -16,8 +16,8 @@ package com.siyeh.ig.numeric; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfLongType; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -35,10 +35,10 @@ import javax.swing.*; public class CastThatLosesPrecisionInspection extends BaseInspection { - @SuppressWarnings({"PublicField"}) + @SuppressWarnings("PublicField") public boolean ignoreIntegerCharCasts = false; - @SuppressWarnings({"PublicField"}) + @SuppressWarnings("PublicField") public boolean ignoreOverflowingByteCasts = false; @Pattern(VALID_ID_PATTERN) @@ -120,18 +120,13 @@ public class CastThatLosesPrecisionInspection extends BaseInspection { LongRangeSet targetRange = LongRangeSet.fromType(castType); LongRangeSet lostRange = LongRangeSet.all(); if (targetRange != null && LongRangeSet.fromType(operandType) != null) { - LongRangeSet valueRange = getValueRange(operand); + LongRangeSet valueRange = DfLongType.extractRange(CommonDataflow.getDfType(operand)); lostRange = valueRange.subtract(targetRange); if (lostRange.isEmpty()) return; } registerError(castTypeElement, operandType, lostRange.max() < 0); } - private LongRangeSet getValueRange(@NotNull PsiExpression operand) { - LongRangeSet fact = CommonDataflow.getExpressionFact(operand, DfaFactType.RANGE); - return fact == null ? LongRangeSet.all() : fact; - } - private boolean valueIsContainableInType(Number value, PsiType type) { final long longValue = value.longValue(); final double doubleValue = value.doubleValue(); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/DivideByZeroInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/DivideByZeroInspection.java index 5d61bb72c549..24cb10be8b72 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/DivideByZeroInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/DivideByZeroInspection.java @@ -17,8 +17,8 @@ package com.siyeh.ig.numeric; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; -import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfConstantType; +import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; @@ -35,7 +35,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class DivideByZeroInspection extends BaseInspection { - private static final LongRangeSet ZERO_RANGE = LongRangeSet.point(0); @Pattern(VALID_ID_PATTERN) @Override @@ -110,8 +109,9 @@ public class DivideByZeroInspection extends BaseInspection { final double constantValue = ((Double)value).doubleValue(); return constantValue == 0.0; } - LongRangeSet range = CommonDataflow.getExpressionFact(expression, DfaFactType.RANGE); - return ZERO_RANGE.equals(range); + DfType dfType = CommonDataflow.getDfType(expression); + Number val = DfConstantType.getConstantOfType(dfType, Number.class); + return val != null && val.doubleValue() == 0.0; } private static class ReplaceWithNaNFix extends InspectionGadgetsFix { diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/IntegerMultiplicationImplicitCastToLongInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/IntegerMultiplicationImplicitCastToLongInspection.java index e2a42055cf94..5978007a6bd2 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/IntegerMultiplicationImplicitCastToLongInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/IntegerMultiplicationImplicitCastToLongInspection.java @@ -17,8 +17,8 @@ package com.siyeh.ig.numeric; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfLongType; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -61,7 +61,7 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe s_typesToCheck.add(CommonClassNames.JAVA_LANG_CHARACTER); } - @SuppressWarnings({"PublicField"}) + @SuppressWarnings("PublicField") public boolean ignoreNonOverflowingCompileTimeConstants = true; @Nullable @@ -250,8 +250,7 @@ public class IntegerMultiplicationImplicitCastToLongInspection extends BaseInspe if (dfr != null) { long min = 1, max = 1; for (PsiExpression operand : operands) { - LongRangeSet set = dfr.getExpressionFact(PsiUtil.skipParenthesizedExprDown(operand), DfaFactType.RANGE); - if (set == null) return false; + LongRangeSet set = DfLongType.extractRange(dfr.getDfType(PsiUtil.skipParenthesizedExprDown(operand))); long nextMin = set.min(); long nextMax = set.max(); if (operand == operands[0]) { diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java index 76fcc0200da6..735d1061b1e5 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java @@ -1121,6 +1121,7 @@ public class ExpressionUtils { public static boolean isNewObject(@Nullable PsiExpression expression) { return expression != null && nonStructuralChildren(expression).allMatch(call -> { if (call instanceof PsiNewExpression) return true; + if (call instanceof PsiArrayInitializerExpression) return true; if (call instanceof PsiMethodCallExpression) { ContractReturnValue returnValue = JavaMethodContractUtil.getNonFailingReturnValue(JavaMethodContractUtil.getMethodCallContracts((PsiCallExpression)call)); diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/MalformedFormatStringInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/MalformedFormatStringInspection.java index 38d6f76395a1..b4535a0dc2e7 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/MalformedFormatStringInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/MalformedFormatStringInspection.java @@ -16,7 +16,6 @@ package com.siyeh.ig.bugs; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.TypeConstraint; import com.intellij.codeInspection.ui.ListTable; import com.intellij.codeInspection.ui.ListWrappingTableModel; @@ -212,8 +211,8 @@ public class MalformedFormatStringInspection extends BaseInspection { continue; } if (validator != null && !validator.valid(argumentType)) { - TypeConstraint fact = CommonDataflow.getExpressionFact(argument, DfaFactType.TYPE_CONSTRAINT); - PsiType preciseType = fact != null ? fact.getPsiType() : null; + TypeConstraint fact = TypeConstraint.fromDfType(CommonDataflow.getDfType(argument)); + PsiType preciseType = fact.getPsiType(); if (preciseType == null || !validator.valid(preciseType)) { registerError(argument, validators, Integer.valueOf(argumentCount), argumentType, validator); } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/SubtractionInCompareToInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/SubtractionInCompareToInspection.java index a836d94fb8c8..e0189dd9ce5b 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/SubtractionInCompareToInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/bugs/SubtractionInCompareToInspection.java @@ -16,7 +16,6 @@ package com.siyeh.ig.bugs; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.ui.ListTable; import com.intellij.codeInspection.ui.ListWrappingTableModel; @@ -152,8 +151,8 @@ public class SubtractionInCompareToInspection extends BaseInspection { return true; } if (isSafeOperand(lhs) && isSafeOperand(rhs)) return true; - LongRangeSet leftRange = CommonDataflow.getExpressionFact(lhs, DfaFactType.RANGE); - LongRangeSet rightRange = CommonDataflow.getExpressionFact(rhs, DfaFactType.RANGE); + LongRangeSet leftRange = CommonDataflow.getExpressionRange(lhs); + LongRangeSet rightRange = CommonDataflow.getExpressionRange(rhs); if (leftRange != null && !leftRange.isEmpty() && rightRange != null && !rightRange.isEmpty()) { if (!leftRange.subtractionMayOverflow(rightRange, PsiType.LONG.equals(type))) return true; } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ConditionCoveredByFurtherConditionInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ConditionCoveredByFurtherConditionInspection.java index 6ff32144b396..61a13c96ac32 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ConditionCoveredByFurtherConditionInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ConditionCoveredByFurtherConditionInspection.java @@ -4,7 +4,9 @@ package com.siyeh.ig.controlflow; import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.dataFlow.*; -import com.intellij.codeInspection.dataFlow.value.DfaConstValue; +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.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.openapi.diagnostic.Attachment; @@ -135,7 +137,10 @@ public class ConditionCoveredByFurtherConditionInspection extends AbstractBaseJa DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem problem) { if (value instanceof DfaVariableValue) { - state.forceVariableFact((DfaVariableValue)value, DfaFactType.NULLABILITY, DfaNullability.NULLABLE); + DfType dfType = state.getDfType(value); + if (dfType instanceof DfReferenceType) { + state.setDfType(value, ((DfReferenceType)dfType).dropNullability().meet(DfaNullability.NULLABLE.asDfType())); + } } return true; } @@ -150,11 +155,9 @@ public class ConditionCoveredByFurtherConditionInspection extends AbstractBaseJa ThreeState old = values.get(expression); if (old == ThreeState.UNSURE) return; ThreeState result = ThreeState.UNSURE; - if (value instanceof DfaConstValue) { - Object bool = ((DfaConstValue)value).getValue(); - if (bool instanceof Boolean) { - result = ThreeState.fromBoolean((Boolean)bool); - } + Boolean bool = DfConstantType.getConstantOfType(state.getDfType(value), Boolean.class); + if (bool != null) { + result = ThreeState.fromBoolean(bool); } values.put(expression, old == null || old == result ? result : ThreeState.UNSURE); } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/EnumSwitchStatementWhichMissesCasesInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/EnumSwitchStatementWhichMissesCasesInspection.java index 46029bd7f6f9..4691ee07e2b4 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/EnumSwitchStatementWhichMissesCasesInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/EnumSwitchStatementWhichMissesCasesInspection.java @@ -19,6 +19,8 @@ import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.types.DfAntiConstantType; +import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.util.TextRange; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; @@ -34,6 +36,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -115,7 +118,8 @@ public class EnumSwitchStatementWhichMissesCasesInspection extends AbstractBaseJ if (constants.isEmpty()) return; CommonDataflow.DataflowResult dataflow = CommonDataflow.getDataflowResult(expression); if (dataflow != null) { - Set notValues = dataflow.getValuesNotEqualToExpression(expression); + DfType type = dataflow.getDfType(expression); + Set notValues = type instanceof DfAntiConstantType ? ((DfAntiConstantType)type).getNotValues() : Collections.emptySet(); for (Object value : notValues) { if (value instanceof PsiEnumConstant) { constants.remove(((PsiEnumConstant)value).getName()); diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ExcessiveRangeCheckInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ExcessiveRangeCheckInspection.java index 43ef1c9b687a..e06d0024fbbf 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ExcessiveRangeCheckInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/controlflow/ExcessiveRangeCheckInspection.java @@ -4,7 +4,7 @@ package com.siyeh.ig.controlflow; import com.intellij.codeInspection.*; import com.intellij.codeInspection.dataFlow.SpecialField; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; -import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; +import com.intellij.codeInspection.dataFlow.types.DfLongType; import com.intellij.codeInspection.dataFlow.value.RelationType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; @@ -146,8 +146,7 @@ public class ExcessiveRangeCheckInspection extends AbstractBaseJavaLocalInspecti LongRangeSet getFullRange() { LongRangeSet result; if (myField != null) { - DfaValueFactory factory = new DfaValueFactory(null, false); - result = LongRangeSet.fromDfaValue(myField.getDefaultValue(factory, false)); + result = DfLongType.extractRange(myField.getDefaultValue(false)); } else { result = LongRangeSet.fromType(myExpression.getType()); diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java index a9f5fd6ece65..453b5d2cf937 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java @@ -176,7 +176,7 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp for (DfaMemoryState memState : memStates) { memState.applyCondition(myExceptionVar.eq(myStableExceptionVar)); memState.applyCondition( - myExceptionVar.cond(RelationType.IS, factory.createTypeValue(exception, Nullability.NOT_NULL))); + myExceptionVar.cond(RelationType.IS, factory.getObjectType(exception, Nullability.NOT_NULL))); } return super.createInitialInstructionStates(psiBlock, memStates, flow); } @@ -220,7 +220,7 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp // Methods like "getCause" and "getMessage" return "null" for our test exception if (memState.areEqual(qualifier, myExceptionVar)) { memState.pop(); - memState.push(runner.getFactory().getConstFactory().getNull()); + memState.push(runner.getFactory().getNull()); return nextInstruction(instruction, runner, memState); } } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/performance/ObjectInstantiationInEqualsHashCodeInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/performance/ObjectInstantiationInEqualsHashCodeInspection.java index 19cf0f3073d6..398542eaf797 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/performance/ObjectInstantiationInEqualsHashCodeInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/performance/ObjectInstantiationInEqualsHashCodeInspection.java @@ -1,7 +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.siyeh.ig.performance; -import com.intellij.codeInspection.dataFlow.*; +import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.ContractReturnValue; +import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil; +import com.intellij.codeInspection.dataFlow.StandardMethodContract; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -107,7 +110,7 @@ public class ObjectInstantiationInEqualsHashCodeInspection extends BaseInspectio } private static boolean isAutoBoxingFromCache(PsiExpression expression) { - final LongRangeSet range = CommonDataflow.getExpressionFact(expression, DfaFactType.RANGE); + final LongRangeSet range = CommonDataflow.getExpressionRange(expression); if (range != null && !range.isEmpty() && range.min() >= -128 && range.max() <= 127) { return true; } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantOperationOnEmptyContainerInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantOperationOnEmptyContainerInspection.java index 71ec588d9380..bae960efe517 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantOperationOnEmptyContainerInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantOperationOnEmptyContainerInspection.java @@ -7,11 +7,9 @@ import com.intellij.codeInspection.InspectionsBundle; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.SpecialField; -import com.intellij.codeInspection.dataFlow.SpecialFieldValue; -import com.intellij.codeInspection.dataFlow.value.DfaConstValue; -import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.types.DfConstantType; +import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; @@ -101,9 +99,10 @@ public class RedundantOperationOnEmptyContainerInspection extends AbstractBaseJa } else { return null; } - SpecialFieldValue fact = CommonDataflow.getExpressionFact(value, DfaFactType.SPECIAL_FIELD_VALUE); - DfaValue length = lengthField.extract(fact); - if (length instanceof DfaConstValue && Long.valueOf(0).equals(((DfaConstValue)length).getValue())) { + DfType dfType = CommonDataflow.getDfType(value); + DfType length = lengthField.getFromQualifier(dfType); + // TODO: remove 0L when refactoring is complete + if (DfConstantType.isConst(length, 0) || DfConstantType.isConst(length, 0L)) { return message; } return null; diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchbranches/CreateMissingSwitchBranchesAction.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchbranches/CreateMissingSwitchBranchesAction.java index bf4928a7238c..41cb042cd7f9 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchbranches/CreateMissingSwitchBranchesAction.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchbranches/CreateMissingSwitchBranchesAction.java @@ -4,8 +4,8 @@ package com.siyeh.ipp.switchbranches; import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.codeInspection.dataFlow.CommonDataflow; -import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfIntType; import com.intellij.codeInspection.magicConstant.MagicConstantUtils; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; @@ -84,8 +84,8 @@ public class CreateMissingSwitchBranchesAction extends PsiElementBaseIntentionAc CommonDataflow.DataflowResult dfr = CommonDataflow.getDataflowResult(expression); PsiType type = expression.getType(); if (dfr != null) { - LongRangeSet range = dfr.getExpressionFact(expression, DfaFactType.RANGE); - if (range != null && type != null && PsiType.INT.isAssignableFrom(type) && !range.isCardinalityBigger(MAX_NUMBER_OF_BRANCHES)) { + LongRangeSet range = DfIntType.extractRange(dfr.getDfType(expression)); + if (type != null && PsiType.INT.isAssignableFrom(type) && !range.isCardinalityBigger(MAX_NUMBER_OF_BRANCHES)) { return range.stream().mapToObj(c -> Value.fromConstant(TypeConversionUtil.computeCastTo(c, type))).collect(Collectors.toList()); } Set values = dfr.getExpressionValues(expression); diff --git a/uast/uast-java/src/org/jetbrains/uast/java/analysis/JavaUastAnalysisPlugin.kt b/uast/uast-java/src/org/jetbrains/uast/java/analysis/JavaUastAnalysisPlugin.kt index 3a7c9a642e7b..feecbf5e463c 100644 --- a/uast/uast-java/src/org/jetbrains/uast/java/analysis/JavaUastAnalysisPlugin.kt +++ b/uast/uast-java/src/org/jetbrains/uast/java/analysis/JavaUastAnalysisPlugin.kt @@ -2,7 +2,6 @@ package org.jetbrains.uast.java.analysis import com.intellij.codeInspection.dataFlow.CommonDataflow -import com.intellij.codeInspection.dataFlow.DfaFactType import com.intellij.codeInspection.dataFlow.DfaNullability import com.intellij.lang.java.JavaLanguage import com.intellij.psi.PsiExpression @@ -16,8 +15,8 @@ class JavaUastAnalysisPlugin : UastAnalysisPlugin { when (fact) { is UExpressionFact.UNullabilityFact -> { val psiExpression = sourcePsi as? PsiExpression ?: return null - val nullability = CommonDataflow.getExpressionFact(psiExpression, DfaFactType.NULLABILITY)?.toUNullability() - ?: UNullability.UNKNOWN + val dfType = CommonDataflow.getDfType(psiExpression) + val nullability = DfaNullability.fromDfType(dfType).toUNullability() @Suppress("UNCHECKED_CAST") return nullability as T