[java-dfa] IDEA-326612 Perform double/float math on constants in data flow analysis

GitOrigin-RevId: 81580363852afff9f54c1a04c51d7116d71dfd1b
This commit is contained in:
Tagir Valeev
2023-07-28 09:45:26 +00:00
committed by intellij-monorepo-bot
parent 8d856b3e23
commit 159dbaaaa8
7 changed files with 137 additions and 16 deletions
@@ -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() {
@@ -7,11 +7,27 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class DfDoubleConstantType extends DfConstantType<Double> implements DfDoubleType {
public final class DfDoubleConstantType extends DfConstantType<Double> 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<Double> 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);
}
}
@@ -7,9 +7,25 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class DfFloatConstantType extends DfConstantType<Float> implements DfFloatType {
public final class DfFloatConstantType extends DfConstantType<Float> 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<Float> 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);
}
}
@@ -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 (<warning descr="Condition 'res > 2.41 && res < 2.42' is always 'true'"><warning descr="Condition 'res > 2.41' is always 'true'">res > 2.41</warning> && <warning descr="Condition 'res < 2.42' is always 'true' when reached">res < 2.42</warning></warning>) {}
}
void testNan() {
double res = Math.sqrt(Math.sqrt(2) - 2);
if (<warning descr="Condition 'Double.isNaN(res)' is always 'true'">Double.isNaN(res)</warning>) {}
}
void testDouble() {
double d1 = 4;
double d2 = 5.5;
if (<warning descr="Condition 'd1 * d2 == 22.0' is always 'true'">d1 * d2 == 22.0</warning>) {}
if (<warning descr="Condition 'd2 / d1 == 1.375' is always 'true'">d2 / d1 == 1.375</warning>) {}
if (<warning descr="Condition 'd1 + d2 == 9.5' is always 'true'">d1 + d2 == 9.5</warning>) {}
if (<warning descr="Condition 'd1 - d2 == -1.5' is always 'true'">d1 - d2 == -1.5</warning>) {}
if (<warning descr="Condition 'd2 % d1 == 1.5' is always 'true'">d2 % d1 == 1.5</warning>) {}
}
void testFloat() {
float d1 = 4f;
float d2 = 5.5f;
if (<warning descr="Condition 'd1 * d2 == 18' is always 'false'">d1 * d2 == 18</warning>) {}
if (<warning descr="Condition 'd2 / d1 == 1.375' is always 'true'">d2 / d1 == 1.375</warning>) {}
if (<warning descr="Condition 'd1 + d2 == 9.5' is always 'true'">d1 + d2 == 9.5</warning>) {}
if (<warning descr="Condition 'd1 - d2 == -1.5' is always 'true'">d1 - d2 == -1.5</warning>) {}
if (<warning descr="Condition 'd2 % d1 == 1.5' is always 'true'">d2 % d1 == 1.5</warning>) {}
}
}
@@ -31,13 +31,12 @@ public class UnaryPlusMinus {
}
void testDouble() {
// Not supported
double x = 0;
x++;
if (x == 1) {}
if (<warning descr="Condition 'x == 1' is always 'true'">x == 1</warning>) {}
x = 1e15;
x++;
if (x == 1e15) {}
if (<warning descr="Condition 'x == 1e15' is always 'false'">x == 1e15</warning>) {}
}
void testArray() {
@@ -1,9 +1,13 @@
class Fun {
public static void main(String[] args) throws Exception {
float f1 = Float.parseFloat("NaN");
if (f1 == f1) {
if (<warning descr="Condition 'f1 == f1' is always 'false'">f1 == f1</warning>) {
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();
}
@@ -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(); }