mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-184733 Better array access tracking in DFA
This commit is contained in:
+5
-17
@@ -221,22 +221,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
addInstruction(new AssignInstruction(rExpr, myFactory.createValue(lExpr)));
|
||||
|
||||
flushArrayElementsOnUnknownIndexAssignment(lExpr);
|
||||
|
||||
finishElement(expression);
|
||||
}
|
||||
|
||||
private void flushArrayElementsOnUnknownIndexAssignment(PsiExpression lExpr) {
|
||||
if (lExpr instanceof PsiArrayAccessExpression &&
|
||||
!(myFactory.createValue(lExpr) instanceof DfaVariableValue) // check for unknown index, otherwise AssignInstruction will flush only that element
|
||||
) {
|
||||
DfaValue arrayVar = myFactory.createValue(((PsiArrayAccessExpression)lExpr).getArrayExpression());
|
||||
if (arrayVar instanceof DfaVariableValue) {
|
||||
addInstruction(new FlushVariableInstruction((DfaVariableValue)arrayVar, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDefaultAssignmentBinOp(PsiExpression lExpr, PsiExpression rExpr, final PsiType exprType) {
|
||||
lExpr.accept(this);
|
||||
addInstruction(new DupInstruction());
|
||||
@@ -555,8 +542,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
});
|
||||
}
|
||||
|
||||
addCountingLoopBound(statement);
|
||||
|
||||
PsiExpression condition = statement.getCondition();
|
||||
if (condition != null) {
|
||||
condition.accept(this);
|
||||
@@ -577,6 +562,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
update.accept(this);
|
||||
}
|
||||
|
||||
addCountingLoopBound(statement);
|
||||
|
||||
ControlFlow.ControlFlowOffset offset = initialization != null
|
||||
? getEndOffset(initialization)
|
||||
: getStartOffset(statement);
|
||||
@@ -592,7 +579,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
/**
|
||||
* Add known-to-be-true condition inside counting loop, effectively converting
|
||||
* {@code for(int i=origin; i<bound; i++)} to {@code for(int i=origin; i>=origin && i<bound; i++)}.
|
||||
* {@code for(int i=origin; i<bound; i++)} to
|
||||
* {@code int i = origin; while(i < bound) {... i++; if(i <= origin) break;}}.
|
||||
* This adds a range knowledge to data flow analysis.
|
||||
* <p>
|
||||
* Does nothing if the statement is not a counting loop.
|
||||
@@ -626,7 +614,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
if (origin == null || VariableAccessUtils.variableIsAssigned(counter, statement.getBody())) return;
|
||||
addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(counter, false), null));
|
||||
addInstruction(new PushInstruction(origin, null));
|
||||
addInstruction(new BinopInstruction(JavaTokenType.LT, null, myProject));
|
||||
addInstruction(new BinopInstruction(JavaTokenType.LE, null, myProject));
|
||||
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An immutable collection of facts which are known for some value. Each fact is identified by {@link DfaFactType} and fact value.
|
||||
@@ -91,24 +92,6 @@ public final class DfaFactMap {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the passed fact map is always distinct from this map (i.e. any exact value
|
||||
* which conforms the passed fact map does not conform this fact map).
|
||||
*
|
||||
* @param otherMap a fact map to check
|
||||
* @return true if this fact map is always distinct from other map.
|
||||
*/
|
||||
public boolean isDistinct(DfaFactMap otherMap) {
|
||||
for (DfaFactType<?> key : DfaFactType.getTypes()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
DfaFactType<Object> type = (DfaFactType<Object>)key;
|
||||
Object thisValue = myMap.get(type);
|
||||
Object other = otherMap.get(type);
|
||||
if(thisValue != null && other != null && type.isDistinct(thisValue, other)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fact map which is additionally restricted by supplied fact.
|
||||
* The returned map is a sub-state of this map.
|
||||
@@ -186,7 +169,9 @@ public final class DfaFactMap {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public String toString() {
|
||||
return StreamEx.of(myMap.getKeys()).map(key -> ((DfaFactType<Object>)key).toString(myMap.get(key))).joining(", ");
|
||||
return StreamEx.of(myMap.getKeys())
|
||||
.map(key -> ((DfaFactType<Object>)key).toString(Objects.requireNonNull(myMap.get(key))))
|
||||
.joining(", ");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -94,10 +94,6 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
* When its value is false, then optional is known to be empty (absent).
|
||||
*/
|
||||
public static final DfaFactType<Boolean> OPTIONAL_PRESENCE = new DfaFactType<Boolean>("Optional presense") {
|
||||
@Override
|
||||
public boolean isDistinct(@NotNull Boolean fact, @NotNull Boolean otherFact) {
|
||||
return fact != otherFact;
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString(@NotNull Boolean fact) {
|
||||
@@ -219,10 +215,6 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
return Objects.equals(superFact, subFact);
|
||||
}
|
||||
|
||||
boolean isDistinct(@NotNull T fact, @NotNull T otherFact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isUnknown(@NotNull T fact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+29
-11
@@ -18,7 +18,12 @@ package com.intellij.codeInspection.dataFlow;
|
||||
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.PsiArrayAccessExpression;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -30,10 +35,32 @@ public abstract class InstructionVisitor {
|
||||
|
||||
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
memState.pop();
|
||||
memState.push(memState.pop());
|
||||
DfaValue dest = memState.pop();
|
||||
memState.push(dest);
|
||||
flushArrayOnUnknownAssignment(instruction, runner.getFactory(), dest, memState);
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
}
|
||||
|
||||
protected void flushArrayOnUnknownAssignment(AssignInstruction instruction,
|
||||
DfaValueFactory factory,
|
||||
DfaValue dest,
|
||||
DfaMemoryState memState) {
|
||||
if (dest instanceof DfaVariableValue) return;
|
||||
PsiArrayAccessExpression arrayAccess =
|
||||
ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()), PsiArrayAccessExpression.class);
|
||||
if (arrayAccess != null) {
|
||||
PsiExpression array = arrayAccess.getArrayExpression();
|
||||
DfaValue value = factory.createValue(array);
|
||||
if (value instanceof DfaVariableValue) {
|
||||
for (DfaVariableValue qualified : factory.getVarFactory().getAllQualifiedBy((DfaVariableValue)value)) {
|
||||
if (qualified.isFlushableByCalls()) {
|
||||
memState.flushVariable(qualified);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
}
|
||||
@@ -137,16 +164,7 @@ public abstract class InstructionVisitor {
|
||||
public DfaInstructionState[] visitFlushVariable(FlushVariableInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
final DfaVariableValue variable = instruction.getVariable();
|
||||
if (variable != null) {
|
||||
if (instruction.isDependentsOnly()) {
|
||||
for (DfaVariableValue qualified : runner.getFactory().getVarFactory().getAllQualifiedBy(variable)) {
|
||||
if (qualified.isFlushableByCalls()) {
|
||||
memState.flushVariable(qualified);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
memState.flushVariable(variable);
|
||||
}
|
||||
memState.flushVariable(variable);
|
||||
} else {
|
||||
memState.flushFields();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -44,9 +45,30 @@ public enum SpecialField {
|
||||
((PsiField)accessor).getContainingClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
|
||||
if (initializer instanceof PsiArrayInitializerExpression) {
|
||||
return factory.getInt(((PsiArrayInitializerExpression)initializer).getInitializers().length);
|
||||
}
|
||||
if (initializer instanceof PsiNewExpression) {
|
||||
PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)initializer).getArrayInitializer();
|
||||
if (arrayInitializer != null) {
|
||||
return factory.getInt(arrayInitializer.getInitializers().length);
|
||||
}
|
||||
PsiExpression[] dimensions = ((PsiNewExpression)initializer).getArrayDimensions();
|
||||
if (dimensions.length > 0) {
|
||||
Object length = ExpressionUtils.computeConstantExpression(dimensions[0]);
|
||||
if (length instanceof Integer) {
|
||||
return factory.getInt(((Integer)length).intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) {
|
||||
PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) {
|
||||
if (qualifier == null) return null;
|
||||
PsiClass arrayClass = JavaPsiFacade.getElementFactory(qualifier.getProject())
|
||||
.getArrayClass(PsiUtil.getLanguageLevel(qualifier));
|
||||
@@ -60,7 +82,16 @@ public enum SpecialField {
|
||||
},
|
||||
STRING_LENGTH(CommonClassNames.JAVA_LANG_STRING, "length", true, LongRangeSet.indexRange()) {
|
||||
@Override
|
||||
public DfaValue createFromConstant(DfaValueFactory factory, @NotNull Object obj) {
|
||||
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
|
||||
Object value = ExpressionUtils.computeConstantExpression(initializer);
|
||||
if(value instanceof String) {
|
||||
return factory.getInt(((String)value).length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DfaValue fromConstant(DfaValueFactory factory, @NotNull Object obj) {
|
||||
return obj instanceof String ? factory.getInt(((String)obj).length()) : null;
|
||||
}
|
||||
},
|
||||
@@ -109,7 +140,7 @@ public enum SpecialField {
|
||||
* @return a canonical accessor representing this special field or null if cannot be determined.
|
||||
*/
|
||||
@Nullable
|
||||
public PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) {
|
||||
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());
|
||||
@@ -130,8 +161,19 @@ public enum SpecialField {
|
||||
public DfaValue createValue(DfaValueFactory factory, DfaValue qualifier) {
|
||||
if (qualifier instanceof DfaVariableValue) {
|
||||
DfaVariableValue variableValue = (DfaVariableValue)qualifier;
|
||||
PsiModifierListOwner psiVariable = variableValue.getPsiVariable();
|
||||
if (factory.isHonorFieldInitializers() && psiVariable instanceof PsiField && psiVariable.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& psiVariable.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
PsiExpression initializer = ((PsiField)psiVariable).getInitializer();
|
||||
if (initializer != null) {
|
||||
DfaValue value = fromInitializer(factory, initializer);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiModifierListOwner owner =
|
||||
getCanonicalOwner(variableValue.getPsiVariable(), PsiUtil.resolveClassInClassTypeOnly(variableValue.getVariableType()));
|
||||
getCanonicalOwner(psiVariable, PsiUtil.resolveClassInClassTypeOnly(variableValue.getVariableType()));
|
||||
if (owner != null) {
|
||||
return factory.getVarFactory().createVariableValue(owner, PsiType.INT, false, variableValue);
|
||||
}
|
||||
@@ -139,7 +181,7 @@ public enum SpecialField {
|
||||
if(qualifier instanceof DfaConstValue) {
|
||||
Object obj = ((DfaConstValue)qualifier).getValue();
|
||||
if(obj != null) {
|
||||
DfaValue value = createFromConstant(factory, obj);
|
||||
DfaValue value = fromConstant(factory, obj);
|
||||
if(value != null) {
|
||||
return value;
|
||||
}
|
||||
@@ -148,7 +190,11 @@ public enum SpecialField {
|
||||
return factory.getFactValue(DfaFactType.RANGE, myRange);
|
||||
}
|
||||
|
||||
public DfaValue createFromConstant(DfaValueFactory factory, @NotNull Object obj) {
|
||||
DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DfaValue fromConstant(DfaValueFactory factory, @NotNull Object obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+23
-4
@@ -51,8 +51,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
DfaValue dfaSource = memState.pop();
|
||||
DfaValue dfaDest = memState.pop();
|
||||
|
||||
if (instruction.getAssignedValue() != null) {
|
||||
// It's possible that dfaDest on the stack is cleared to DfaTypeValue due to variable flush
|
||||
if (!(dfaDest instanceof DfaVariableValue) && instruction.getAssignedValue() != null) {
|
||||
// It's possible that dfaDest on the stack is cleared to DfaFactMapValue due to variable flush
|
||||
// (e.g. during StateMerger#mergeByFacts), so we try to restore the original destination.
|
||||
dfaDest = instruction.getAssignedValue();
|
||||
}
|
||||
@@ -88,6 +88,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
}
|
||||
|
||||
memState.push(dfaDest);
|
||||
flushArrayOnUnknownAssignment(instruction, runner.getFactory(), dfaDest, memState);
|
||||
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
}
|
||||
@@ -140,8 +141,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
DfaValue index = memState.pop();
|
||||
DfaValue array = dereference(memState, memState.pop(), NullabilityProblemKind.arrayAccessNPE.problem(arrayExpression));
|
||||
boolean alwaysOutOfBounds = false;
|
||||
DfaValueFactory factory = runner.getFactory();
|
||||
if (index != DfaUnknownValue.getInstance()) {
|
||||
DfaValueFactory factory = runner.getFactory();
|
||||
DfaValue indexNonNegative = factory.createCondition(index, RelationType.GE, factory.getInt(0));
|
||||
if (!memState.applyCondition(indexNonNegative)) {
|
||||
alwaysOutOfBounds = true;
|
||||
@@ -155,7 +156,20 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
}
|
||||
}
|
||||
processArrayAccess(arrayExpression, alwaysOutOfBounds);
|
||||
memState.push(instruction.getValue());
|
||||
|
||||
DfaValue result = instruction.getValue();
|
||||
LongRangeSet rangeSet = memState.getValueFact(index, DfaFactType.RANGE);
|
||||
if (rangeSet != null && !rangeSet.isEmpty() && rangeSet.min() == rangeSet.max()) {
|
||||
long longIdx = rangeSet.min();
|
||||
if(longIdx >= 0 && longIdx <= Integer.MAX_VALUE) {
|
||||
int intIdx = (int)longIdx;
|
||||
DfaValue arrayElementValue = runner.getFactory().getExpressionFactory().getArrayElementValue(array, intIdx);
|
||||
if (arrayElementValue != null) {
|
||||
result = arrayElementValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
memState.push(result);
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
}
|
||||
|
||||
@@ -483,6 +497,11 @@ 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);
|
||||
}
|
||||
}
|
||||
PsiModifierListOwner modifierListOwner = DfaExpressionFactory.getAccessedVariableOrGetter(method);
|
||||
if (modifierListOwner != null) {
|
||||
return factory.getVarFactory().createVariableValue(modifierListOwner, instruction.getResultType(), false,
|
||||
|
||||
+5
-12
@@ -16,24 +16,17 @@
|
||||
|
||||
package com.intellij.codeInspection.dataFlow.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowRunner;
|
||||
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
|
||||
public class FlushVariableInstruction extends Instruction {
|
||||
private final DfaVariableValue myVariable;
|
||||
private final boolean myDependentsOnly;
|
||||
|
||||
public FlushVariableInstruction(DfaVariableValue expr) {
|
||||
this(expr, false);
|
||||
}
|
||||
|
||||
public FlushVariableInstruction(DfaVariableValue variable, boolean dependentsOnly) {
|
||||
myVariable = variable;
|
||||
myDependentsOnly = dependentsOnly;
|
||||
}
|
||||
|
||||
public boolean isDependentsOnly() {
|
||||
return myDependentsOnly;
|
||||
myVariable = expr;
|
||||
}
|
||||
|
||||
public DfaVariableValue getVariable() {
|
||||
|
||||
+49
-14
@@ -61,7 +61,7 @@ public class DfaExpressionFactory {
|
||||
private final DfaValueFactory myFactory;
|
||||
private final Map<Integer, PsiVariable> myMockIndices = ContainerUtil.newHashMap();
|
||||
|
||||
public DfaExpressionFactory(DfaValueFactory factory) {
|
||||
DfaExpressionFactory(DfaValueFactory factory) {
|
||||
myFactory = factory;
|
||||
}
|
||||
|
||||
@@ -77,9 +77,12 @@ public class DfaExpressionFactory {
|
||||
PsiExpression arrayExpression = ((PsiArrayAccessExpression)expression).getArrayExpression();
|
||||
DfaVariableValue qualifier = getQualifierVariable(arrayExpression);
|
||||
if (qualifier != null) {
|
||||
PsiVariable indexVar = getArrayIndexVariable(((PsiArrayAccessExpression)expression).getIndexExpression());
|
||||
if (indexVar != null) {
|
||||
return myFactory.getVarFactory().createVariableValue(indexVar, expression.getType(), false, qualifier);
|
||||
Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression)expression).getIndexExpression());
|
||||
if (index instanceof Integer) {
|
||||
DfaValue arrayElementValue = getArrayElementValue(qualifier, (Integer)index);
|
||||
if (arrayElementValue != null) {
|
||||
return arrayElementValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiType type = expression.getType();
|
||||
@@ -124,6 +127,10 @@ public class DfaExpressionFactory {
|
||||
}
|
||||
|
||||
private DfaValue createReferenceValue(@NotNull PsiReferenceExpression refExpr) {
|
||||
DfaValue specialValue = createFromSpecialField(refExpr);
|
||||
if (specialValue != null) {
|
||||
return specialValue;
|
||||
}
|
||||
PsiModifierListOwner var = getAccessedVariableOrGetter(refExpr.resolve());
|
||||
if (var == null) {
|
||||
return null;
|
||||
@@ -181,6 +188,23 @@ public class DfaExpressionFactory {
|
||||
!DfaUtil.hasInitializationHacks((PsiField)var);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Contract("null -> null")
|
||||
@Nullable
|
||||
public static PsiModifierListOwner getAccessedVariableOrGetter(final PsiElement target) {
|
||||
@@ -195,11 +219,6 @@ public class DfaExpressionFactory {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
for (SpecialField sf : SpecialField.values()) {
|
||||
if (sf.isMyAccessor(method)) {
|
||||
return sf.getCanonicalOwner(null, ((PsiMethod)target).getContainingClass());
|
||||
}
|
||||
}
|
||||
if (method.getParameterList().getParametersCount() == 0) {
|
||||
if ((ControlFlowAnalyzer.isPure(method) ||
|
||||
AnnotationUtil.findAnnotation(method.getContainingClass(), "javax.annotation.concurrent.Immutable") != null) &&
|
||||
@@ -211,12 +230,28 @@ public class DfaExpressionFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
public DfaValue getArrayElementValue(DfaValue array, int index) {
|
||||
if (!(array instanceof DfaVariableValue)) return null;
|
||||
DfaVariableValue arrayDfaVar = (DfaVariableValue)array;
|
||||
PsiType type = arrayDfaVar.getVariableType();
|
||||
if (!(type instanceof PsiArrayType)) return null;
|
||||
PsiType componentType = ((PsiArrayType)type).getComponentType();
|
||||
PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable();
|
||||
if (arrayPsiVar instanceof PsiVariable) {
|
||||
PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable)arrayPsiVar, index);
|
||||
if (constantArrayElement != null) {
|
||||
return getExpressionDfaValue(constantArrayElement);
|
||||
}
|
||||
}
|
||||
PsiVariable indexVariable = getArrayIndexVariable(arrayPsiVar, index);
|
||||
if (indexVariable == null) return null;
|
||||
return myFactory.getVarFactory().createVariableValue(indexVariable, componentType, false, arrayDfaVar);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiVariable getArrayIndexVariable(@Nullable PsiExpression indexExpression) {
|
||||
Object constant = JavaConstantExpressionEvaluator.computeConstantExpression(indexExpression, false);
|
||||
if (constant instanceof Integer && ((Integer)constant).intValue() >= 0) {
|
||||
return myMockIndices
|
||||
.computeIfAbsent((Integer)constant, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, indexExpression));
|
||||
private PsiVariable getArrayIndexVariable(@NotNull PsiElement anchor, int index) {
|
||||
if (index >= 0) {
|
||||
return myMockIndices.computeIfAbsent(index, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, anchor));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+7
-2
@@ -177,11 +177,13 @@ public class DfaValueFactory {
|
||||
|
||||
if(dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) {
|
||||
if(relationType == RelationType.IS || relationType == RelationType.IS_NOT) {
|
||||
boolean isSuperState = ((DfaFactMapValue)dfaRight).getFacts().isSuperStateOf(((DfaFactMapValue)dfaLeft).getFacts());
|
||||
DfaFactMap leftFacts = ((DfaFactMapValue)dfaLeft).getFacts();
|
||||
DfaFactMap rightFacts = ((DfaFactMapValue)dfaRight).getFacts();
|
||||
boolean isSuperState = rightFacts.isSuperStateOf(leftFacts);
|
||||
if (isSuperState) {
|
||||
return getBoolean(relationType == RelationType.IS);
|
||||
}
|
||||
boolean isDistinct = ((DfaFactMapValue)dfaRight).getFacts().isDistinct(((DfaFactMapValue)dfaLeft).getFacts());
|
||||
boolean isDistinct = rightFacts.intersect(leftFacts) == null;
|
||||
if (isDistinct) {
|
||||
return getBoolean(relationType == RelationType.IS_NOT);
|
||||
}
|
||||
@@ -261,4 +263,7 @@ public class DfaValueFactory {
|
||||
public DfaFactMapValue.Factory getFactFactory() {
|
||||
return myFactFactory;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DfaExpressionFactory getExpressionFactory() { return myExpressionFactory;}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ public abstract class Super {
|
||||
|
||||
}
|
||||
|
||||
class Sub extends Zzza {
|
||||
interface Sub extends Zzza {
|
||||
void subMethod() {}
|
||||
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ public abstract class Super {
|
||||
|
||||
}
|
||||
|
||||
class Sub extends Zzza {
|
||||
interface Sub extends Zzza {
|
||||
void subMethod() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import java.util.*;
|
||||
|
||||
class AdvancedArrayAccess {
|
||||
private static final int[] LENGTH = {0, 10, 20, 30};
|
||||
private static final int[] LENGTH2 = {0, 10, 20, 30};
|
||||
|
||||
void testInstanceOf() {
|
||||
Object[] arr = {new String("foo"), new Integer(0)};
|
||||
if(<warning descr="Condition 'arr[0] instanceof Number' is always 'false'">arr[0] instanceof Number</warning>) {
|
||||
System.out.println(((Number)arr[0]).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
void testStaticFinal() {
|
||||
if(<warning descr="Condition 'LENGTH[2] == 20' is always 'true'">LENGTH[2] == 20</warning>) {
|
||||
System.out.println("ok");
|
||||
}
|
||||
}
|
||||
|
||||
int[] getLengths() {
|
||||
// array is still read-only
|
||||
return LENGTH.clone();
|
||||
}
|
||||
|
||||
int getLengthsCount() {
|
||||
// array is still read-only
|
||||
return LENGTH.length;
|
||||
}
|
||||
|
||||
int[] getLength2() {
|
||||
// Reference leaks: do not consider an array as read-only
|
||||
return LENGTH2;
|
||||
}
|
||||
|
||||
void testStaticFinal(int idx) {
|
||||
if(idx == 2 && <warning descr="Condition 'LENGTH[idx] == 20' is always 'true' when reached">LENGTH[idx] == 20</warning>) {
|
||||
System.out.println("ok");
|
||||
}
|
||||
}
|
||||
|
||||
void testStaticFinalRW(int idx) {
|
||||
if(idx == 2 && LENGTH2[idx] == 20) {
|
||||
System.out.println("ok");
|
||||
}
|
||||
}
|
||||
|
||||
void testWrite(int[] arr, int idx) {
|
||||
if(idx != 0) return;
|
||||
arr[idx] = 10;
|
||||
if(<warning descr="Condition 'arr[0] == 11' is always 'false'">arr[0] == 11</warning>) {
|
||||
System.out.println("impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testSplitState(boolean b, String[] data) {
|
||||
data[1] = "A";
|
||||
data[2] = "B";
|
||||
int idx = b ? 0 : 1;
|
||||
data[idx] = "C";
|
||||
if(<warning descr="Condition 'data[2].equals(\"D\")' is always 'false'">data[2].equals("D")</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
if(<warning descr="Condition 'data[1].equals(\"E\")' is always 'false'">data[1].equals("E")</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
if(data[1].equals("A") && <warning descr="Condition 'b' is always 'true' when reached">b</warning>) {
|
||||
System.out.println("Only if b is true");
|
||||
}
|
||||
}
|
||||
|
||||
// Should not be too complex
|
||||
static int max(float[] array) {
|
||||
int max = 0;
|
||||
float val = array[0];
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
if (val < array[i]) {
|
||||
max = i;
|
||||
val = array[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
void processArray(String[] arr) {
|
||||
for(int i=0; i<arr.length; i++) {
|
||||
if(arr[i].isEmpty()) arr[i] = null; // No NPE warning here even if we update arr[i] to null we'll never visit same element twice
|
||||
}
|
||||
}
|
||||
|
||||
void testMethodQualifier() {
|
||||
if(getData()[0] == null) {
|
||||
System.out.println(getData()[0].<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
|
||||
}
|
||||
}
|
||||
|
||||
final String[] getData() {
|
||||
return new String[] {"a", "b", "c"};
|
||||
}
|
||||
|
||||
void testConditional(String[] arr, int idx) {
|
||||
if(idx == 0) {
|
||||
arr[idx] = "foo";
|
||||
}
|
||||
if(<warning descr="Condition 'arr[0].equals(\"bar\") && idx == 0' is always 'false'">arr[0].equals("bar") && <warning descr="Condition 'idx == 0' is always 'false' when reached">idx == 0</warning></warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,4 +68,14 @@ public final class ArrayLength {
|
||||
Arrays.fill(x, -1);
|
||||
Arrays.<warning descr="The call to 'fill' always fails as index is out of bounds">fill</warning>(x, -1, -1, -1);
|
||||
}
|
||||
|
||||
static final String[] ARR1 = {};
|
||||
static final String[] ARR2 = new String[10];
|
||||
static final String[] ARR3 = new String[] {"foo", "bar", "baz"};
|
||||
|
||||
void testFieldInitializers() {
|
||||
if(<warning descr="Condition 'ARR1.length == 0' is always 'true'">ARR1.length == 0</warning>) System.out.println("yes");
|
||||
if(<warning descr="Condition 'ARR2.length == 10' is always 'true'">ARR2.length == 10</warning>) System.out.println("yes");
|
||||
if(<warning descr="Condition 'ARR3.length == 3' is always 'true'">ARR3.length == 3</warning>) System.out.println("yes");
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,11 @@ public class LongRangeBasics {
|
||||
}
|
||||
}
|
||||
|
||||
static final String FOO = "bar";
|
||||
|
||||
void testStaticLength() {
|
||||
if(<warning descr="Condition 'FOO.isEmpty()' is always 'false'">FOO.isEmpty()</warning>) System.out.println("nope");
|
||||
}
|
||||
|
||||
void testWrongMerge(boolean a, boolean b, int c) {
|
||||
int currentLevel = 0;
|
||||
|
||||
@@ -581,4 +581,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testXorNullity() { doTest(); }
|
||||
public void testPrimitiveNull() { doTest(); }
|
||||
public void testLessThanRelations() { doTest(); }
|
||||
public void testAdvancedArrayAccess() { doTest(); }
|
||||
}
|
||||
|
||||
+45
@@ -1153,4 +1153,49 @@ public class ExpressionUtils {
|
||||
if (diffConstant == null) return false;
|
||||
return diffConstant == toConstant - fromConstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an expression which represents an array element with given index if array is known to be never modified
|
||||
* after initialization.
|
||||
*
|
||||
* @param array an array variable
|
||||
* @param index an element index
|
||||
* @return an expression or null if index is out of bounds or array could be modified after initialization
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiExpression getConstantArrayElement(PsiVariable array, int index) {
|
||||
if (index < 0) return null;
|
||||
PsiExpression initializer = array.getInitializer();
|
||||
if (initializer instanceof PsiNewExpression) initializer = ((PsiNewExpression)initializer).getArrayInitializer();
|
||||
if (!(initializer instanceof PsiArrayInitializerExpression)) return null;
|
||||
PsiExpression[] initializers = ((PsiArrayInitializerExpression)initializer).getInitializers();
|
||||
if (index >= initializers.length) return null;
|
||||
if (array instanceof PsiField && !(array.hasModifierProperty(PsiModifier.PRIVATE) && array.hasModifierProperty(PsiModifier.STATIC))) {
|
||||
return null;
|
||||
}
|
||||
Boolean isConstantArray = CachedValuesManager.<Boolean>getCachedValue(array, () -> CachedValueProvider.Result
|
||||
.create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT));
|
||||
return Boolean.TRUE.equals(isConstantArray) ? initializers[index] : null;
|
||||
}
|
||||
|
||||
private static boolean isConstantArray(PsiVariable array) {
|
||||
PsiElement scope = PsiTreeUtil.getParentOfType(array, array instanceof PsiField ? PsiClass.class : PsiCodeBlock.class);
|
||||
if (scope == null) return false;
|
||||
return PsiTreeUtil.processElements(scope, e -> {
|
||||
if (!(e instanceof PsiReferenceExpression)) return true;
|
||||
PsiReferenceExpression ref = (PsiReferenceExpression)e;
|
||||
if (!ref.isReferenceTo(array)) return true;
|
||||
PsiExpression parent = tryCast(PsiUtil.skipParenthesizedExprUp(ref.getParent()), PsiExpression.class);
|
||||
if (parent == null) return false;
|
||||
if (parent instanceof PsiReferenceExpression) {
|
||||
if (isReferenceTo(getArrayFromLengthExpression(parent), array)) return true;
|
||||
if (parent.getParent() instanceof PsiMethodCallExpression &&
|
||||
MethodCallUtils.isCallToMethod((PsiMethodCallExpression)parent.getParent(), CommonClassNames.JAVA_LANG_OBJECT,
|
||||
null, "clone", PsiType.EMPTY_ARRAY)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return parent instanceof PsiArrayAccessExpression && !PsiUtil.isAccessedForWriting(parent);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user