IDEA-179287 Array initializer/dimension should be taken into account to track array length

This commit is contained in:
Tagir Valeev
2017-09-20 17:50:07 +07:00
parent 1fcf616272
commit c1b01aed5c
11 changed files with 128 additions and 22 deletions
@@ -109,8 +109,7 @@ public abstract class ContractValue {
new IndependentValue(factory -> factory.getOptionalFactory().getOptional(true), "present");
static final IndependentValue OPTIONAL_ABSENT =
new IndependentValue(factory -> factory.getOptionalFactory().getOptional(false), "empty");
static final IndependentValue ZERO =
new IndependentValue(factory -> factory.getConstFactory().createFromValue(0, PsiType.INT, null), "0");
static final IndependentValue ZERO = new IndependentValue(factory -> factory.getInt(0), "0");
private final Function<DfaValueFactory, DfaValue> mySupplier;
private final String myPresentation;
@@ -492,7 +492,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
if (length != null) {
addInstruction(new PushInstruction(length.createValue(myFactory, qualifier), null));
addInstruction(new PushInstruction(myFactory.getConstFactory().createFromValue(0, PsiType.INT, null), null));
addInstruction(new PushInstruction(myFactory.getInt(0), null));
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, iteratedValue, myProject));
addInstruction(new ConditionalGotoInstruction(loopEndOffset, false, null));
hasSizeCheck = true;
@@ -1044,13 +1044,46 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(expression);
}
@Nullable
private DfaVariableValue getTargetVariable(PsiExpression expression) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiVariable) {
// initialization
return getFactory().getVarFactory().createVariableValue((PsiVariable)expression.getParent(), false);
}
if (parent instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent;
if (assignmentExpression.getOperationTokenType().equals(JavaTokenType.EQ) &&
PsiTreeUtil.isAncestor(assignmentExpression.getRExpression(), expression, false)) {
DfaValue value = getFactory().createValue(assignmentExpression.getLExpression());
if (value instanceof DfaVariableValue) {
return (DfaVariableValue)value;
}
}
}
return null;
}
@Override
public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) {
startElement(expression);
PsiType type = expression.getType();
PsiType componentType = type instanceof PsiArrayType ? ((PsiArrayType)type).getComponentType() : null;
DfaVariableValue var = getTargetVariable(expression);
processArrayInitializers(expression, componentType, DfaPsiUtil.getTypeNullability(componentType));
pushUnknown();
if (var != null) {
// Declaration: write array length
addInstruction(new PushInstruction(var, null, true));
addInstruction(new PushInstruction(getFactory().createTypeValue(type, Nullness.NOT_NULL), expression));
addInstruction(new AssignInstruction(expression, var));
addInstruction(new PushInstruction(SpecialField.ARRAY_LENGTH.createValue(getFactory(), var), null, true));
addInstruction(new PushInstruction(getFactory().getInt(expression.getInitializers().length), null));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
}
else {
pushUnknown();
}
finishElement(expression);
}
@@ -1567,15 +1600,28 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new PopInstruction());
}
pushUnknown();
PsiType type = expression.getType();
if (type instanceof PsiArrayType) {
final PsiExpression[] dimensions = expression.getArrayDimensions();
for (final PsiExpression dimension : dimensions) {
dimension.accept(this);
DfaVariableValue var = getTargetVariable(expression);
if (var == null) {
var = getFactory().getVarFactory().createVariableValue(createTempVariable(type), false);
}
for (PsiExpression ignored : dimensions) {
addInstruction(new PushInstruction(var, null, true));
addInstruction(new PushInstruction(getFactory().createTypeValue(type, Nullness.NOT_NULL), expression));
addInstruction(new AssignInstruction(expression, var)); // var remains on stack as an instruction result
final PsiExpression[] dimensions = expression.getArrayDimensions();
boolean sizeAssigned = false;
DfaValue length = SpecialField.ARRAY_LENGTH.createValue(getFactory(), var);
for (final PsiExpression dimension : dimensions) {
if (!sizeAssigned) {
addInstruction(new PushInstruction(length, null, true));
dimension.accept(this);
addInstruction(new AssignInstruction(dimension, null));
sizeAssigned = true;
}
else {
dimension.accept(this);
}
addInstruction(new PopInstruction());
}
final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
@@ -1588,11 +1634,14 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
}
processArrayInitializers(arrayInitializer, ((PsiArrayType)type).getComponentType(), nullability);
addInstruction(new PushInstruction(length, null, true));
addInstruction(new PushInstruction(getFactory().getInt(arrayInitializer.getInitializers().length), null));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
}
addConditionalRuntimeThrow();
addInstruction(new MethodCallInstruction(expression, null, Collections.emptyList()));
}
else {
pushUnknown(); // qualifier
PsiMethod constructor = pushConstructorArguments(expression);
addConditionalRuntimeThrow();
@@ -1779,8 +1828,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
startElement(block);
try {
block.accept(this);
// return value for void or incomplete block
pushUnknown();
}
finally {
finishElement(block);
@@ -965,7 +965,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (dfaRight instanceof DfaConstValue) {
Object constVal = ((DfaConstValue)dfaRight).getValue();
if (constVal instanceof Boolean) {
DfaConstValue negVal = myFactory.getConstFactory().createFromValue(!((Boolean)constVal).booleanValue(), PsiType.BOOLEAN, null);
DfaConstValue negVal = myFactory.getBoolean(!((Boolean)constVal).booleanValue());
if (!applyRelation(dfaLeft, negVal, !negated)) {
return false;
}
@@ -54,7 +54,7 @@ public enum SpecialField {
STRING_LENGTH(CommonClassNames.JAVA_LANG_STRING, "length", true, LongRangeSet.indexRange()) {
@Override
public DfaValue createFromConstant(DfaValueFactory factory, @NotNull Object obj) {
return obj instanceof String ? factory.getConstFactory().createFromValue(((String)obj).length(), PsiType.INT, null) : null;
return obj instanceof String ? factory.getInt(((String)obj).length()) : null;
}
},
COLLECTION_SIZE(CommonClassNames.JAVA_UTIL_COLLECTION, "size", false, LongRangeSet.indexRange()),
@@ -120,8 +120,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean alwaysOutOfBounds = false;
if (index != DfaUnknownValue.getInstance()) {
DfaValueFactory factory = runner.getFactory();
DfaValue indexNonNegative =
factory.createCondition(index, RelationType.GE, factory.getConstFactory().createFromValue(0, PsiType.INT, null));
DfaValue indexNonNegative = factory.createCondition(index, RelationType.GE, factory.getInt(0));
if (!memState.applyCondition(indexNonNegative)) {
alwaysOutOfBounds = true;
}
@@ -22,7 +22,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.PsiType;
import com.intellij.psi.PsiVariable;
import com.siyeh.ig.callMatcher.CallMapper;
import org.jetbrains.annotations.NotNull;
@@ -64,7 +63,7 @@ public class CollectionFactoryInliner implements CallInliner {
.push(factory.createTypeValue(call.getType(), Nullness.NOT_NULL))
.assign() // leave tmpVar on stack: it's result of method call
.push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = <size>
.push(factory.getConstFactory().createFromValue(factoryInfo.mySize, PsiType.INT, null))
.push(factory.getInt(factoryInfo.mySize))
.assign()
.pop();
return true;
@@ -552,7 +552,7 @@ public class StreamChainInliner implements CallInliner {
.checkNotNull(qualifierExpression, NullabilityProblem.passingNullableToNotNullParameter)
.pop()
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
.push(builder.getFactory().getInt(0))
.ifCondition(JavaTokenType.GT)
.chain(b -> makeMainLoop(b, firstStep, inType))
.endIf();
@@ -568,7 +568,7 @@ public class StreamChainInliner implements CallInliner {
.checkNotNull(sourceCall, NullabilityProblem.callNPE)
.pop()
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
.push(builder.getFactory().getInt(0))
.ifCondition(JavaTokenType.GT)
.chain(b -> makeMainLoop(b, firstStep, inType))
.endIf();
@@ -107,6 +107,11 @@ public class DfaValueFactory {
return myExpressionFactory.getExpressionDfaValue(psiExpression);
}
@NotNull
public DfaConstValue getInt(int value) {
return getConstFactory().createFromValue(value, PsiType.INT, null);
}
@Nullable
public DfaValue createLiteralValue(PsiLiteralExpression literal) {
return getConstFactory().create(literal);
@@ -40,6 +40,7 @@ public class DfaVariableValue extends DfaValue {
myFactory = factory;
}
@NotNull
public DfaVariableValue createVariableValue(PsiVariable myVariable, boolean isNegated) {
PsiType varType = myVariable.getType();
if (varType instanceof PsiEllipsisType) {
@@ -0,0 +1,55 @@
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
class ArrayInitializerLength {
void testDeclaration() {
String[] arr = {"foo"};
if(<warning descr="Condition 'arr.length == 2' is always 'false'">arr.length == 2</warning>) {
System.out.println("oops");
}
}
void testNewExpression() {
String[] arr = new String[]{"foo"};
if(<warning descr="Condition 'arr.length == 2' is always 'false'">arr.length == 2</warning>) {
System.out.println("oops");
}
}
void testDimension() {
int[] arr = new int[3];
if(<warning descr="Condition 'arr.length == 1' is always 'false'">arr.length == 1</warning>) {
System.out.println("oops");
}
}
void testIterate() {
int[] arr = new int[0];
for (int i : <warning descr="Array 'arr' is always empty">arr</warning>) {
System.out.println("never");
}
}
void testConditional() {
int[] arr = Math.random() > 0.5 ? new int[2] : new int[4];
if(<warning descr="Condition 'arr.length == 3' is always 'false'">arr.length == 3</warning>) {
System.out.println("never");
}
if(arr.length == 2) {
System.out.println("possible");
}
}
void testMultiDimensional() {
int[][][] arr = new int[1][2][3];
if(<warning descr="Condition 'arr.length == 1' is always 'true'">arr.length == 1</warning>) {
System.out.println("ok");
}
if(<warning descr="Condition 'arr.length == 2' is always 'false'">arr.length == 2</warning>) {
System.out.println("not ok");
}
if(<warning descr="Condition 'arr.length == 3' is always 'false'">arr.length == 3</warning>) {
System.out.println("not ok");
}
}
}
@@ -541,4 +541,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testIteratePositiveCheck() { doTest(); }
public void testInnerClass() { doTest(); }
public void testCovariantReturn() { doTest(); }
public void testArrayInitializerLength() { doTest(); }
}