From 159dbaaaa86a28cacff6172578b2e5f26e9255e3 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 27 Jul 2023 16:14:14 +0200 Subject: [PATCH] [java-dfa] IDEA-326612 Perform double/float math on constants in data flow analysis GitOrigin-RevId: 81580363852afff9f54c1a04c51d7116d71dfd1b --- .../java/inst/NumericBinaryInstruction.java | 45 +++++++++++++++---- .../dataFlow/types/DfDoubleConstantType.java | 31 ++++++++++++- .../dataFlow/types/DfFloatConstantType.java | 29 +++++++++++- .../fixture/FloatingPointConstantMath.java | 35 +++++++++++++++ .../dataFlow/fixture/UnaryPlusMinus.java | 5 +-- .../fixture/UnknownFloatMayBeNaN.java | 7 ++- .../DataFlowRangeAnalysisTest.java | 1 + 7 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/FloatingPointConstantMath.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/NumericBinaryInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/NumericBinaryInstruction.java index 7251a24cb42c..46e02256fd92 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/NumericBinaryInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/NumericBinaryInstruction.java @@ -5,12 +5,9 @@ import com.intellij.codeInspection.dataFlow.lang.DfaAnchor; import com.intellij.codeInspection.dataFlow.lang.ir.EvalInstruction; import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeBinOp; -import com.intellij.codeInspection.dataFlow.types.DfIntegralType; -import com.intellij.codeInspection.dataFlow.types.DfLongType; -import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.codeInspection.dataFlow.types.*; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; -import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,11 +26,41 @@ public class NumericBinaryInstruction extends EvalInstruction { if (myBinOp == null) return factory.getUnknown(); DfaValue left = arguments[0]; DfaValue right = arguments[1]; - DfIntegralType leftType = ObjectUtils.tryCast(state.getDfType(left), DfIntegralType.class); - DfIntegralType rightType = ObjectUtils.tryCast(state.getDfType(right), DfIntegralType.class); - if (leftType == null || rightType == null) return factory.getUnknown(); - DfIntegralType resultType = leftType instanceof DfLongType ? DfTypes.LONG : DfTypes.INT; - return factory.getBinOpFactory().create(left, right, state, resultType, myBinOp); + DfType leftType = state.getDfType(left); + DfType rightType = state.getDfType(right); + if (leftType instanceof DfIntegralType && rightType instanceof DfIntegralType) { + DfIntegralType resultType = leftType instanceof DfLongType ? DfTypes.LONG : DfTypes.INT; + return factory.getBinOpFactory().create(left, right, state, resultType, myBinOp); + } + if (leftType instanceof DfDoubleConstantType d1 && rightType instanceof DfDoubleConstantType d2) { + return factory.fromDfType(eval(myBinOp, d1.getValue(), d2.getValue())); + } + if (leftType instanceof DfFloatConstantType f1 && rightType instanceof DfFloatConstantType f2) { + return factory.fromDfType(eval(myBinOp, f1.getValue(), f2.getValue())); + } + return factory.getUnknown(); + } + + private static DfType eval(LongRangeBinOp op, float f1, float f2) { + return switch (op) { + case PLUS -> DfTypes.floatValue(f1 + f2).makeWide(); + case MINUS -> DfTypes.floatValue(f1 - f2).makeWide(); + case MUL -> DfTypes.floatValue(f1 * f2).makeWide(); + case DIV -> DfTypes.floatValue(f1 / f2).makeWide(); + case MOD -> DfTypes.floatValue(f1 % f2).makeWide(); + default -> DfType.TOP; + }; + } + + private static DfType eval(LongRangeBinOp op, double d1, double d2) { + return switch (op) { + case PLUS -> DfTypes.doubleValue(d1 + d2).makeWide(); + case MINUS -> DfTypes.doubleValue(d1 - d2).makeWide(); + case MUL -> DfTypes.doubleValue(d1 * d2).makeWide(); + case DIV -> DfTypes.doubleValue(d1 / d2).makeWide(); + case MOD -> DfTypes.doubleValue(d1 % d2).makeWide(); + default -> DfType.TOP; + }; } public String toString() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java index 8df8fb770368..10e9dbc7e2fb 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfDoubleConstantType.java @@ -7,11 +7,27 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; -public class DfDoubleConstantType extends DfConstantType implements DfDoubleType { +public final class DfDoubleConstantType extends DfConstantType implements DfDoubleType { + private final boolean shouldWiden; + DfDoubleConstantType(double value) { - super(value); + this(value, false); } + private DfDoubleConstantType(double value, boolean widen) { + super(value); + shouldWiden = widen; + } + + public DfDoubleConstantType makeWide() { + return shouldWiden ? this : new DfDoubleConstantType(getValue(), true); + } + + @Override + public DfType widen() { + return shouldWiden ? DfTypes.DOUBLE : super.widen(); + } + @NotNull @Override public DfType join(@NotNull DfType other) { @@ -63,4 +79,15 @@ public class DfDoubleConstantType extends DfConstantType implements DfDo } return DfDoubleRangeType.create(value, value, true, true); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o instanceof DfDoubleConstantType that && super.equals(o) && shouldWiden == that.shouldWiden; + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + (shouldWiden ? 1 : 0); + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java index 1f2f97842594..13478dedcca6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/types/DfFloatConstantType.java @@ -7,9 +7,25 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; -public class DfFloatConstantType extends DfConstantType implements DfFloatType { +public final class DfFloatConstantType extends DfConstantType implements DfFloatType { + private final boolean shouldWiden; + DfFloatConstantType(float value) { + this(value, false); + } + + private DfFloatConstantType(float value, boolean widen) { super(value); + shouldWiden = widen; + } + + public DfFloatConstantType makeWide() { + return shouldWiden ? this : new DfFloatConstantType(getValue(), true); + } + + @Override + public DfType widen() { + return shouldWiden ? DfTypes.FLOAT : super.widen(); } @NotNull @@ -63,4 +79,15 @@ public class DfFloatConstantType extends DfConstantType implements DfFloa } return DfFloatRangeType.create(value, value, true, true); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o instanceof DfFloatConstantType that && super.equals(o) && shouldWiden == that.shouldWiden; + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + (shouldWiden ? 1 : 0); + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/FloatingPointConstantMath.java b/java/java-tests/testData/inspection/dataFlow/fixture/FloatingPointConstantMath.java new file mode 100644 index 000000000000..6eeb83f6f74f --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/FloatingPointConstantMath.java @@ -0,0 +1,35 @@ +import java.util.*; + +class Test { + void test() { + double val = Math.sqrt(2); + float f = 1.0f; + double res = val + f; + if (res > 2.41 && res < 2.42) {} + } + + void testNan() { + double res = Math.sqrt(Math.sqrt(2) - 2); + if (Double.isNaN(res)) {} + } + + void testDouble() { + double d1 = 4; + double d2 = 5.5; + if (d1 * d2 == 22.0) {} + if (d2 / d1 == 1.375) {} + if (d1 + d2 == 9.5) {} + if (d1 - d2 == -1.5) {} + if (d2 % d1 == 1.5) {} + } + + void testFloat() { + float d1 = 4f; + float d2 = 5.5f; + if (d1 * d2 == 18) {} + if (d2 / d1 == 1.375) {} + if (d1 + d2 == 9.5) {} + if (d1 - d2 == -1.5) {} + if (d2 % d1 == 1.5) {} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/UnaryPlusMinus.java b/java/java-tests/testData/inspection/dataFlow/fixture/UnaryPlusMinus.java index 374a359cc3dc..05c28bf2a5ed 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/UnaryPlusMinus.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/UnaryPlusMinus.java @@ -31,13 +31,12 @@ public class UnaryPlusMinus { } void testDouble() { - // Not supported double x = 0; x++; - if (x == 1) {} + if (x == 1) {} x = 1e15; x++; - if (x == 1e15) {} + if (x == 1e15) {} } void testArray() { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/UnknownFloatMayBeNaN.java b/java/java-tests/testData/inspection/dataFlow/fixture/UnknownFloatMayBeNaN.java index 1c29ebc59111..516f414fd4dd 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/UnknownFloatMayBeNaN.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/UnknownFloatMayBeNaN.java @@ -1,9 +1,13 @@ class Fun { public static void main(String[] args) throws Exception { float f1 = Float.parseFloat("NaN"); - if (f1 == f1) { + if (f1 == f1) { System.err.println("ELVIS LIVES!"); } + float f3 = getFloat(); + if (f3 == f3) { + System.out.println("ELVIS LIVES!"); + } float f2 = Float.NaN; // Warning: Condition 'f2 == f2' is always 'false' // Correct, but if you extract the assignment to a method the inspection flips @@ -12,4 +16,5 @@ class Fun { } } + private static native float getFloat(); } \ 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 57b824030283..62e9f8761340 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -88,6 +88,7 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase { public void testFloatingPointRanges() { doTest(); } public void testFloatingPointCasts() { doTest(); } public void testFloatingPointMaxLoop() { doTest(); } + public void testFloatingPointConstantMath() { doTest(); } public void testStringIndexOfRelation() { doTest(); } public void testIncompleteLoop() { doTest(); } public void testTwoFlagsMixed() { doTest(); }