[java-dfa] IDEA-327531 Improve string concatenation handling in data flow analysis

GitOrigin-RevId: 353a1632dbb588f7c4f95cde16deec03764bceb2
This commit is contained in:
Tagir Valeev
2023-08-04 15:19:59 +00:00
committed by intellij-monorepo-bot
parent d343cf08e1
commit eafaa0e48b
4 changed files with 121 additions and 11 deletions
@@ -147,6 +147,12 @@ public final class CustomMethodHandlers {
.register(anyOf(instanceCall("java.io.InputStream", "skip").parameterTypes("long"),
instanceCall("java.io.Reader", "skip").parameterTypes("long")),
toValue((args, memState, factory, method) -> skip(args.myArguments, memState)))
.register(anyOf(
staticCall(JAVA_LANG_INTEGER, "toString").parameterCount(1),
staticCall(JAVA_LANG_LONG, "toString").parameterCount(1),
staticCall(JAVA_LANG_STRING, "valueOf").parameterTypes("int"),
staticCall(JAVA_LANG_STRING, "valueOf").parameterTypes("long")
), toValue((args, memState, factory, method) -> numberAsDecimalString(args, memState)))
.register(staticCall(JAVA_LANG_INTEGER, "toHexString").parameterCount(1),
toValue((args, memState, factory, method) -> numberAsString(args, memState, 4, Integer.SIZE)))
.register(staticCall(JAVA_LANG_INTEGER, "toOctalString").parameterCount(1),
@@ -505,6 +511,30 @@ public final class CustomMethodHandlers {
return longRange(LongRangeSet.range(0, Math.max(0, range.max())));
}
private static @NotNull DfType numberAsDecimalString(DfaCallArguments args, DfaMemoryState state) {
DfaValue arg = args.myArguments[0];
if (arg == null) return DfType.TOP;
return numberAsDecimalString(state, arg);
}
public static @NotNull DfType numberAsDecimalString(@NotNull DfaMemoryState state, @NotNull DfaValue arg) {
LongRangeSet range = DfLongType.extractRange(state.getDfType(arg));
if (range.isEmpty()) return DfType.TOP;
long min = range.min();
long max = range.max();
DfType length;
if (min >= 0) {
length = intRange(LongRangeSet.range(String.valueOf(min).length(), String.valueOf(max).length()));
}
else if (max <= 0) {
length = intRange(LongRangeSet.range(String.valueOf(max).length(), String.valueOf(min).length()));
}
else {
length = intRange(LongRangeSet.range(1, Math.max(String.valueOf(max).length(), String.valueOf(min).length())));
}
return STRING_LENGTH.asDfType(length);
}
private static @NotNull DfType numberAsString(DfaCallArguments args, DfaMemoryState state, int bitsPerChar, int maxBits) {
DfaValue arg = args.myArguments[0];
if (arg == null) return DfType.TOP;
@@ -1654,12 +1654,12 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
if (leftCast != null) {
generateBoxingUnboxingInstructionFor(lExpr, leftCast);
generateBoxingUnboxingInstructionFor(lExpr, lType, leftCast, false);
}
rExpr.accept(this);
if (rightCast != null) {
generateBoxingUnboxingInstructionFor(rExpr, rightCast);
generateBoxingUnboxingInstructionFor(rExpr, rType, rightCast, false);
}
}
@@ -8,8 +8,8 @@ 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.DfIntType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import org.jetbrains.annotations.NotNull;
@@ -31,23 +31,50 @@ public class StringConcatInstruction extends EvalInstruction {
@NotNull DfaValue @NotNull ... arguments) {
DfaValue left = arguments[0];
DfaValue right = arguments[1];
String leftString = state.getDfType(left).getConstantOfType(String.class);
String rightString = state.getDfType(right).getConstantOfType(String.class);
String leftString = getString(state, left);
String rightString = getString(state, right);
if (leftString != null && rightString != null &&
leftString.length() + rightString.length() <= CustomMethodHandlers.MAX_STRING_CONSTANT_LENGTH_TO_TRACK) {
return factory.fromDfType(concatenationResult(leftString + rightString, myStringType));
}
DfaValue leftLength = SpecialField.STRING_LENGTH.createValue(factory, left);
DfaValue rightLength = SpecialField.STRING_LENGTH.createValue(factory, right);
DfType leftRange = state.getDfType(leftLength);
DfType rightRange = state.getDfType(rightLength);
DfType resultRange = leftRange instanceof DfIntType ? ((DfIntType)leftRange).eval(rightRange, LongRangeBinOp.PLUS) : INT;
DfIntType leftRange = getLength(factory, state, left, leftString);
DfIntType rightRange = getLength(factory, state, right, rightString);
DfType resultRange = leftRange.eval(rightRange, LongRangeBinOp.PLUS);
DfType result = resultRange.isConst(0)
? referenceConstant("", myStringType)
: SpecialField.STRING_LENGTH.asDfType(resultRange).meet(myStringType.asDfType());
return factory.fromDfType(result);
}
@NotNull
private static DfIntType getLength(@NotNull DfaValueFactory factory, @NotNull DfaMemoryState state, @NotNull DfaValue dfaValue, @Nullable String constValue) {
if (constValue != null) {
return intValue(constValue.length());
}
DfType lengthType;
if (dfaValue.getDfType() instanceof DfIntegralType) {
DfType decimalString = CustomMethodHandlers.numberAsDecimalString(state, dfaValue);
lengthType = SpecialField.STRING_LENGTH.getFromQualifier(decimalString);
} else if (dfaValue.getDfType() instanceof DfBooleanType) {
lengthType = intRange(LongRangeSet.range(4, 5)); // "true" or "false"
} else if (dfaValue.getDfType() instanceof DfFloatingPointType) {
lengthType = intRange(LongRangeSet.range(3, 26));
} else {
DfaValue lengthValue = SpecialField.STRING_LENGTH.createValue(factory, dfaValue);
lengthType = state.getDfType(lengthValue);
}
if (lengthType instanceof DfIntType intType) return intType;
return (DfIntType)intRange(LongRangeSet.range(0, Integer.MAX_VALUE));
}
@Nullable
private static String getString(@NotNull DfaMemoryState state, DfaValue value) {
Object constant = state.getDfType(value).getConstantOfType(Object.class);
// Do not process float/double constants, as their string representation may depend on JDK version
return constant instanceof String || constant instanceof Integer || constant instanceof Long ||
constant instanceof Boolean ? constant.toString() : null;
}
public String toString() {
return "STRING_CONCAT";
}
@@ -13,4 +13,57 @@ public class StringConcat {
if (<warning descr="Condition 'res.length() < 6' is always 'false'">res.length() < 6</warning>) {}
}
}
void testConcatBoolean(boolean b) {
String str;
if (b) {
str = "Value: " + b;
if (<warning descr="Condition 'str.equals(\"Value: true\")' is always 'true'">str.equals("Value: true")</warning>) {
}
}
str = b+":";
if (<warning descr="Condition 'str.length() == 5 || str.length() == 6' is always 'true'">str.length() == 5 || <warning descr="Condition 'str.length() == 6' is always 'true' when reached">str.length() == 6</warning></warning>) {}
}
void testConcat(int a, long b) {
String s = a + ":" + b;
if (<warning descr="Condition 's.length() >= 3 && s.length() <= 32' is always 'true'"><warning descr="Condition 's.length() >= 3' is always 'true'">s.length() >= 3</warning> && <warning descr="Condition 's.length() <= 32' is always 'true' when reached">s.length() <= 32</warning></warning>) {}
if (a > 0 && a < 10) {
String s1 = "Value: " + a;
if (<warning descr="Condition 's1.length() == 8' is always 'true'">s1.length() == 8</warning>) {}
if (b > -200 && b < -100) {
s1 += b;
if (<warning descr="Condition 's1.length() == 12' is always 'true'">s1.length() == 12</warning>) {}
}
}
if (b >= Integer.MIN_VALUE && b <= Integer.MAX_VALUE) {
String bs = Long.toString(b);
if (<warning descr="Condition 'bs.isEmpty()' is always 'false'">bs.isEmpty()</warning>) {}
if (<warning descr="Condition 'bs.length() >= 1 && bs.length() <= 11' is always 'true'"><warning descr="Condition 'bs.length() >= 1' is always 'true'">bs.length() >= 1</warning> && <warning descr="Condition 'bs.length() <= 11' is always 'true' when reached">bs.length() <= 11</warning></warning>) {
}
}
if (a == 10) {
String as = "Value: " + a;
if (<warning descr="Condition 'as.equals(\"Value: 10\")' is always 'true'">as.equals("Value: 10")</warning>) {
}
}
}
void testConcatFloat(float a, double b, String s2) {
if (a == 1) {
String s = a+":";
if (s.equals("1.0:")) {}
}
String s1 = a + ":" + b;
if (<warning descr="Condition 's1.length() >= 7 && s1.length() <= 53' is always 'true'"><warning descr="Condition 's1.length() >= 7' is always 'true'">s1.length() >= 7</warning> && <warning descr="Condition 's1.length() <= 53' is always 'true' when reached">s1.length() <= 53</warning></warning>) {
}
String s3 = s2 + b + a;
if (<warning descr="Condition 's3.isEmpty()' is always 'false'">s3.isEmpty()</warning>) {}
if (<warning descr="Condition 's3.length() < 6' is always 'false'">s3.length() < 6</warning>) {}
}
}