IDEA-231391 Evaluate 'x.contains(x)' as 'always true'

Also, restore common known type information on state merge

GitOrigin-RevId: f40b56b17c4f1f255c4533b5c8c65bfe3d0c9ebb
This commit is contained in:
Tagir Valeev
2020-01-27 12:07:22 +07:00
committed by intellij-monorepo-bot
parent d14f04de7d
commit a0a2a7dd5d
4 changed files with 31 additions and 11 deletions

View File

@@ -125,6 +125,9 @@ public class HardcodedContracts {
singleConditionContract( singleConditionContract(
ContractValue.argument(0).specialField(SpecialField.STRING_LENGTH), RelationType.EQ, ContractValue.argument(0).specialField(SpecialField.STRING_LENGTH), RelationType.EQ,
ContractValue.zero(), returnTrue()), ContractValue.zero(), returnTrue()),
singleConditionContract(
ContractValue.argument(0), RelationType.EQ,
ContractValue.qualifier(), returnTrue()),
singleConditionContract( singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.STRING_LENGTH), RelationType.LT, ContractValue.qualifier().specialField(SpecialField.STRING_LENGTH), RelationType.LT,
ContractValue.argument(0).specialField(SpecialField.STRING_LENGTH), returnFalse()))) ContractValue.argument(0).specialField(SpecialField.STRING_LENGTH), returnFalse())))

View File

@@ -8,7 +8,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.MultiMap;
import one.util.streamex.LongStreamEx; import one.util.streamex.LongStreamEx;
@@ -57,11 +56,7 @@ class StateMerger {
final Collection<DfaMemoryStateImpl> group = ContainerUtil.newArrayList(ContainerUtil.concat(group1, group2)); final Collection<DfaMemoryStateImpl> group = ContainerUtil.newArrayList(ContainerUtil.concat(group1, group2));
replacements.stripAndMerge(group, original -> { replacements.stripAndMerge(group, fact);
DfaMemoryStateImpl copy = original.createCopy();
fact.removeFromState(copy);
return copy;
});
} }
if (replacements.hasMerges()) return replacements.getMergeResult(); if (replacements.hasMerges()) return replacements.getMergeResult();
@@ -339,6 +334,11 @@ class StateMerger {
abstract void removeFromState(@NotNull DfaMemoryStateImpl state); abstract void removeFromState(@NotNull DfaMemoryStateImpl state);
void restoreCommonState(DfaMemoryStateImpl stripped, Collection<DfaMemoryStateImpl> merged) {
DfType commonType = StreamEx.of(merged).map(s -> s.getDfType(myVar)).foldLeft(DfTypes.BOTTOM, DfType::join);
stripped.meetDfType(myVar, commonType);
}
@NotNull @NotNull
static EqualityFact createEqualityFact(@NotNull DfaVariableValue var, @NotNull DfaValue val) { static EqualityFact createEqualityFact(@NotNull DfaVariableValue var, @NotNull DfaValue val) {
if (val instanceof DfaVariableValue && val.getID() < var.getID()) { if (val instanceof DfaVariableValue && val.getID() < var.getID()) {
@@ -491,22 +491,25 @@ class StateMerger {
return null; return null;
} }
private void stripAndMerge(@NotNull Collection<DfaMemoryStateImpl> group, private void stripAndMerge(@NotNull Collection<DfaMemoryStateImpl> group, @NotNull Fact fact) {
@NotNull Function<DfaMemoryStateImpl, DfaMemoryStateImpl> stripper) {
if (group.size() <= 1) return; if (group.size() <= 1) return;
MultiMap<DfaMemoryStateImpl, DfaMemoryStateImpl> strippedToOriginals = MultiMap.create(); MultiMap<DfaMemoryStateImpl, DfaMemoryStateImpl> strippedToOriginals = MultiMap.create();
for (DfaMemoryStateImpl original : group) { for (DfaMemoryStateImpl original : group) {
strippedToOriginals.putValue(stripper.fun(original), original); DfaMemoryStateImpl copy = original.createCopy();
fact.removeFromState(copy);
strippedToOriginals.putValue(copy, original);
} }
for (Map.Entry<DfaMemoryStateImpl, Collection<DfaMemoryStateImpl>> entry : strippedToOriginals.entrySet()) { for (Map.Entry<DfaMemoryStateImpl, Collection<DfaMemoryStateImpl>> entry : strippedToOriginals.entrySet()) {
Collection<DfaMemoryStateImpl> merged = entry.getValue(); Collection<DfaMemoryStateImpl> merged = entry.getValue();
if (merged.size() > 1) { if (merged.size() > 1) {
DfaMemoryStateImpl stripped = entry.getKey();
fact.restoreCommonState(stripped, merged);
for (DfaMemoryStateImpl state : merged) { for (DfaMemoryStateImpl state : merged) {
entry.getKey().afterMerge(state); stripped.afterMerge(state);
} }
myRemovedStates.addAll(merged); myRemovedStates.addAll(merged);
myMerged.add(entry.getKey()); myMerged.add(stripped);
} }
} }
} }

View File

@@ -0,0 +1,13 @@
import org.jetbrains.annotations.*;
class Test {
public static boolean test(@Nullable String name, @NotNull String prefix) {
return name != null && name.startsWith(prefix) && name.length() > prefix.length();
}
void test1(String s1, String s2) {
if (<warning descr="Condition 's1.contains(s1)' is always 'true'">s1.contains(s1)</warning>) {}
if (s1.contains(s2) || <warning descr="Condition 's1.equals(s2)' is always 'false' when reached">s1.equals(s2)</warning>) {}
}
}

View File

@@ -667,4 +667,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testCompareEqualObjectWithNull() { doTest(); } public void testCompareEqualObjectWithNull() { doTest(); }
public void testNullabilityAfterCastAndInstanceOf() { doTest(); } public void testNullabilityAfterCastAndInstanceOf() { doTest(); }
public void testInstanceOfTernary() { doTest(); } public void testInstanceOfTernary() { doTest(); }
public void testStringContains() { doTest(); }
} }