mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Fix IDEA-173235 Control flow analysis does not know about 'Collections.empty...()' and 'Collections.singleton...()'
This commit is contained in:
+2
-1
@@ -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
|
||||
|
||||
+71
@@ -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();}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user