diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index fac650810299..cc3ce62edc0d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -167,5 +167,8 @@ public interface DfaMemoryState { */ boolean shouldCompareByEquals(DfaValue dfaLeft, DfaValue dfaRight); + /** + * Widen this memory state on back-branches + */ void widen(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 9e5f92e15745..17f3117f3127 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java index c184bcac7745..3d7d7ff0b5c0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java @@ -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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java index d2ed601c3e44..de548da9e286 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntType.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java index da89ad278d20..04272bbd69c1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfIntegralType.java @@ -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); + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java index b687b1a1ef30..5193a5baadb4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfLongType.java @@ -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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java index cfed1d2cbabf..a276346d82a2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfPrimitiveType.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java index 688728cde5aa..ffe44e345d4c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceConstantType.java @@ -19,14 +19,25 @@ public class DfReferenceConstantType extends DfConstantType 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 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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java index 44a779b62c8b..44f1d13e934c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfReferenceType.java @@ -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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java index 7692fae5f4d5..4fd51cd5fa3a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfType.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java index ea97fd681ee9..9af6cb1165de 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfTypes.java @@ -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); } /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java index b3a3eddab5ab..b2fb4b4d4a2a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaCondition.java @@ -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); - } } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/DontWidenPlusInLoop.java b/java/java-tests/testData/inspection/dataFlow/fixture/DontWidenPlusInLoop.java new file mode 100644 index 000000000000..1c79992d85b1 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/DontWidenPlusInLoop.java @@ -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 (i != -1); + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/WidenMismatch.java b/java/java-tests/testData/inspection/dataFlow/fixture/WidenMismatch.java new file mode 100644 index 000000000000..8ba0b1c29dde --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/WidenMismatch.java @@ -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 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; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java index 270c9ab93dca..baccd3360d57 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -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(); } }