DfaVariableSource introduced; now DfaVariableValue does not require a backing PSI element

DfaConstValue preserves element PsiType
Other polishing
This commit is contained in:
Tagir Valeev
2018-03-27 10:01:08 +07:00
parent 6a2e1e39c2
commit 6c125dfd9e
24 changed files with 403 additions and 244 deletions
@@ -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<JumpInstruction> myBranches = new ArrayDeque<>();
private final Map<PsiExpression, PsiVariable> myMethodRefQualifiers = new HashMap<>();
private final Map<PsiExpression, DfaVariableValue> 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.
* <p>
* Stack before: ...
* <p>
@@ -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)}).
* <p>
* Stack before: ... variable_for_write value
* <p>
@@ -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
* <p>
* Stack before: ... value
* <p>
* 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);
}
@@ -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));
}
}
@@ -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));
}
@@ -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();
@@ -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<DfaVariableValue> 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> {
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;
@@ -100,7 +100,7 @@ public abstract class DfaFactType<T> extends Key<T> {
@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<T> extends Key<T> {
@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
@@ -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<DfaVariableValue> 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) {
@@ -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);
@@ -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<DfaVariableValue> 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);
}
}
@@ -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)));
}
}
@@ -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);
@@ -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;
}
}
@@ -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());
}
@@ -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
}
@@ -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 = <Value of collection type>
DfaVariableValue variableValue = builder.createTempVariable(call.getType());
builder.pushForWrite(variableValue) // tmpVar = <Value of collection type>
.push(result)
.assign() // leave tmpVar on stack: it's result of method call
.push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = <size>
@@ -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;
}
@@ -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
@@ -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)
@@ -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;
}
@@ -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<Integer, PsiVariable> myMockIndices = ContainerUtil.newHashMap();
private final Map<Integer, ArrayElementSource> 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;
}
}
}
@@ -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.
* <p>
* 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();
}
@@ -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<Boolean,String,DfaVariableValue> 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<Boolean, String, DfaVariableValue> 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<DfaVariableValue> 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();
}
}
@@ -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;
@@ -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<DfaMemoryState> 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);
}
}