From 49c3fb1013afc2ebd7b7f05669c4b0b78be78320 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 9 Oct 2019 15:38:55 +0700 Subject: [PATCH] Produce sized string from String.substring() call (IDEA-224327) GitOrigin-RevId: c36f178be70dcb419c4296d04d5b917390aae8d7 --- .../dataFlow/CustomMethodHandlers.java | 25 +++++++ .../dataFlow/StandardInstructionVisitor.java | 66 ++++++++-------- .../dataFlow/value/DfaValueFactory.java | 14 ++++ .../dataFlow/fixture/StringSubstring.java | 75 +++++++++++++++++++ .../DataFlowRangeAnalysisTest.java | 3 + 5 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java 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 e6fea10ba4f2..01fe8de6617e 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 @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 92bc14b016a7..aa2327a8a084 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -319,28 +319,26 @@ public class StandardInstructionVisitor extends InstructionVisitor { beforeMethodCall(instruction.getExpression(), callArguments, runner, memState); } - Set finalStates = new LinkedHashSet<>(handleKnownMethods(instruction, runner, memState, callArguments)); + Set finalStates = new LinkedHashSet<>(); - if (finalStates.isEmpty()) { - Set 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 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 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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index 8dbbbdac828e..b66512b2af07 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -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); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java b/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java new file mode 100644 index 000000000000..24d8f4779749 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java @@ -0,0 +1,75 @@ +import org.jetbrains.annotations.Nullable; + +class StringSubstring { + void testSubString(String s) { + if (s.substring(1, 3).equals("_")) {} + if (s.length() > 1) {} + if (s.substring(1).isEmpty()) {} + } + + @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 (packagePrefix.length() > 0) { + 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 (text.length() == 0) { + throw new IllegalArgumentException(); + } + System.out.println(xCnt + ":" + yCnt); + } + + public void processPrefix(String text) { + String currentPrefix = text.isEmpty() ? "^" : text.substring(0, 1); + if (!currentPrefix.isEmpty() && 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 (reference.length() > 1) {} + } + } + + String getShortName(String fullName) { + int end = fullName.lastIndexOf(".ext"); + if (end > 0) { + String shortName = fullName.substring(0, end); + fullName = shortName.isEmpty() ? fullName : shortName; + } + return fullName; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java index 11dba5d76e42..b9ec4cb44cb3 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -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(); }