From c2692984510b46f76a8a0fd8d3c65601cecce232 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 21 Jul 2017 19:49:29 +0700 Subject: [PATCH] Fix IDEA-173235 Control flow analysis does not know about 'Collections.empty...()' and 'Collections.singleton...()' --- .../dataFlow/ControlFlowAnalyzer.java | 3 +- .../inliner/CollectionFactoryInliner.java | 71 +++++++++++++++++++ .../dataFlow/fixture/EmptySingletonMap.java | 42 +++++++++++ .../DataFlowInspectionTest.java | 1 + 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index 89765ae16d7a..679656c97057 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java new file mode 100644 index 000000000000..dc355923e203 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java @@ -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 STATIC_FACTORIES = new CallMapper() + .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 = + .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 = + .push(factory.getConstFactory().createFromValue(factoryInfo.mySize, PsiType.INT, null)) + .assign() + .pop(); + return true; + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java b/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java new file mode 100644 index 000000000000..bd316c1bf5ec --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java @@ -0,0 +1,42 @@ +import java.util.*; + +public class EmptySingletonMap { + void testEmpty() { + List objects = Collections.emptyList(); + for (Object o : objects) { + System.out.println("hello"); + } + } + + void testMixed(int x) { + Collection 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(strings.size() >= 2 && x > 3) { + System.out.println("never"); + } + } + + void testMap(boolean b) { + Map map; + if (b) { + map = Collections.emptyMap(); + } + else { + map = Collections.singletonMap("a", "b"); + } + if(b && map.isEmpty()) { + System.out.println("true"); + } + if(!b && map.size() == 1) { + System.out.println("??"); + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 219195c736d5..728294cbe954 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -532,4 +532,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { assertEmpty(ContainerUtil.findAll(myFixture.getAvailableIntentions(), i -> i.getText().contains("null"))); } + public void testEmptySingletonMap() {doTest();} }