IDEA-184733 Better array access tracking in DFA

This commit is contained in:
Tagir Valeev
2018-01-10 12:26:47 +07:00
parent c5421d1260
commit af97567674
16 changed files with 345 additions and 95 deletions
@@ -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;
}
@@ -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;
}
@@ -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,
@@ -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() {
@@ -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;
}
@@ -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;}
}
@@ -9,7 +9,7 @@ public abstract class Super {
}
class Sub extends Zzza {
interface Sub extends Zzza {
void subMethod() {}
}
@@ -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(); }
}
@@ -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);
});
}
}