From eafaa0e48b5ed2a2dd849ea2aca1fed8d47f82a6 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 4 Aug 2023 11:35:53 +0200 Subject: [PATCH] [java-dfa] IDEA-327531 Improve string concatenation handling in data flow analysis GitOrigin-RevId: 353a1632dbb588f7c4f95cde16deec03764bceb2 --- .../dataFlow/CustomMethodHandlers.java | 30 +++++++++++ .../dataFlow/java/ControlFlowAnalyzer.java | 4 +- .../java/inst/StringConcatInstruction.java | 45 ++++++++++++---- .../dataFlow/fixture/StringConcat.java | 53 +++++++++++++++++++ 4 files changed, 121 insertions(+), 11 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index 16a170a6cc83..4cdee8f0c47d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index 4440f82596f8..610c65fd5229 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -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); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/StringConcatInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/StringConcatInstruction.java index 507ac87635ca..21811b3318c6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/StringConcatInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/StringConcatInstruction.java @@ -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"; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StringConcat.java b/java/java-tests/testData/inspection/dataFlow/fixture/StringConcat.java index b31c61336843..47accd285926 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StringConcat.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StringConcat.java @@ -13,4 +13,57 @@ public class StringConcat { if (res.length() < 6) {} } } + + void testConcatBoolean(boolean b) { + String str; + if (b) { + str = "Value: " + b; + if (str.equals("Value: true")) { + + } + } + + str = b+":"; + if (str.length() == 5 || str.length() == 6) {} + } + + void testConcat(int a, long b) { + String s = a + ":" + b; + if (s.length() >= 3 && s.length() <= 32) {} + if (a > 0 && a < 10) { + String s1 = "Value: " + a; + if (s1.length() == 8) {} + if (b > -200 && b < -100) { + s1 += b; + if (s1.length() == 12) {} + } + } + if (b >= Integer.MIN_VALUE && b <= Integer.MAX_VALUE) { + String bs = Long.toString(b); + if (bs.isEmpty()) {} + if (bs.length() >= 1 && bs.length() <= 11) { + } + } + if (a == 10) { + String as = "Value: " + a; + if (as.equals("Value: 10")) { + + } + } + } + + void testConcatFloat(float a, double b, String s2) { + if (a == 1) { + String s = a+":"; + if (s.equals("1.0:")) {} + } + String s1 = a + ":" + b; + if (s1.length() >= 7 && s1.length() <= 53) { + + } + String s3 = s2 + b + a; + if (s3.isEmpty()) {} + if (s3.length() < 6) {} + } + }