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) {}
+ }
+
}