Produce sized string from String.substring() call (IDEA-224327)

GitOrigin-RevId: c36f178be70dcb419c4296d04d5b917390aae8d7
This commit is contained in:
Tagir Valeev
2019-10-09 09:03:09 +00:00
committed by intellij-monorepo-bot
parent 7ff6382c14
commit 49c3fb1013
5 changed files with 147 additions and 36 deletions
@@ -3,6 +3,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.util.OptionalUtil;
@@ -65,6 +66,8 @@ class CustomMethodHandlers {
(args, memState, factory) -> mathAbs(args.myArguments, memState, factory, false))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
(args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true))
.register(exactInstanceCall(JAVA_LANG_STRING, "substring"),
(args, memState, factory) -> substring(args, memState, factory))
.register(OptionalUtil.OPTIONAL_OF_NULLABLE,
(args, memState, factory) -> ofNullable(args.myArguments[0], memState, factory))
.register(instanceCall(JAVA_UTIL_CALENDAR, "get").parameterTypes("int"),
@@ -208,6 +211,28 @@ class CustomMethodHandlers {
return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1));
}
private static DfaValue substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory) {
DfaValue qualifier = args.myQualifier;
DfaValue[] arguments = args.myArguments;
if (arguments.length < 1 || arguments.length > 2 || arguments[0] == null) return null;
LongRangeSet fromPos = state.getValueFact(arguments[0], DfaFactType.RANGE);
if (fromPos == null) return null;
LongRangeSet length = state.getValueFact(SpecialField.STRING_LENGTH.createValue(factory, qualifier), DfaFactType.RANGE);
LongRangeSet toPos = arguments.length == 1 ? length : state.getValueFact(arguments[1], DfaFactType.RANGE);
if (toPos == null) return null;
LongRangeSet resultLen = toPos.minus(fromPos, false)
.intersect(LongRangeSet.point(0).fromRelation(DfaRelationValue.RelationType.GE));
if (length != null) {
resultLen = resultLen.intersect(length.fromRelation(DfaRelationValue.RelationType.LT));
}
return factory.getFactFactory().createValue(
DfaFactMap.EMPTY
.with(DfaFactType.TYPE_CONSTRAINT, state.getValueFact(qualifier, DfaFactType.TYPE_CONSTRAINT))
.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
.with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.STRING_LENGTH.withValue(
factory.getFactValue(DfaFactType.RANGE, resultLen))));
}
private static DfaValue ofNullable(DfaValue argument, DfaMemoryState state, DfaValueFactory factory) {
if (state.isNull(argument)) {
return DfaOptionalSupport.getOptionalValue(factory, false);
@@ -319,28 +319,26 @@ public class StandardInstructionVisitor extends InstructionVisitor {
beforeMethodCall(instruction.getExpression(), callArguments, runner, memState);
}
Set<DfaMemoryState> finalStates = new LinkedHashSet<>(handleKnownMethods(instruction, runner, memState, callArguments));
Set<DfaMemoryState> finalStates = new LinkedHashSet<>();
if (finalStates.isEmpty()) {
Set<DfaCallState> currentStates = Collections.singleton(new DfaCallState(memState, callArguments));
DfaValue defaultResult = getMethodResultValue(instruction, callArguments.myQualifier, memState, factory);
if (callArguments.myArguments != null) {
for (MethodContract contract : instruction.getContracts()) {
currentStates = addContractResults(contract, currentStates, factory, finalStates, defaultResult, instruction.getExpression());
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
if (LOG.isDebugEnabled()) {
LOG.debug("Too complex contract on " + instruction.getContext() + ", skipping contract processing");
}
finalStates.clear();
currentStates = Collections.singleton(new DfaCallState(memState, callArguments));
break;
Set<DfaCallState> currentStates = Collections.singleton(new DfaCallState(memState, callArguments));
DfaValue defaultResult = getMethodResultValue(instruction, callArguments, memState, factory);
if (callArguments.myArguments != null && !(defaultResult instanceof DfaConstValue)) {
for (MethodContract contract : instruction.getContracts()) {
currentStates = addContractResults(contract, currentStates, factory, finalStates, defaultResult, instruction.getExpression());
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
if (LOG.isDebugEnabled()) {
LOG.debug("Too complex contract on " + instruction.getContext() + ", skipping contract processing");
}
finalStates.clear();
currentStates = Collections.singleton(new DfaCallState(memState, callArguments));
break;
}
}
for (DfaCallState callState : currentStates) {
pushExpressionResult(defaultResult, instruction, callState.myMemoryState);
finalStates.add(callState.myMemoryState);
}
}
for (DfaCallState callState : currentStates) {
pushExpressionResult(defaultResult, instruction, callState.myMemoryState);
finalStates.add(callState.myMemoryState);
}
DfaInstructionState[] result = new DfaInstructionState[finalStates.size()];
@@ -354,23 +352,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return result;
}
@NotNull
private List<DfaMemoryState> handleKnownMethods(MethodCallInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaCallArguments callArguments) {
if (callArguments.myArguments == null) return Collections.emptyList();
PsiMethod method = instruction.getTargetMethod();
if (method == null) return Collections.emptyList();
CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(method);
if (handler == null) return Collections.emptyList();
DfaValue result = handler.getMethodResult(callArguments, memState, runner.getFactory());
if (result == null) return Collections.emptyList();
pushExpressionResult(result, instruction, memState);
return Collections.singletonList(memState);
}
@NotNull
protected DfaCallArguments popCall(MethodCallInstruction instruction, DfaValueFactory factory, DfaMemoryState memState) {
PsiMethod method = instruction.getTargetMethod();
@@ -542,8 +523,21 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@NotNull
private static DfaValue getMethodResultValue(MethodCallInstruction instruction,
@Nullable DfaValue qualifierValue,
@NotNull DfaCallArguments callArguments,
DfaMemoryState state, DfaValueFactory factory) {
if (callArguments.myArguments != null) {
PsiMethod method = instruction.getTargetMethod();
if (method != null) {
CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(method);
if (handler != null) {
DfaValue result = handler.getMethodResult(callArguments, state, factory);
if (result != null) {
return result;
}
}
}
}
DfaValue qualifierValue = callArguments.myQualifier;
DfaValue precalculated = instruction.getPrecalculatedReturnValue();
if (precalculated != null) {
return getPrecalculatedResult(qualifierValue, state, factory, precalculated);
@@ -194,6 +194,20 @@ public class DfaValueFactory {
}
}
}
if (relationType == RelationType.EQ || relationType == RelationType.NE) {
SpecialField leftSpecialField = SpecialField.fromQualifier(dfaLeft);
if (leftSpecialField != null) {
SpecialField rightSpecialField = SpecialField.fromQualifier(dfaRight);
if (rightSpecialField == leftSpecialField) {
DfaValue leftValue = leftSpecialField.createValue(this, dfaLeft);
DfaValue rightValue = leftSpecialField.createValue(this, dfaRight);
DfaConstValue specialFieldComparison = tryEvaluate(leftValue, RelationType.EQ, rightValue);
if (specialFieldComparison != null && Boolean.FALSE.equals(specialFieldComparison.getValue())) {
return getBoolean(relationType == RelationType.NE);
}
}
}
}
LongRangeSet leftRange = LongRangeSet.fromDfaValue(dfaLeft);
LongRangeSet rightRange = LongRangeSet.fromDfaValue(dfaRight);
@@ -0,0 +1,75 @@
import org.jetbrains.annotations.Nullable;
class StringSubstring {
void testSubString(String s) {
if (<warning descr="Condition 's.substring(1, 3).equals(\"_\")' is always 'false'">s.substring(1, 3).equals("_")</warning>) {}
if (<warning descr="Condition 's.length() > 1' is always 'true'">s.length() > 1</warning>) {}
if (<warning descr="Condition 's.substring(1).isEmpty()' is always 'false'">s.substring(1).isEmpty()</warning>) {}
}
@Nullable
static String parseDir(String packageName, String dirName) {
int index = packageName.length();
while (index > 0) {
int index1 = packageName.lastIndexOf('.', index - 1);
String token = packageName.substring(index1 + 1, index);
final boolean equalsToToken = dirName.equals(token);
if (!equalsToToken) {
String packagePrefix = packageName.substring(0, index);
if (<warning descr="Condition 'packagePrefix.length() > 0' is always 'true'">packagePrefix.length() > 0</warning>) {
return null;
}
return packagePrefix;
}
index = index1;
}
return null;
}
public static void parse(String text) {
int xCnt = 0, yCnt = 0;
int pos = text.length() - 1;
for (; pos >= 0; --pos) {
char ch = text.charAt(pos);
if (ch == 'X' || ch == 'x') ++xCnt;
else if (ch == 'Y' || ch == 'y') ++yCnt;
else if (Character.isDigit(ch)) {
++pos;
break;
}
}
text = text.substring(0, pos);
if (<warning descr="Condition 'text.length() == 0' is always 'false'">text.length() == 0</warning>) {
throw new IllegalArgumentException();
}
System.out.println(xCnt + ":" + yCnt);
}
public void processPrefix(String text) {
String currentPrefix = text.isEmpty() ? "^" : text.substring(0, 1);
if (<warning descr="Condition '!currentPrefix.isEmpty()' is always 'true'">!<warning descr="Result of 'currentPrefix.isEmpty()' is always 'false'">currentPrefix.isEmpty()</warning></warning> && Character.isDigit(currentPrefix.charAt(0))) {
currentPrefix = "";
}
System.out.println(currentPrefix);
}
void parseText(String text) {
if (text.length() < 5) {
System.out.println("Short");
} else {
String kind = text.substring(0, 2);
String reference = text.substring(3);
if (<warning descr="Condition 'reference.length() > 1' is always 'true'">reference.length() > 1</warning>) {}
}
}
String getShortName(String fullName) {
int end = fullName.lastIndexOf(".ext");
if (end > 0) {
String shortName = fullName.substring(0, end);
fullName = <warning descr="Condition 'shortName.isEmpty()' is always 'false'">shortName.isEmpty()</warning> ? fullName : shortName;
}
return fullName;
}
}
@@ -45,6 +45,9 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
public void testLongRangeKnownMethods() {
doTest();
}
public void testStringSubstring() {
doTest();
}
public void testLongRangeMod() { doTest(); }
public void testLongRangeDivShift() { doTest(); }