mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Produce sized string from String.substring() call (IDEA-224327)
GitOrigin-RevId: c36f178be70dcb419c4296d04d5b917390aae8d7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7ff6382c14
commit
49c3fb1013
+25
@@ -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);
|
||||
|
||||
+30
-36
@@ -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);
|
||||
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
@@ -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(); }
|
||||
|
||||
Reference in New Issue
Block a user