From 6c125dfd9e74e8523ee2d042cd02d6958bada27b Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Mar 2018 09:58:43 +0700 Subject: [PATCH] DfaVariableSource introduced; now DfaVariableValue does not require a backing PSI element DfaConstValue preserves element PsiType Other polishing --- .../codeInspection/dataFlow/CFGBuilder.java | 45 +++-- .../dataFlow/ContractChecker.java | 2 +- .../dataFlow/ContractValue.java | 3 +- .../codeInspection/dataFlow/ControlFlow.java | 4 +- .../dataFlow/ControlFlowAnalyzer.java | 64 +++--- .../codeInspection/dataFlow/DfaFactType.java | 14 +- .../dataFlow/DfaMemoryStateImpl.java | 28 +-- .../codeInspection/dataFlow/DfaPsiUtil.java | 2 +- .../dataFlow/LiveVariablesAnalyzer.java | 4 +- .../NullParameterConstraintChecker.java | 4 +- .../codeInspection/dataFlow/NullnessUtil.java | 1 + .../codeInspection/dataFlow/SpecialField.java | 56 ++---- .../dataFlow/StandardInstructionVisitor.java | 16 +- .../dataFlow/controlTransfer.kt | 4 +- .../inliner/CollectionFactoryInliner.java | 6 +- .../dataFlow/inliner/LambdaInliner.java | 12 +- .../inliner/OptionalChainInliner.java | 2 +- .../dataFlow/inliner/StreamChainInliner.java | 21 +- .../dataFlow/value/DfaConstValue.java | 27 ++- .../dataFlow/value/DfaExpressionFactory.java | 190 ++++++++++++++---- .../dataFlow/value/DfaVariableSource.java | 41 ++++ .../dataFlow/value/DfaVariableValue.java | 90 +++++---- ...SuspiciousComparatorCompareInspection.java | 4 +- .../CatchMayIgnoreExceptionInspection.java | 7 +- 24 files changed, 403 insertions(+), 244 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableSource.java 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 af4b5745d508..d71541dce221 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 @@ -20,6 +20,7 @@ import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; 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.tree.IElementType; import com.intellij.psi.util.PsiUtil; @@ -41,7 +42,7 @@ import java.util.function.Consumer; public class CFGBuilder { private final ControlFlowAnalyzer myAnalyzer; private final Deque myBranches = new ArrayDeque<>(); - private final Map myMethodRefQualifiers = new HashMap<>(); + private final Map myMethodRefQualifiers = new HashMap<>(); CFGBuilder(ControlFlowAnalyzer analyzer) { myAnalyzer = analyzer; @@ -91,7 +92,7 @@ public class CFGBuilder { } /** - * Generate instructions to push given variable on stack for subsequent write. + * Generate instructions to push given variable value on stack for subsequent write. *

* Stack before: ... *

@@ -100,9 +101,8 @@ public class CFGBuilder { * @param variable to push * @return this builder */ - public CFGBuilder pushVariable(PsiVariable variable) { - myAnalyzer.addInstruction( - new PushInstruction(getFactory().getVarFactory().createVariableValue(variable, false), null, true)); + public CFGBuilder pushForWrite(DfaVariableValue variable) { + myAnalyzer.addInstruction(new PushInstruction(variable, null, true)); return this; } @@ -394,7 +394,7 @@ public class CFGBuilder { /** * Generate instructions to assign top stack value to the second stack value - * (usually pushed via {@link #pushVariable(PsiVariable)}). + * (usually pushed via {@link #pushForWrite(DfaVariableValue)}). *

* Stack before: ... variable_for_write value *

@@ -417,7 +417,20 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder assignTo(PsiVariable var) { - return pushVariable(var).swap().assign(); + return pushForWrite(getFactory().getVarFactory().createVariableValue(var)).swap().assign(); + } + + /** + * Generate instructions to assign top stack value to the specified variable + *

+ * Stack before: ... value + *

+ * Stack after: ... variable + * + * @return this builder + */ + public CFGBuilder assignTo(DfaVariableValue var) { + return pushForWrite(var).swap().assign(); } /** @@ -445,8 +458,8 @@ public class CFGBuilder { PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped; PsiExpression qualifier = methodRef.getQualifierExpression(); if (qualifier != null && !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef)) { - PsiVariable qualifierBinding = createTempVariable(qualifier.getType()); - pushVariable(qualifierBinding) + DfaVariableValue qualifierBinding = createTempVariable(qualifier.getType()); + pushForWrite(qualifierBinding) .pushExpression(qualifier) .checkNotNull(qualifier, NullabilityProblemKind.fieldAccessNPE) .assign() @@ -509,10 +522,8 @@ public class CFGBuilder { } if (argCount == expectedArgCount) { if (pushQualifier) { - PsiVariable qualifierVar = myMethodRefQualifiers.remove(methodRef); - DfaValue qualifierValue = qualifierVar == null ? DfaUnknownValue.getInstance() : - getFactory().getVarFactory().createVariableValue(qualifierVar, false); - push(qualifierValue); + DfaValue qualifierValue = myMethodRefQualifiers.remove(methodRef); + push(qualifierValue == null ? DfaUnknownValue.getInstance() : qualifierValue); moveTopValue(argCount); } myAnalyzer.addBareCall(null, methodRef); @@ -600,9 +611,9 @@ public class CFGBuilder { checkNotNull(expression, NullabilityProblemKind.nullableFunctionReturn); } } else if(body instanceof PsiCodeBlock) { - PsiVariable variable = createTempVariable(LambdaUtil.getFunctionalInterfaceReturnType(lambda)); + DfaVariableValue variable = createTempVariable(LambdaUtil.getFunctionalInterfaceReturnType(lambda)); myAnalyzer.inlineBlock((PsiCodeBlock)body, resultNullness, variable); - push(getFactory().getVarFactory().createVariableValue(variable, false)); + push(variable); } else { pushUnknown(); } @@ -610,13 +621,13 @@ public class CFGBuilder { } /** - * Create a temporary {@link PsiVariable} (not declared in the original code) to be used within this control flow. + * Create a synthetic variable (not declared in the original code) to be used within this control flow. * * @param type a type of variable to create * @return newly created variable */ @NotNull - public PsiVariable createTempVariable(@Nullable PsiType type) { + public DfaVariableValue createTempVariable(@Nullable PsiType type) { return myAnalyzer.createTempVariable(type); } 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 9afabce34bdc..77a265dd17ab 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 @@ -51,7 +51,7 @@ class ContractChecker extends DataFlowRunner { DfaConstValue comparisonValue = constraint.getComparisonValue(factory); if (comparisonValue != null) { boolean negated = constraint.shouldUseNonEqComparison(); - DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i], false); + DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i]); initialState.applyCondition(factory.createCondition(dfaParam, RelationType.equivalence(!negated), comparisonValue)); } } 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 84b8aac05946..c424d760c56d 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 @@ -21,6 +21,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.psi.PsiType; import com.intellij.util.Function; +import org.jetbrains.annotations.NotNull; /** * @author Tagir Valeev @@ -52,7 +53,7 @@ public abstract class ContractValue { return new Spec(this, field); } - public static ContractValue constant(Object value, PsiType type) { + public static ContractValue constant(Object value, @NotNull PsiType type) { return new IndependentValue(factory -> factory.getConstFactory().createFromValue(value, type, null), String.valueOf(value)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlow.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlow.java index 4485c6c4fe63..3673f32ea108 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlow.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlow.java @@ -74,7 +74,7 @@ public class ControlFlow { public void removeVariable(@Nullable PsiVariable variable) { if (variable == null) return; - addInstruction(new FlushVariableInstruction(myFactory.getVarFactory().createVariableValue(variable, false))); + addInstruction(new FlushVariableInstruction(myFactory.getVarFactory().createVariableValue(variable))); } /** @@ -111,7 +111,7 @@ public class ControlFlow { for (int i = 0; i < instructions.size(); i++) { Instruction instruction = instructions.get(i); - result.append(Integer.toString(i)).append(": ").append(instruction.toString()); + result.append(i).append(": ").append(instruction.toString()); result.append("\n"); } return result.toString(); 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 e45307c8f504..00114059dcce 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 @@ -24,7 +24,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.*; -import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; @@ -282,7 +281,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } else if (!field.hasModifierProperty(PsiModifier.FINAL)) { // initialize with default value - DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(field, false); + DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(field); addInstruction(new PushInstruction(dfaVariable, null, true)); addInstruction(new PushInstruction(myFactory.getConstFactory().createDefault(field.getType()), null)); addInstruction(new AssignInstruction(null, dfaVariable)); @@ -297,7 +296,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { private void initializeVariable(PsiVariable variable, PsiExpression initializer) { if (DfaUtil.ignoreInitializer(variable)) return; - DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(variable, false); + DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(variable); addInstruction(new PushInstruction(dfaVariable, initializer, true)); initializer.accept(this); generateBoxingUnboxingInstructionFor(initializer, variable.getType()); @@ -403,7 +402,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { @NotNull private List getVariablesInside(PsiElement exitedStatement) { return ContainerUtil.map(PsiTreeUtil.findChildrenOfType(exitedStatement, PsiVariable.class), - var -> myFactory.getVarFactory().createVariableValue(var, false)); + myFactory.getVarFactory()::createVariableValue); } @Override public void visitContinueStatement(PsiContinueStatement statement) { @@ -492,7 +491,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } ControlFlow.ControlFlowOffset offset = myCurrentFlow.getNextOffset(); - DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(parameter, false); + DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(parameter); addInstruction(new FlushVariableInstruction(dfaVariable)); if (!hasSizeCheck) { @@ -606,22 +605,23 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (end == null || end == Long.MAX_VALUE || end == Integer.MAX_VALUE) return false; } PsiExpression initializer = loop.getInitializer(); - if (!PsiType.INT.equals(initializer.getType()) && !PsiType.LONG.equals(initializer.getType())) return false; + PsiType type = initializer.getType(); + if (!PsiType.INT.equals(type) && !PsiType.LONG.equals(type)) return false; DfaValue origin = null; Object initialValue = ExpressionUtils.computeConstantExpression(initializer); if (initialValue instanceof Number) { - origin = myFactory.getConstFactory().createFromValue(initialValue, initializer.getType(), null); + origin = myFactory.getConstFactory().createFromValue(initialValue, type, null); } else if (initializer instanceof PsiReferenceExpression) { PsiVariable initialVariable = ObjectUtils.tryCast(((PsiReferenceExpression)initializer).resolve(), PsiVariable.class); if ((initialVariable instanceof PsiLocalVariable || initialVariable instanceof PsiParameter) && !VariableAccessUtils.variableIsAssigned(initialVariable, statement.getBody())) { - origin = myFactory.getVarFactory().createVariableValue(initialVariable, false); + origin = myFactory.getVarFactory().createVariableValue(initialVariable); } } if (origin == null) return false; long diff = start == null || end == null ? -1 : end - start; - DfaVariableValue loopVar = myFactory.getVarFactory().createVariableValue(counter, false); + DfaVariableValue loopVar = myFactory.getVarFactory().createVariableValue(counter); addInstruction(new PushInstruction(loopVar, null, true)); if(diff >= 0 && diff <= MAX_UNROLL_SIZE) { // Unroll small loops @@ -718,7 +718,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (myInlinedBlockContext != null) { if (returnValue != null) { - DfaVariableValue var = myFactory.getVarFactory().createVariableValue(myInlinedBlockContext.myTarget, false); + DfaVariableValue var = myInlinedBlockContext.myTarget; addInstruction(new PushInstruction(var, null, true)); returnValue.accept(this); generateBoxingUnboxingInstructionFor(returnValue, var.getVariableType()); @@ -1085,7 +1085,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } if (parent instanceof PsiVariable) { // initialization - return getFactory().getVarFactory().createVariableValue((PsiVariable)parent, false); + return getFactory().getVarFactory().createVariableValue((PsiVariable)parent); } if (parent instanceof PsiAssignmentExpression) { PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent; @@ -1113,13 +1113,14 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { DfaVariableValue var = getTargetVariable(expression); DfaVariableValue arrayWriteTarget = var; if (var == null) { - var = getFactory().getVarFactory().createVariableValue(createTempVariable(type), false); + var = createTempVariable(type); } PsiExpression[] initializers = expression.getInitializers(); DfaExpressionFactory expressionFactory = myFactory.getExpressionFactory(); if (arrayWriteTarget != null) { PsiVariable arrayVariable = (PsiVariable)arrayWriteTarget.getPsiVariable(); - if ((arrayVariable instanceof PsiField && !arrayVariable.hasModifierProperty(PsiModifier.FINAL)) || + if (arrayWriteTarget.isFlushableByCalls() || + arrayVariable == null || VariableAccessUtils.variableIsUsed(arrayVariable, expression) || ExpressionUtils.getConstantArrayElements(arrayVariable) != null || !(expressionFactory.getArrayElementValue(arrayWriteTarget, 0) instanceof DfaVariableValue)) { @@ -1675,7 +1676,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } DfaVariableValue var = getTargetVariable(expression); if (var == null) { - var = getFactory().getVarFactory().createVariableValue(createTempVariable(type), false); + var = createTempVariable(type); } DfaValue length = SpecialField.ARRAY_LENGTH.createValue(getFactory(), var); addInstruction(new PushInstruction(length, null, true)); @@ -1913,7 +1914,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { * @param resultNullness desired nullness returned by block return statement * @param target a variable to store the block result (returned via {@code return} statement) */ - void inlineBlock(@NotNull PsiCodeBlock block, @NotNull Nullness resultNullness, @NotNull PsiVariable target) { + void inlineBlock(@NotNull PsiCodeBlock block, @NotNull Nullness resultNullness, @NotNull DfaVariableValue target) { InlinedBlockContext oldBlock = myInlinedBlockContext; // Transfer value is pushed to avoid emptying stack beyond this point addInstruction(new PushInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, this.myTrapStack), null)); @@ -1931,17 +1932,17 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } /** - * Create a temporary {@link PsiVariable} (not declared in the original code) to be used within this control flow. + * Create a synthetic variable (not declared in the original code) to be used within this control flow. * * @param type a type of variable to create * @return newly created variable */ @NotNull - PsiVariable createTempVariable(@Nullable PsiType type) { + DfaVariableValue createTempVariable(@Nullable PsiType type) { if(type == null) { type = PsiType.VOID; } - return new TempVariable(getInstructionCount(), type, getContext()); + return getFactory().getVarFactory().createVariableValue(new Synthetic(getInstructionCount()), type); } /** @@ -1950,22 +1951,35 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { * @param variable to check * @return true if supplied variable is a temp variable. */ - public static boolean isTempVariable(PsiModifierListOwner variable) { - return variable instanceof TempVariable; + public static boolean isTempVariable(@NotNull DfaVariableValue variable) { + return variable.getSource() instanceof Synthetic; } - private static class TempVariable extends LightVariableBuilder { - TempVariable(int index, @NotNull PsiType type, @NotNull PsiElement navigationElement) { - super("tmp$" + index, type, navigationElement); + private static final class Synthetic implements DfaVariableSource { + private final int myLocation; + + public Synthetic(int location) { + myLocation = location; + } + + @NotNull + @Override + public String toString() { + return "tmp$" + myLocation; + } + + @Override + public boolean isStable() { + return true; } } public static class InlinedBlockContext { final PsiCodeBlock myCodeBlock; final boolean myForceNonNullBlockResult; - final PsiVariable myTarget; + final DfaVariableValue myTarget; - public InlinedBlockContext(PsiCodeBlock codeBlock, boolean forceNonNullBlockResult, PsiVariable target) { + public InlinedBlockContext(PsiCodeBlock codeBlock, boolean forceNonNullBlockResult, DfaVariableValue target) { myCodeBlock = codeBlock; myForceNonNullBlockResult = forceNonNullBlockResult; myTarget = target; 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 f7169e004db5..b4b884a34783 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 @@ -100,7 +100,7 @@ public abstract class DfaFactType extends Key { @Override Mutability calcFromVariable(@NotNull DfaVariableValue value) { PsiModifierListOwner variable = value.getPsiVariable(); - return Mutability.getMutability(variable); + return variable == null ? Mutability.UNKNOWN : Mutability.getMutability(variable); } }; @@ -145,16 +145,12 @@ public abstract class DfaFactType extends Key { @Nullable @Override LongRangeSet calcFromVariable(@NotNull DfaVariableValue var) { - if (var.getQualifier() != null) { - for (SpecialField sf : SpecialField.values()) { - if (sf.isMyAccessor(var.getPsiVariable())) { - return sf.getRange(); - } - } + DfaVariableSource source = var.getSource(); + if(source instanceof SpecialField) { + return ((SpecialField)source).getRange(); } - PsiModifierListOwner psiVariable = var.getPsiVariable(); LongRangeSet fromType = LongRangeSet.fromType(var.getVariableType()); - return fromType == null ? null : LongRangeSet.fromPsiElement(psiVariable).intersect(fromType); + return fromType == null ? null : LongRangeSet.fromPsiElement(var.getPsiVariable()).intersect(fromType); } @Nullable 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 5e1d76e2d4e7..fa0bf07ab766 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 @@ -320,14 +320,12 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!(dfaValue instanceof DfaVariableValue)) return classIndex; DfaVariableValue variableValue = (DfaVariableValue)dfaValue; DfaVariableValue qualifier = variableValue.getQualifier(); - PsiModifierListOwner variable = variableValue.getPsiVariable(); if (qualifier == null) return classIndex; Integer index = getOrCreateEqClassIndex(qualifier); if (index == null) return classIndex; for (DfaValue eqQualifier : myEqClasses.get(index).getMemberValues()) { if (eqQualifier != qualifier && eqQualifier instanceof DfaVariableValue) { - DfaVariableValue eqValue = getFactory().getVarFactory() - .createVariableValue(variable, variableValue.getVariableType(), variableValue.isNegated(), (DfaVariableValue)eqQualifier); + DfaVariableValue eqValue = variableValue.withQualifier((DfaVariableValue)eqQualifier); int i = getEqClassIndex(eqValue); if (i != -1) { uniteClasses(i, classIndex); @@ -639,7 +637,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { getConstantValue(var) == null && StreamEx.of(getEquivalentValues(var)).without(var).select(DfaVariableValue.class).findFirst().isPresent(); List values = StreamEx.ofKeys(myVariableStates) - .filter(var -> ControlFlowAnalyzer.isTempVariable(var.getPsiVariable())) + .filter(ControlFlowAnalyzer::isTempVariable) .remove(sharesState) .toList(); values.forEach(this::flushVariable); @@ -876,16 +874,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { setVariableState(dfaVar, getVariableState(dfaVar).withFact(DfaFactType.CAN_BE_NULL, true)); return; } - PsiType psiType; - if (constValue instanceof PsiVariable) { - psiType = ((PsiVariable)constValue).getType(); - } - else { - PsiModifierListOwner context = dfaVar.getPsiVariable(); - psiType = JavaPsiFacade.getElementFactory(context.getProject()) - .createTypeByFQClassName(constValue.getClass().getName(), context.getResolveScope()); - } - DfaPsiType dfaType = myFactory.createDfaType(psiType); + DfaPsiType dfaType = myFactory.createDfaType(((DfaConstValue)value).getType()); DfaVariableState state = getVariableState(dfaVar).withInstanceofValue(dfaType); if (state != null) { setVariableState(dfaVar, state); @@ -1136,10 +1125,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } DfaVariableValue qualifier = var.getQualifier(); if (qualifier != null) { - return StreamEx.of(SpecialField.values()) - .filter(sf -> sf.isMyAccessor(var.getPsiVariable())) - .map(sf -> sf.createValue(myFactory, qualifier)) - .nonNull().findFirst().orElse(var); + DfaValue value = SpecialField.tryCreateValue(qualifier, var.getPsiVariable()); + if (value != null) { + return value; + } } return var; } @@ -1184,8 +1173,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (qualifierIndex == -1) return StreamEx.empty(); return StreamEx.of(myEqClasses.get(qualifierIndex).getMemberValues()) .without(qualifier).select(DfaVariableValue.class) - .map(eqQualifier -> getFactory().getVarFactory() - .createVariableValue(var.getPsiVariable(), var.getVariableType(), var.isNegated(), eqQualifier)); + .map(var::withQualifier); } private DfaVariableState findVariableState(DfaVariableValue var) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index 75a52073e7ea..0da3416708f7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -352,7 +352,7 @@ public class DfaPsiUtil { if (isCallExposingNonInitializedFields(instruction) || instruction instanceof ReturnInstruction && !((ReturnInstruction)instruction).isViaException()) { for (PsiField field : containingClass.getFields()) { - if (!instructionState.getMemoryState().isNotNull(getFactory().getVarFactory().createVariableValue(field, false))) { + if (!instructionState.getMemoryState().isNotNull(getFactory().getVarFactory().createVariableValue(field))) { map.put(field, false); } else if (!map.containsKey(field)) { map.put(field, true); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java index 45c8f27f2c89..f8c855a507d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java @@ -28,7 +28,6 @@ import com.intellij.util.PairFunction; import com.intellij.util.containers.*; import com.intellij.util.containers.Queue; import one.util.streamex.IntStreamEx; -import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -209,8 +208,7 @@ public class LiveVariablesAnalyzer { for (FinishElementInstruction instruction : toFlush.keySet()) { Collection values = toFlush.get(instruction); // Do not flush special values as they could be used implicitly - values.removeIf(var -> var.getQualifier() != null && - StreamEx.of(SpecialField.values()).anyMatch(sf -> sf.isMyAccessor(var.getPsiVariable()))); + values.removeIf(var -> var.getSource() instanceof SpecialField); instruction.getVarsToFlush().addAll(values); } } 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 b49bc4b1d0bb..b19a65fb8886 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 @@ -98,7 +98,7 @@ class NullParameterConstraintChecker extends DataFlowRunner { if (instruction instanceof ReturnInstruction && !((ReturnInstruction)instruction).isViaException()) { DfaMemoryState memState = instructionState.getMemoryState(); for (PsiParameter parameter : myPossiblyViolatedParameters.toArray(PsiParameter.EMPTY_ARRAY)) { - final DfaVariableValue dfaVar = getFactory().getVarFactory().createVariableValue(parameter, false); + final DfaVariableValue dfaVar = getFactory().getVarFactory().createVariableValue(parameter); if (memState.isNotNull(dfaVar)) { myParametersWithSuccessfulExecutionInNotNullState.add(parameter); } @@ -122,7 +122,7 @@ class NullParameterConstraintChecker extends DataFlowRunner { protected MyDfaMemoryState(DfaValueFactory factory) { super(factory); for (PsiParameter parameter : myPossiblyViolatedParameters) { - setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false), + setVariableState(getFactory().getVarFactory().createVariableValue(parameter), new DfaVariableState(DfaFactMap.EMPTY.with(DfaFactType.CAN_BE_NULL, true))); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java index 568595196178..767e330e4fba 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java @@ -30,6 +30,7 @@ public class NullnessUtil { if (nullability != Nullness.UNKNOWN) { return toBoolean(nullability); } + if (var == null) return null; Nullness defaultNullability = value.getFactory().suggestNullabilityForNonAnnotatedMember(var); 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 48f2c03903ed..024a621a8cd0 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 @@ -3,14 +3,11 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.value.*; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; -import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodUtils; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; @@ -22,7 +19,7 @@ import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstrain * * @author Tagir Valeev */ -public enum SpecialField { +public enum SpecialField implements DfaVariableSource { ARRAY_LENGTH(null, "length", true, LongRangeSet.indexRange()) { @Override public boolean isMyAccessor(PsiModifierListOwner accessor) { @@ -51,20 +48,6 @@ public enum SpecialField { } return null; } - - @Nullable - @Override - PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { - if (qualifier == null) return null; - PsiClass arrayClass = JavaPsiFacade.getElementFactory(qualifier.getProject()) - .getArrayClass(PsiUtil.getLanguageLevel(qualifier)); - return arrayClass.findFieldByName("length", false); - } - - @Override - public String toString() { - return "Array.length"; - } }, STRING_LENGTH(CommonClassNames.JAVA_LANG_STRING, "length", true, LongRangeSet.indexRange()) { @Override @@ -96,7 +79,8 @@ public enum SpecialField { myRange = range; } - public boolean isFinal() { + @Override + public boolean isStable() { return myFinal; } @@ -118,23 +102,17 @@ public enum SpecialField { return accessor instanceof PsiMethod && MethodUtils.methodMatches((PsiMethod)accessor, myClassName, null, myMethodName); } - /** - * Returns a canonical accessor which can be used to read this special field - * - * @param qualifier a qualifier accessor (if known) - * @param psiClass a class for which the canonical method should be resolved - * @return a canonical accessor representing this special field or null if cannot be determined. - */ - @Nullable - PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { - if (psiClass == null) return null; - if (!myClassName.equals(psiClass.getQualifiedName())) { - PsiClass myClass = JavaPsiFacade.getInstance(psiClass.getProject()).findClass(myClassName, psiClass.getResolveScope()); - if (!InheritanceUtil.isInheritorOrSelf(psiClass, myClass, true)) return null; - psiClass = myClass; + public static DfaValue tryCreateValue(DfaValue qualifier, PsiElement element) { + if (qualifier == null) return null; + DfaValueFactory factory = qualifier.getFactory(); + if (factory == null) return null; + if (!(element instanceof PsiVariable) && !(element instanceof PsiMethod)) return null; + for (SpecialField field : values()) { + if (field.isMyAccessor((PsiModifierListOwner)element)) { + return field.createValue(factory, qualifier); + } } - PsiMethod[] methods = psiClass.findMethodsByName(myMethodName, false); - return methods.length == 1 ? methods[0] : null; + return null; } /** @@ -160,11 +138,7 @@ public enum SpecialField { } } } - PsiModifierListOwner owner = - getCanonicalOwner(psiVariable, PsiUtil.resolveClassInClassTypeOnly(variableValue.getVariableType())); - if (owner != null) { - return factory.getVarFactory().createVariableValue(owner, PsiType.INT, false, variableValue); - } + return factory.getVarFactory().createVariableValue(this, PsiType.INT, variableValue); } if(qualifier instanceof DfaConstValue) { Object obj = ((DfaConstValue)qualifier).getValue(); @@ -205,6 +179,6 @@ public enum SpecialField { @Override public String toString() { - return StringUtil.getShortName(myClassName)+"."+myMethodName+"()"; + return myMethodName; } } 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 0a08df6b6fe5..564fb251bc16 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 @@ -503,15 +503,13 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (methodType == MethodCallInstruction.MethodType.METHOD_REFERENCE_CALL && qualifierValue instanceof DfaVariableValue) { PsiMethod method = instruction.getTargetMethod(); - for (SpecialField sf : SpecialField.values()) { - if (sf.isMyAccessor(method)) { - return sf.createValue(factory, qualifierValue); - } + DfaValue value = SpecialField.tryCreateValue(qualifierValue, method); + if (value != null) { + return value; } - PsiModifierListOwner modifierListOwner = DfaExpressionFactory.getAccessedVariableOrGetter(method); - if (modifierListOwner != null) { - return factory.getVarFactory().createVariableValue(modifierListOwner, instruction.getResultType(), false, - (DfaVariableValue)qualifierValue); + DfaVariableSource source = DfaExpressionFactory.getAccessedVariableOrGetter(method); + if (source != null) { + return factory.getVarFactory().createVariableValue(source, instruction.getResultType(), (DfaVariableValue)qualifierValue); } } @@ -526,7 +524,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (methodType == MethodCallInstruction.MethodType.CAST) { assert qualifierValue != null; - if (qualifierValue instanceof DfaConstValue) { + if (qualifierValue instanceof DfaConstValue && type != null) { Object casted = TypeConversionUtil.computeCastTo(((DfaConstValue)qualifierValue).getValue(), type); return factory.getConstFactory().createFromValue(casted, type, ((DfaConstValue)qualifierValue).getConstant()); } 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 462a16dc5ecc..fb5cf60061f5 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 @@ -145,12 +145,12 @@ private class ControlTransferHandler(val state: DfaMemoryState, val runner: Data private fun stateForCatchClause(param: PsiParameter, varState: DfaVariableState): DfaMemoryState { val catchingCopy = state.createCopy() as DfaMemoryStateImpl - catchingCopy.setVariableState(catchingCopy.factory.varFactory.createVariableValue(param, false), varState) + catchingCopy.setVariableState(catchingCopy.factory.varFactory.createVariableValue(param), varState) return catchingCopy } private fun initVariableState(param: PsiParameter, throwable: DfaPsiType?): DfaVariableState { - val sampleVar = (state as DfaMemoryStateImpl).factory.varFactory.createVariableValue(param, false) + val sampleVar = (state as DfaMemoryStateImpl).factory.varFactory.createVariableValue(param) val varState = state.createVariableState(sampleVar).withFact(DfaFactType.CAN_BE_NULL, false) return if (throwable != null) varState.withInstanceofValue(throwable)!! else varState } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java index 5aee477310e2..a7bcc2aca5d5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java @@ -21,7 +21,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiVariable; import com.siyeh.ig.callMatcher.CallMapper; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.MethodCallUtils; @@ -104,9 +103,8 @@ public class CollectionFactoryInliner implements CallInliner { if (factoryInfo.mySize == -1) { builder.push(result); } else { - PsiVariable variable = builder.createTempVariable(call.getType()); - DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false); - builder.pushVariable(variable) // tmpVar = + DfaVariableValue variableValue = builder.createTempVariable(call.getType()); + builder.pushForWrite(variableValue) // tmpVar = .push(result) .assign() // leave tmpVar on stack: it's result of method call .push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java index 34737994dc5e..bedf20e44fef 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java @@ -42,12 +42,12 @@ public class LambdaInliner implements CallInliner { PsiExpression[] args = call.getArgumentList().getExpressions(); PsiParameter[] parameters = lambda.getParameterList().getParameters(); if (args.length != parameters.length) return false; - EntryStream.zip(args, parameters).forKeyValue((arg, parameter) -> - builder.pushVariable(parameter) - .pushExpression(arg) - .boxUnbox(arg, parameter.getType()) - .assign() - .pop()); + EntryStream.zip(args, parameters).forKeyValue( + (arg, parameter) -> builder.pushForWrite(builder.getFactory().getVarFactory().createVariableValue(parameter)) + .pushExpression(arg) + .boxUnbox(arg, parameter.getType()) + .assign() + .pop()); builder.inlineLambda(lambda, Nullness.UNKNOWN); return true; } 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 6a1214402243..18d6dc58e2a6 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 @@ -234,7 +234,7 @@ public class OptionalChainInliner implements CallInliner { return; } // Restore stack for common invokeFunction - StreamEx.of(parameters).forEach(p -> builder.push(builder.getFactory().getVarFactory().createVariableValue(p, false))); + StreamEx.of(parameters).map(builder.getFactory().getVarFactory()::createVariableValue).forEach(builder::push); } } builder 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 f8d6d9076f67..17dbfe6b54a3 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 @@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow.inliner; import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; import com.intellij.util.ArrayUtil; @@ -190,7 +191,7 @@ public class StreamChainInliner implements CallInliner { } static abstract class TerminalStep extends Step { - PsiVariable myResult; + DfaVariableValue myResult; TerminalStep(@NotNull PsiMethodCallExpression call, PsiExpression function) { super(call, null, function); @@ -199,7 +200,7 @@ public class StreamChainInliner implements CallInliner { @Override void before(CFGBuilder builder) { myResult = builder.createTempVariable(myCall.getType()); - builder.pushVariable(myResult) + builder.pushForWrite(myResult) .chain(this::pushInitialValue) .assign() .pop() @@ -210,7 +211,7 @@ public class StreamChainInliner implements CallInliner { @Override void pushResult(CFGBuilder builder) { - builder.push(builder.getFactory().getVarFactory().createVariableValue(myResult, false)); + builder.push(myResult); } } @@ -236,12 +237,12 @@ public class StreamChainInliner implements CallInliner { if (!(type instanceof PsiPrimitiveType)) { type = PsiPrimitiveType.getUnboxedType(type); } - builder.push(builder.getFactory().getConstFactory().createDefault(type)); + builder.push(builder.getFactory().getConstFactory().createDefault(Objects.requireNonNull(type))); } @Override void iteration(CFGBuilder builder) { - builder.pushVariable(myResult).pushUnknown().assign().splice(2); + builder.pushForWrite(myResult).pushUnknown().assign().splice(2); } } @@ -260,7 +261,7 @@ public class StreamChainInliner implements CallInliner { if (myFunction != null) { builder.pushUnknown().invokeFunction(2, myFunction); } - builder.pushVariable(myResult).push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true)).assign().splice(2); + builder.pushForWrite(myResult).push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true)).assign().splice(2); } } @@ -286,7 +287,7 @@ public class StreamChainInliner implements CallInliner { @Override void iteration(CFGBuilder builder) { myComparatorModel.invoke(builder); - builder.pushVariable(myResult).push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true)).assign().pop(); + builder.pushForWrite(myResult).push(builder.getFactory().getFactValue(DfaFactType.OPTIONAL_PRESENCE, true)).assign().pop(); } @Override @@ -309,7 +310,7 @@ public class StreamChainInliner implements CallInliner { void iteration(CFGBuilder builder) { builder.invokeFunction(1, myFunction) .ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName())) - .pushVariable(myResult) + .pushForWrite(myResult) .push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName()))) .assign() .pop() @@ -645,7 +646,7 @@ public class StreamChainInliner implements CallInliner { .evaluateFunction(fn) .chain(firstStep::before) .doWhile() - .pushVariable(builder.createTempVariable(inType)) + .pushForWrite(builder.createTempVariable(inType)) .invokeFunction(0, fn) .assign() .chain(firstStep::iteration) @@ -731,7 +732,7 @@ public class StreamChainInliner implements CallInliner { private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) { builder.doWhile() - .pushVariable(builder.createTempVariable(inType)) + .pushForWrite(builder.createTempVariable(inType)) .push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType))) .assign() .chain(firstStep::iteration) 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 index 2c188868c538..ec79fc9224a5 100644 --- 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 @@ -41,15 +41,16 @@ public class DfaConstValue extends DfaValue { Factory(DfaValueFactory factory) { myFactory = factory; - dfaNull = new DfaConstValue(null, factory, null); - dfaFalse = new DfaConstValue(Boolean.FALSE, factory, null); - dfaTrue = new DfaConstValue(Boolean.TRUE, factory, null); - dfaFail = new DfaConstValue(ourThrowable, factory, null); + dfaNull = new DfaConstValue(null, PsiType.NULL, factory, null); + dfaFalse = new DfaConstValue(Boolean.FALSE, PsiType.BOOLEAN, factory, null); + dfaTrue = new DfaConstValue(Boolean.TRUE, PsiType.BOOLEAN, factory, null); + dfaFail = new DfaConstValue(ourThrowable, PsiType.VOID, factory, null); } @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; @@ -95,18 +96,19 @@ public class DfaConstValue extends DfaValue { * @return a constant (e.g. 0 from int, false for boolean, null for reference type). */ @NotNull - public DfaConstValue createDefault(PsiType type) { + public DfaConstValue createDefault(@NotNull PsiType type) { return createFromValue(PsiTypesUtil.getDefaultValue(type), type, null); } @NotNull - public DfaConstValue createFromValue(Object value, final PsiType type, @Nullable PsiVariable constant) { + public DfaConstValue createFromValue(Object value, @NotNull PsiType type, @Nullable PsiVariable constant) { if (value == Boolean.TRUE) return dfaTrue; if (value == Boolean.FALSE) return dfaFalse; if (value == null) return dfaNull; if (TypeConversionUtil.isNumericType(type) && !TypeConversionUtil.isFloatOrDoubleType(type)) { - value = TypeConversionUtil.computeCastTo(value, PsiType.LONG); + type = PsiType.LONG; + value = TypeConversionUtil.computeCastTo(value, type); } if (value instanceof Double || value instanceof Float) { double doubleValue = ((Number)value).doubleValue(); @@ -115,7 +117,7 @@ public class DfaConstValue extends DfaValue { } DfaConstValue instance = myValues.get(value); if (instance == null) { - instance = new DfaConstValue(value, myFactory, constant); + instance = new DfaConstValue(value, type, myFactory, constant); myValues.put(value, instance); } @@ -141,10 +143,12 @@ public class DfaConstValue extends DfaValue { private final Object myValue; @Nullable private final PsiVariable myConstant; + @NotNull private final PsiType myType; - private DfaConstValue(Object value, DfaValueFactory factory, @Nullable PsiVariable constant) { + private DfaConstValue(Object value, @NotNull PsiType type, DfaValueFactory factory, @Nullable PsiVariable constant) { super(factory); myValue = value; + myType = type; myConstant = constant; } @@ -154,6 +158,11 @@ public class DfaConstValue extends DfaValue { return myValue.toString(); } + @NotNull + public PsiType getType() { + return myType; + } + public Object getValue() { return myValue; } 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 c65fdc77b2c7..ae5fc1775345 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 @@ -26,7 +26,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.JavaConstantExpressionEvaluator; -import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.util.PropertyUtilBase; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -62,7 +61,7 @@ public class DfaExpressionFactory { } private final DfaValueFactory myFactory; - private final Map myMockIndices = ContainerUtil.newHashMap(); + private final Map myMockIndices = ContainerUtil.newHashMap(); DfaExpressionFactory(DfaValueFactory factory) { myFactory = factory; @@ -122,7 +121,7 @@ public class DfaExpressionFactory { PsiJavaCodeReferenceElement qualifier = ((PsiThisExpression)expression).getQualifier(); PsiElement target = qualifier == null ? null : qualifier.resolve(); if (target instanceof PsiClass) { - return myFactory.getVarFactory().createVariableValue((PsiModifierListOwner)target, null, false, null); + return myFactory.getVarFactory().createVariableValue(new ThisSource((PsiClass)target), expression.getType()); } } @@ -134,30 +133,33 @@ public class DfaExpressionFactory { if (specialValue != null) { return specialValue; } - PsiModifierListOwner var = getAccessedVariableOrGetter(refExpr.resolve()); + DfaVariableSource var = getAccessedVariableOrGetter(refExpr.resolve()); if (var == null) { return null; } - if (!var.hasModifierProperty(PsiModifier.VOLATILE)) { - if (var instanceof PsiVariable && var.hasModifierProperty(PsiModifier.FINAL) && !PsiUtil.isAccessedForWriting(refExpr)) { - DfaValue constValue = myFactory.getConstFactory().create((PsiVariable)var); - if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, var)) return constValue; - } - - if (ExpressionUtil.isEffectivelyUnqualified(refExpr) || isStaticFinalConstantWithoutInitializationHacks(var) || - (var instanceof PsiMethod && var.hasModifierProperty(PsiModifier.STATIC))) { - return myFactory.getVarFactory().createVariableValue(var, refExpr.getType(), false, null); - } - - DfaVariableValue qualifier = getQualifierVariable(refExpr.getQualifierExpression()); - if (qualifier != null) { - return myFactory.getVarFactory().createVariableValue(var, refExpr.getType(), false, qualifier); - } + PsiModifierListOwner psiElement = var.getPsiElement(); + boolean isVolatile = psiElement != null && psiElement.hasModifierProperty(PsiModifier.VOLATILE); + if (isVolatile) { + PsiType type = refExpr.getType(); + return myFactory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, psiElement)); + } + if (psiElement instanceof PsiVariable && psiElement.hasModifierProperty(PsiModifier.FINAL) && !PsiUtil.isAccessedForWriting(refExpr)) { + DfaValue constValue = myFactory.getConstFactory().create((PsiVariable)psiElement); + if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, psiElement)) return constValue; + } + if (psiElement != null && + (ExpressionUtil.isEffectivelyUnqualified(refExpr) || isStaticFinalConstantWithoutInitializationHacks(psiElement) || + (psiElement instanceof PsiMethod && psiElement.hasModifierProperty(PsiModifier.STATIC)))) { + return myFactory.getVarFactory().createVariableValue(var, refExpr.getType()); + } + DfaVariableValue qualifier = getQualifierVariable(refExpr.getQualifierExpression()); + if (qualifier != null) { + return myFactory.getVarFactory().createVariableValue(var, refExpr.getType(), qualifier); } PsiType type = refExpr.getType(); - return myFactory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, var)); + return myFactory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, psiElement)); } private DfaVariableValue getQualifierVariable(PsiExpression qualifierExpression) { @@ -169,7 +171,7 @@ public class DfaExpressionFactory { else if (qualifierValue instanceof DfaConstValue) { Object constValue = ((DfaConstValue)qualifierValue).getValue(); if (constValue instanceof PsiVariable) { - qualifier = myFactory.getVarFactory().createVariableValue((PsiVariable)constValue, false); + qualifier = myFactory.getVarFactory().createVariableValue((PsiVariable)constValue); } } return qualifier; @@ -194,39 +196,30 @@ public class DfaExpressionFactory { @Nullable private DfaValue createFromSpecialField(PsiReferenceExpression refExpr) { PsiElement target = refExpr.resolve(); - if (!(target instanceof PsiModifierListOwner)) { - return null; - } - for (SpecialField sf : SpecialField.values()) { - if (sf.isMyAccessor((PsiModifierListOwner)target)) { - DfaVariableValue qualifier = getQualifierVariable(refExpr.getQualifierExpression()); - if (qualifier != null) { - return sf.createValue(myFactory, qualifier); - } - } - } - return null; + if (!(target instanceof PsiModifierListOwner)) return null; + DfaVariableValue qualifier = getQualifierVariable(refExpr.getQualifierExpression()); + return SpecialField.tryCreateValue(qualifier, target); } @Contract("null -> null") @Nullable - public static PsiModifierListOwner getAccessedVariableOrGetter(final PsiElement target) { + public static DfaVariableSource getAccessedVariableOrGetter(final PsiElement target) { if (target instanceof PsiVariable) { - return (PsiVariable)target; + return new PlainSource((PsiVariable)target); } if (target instanceof PsiMethod) { PsiMethod method = (PsiMethod)target; if (PropertyUtilBase.isSimplePropertyGetter(method) && ControlFlowAnalyzer.getMethodCallContracts(method, null).isEmpty()) { String qName = PsiUtil.getMemberQualifiedName(method); if (qName == null || !FALSE_GETTERS.value(qName)) { - return method; + return new GetterSource(method); } } if (method.getParameterList().isEmpty()) { if ((ControlFlowAnalyzer.isPure(method) || AnnotationUtil.findAnnotation(method.getContainingClass(), "javax.annotation.concurrent.Immutable") != null) && ControlFlowAnalyzer.getMethodCallContracts(method, null).isEmpty()) { - return method; + return new GetterSource(method); } } } @@ -289,16 +282,131 @@ public class DfaExpressionFactory { return getAdvancedExpressionDfaValue(constantArrayElement); } } - PsiVariable indexVariable = getArrayIndexVariable(arrayPsiVar, index); + ArrayElementSource indexVariable = getArrayIndexVariable(index); if (indexVariable == null) return null; - return myFactory.getVarFactory().createVariableValue(indexVariable, componentType, false, arrayDfaVar); + return myFactory.getVarFactory().createVariableValue(indexVariable, componentType, arrayDfaVar); } @Nullable - private PsiVariable getArrayIndexVariable(@NotNull PsiElement anchor, int index) { + private ArrayElementSource getArrayIndexVariable(int index) { if (index >= 0) { - return myMockIndices.computeIfAbsent(index, k -> new LightVariableBuilder<>("[" + k + "]", PsiType.INT, anchor)); + return myMockIndices.computeIfAbsent(index, ArrayElementSource::new); } return null; } + + static final class PlainSource implements DfaVariableSource { + private final @NotNull PsiVariable myVariable; + + PlainSource(@NotNull PsiVariable variable) { + myVariable = variable; + } + + @NotNull + @Override + public String toString() { + return String.valueOf(myVariable.getName()); + } + + @Override + public PsiVariable getPsiElement() { + return myVariable; + } + + @Override + public boolean isStable() { + return myVariable instanceof PsiLocalVariable || + myVariable instanceof PsiParameter || + myVariable.hasModifierProperty(PsiModifier.FINAL); + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof PlainSource && ((PlainSource)obj).myVariable == myVariable; + } + } + + private static final class GetterSource implements DfaVariableSource { + private final @NotNull PsiMethod myGetter; + + GetterSource(@NotNull PsiMethod getter) { + myGetter = getter; + } + + @NotNull + @Override + public String toString() { + return myGetter.getName(); + } + + @NotNull + @Override + public PsiMethod getPsiElement() { + return myGetter; + } + + @Override + public boolean isStable() { + return false; + } + + @Override + public boolean isCall() { + return true; + } + + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof GetterSource && ((GetterSource)obj).myGetter == myGetter); + } + } + + private static final class ArrayElementSource implements DfaVariableSource { + private final int myIndex; + + ArrayElementSource(int index) { + myIndex = index; + } + + @NotNull + @Override + public String toString() { + return "[" + myIndex + "]"; + } + + @Override + public boolean isStable() { + return false; + } + } + + private static final class ThisSource implements DfaVariableSource { + @Nullable + private final PsiClass myQualifier; + + ThisSource(@Nullable PsiClass qualifier) { + myQualifier = qualifier; + } + + @NotNull + @Override + public String toString() { + return myQualifier == null ? "this" : myQualifier.getQualifiedName() + ".this"; + } + + @Override + public PsiModifierListOwner getPsiElement() { + return myQualifier; + } + + @Override + public boolean isStable() { + return true; + } + + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof ThisSource && ((ThisSource)obj).myQualifier == myQualifier; + } + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableSource.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableSource.java new file mode 100644 index 000000000000..c45c73d035ec --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableSource.java @@ -0,0 +1,41 @@ +// 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.psi.PsiModifierListOwner; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a source of {@link DfaVariableValue}. Two variables are the same if they have the same source, qualifier and negation flag. + * A source could be a PsiVariable, getter method, array element with given index, this expression, etc. + *

+ * Subclasses must have proper {@link Object#equals(Object)} and implementation or instantiation must be controlled to prevent + * creating equal objects. Also {@link #toString()} must return sane representation of the source. + */ +public interface DfaVariableSource { + /** + * @return a PSI element associated with given source or null if not applicable + */ + @Nullable + default PsiModifierListOwner getPsiElement() { + return null; + } + + /** + * @return true if the value stored in this source cannot be changed implicitly (e.g. inside the unknown method call) + */ + boolean isStable(); + + /** + * @return true if the value behind this source is a method call which result might be computed from other sources + */ + default boolean isCall() { + return false; + } + + /** + * Must be overridden to return stable string representation of the source. + * In particular {@code source1.equals(source2)} implies that {@code source1.toString().equals(source2.toString())} + */ + @Override + String toString(); +} 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 564e7c9963bc..8c5d6592ceaa 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 @@ -16,10 +16,16 @@ package com.intellij.codeInspection.dataFlow.value; -import com.intellij.codeInspection.dataFlow.*; +import com.intellij.codeInspection.dataFlow.DfaFactMap; +import com.intellij.codeInspection.dataFlow.DfaFactType; +import com.intellij.codeInspection.dataFlow.Nullness; +import com.intellij.codeInspection.dataFlow.NullnessUtil; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Trinity; -import com.intellij.psi.*; +import com.intellij.psi.PsiEllipsisType; +import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiVariable; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.SmartList; @@ -27,7 +33,6 @@ import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; import java.util.List; public class DfaVariableValue extends DfaValue { @@ -41,24 +46,37 @@ public class DfaVariableValue extends DfaValue { } @NotNull - public DfaVariableValue createVariableValue(PsiVariable myVariable, boolean isNegated) { - PsiType varType = myVariable.getType(); + public DfaVariableValue createVariableValue(PsiVariable variable) { + PsiType varType = variable.getType(); if (varType instanceof PsiEllipsisType) { - varType = new PsiArrayType(((PsiEllipsisType)varType).getComponentType()); + varType = ((PsiEllipsisType)varType).toArrayType(); } - return createVariableValue(myVariable, varType, isNegated, null); + return createVariableValue(new DfaExpressionFactory.PlainSource(variable), varType); } + @NotNull - public DfaVariableValue createVariableValue(@NotNull PsiModifierListOwner myVariable, + public DfaVariableValue createVariableValue(@NotNull DfaVariableSource source, @Nullable PsiType varType) { + return createVariableValue(source, varType, false, null); + } + + @NotNull + public DfaVariableValue createVariableValue(@NotNull DfaVariableSource source, @Nullable PsiType varType, - boolean isNegated, @Nullable DfaVariableValue qualifier) { - Trinity key = Trinity.create(isNegated, ((PsiNamedElement)myVariable).getName(), qualifier); + return createVariableValue(source, varType, false, qualifier); + } + + @NotNull + DfaVariableValue createVariableValue(@NotNull DfaVariableSource source, + @Nullable PsiType varType, + boolean isNegated, + @Nullable DfaVariableValue qualifier) { + Trinity key = Trinity.create(isNegated, source.toString(), qualifier); for (DfaVariableValue aVar : myExistingVars.get(key)) { - if (aVar.hardEquals(myVariable, varType, isNegated, qualifier)) return aVar; + if (aVar.hardEquals(source, varType, isNegated, qualifier)) return aVar; } - DfaVariableValue result = new DfaVariableValue(myVariable, varType, isNegated, myFactory, qualifier); + DfaVariableValue result = new DfaVariableValue(source, varType, isNegated, myFactory, qualifier); myExistingVars.putValue(key, result); while (qualifier != null) { qualifier.myDependents.add(result); @@ -73,7 +91,7 @@ public class DfaVariableValue extends DfaValue { } } - private final PsiModifierListOwner myVariable; + @NotNull private final DfaVariableSource mySource; private final PsiType myVarType; @Nullable private final DfaVariableValue myQualifier; private DfaVariableValue myNegatedValue; @@ -82,15 +100,19 @@ public class DfaVariableValue extends DfaValue { private final DfaPsiType myDfaType; private final List myDependents = new SmartList<>(); - private DfaVariableValue(@NotNull PsiModifierListOwner variable, @Nullable PsiType varType, boolean isNegated, DfaValueFactory factory, @Nullable DfaVariableValue qualifier) { + private DfaVariableValue(@NotNull DfaVariableSource source, + @Nullable PsiType varType, + boolean isNegated, + DfaValueFactory factory, + @Nullable DfaVariableValue qualifier) { super(factory); - myVariable = variable; + mySource = source; myIsNegated = isNegated; myQualifier = qualifier; myVarType = varType; myDfaType = varType == null ? null : myFactory.createDfaType(varType); if (varType != null && !varType.isValid()) { - PsiUtil.ensureValidType(varType, "Variable: " + variable + " of class " + variable.getClass()); + PsiUtil.ensureValidType(varType, "Variable: " + source + " of class " + source.getClass()); } } @@ -99,9 +121,14 @@ public class DfaVariableValue extends DfaValue { return myDfaType; } - @NotNull + @Nullable public PsiModifierListOwner getPsiVariable() { - return myVariable; + return mySource.getPsiElement(); + } + + @NotNull + public DfaVariableSource getSource() { + return mySource; } @Nullable @@ -123,16 +150,21 @@ public class DfaVariableValue extends DfaValue { if (myNegatedValue != null) { return myNegatedValue; } - return myNegatedValue = myFactory.getVarFactory().createVariableValue(myVariable, myVarType, !myIsNegated, myQualifier); + return myNegatedValue = myFactory.getVarFactory().createVariableValue(mySource, myVarType, !myIsNegated, myQualifier); + } + + @NotNull + public DfaVariableValue withQualifier(DfaVariableValue newQualifier) { + return myFactory.getVarFactory().createVariableValue(mySource, myVarType, myIsNegated, newQualifier); } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { - return (myIsNegated ? "!" : "") + (myQualifier == null ? "" : myQualifier + ".") + ((PsiNamedElement)myVariable).getName(); + return (myIsNegated ? "!" : "") + (myQualifier == null ? "" : myQualifier + ".") + mySource; } - private boolean hardEquals(PsiModifierListOwner psiVar, PsiType varType, boolean negated, DfaVariableValue qualifier) { - return (psiVar == myVariable || SpecialField.ARRAY_LENGTH.isMyAccessor(psiVar) && SpecialField.ARRAY_LENGTH.isMyAccessor(myVariable)) && + private boolean hardEquals(DfaVariableSource source, PsiType varType, boolean negated, DfaVariableValue qualifier) { + return source.equals(mySource) && negated == myIsNegated && qualifier == myQualifier && Comparing.equal(TypeConversionUtil.erasure(varType), TypeConversionUtil.erasure(myVarType)); @@ -157,20 +189,10 @@ public class DfaVariableValue extends DfaValue { } public boolean isFlushableByCalls() { - if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter || ControlFlowAnalyzer.isTempVariable(myVariable)) { - return false; - } - boolean finalField = myVariable instanceof PsiVariable && myVariable.hasModifierProperty(PsiModifier.FINAL); - boolean specialFinalField = myVariable instanceof PsiMethod && - Arrays.stream(SpecialField.values()).anyMatch(sf -> sf.isFinal() && sf.isMyAccessor(myVariable)); - if (finalField || specialFinalField) { - return myQualifier != null && myQualifier.isFlushableByCalls(); - } - return true; + return !mySource.isStable() || (myQualifier != null && myQualifier.isFlushableByCalls()); } public boolean containsCalls() { - return myVariable instanceof PsiMethod || myQualifier != null && myQualifier.containsCalls(); + return mySource.isCall() || myQualifier != null && myQualifier.containsCalls(); } - } 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 7643b610c324..a1eed0ccd7c0 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/SuspiciousComparatorCompareInspection.java @@ -130,8 +130,8 @@ public class SuspiciousComparatorCompareInspection extends BaseInspection { @Override protected DfaMemoryState createMemoryState() { DfaMemoryState state = super.createMemoryState(); - DfaVariableValue var1 = getFactory().getVarFactory().createVariableValue(parameters[0], false); - DfaVariableValue var2 = getFactory().getVarFactory().createVariableValue(parameters[1], false); + DfaVariableValue var1 = getFactory().getVarFactory().createVariableValue(parameters[0]); + DfaVariableValue var2 = getFactory().getVarFactory().createVariableValue(parameters[1]); DfaValue condition = getFactory().createCondition(var1, DfaRelationValue.RelationType.EQ, var2); state.applyCondition(condition); return state; diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java index 61612a354ced..75243ae2b515 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/errorhandling/CatchMayIgnoreExceptionInspection.java @@ -127,9 +127,8 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp DataFlowRunner runner = new StandardDataFlowRunner(false, block); DfaValueFactory factory = runner.getFactory(); - DfaVariableValue exceptionVar = factory.getVarFactory().createVariableValue(parameter, false); - DfaVariableValue stableExceptionVar = - factory.getVarFactory().createVariableValue(new LightParameter("tmp", exception, block), false); + DfaVariableValue exceptionVar = factory.getVarFactory().createVariableValue(parameter); + DfaVariableValue stableExceptionVar = factory.getVarFactory().createVariableValue(new LightParameter("tmp", exception, block)); StandardInstructionVisitor visitor = new IgnoredExceptionVisitor(parameter, block, exceptionClass, stableExceptionVar); Consumer stateAdjuster = state -> { @@ -178,7 +177,7 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp protected boolean isModificationAllowed(DfaVariableValue variable) { PsiModifierListOwner owner = variable.getPsiVariable(); - return owner == myParameter || PsiTreeUtil.isAncestor(myBlock, owner, false); + return owner == myParameter || owner != null && PsiTreeUtil.isAncestor(myBlock, owner, false); } }