Fix IDEA-173235 Control flow analysis does not know about 'Collections.empty...()' and 'Collections.singleton...()'

This commit is contained in:
Tagir Valeev
2017-07-27 11:56:58 +07:00
parent 5cac0baf4a
commit c269298451
4 changed files with 116 additions and 1 deletions
@@ -18,6 +18,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInspection.dataFlow.inliner.CallInliner;
import com.intellij.codeInspection.dataFlow.inliner.CollectionFactoryInliner;
import com.intellij.codeInspection.dataFlow.inliner.LambdaInliner;
import com.intellij.codeInspection.dataFlow.inliner.OptionalChainInliner;
import com.intellij.codeInspection.dataFlow.instructions.*;
@@ -1683,7 +1684,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitClass(PsiClass aClass) {
}
static final CallInliner[] INLINERS = {new OptionalChainInliner(), new LambdaInliner()};
static final CallInliner[] INLINERS = {new OptionalChainInliner(), new LambdaInliner(), new CollectionFactoryInliner()};
/**
* A facade for building control flow graph used by {@link CallInliner} implementations
@@ -0,0 +1,71 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.siyeh.ig.callMatcher.CallMapper;
import static com.intellij.codeInspection.dataFlow.SpecialField.COLLECTION_SIZE;
import static com.intellij.codeInspection.dataFlow.SpecialField.MAP_SIZE;
import static com.intellij.psi.CommonClassNames.JAVA_UTIL_COLLECTIONS;
import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
public class CollectionFactoryInliner implements CallInliner {
static final class FactoryInfo {
int mySize;
SpecialField mySizeField;
public FactoryInfo(int size, SpecialField sizeField) {
mySize = size;
mySizeField = sizeField;
}
}
private static final CallMapper<FactoryInfo> STATIC_FACTORIES = new CallMapper<FactoryInfo>()
.register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyList", "emptySet").parameterCount(0), new FactoryInfo(0, COLLECTION_SIZE))
.register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1), new FactoryInfo(1, COLLECTION_SIZE))
.register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyMap").parameterCount(0), new FactoryInfo(0, MAP_SIZE))
.register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonMap").parameterCount(2), new FactoryInfo(1, MAP_SIZE));
@Override
public boolean tryInlineCall(ControlFlowAnalyzer.CFGBuilder builder, PsiMethodCallExpression call) {
FactoryInfo factoryInfo = STATIC_FACTORIES.mapFirst(call);
if (factoryInfo == null) return false;
PsiExpression[] args = call.getArgumentList().getExpressions();
for (PsiExpression arg : args) {
builder.pushExpression(arg).pop();
}
PsiParameter variable = builder.createTempVariable(call.getType());
DfaValueFactory factory = builder.getFactory();
DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false);
builder.pushVariable(variable) // tmpVar = <Value of collection type>
.push(factory.createTypeValue(call.getType(), Nullness.NOT_NULL))
.assign() // leave tmpVar on stack: it's result of method call
.push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = <size>
.push(factory.getConstFactory().createFromValue(factoryInfo.mySize, PsiType.INT, null))
.assign()
.pop();
return true;
}
}
@@ -0,0 +1,42 @@
import java.util.*;
public class EmptySingletonMap {
void testEmpty() {
List<Object> objects = Collections.emptyList();
for (Object o : <warning descr="Collection 'objects' is always empty">objects</warning>) {
System.out.println("hello");
}
}
void testMixed(int x) {
Collection<String> strings;
if(x > 10) {
strings = Collections.singleton("foo");
} else if(x > 6) {
strings = Collections.emptyList();
} else if(x > 2) {
strings = Collections.singletonList("bar");
} else {
strings = Arrays.asList("a", "b", "c");
}
if(<warning descr="Condition 'strings.size() >= 2 && x > 3' is always 'false'">strings.size() >= 2 && <warning descr="Condition 'x > 3' is always 'false' when reached">x > 3</warning></warning>) {
System.out.println("never");
}
}
void testMap(boolean b) {
Map<String, String> map;
if (b) {
map = Collections.emptyMap();
}
else {
map = Collections.singletonMap("a", "b");
}
if(b && <warning descr="Condition 'map.isEmpty()' is always 'true' when reached">map.isEmpty()</warning>) {
System.out.println("true");
}
if(!b && <warning descr="Condition 'map.size() == 1' is always 'true' when reached">map.size() == 1</warning>) {
System.out.println("??");
}
}
}
@@ -532,4 +532,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
assertEmpty(ContainerUtil.findAll(myFixture.getAvailableIntentions(), i -> i.getText().contains("null")));
}
public void testEmptySingletonMap() {doTest();}
}