[java-dfa] Fixes for new widening procedure

GitOrigin-RevId: 615c6f1cd89002a56a65bc8f886e60e1c74c099f
This commit is contained in:
Tagir Valeev
2021-02-09 07:49:53 +00:00
committed by intellij-monorepo-bot
parent 11fa1e5d48
commit ae140ee21e
15 changed files with 214 additions and 35 deletions
@@ -167,5 +167,8 @@ public interface DfaMemoryState {
*/
boolean shouldCompareByEquals(DfaValue dfaLeft, DfaValue dfaRight);
/**
* Widen this memory state on back-branches
*/
void widen();
}
@@ -895,7 +895,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
String rightString = memState.getDfType(right).getConstantOfType(String.class);
if (leftString != null && rightString != null &&
leftString.length() + rightString.length() <= CustomMethodHandlers.MAX_STRING_CONSTANT_LENGTH_TO_TRACK) {
return factory.getConstant(leftString + rightString, stringType);
return factory.fromDfType(concatenationResult(leftString + rightString, stringType));
}
DfaValue leftLength = SpecialField.STRING_LENGTH.createValue(factory, left);
DfaValue rightLength = SpecialField.STRING_LENGTH.createValue(factory, right);
@@ -2,9 +2,7 @@
package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfLongType;
import com.intellij.codeInspection.dataFlow.types.DfPrimitiveType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaBinOpValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
@@ -17,8 +15,6 @@ import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.rangeClamped;
/**
* A unary instruction that converts a primitive value from the stack to the desired type
*/
@@ -47,13 +43,8 @@ public class PrimitiveConversionInstruction extends EvalInstruction {
}
DfType dfType = state.getDfType(value);
if (dfType instanceof DfConstantType && type != null) {
Object casted = TypeConversionUtil.computeCastTo(((DfConstantType<?>)dfType).getValue(), type);
return factory.getConstant(casted, type);
}
if (TypeConversionUtil.isIntegralNumberType(type)) {
LongRangeSet range = DfLongType.extractRange(dfType);
return factory.fromDfType(rangeClamped(range.castTo(type), PsiType.LONG.equals(type)));
if (dfType instanceof DfPrimitiveType && type != null) {
return factory.fromDfType(((DfPrimitiveType)dfType).castTo(type));
}
return factory.getUnknown();
}
@@ -22,11 +22,17 @@ public interface DfIntType extends DfIntegralType {
getWideRange().contains(intType.getWideRange());
}
@Override
default boolean containsConstant(@NotNull DfConstantType<?> constant) {
Integer value = constant.getConstantOfType(Integer.class);
return value != null && getRange().contains(value);
}
@Override
default @NotNull DfType eval(@NotNull DfType other, @NotNull LongRangeBinOp op) {
if (!(other instanceof DfIntType)) return DfTypes.INT;
LongRangeSet result = op.eval(getRange(), ((DfIntType)other).getRange(), false);
LongRangeSet wideResult = op.evalWide(getRange(), ((DfIntType)other).getRange(), false);
LongRangeSet wideResult = op.evalWide(getWideRange(), ((DfIntType)other).getWideRange(), false);
return DfTypes.intRange(result, wideResult);
}
@@ -4,6 +4,9 @@ package com.intellij.codeInspection.dataFlow.types;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeBinOp;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
/**
@@ -16,7 +19,13 @@ public interface DfIntegralType extends DfPrimitiveType {
default @NotNull LongRangeSet getWideRange() {
return getRange();
}
/**
* Perform binary operation between this type and other type
* @param other other operand
* @param op operation to perform
* @return result of the operation
*/
@NotNull DfType eval(@NotNull DfType other, @NotNull LongRangeBinOp op);
@NotNull
@@ -29,4 +38,18 @@ public interface DfIntegralType extends DfPrimitiveType {
}
@NotNull DfType meetRange(@NotNull LongRangeSet range);
/**
* Cast this type to the specified primitive type
* @param type target type
* @return result of the cast
*/
default @NotNull DfType castTo(@NotNull PsiPrimitiveType type) {
if (!TypeConversionUtil.isIntegralNumberType(type)) {
return DfPrimitiveType.super.castTo(type);
}
LongRangeSet range = getRange().castTo(type);
LongRangeSet wideRange = getWideRange().castTo(type);
return type.equals(PsiType.LONG) ? DfTypes.longRange(range, wideRange) : DfTypes.intRange(range, wideRange);
}
}
@@ -22,11 +22,17 @@ public interface DfLongType extends DfIntegralType {
getWideRange().contains(longType.getWideRange());
}
@Override
default boolean containsConstant(@NotNull DfConstantType<?> constant) {
Long value = constant.getConstantOfType(Long.class);
return value != null && getRange().contains(value);
}
@Override
default @NotNull DfType eval(@NotNull DfType other, @NotNull LongRangeBinOp op) {
if (!(other instanceof DfLongType)) return DfTypes.LONG;
LongRangeSet result = op.eval(getRange(), ((DfLongType)other).getRange(), true);
LongRangeSet wideResult = op.evalWide(getRange(), ((DfLongType)other).getRange(), true);
LongRangeSet wideResult = op.evalWide(getWideRange(), ((DfLongType)other).getWideRange(), true);
return DfTypes.longRange(result, wideResult);
}
@@ -2,6 +2,7 @@
package com.intellij.codeInspection.dataFlow.types;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
/**
@@ -10,4 +11,17 @@ import org.jetbrains.annotations.NotNull;
public interface DfPrimitiveType extends DfType {
@NotNull
PsiPrimitiveType getPsiType();
/**
* Cast this type to the specified primitive type
* @param type target type
* @return result of the cast
*/
default @NotNull DfType castTo(@NotNull PsiPrimitiveType type) {
Object value = TypeConversionUtil.computeCastTo(getConstantOfType(Object.class), type);
if (value != null) {
return DfTypes.constant(value, type);
}
return DfTypes.TOP;
}
}
@@ -19,14 +19,25 @@ public class DfReferenceConstantType extends DfConstantType<Object> implements D
private final @NotNull Mutability myMutability;
private final @Nullable SpecialField mySpecialField;
private final @NotNull DfType mySpecialFieldType;
private final boolean myDropConstantOnWiden;
DfReferenceConstantType(@NotNull Object constant, @NotNull PsiType psiType, @NotNull TypeConstraint type) {
DfReferenceConstantType(@NotNull Object constant, @NotNull PsiType psiType, @NotNull TypeConstraint type, boolean dropConstantOnWiden) {
super(constant);
myPsiType = psiType;
myConstraint = type;
myMutability = constant instanceof PsiModifierListOwner ? Mutability.getMutability((PsiModifierListOwner)constant) : Mutability.UNKNOWN;
mySpecialField = SpecialField.fromQualifierType(psiType);
mySpecialFieldType = mySpecialField == null ? BOTTOM : mySpecialField.fromConstant(constant);
myDropConstantOnWiden = dropConstantOnWiden;
}
@Override
public DfType widen() {
if (myDropConstantOnWiden) {
return new DfGenericObjectType(Set.of(), myConstraint, DfaNullability.NOT_NULL, myMutability,
mySpecialField, mySpecialFieldType.widen(), false);
}
return this;
}
@NotNull
@@ -39,7 +50,7 @@ public class DfReferenceConstantType extends DfConstantType<Object> implements D
if (type.isSuperType(this)) return this;
TypeConstraint constraint = type.getConstraint().meet(myConstraint);
if (constraint != TypeConstraints.BOTTOM) {
DfReferenceConstantType subConstant = new DfReferenceConstantType(getValue(), myPsiType, constraint);
DfReferenceConstantType subConstant = new DfReferenceConstantType(getValue(), myPsiType, constraint, myDropConstantOnWiden);
if (type.isSuperType(subConstant)) return subConstant;
}
}
@@ -123,6 +123,11 @@ public interface DfReferenceType extends DfType {
return this;
}
@Override
default boolean containsConstant(@NotNull DfConstantType<?> constant) {
return dropTypeConstraint().isSuperType(constant);
}
@Override
@NlsSafe String toString();
}
@@ -18,6 +18,14 @@ public interface DfType {
* @return true if this type is the supertype of other.
*/
boolean isSuperType(@NotNull DfType other);
/**
* @param constant
* @return true given constant value may be contained by this supertype
*/
default boolean containsConstant(@NotNull DfConstantType<?> constant) {
return isSuperType(constant);
}
default boolean isMergeable(@NotNull DfType other) {
return isSuperType(other);
@@ -219,13 +219,10 @@ public final class DfTypes {
}
static @NotNull DfType intRange(@NotNull LongRangeSet range, @Nullable LongRangeSet wideRange) {
if (wideRange == null || wideRange.equals(range)) return intRange(range);
if (wideRange == null || wideRange.equals(range) || wideRange.isEmpty()) return intRange(range);
if (range.isEmpty()) {
return BOTTOM;
}
if (wideRange.isEmpty() || wideRange.min() > range.min() || wideRange.max() < range.max()) {
throw new IllegalArgumentException("Wide range " + wideRange + " must contain range " + range);
}
Long value = range.getConstantValue();
if (value != null) {
return new DfIntConstantType(Math.toIntExact(value), wideRange);
@@ -265,13 +262,10 @@ public final class DfTypes {
}
static @NotNull DfType longRange(@NotNull LongRangeSet range, @Nullable LongRangeSet wideRange) {
if (wideRange == null || wideRange.equals(range)) return longRange(range);
if (wideRange == null || wideRange.equals(range) || wideRange.isEmpty()) return longRange(range);
if (range.isEmpty()) {
return BOTTOM;
}
if (wideRange.isEmpty() || wideRange.min() > range.min() || wideRange.max() < range.max()) {
throw new IllegalArgumentException("Wide range " + wideRange + " must contain range " + range);
}
Long value = range.getConstantValue();
if (value != null) {
return new DfLongConstantType(value, wideRange);
@@ -451,7 +445,17 @@ public final class DfTypes {
if (constant instanceof Double) {
return doubleValue((Double)constant);
}
return new DfReferenceConstantType(constant, type, TypeConstraints.instanceOf(type));
return new DfReferenceConstantType(constant, type, TypeConstraints.instanceOf(type), false);
}
/**
* @param constant string constant
* @param stringType string type
* @return concatenation result string
*/
@NotNull
public static DfConstantType<?> concatenationResult(@NotNull String constant, @NotNull PsiType stringType) {
return new DfReferenceConstantType(constant, stringType, TypeConstraints.exact(stringType), true);
}
/**
@@ -2,7 +2,10 @@
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfIntegralType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -98,17 +101,18 @@ public abstract class DfaCondition {
if (relationType == RelationType.EQ || relationType == RelationType.NE) {
if (leftType instanceof DfConstantType) {
DfConstantType<?> leftConst = (DfConstantType<?>)leftType;
if (rightType instanceof DfConstantType) {
return fromBoolean(leftType.isConst(((DfConstantType<?>)rightType).getValue()) ^
!DfaUtil.isNaN(((DfConstantType<?>)leftType).getValue()) ^
!DfaUtil.isNaN(leftConst.getValue()) ^
relationType == RelationType.EQ);
}
if (!couldBeEqualToConstant(rightType, leftType)) {
if (!rightType.containsConstant(leftConst)) {
return fromBoolean(relationType == RelationType.NE);
}
}
else if (rightType instanceof DfConstantType) {
if (!couldBeEqualToConstant(leftType, rightType)) {
if (!leftType.containsConstant((DfConstantType<?>)rightType)) {
return fromBoolean(relationType == RelationType.NE);
}
}
@@ -138,9 +142,5 @@ public abstract class DfaCondition {
return null;
}
private static boolean couldBeEqualToConstant(DfType type, DfType constantType) {
return (type instanceof DfReferenceType ? ((DfReferenceType)type).dropTypeConstraint() : type).isSuperType(constantType);
}
}
}
@@ -0,0 +1,15 @@
public class DontWidenPlusInLoop {
public static int parse(String value, int i) {
do {
i = value.indexOf('{', i);
if (i != -1 && i == value.indexOf("{{", i)) {
i += 2;
}
else {
break;
}
}
while (<warning descr="Condition 'i != -1' is always 'true'">i != -1</warning>);
return i;
}
}
@@ -0,0 +1,91 @@
import java.util.List;
public class WidenMismatch {
void listSize(WidenMismatch m) {
int N = 200;
while (m.makeList().size() == N + 1 + N) {
System.gc();
}
}
native List<String> makeList();
int test(int value) {
if (value < 100) {
return value;
}
else if (value % 10 < 5) {
return value - value % 10;
}
else {
return value + 10 - value % 10;
}
}
void test2(int n) {
int members = 5 * n;
int y = 100 + members * 40 * 3;
}
int myRelativeLevel;
private String getDots() {
String dots = "";
for (int i = 0; i < myRelativeLevel; i += 1) {
dots += ".";
}
return dots;
}
long longDecode(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int multiplier;
switch (s.charAt(s.length() - 1)) {
case 'K':
multiplier = 1000;
break;
case 'M':
multiplier = 1000000;
break;
case 'G':
multiplier = 1000000000;
break;
default:
multiplier = 1;
}
try {
if (multiplier == 1) {
return Long.decode(s);
}
else {
return Long.decode(s.substring(0, s.length() - 1)).longValue() * multiplier;
}
}
catch (NumberFormatException e) {
return 0;
}
}
void test(String testName) {
int result = 0;
int multiplier = 1;
for(int i = testName.length() - 1; i >=0 ; --i) {
final char ch = testName.charAt(i);
if (Character.isDigit(ch)) {
result += (ch - '0')* multiplier;
multiplier *= 10;
} else {
break;
}
}
}
int parse(int len, int ret) {
int k = ((ret >> 12) & 0xF) * 0x11000000;
k |= ((ret >> 8) & 0xF) * 0x110000;
return ret;
}
}
@@ -74,4 +74,6 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
public void testReduceBinOpOnCast() { doTest(); }
public void testSuppressZeroReport() { doTest(); }
public void testCompareMethods() { doTest(); }
public void testWidenMismatch() { doTest(); }
public void testDontWidenPlusInLoop() { doTest(); }
}