[java-inspection] SlowListContainsAllInspection (IDEA-274813)

GitOrigin-RevId: 8c8381b699eb83cec89889ad596fc35e81afd6f1
This commit is contained in:
Andrey.Cherkasov
2021-11-30 23:51:31 +03:00
committed by intellij-monorepo-bot
parent 5215d2a116
commit 020ffc7630
10 changed files with 183 additions and 3 deletions

View File

@@ -17,7 +17,6 @@ import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -71,10 +70,17 @@ public class SlowAbstractSetRemoveAllInspection extends AbstractBaseJavaLocalIns
};
}
/**
* Returns the set of possible values for the collection size
*
* @param collection a collection to get the range of possible values for its size
* @return the set of possible values for the collection size
*/
@NotNull
private static LongRangeSet getSizeRangeOfCollection(PsiExpression expression) {
public static LongRangeSet getSizeRangeOfCollection(PsiExpression collection) {
final SpecialField lengthField = SpecialField.COLLECTION_SIZE;
final DfType origType = CommonDataflow.getDfType(expression);
final DfType origType = CommonDataflow.getDfType(collection);
final DfType length = lengthField.getFromQualifier(origType);
final DfIntegralType dfType = ObjectUtils.tryCast(length, DfIntegralType.class);
if (dfType == null) return LongRangeSet.all();

View File

@@ -0,0 +1,77 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
public class SlowListContainsAllInspection extends AbstractBaseJavaLocalInspectionTool {
private static final CallMatcher LIST_CONTAINS_ALL =
instanceCall(CommonClassNames.JAVA_UTIL_LIST, "containsAll").parameterTypes(CommonClassNames.JAVA_UTIL_COLLECTION);
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
if (!LIST_CONTAINS_ALL.test(call)) return;
PsiReferenceExpression expression = call.getMethodExpression();
final PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(expression);
if (qualifier == null) return;
final LongRangeSet listSizeRange = SlowAbstractSetRemoveAllInspection.getSizeRangeOfCollection(qualifier);
if (listSizeRange.isEmpty() || listSizeRange.max() <= 1) return;
holder.registerProblem(call,
JavaBundle.message("inspection.slow.list.contains.all.description"),
ProblemHighlightType.WARNING,
expression.getTextRangeInParent(),
new ReplaceWithHashSetContainsAllFix(qualifier.getText()));
}
};
}
private static class ReplaceWithHashSetContainsAllFix implements LocalQuickFix {
private final String myCollectionText;
ReplaceWithHashSetContainsAllFix(String collectionText) {
myCollectionText = collectionText;
}
@Nls
@NotNull
@Override
public String getName() {
return JavaBundle.message("inspection.slow.list.contains.all.fix.name", myCollectionText);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return JavaBundle.message("inspection.slow.list.contains.all.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiMethodCallExpression call = ObjectUtils.tryCast(descriptor.getPsiElement(), PsiMethodCallExpression.class);
if (call == null) return;
final PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
final CommentTracker ct = new CommentTracker();
PsiElement result = ct.replace(qualifier, "new java.util.HashSet<>(" + ct.text(qualifier) + ")");
JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
}
}
}

View File

@@ -1433,6 +1433,12 @@
bundle="messages.JavaBundle"
key="inspection.slow.abstract.set.remove.all.description"
implementationClass="com.intellij.codeInspection.SlowAbstractSetRemoveAllInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="SlowListContainsAll"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.performance.issues" enabledByDefault="true" level="WARNING"
bundle="messages.JavaBundle"
key="inspection.slow.list.contains.all.description"
implementationClass="com.intellij.codeInspection.SlowListContainsAllInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantUnmodifiable"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"

View File

@@ -0,0 +1,27 @@
<html>
<body>
Reports calls to <code>containsAll()</code> on <code>java.util.List</code>.
<p>
The time complexity of this method call is O(n&middot;m). When the list is large, this can be an expensive operation.
</p>
<p>
The quick-fix wraps the list in <code>new java.util.HashSet&lt;&gt;()</code> since the time required to create
<code>java.util.HashSet</code> from <code>java.util.List</code> and execute <code>containsAll()</code> on
<code>java.util.HashSet</code> is O(n+m).
</p>
<p><b>Example:</b></p>
<pre><code> public boolean check(List&lt;String&gt; list, Collection&lt;String&gt; collection) {
// O(n&middot;m) complexity
return list.containsAll(collection);
}
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code> public boolean check(List&lt;String&gt; list, Collection&lt;String&gt; collection) {
// O(n+m) complexity
return new HashSet&lt;&gt;(list).containsAll(collection);
}
</code></pre>
<!-- tooltip end -->
<p><small>New in 2022.1</small></p>
</body>
</html>

View File

@@ -0,0 +1,10 @@
// "Wrap 'list' in 'HashSet' constructor" "true"
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
class Test {
boolean check(List<String> list, Collection<String> collection) {
return new HashSet<>(list).containsAll(collection);
}
}

View File

@@ -0,0 +1,9 @@
// "Wrap 'list' in 'HashSet' constructor" "true"
import java.util.Collection;
import java.util.List;
class Test {
boolean check(List<String> list, Collection<String> collection) {
return list.containsAll<caret>(collection);
}
}

View File

@@ -0,0 +1,10 @@
// "Wrap 'list' in 'HashSet' constructor" "false"
import java.util.Collection;
import java.util.List;
class Test {
boolean check(List<String> list, Collection<String> collection) {
if (!list.isEmpty()) return true;
return list.containsAll<caret>(collection);
}
}

View File

@@ -0,0 +1,10 @@
// "Wrap 'list' in 'HashSet' constructor" "false"
import java.util.Collection;
import java.util.List;
class Test {
boolean check(List<String> list, Collection<String> collection) {
if (list.size() > 1) return true;
return list.containsAll<caret>(collection);
}
}

View File

@@ -0,0 +1,22 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.SlowListContainsAllInspection;
import org.jetbrains.annotations.NotNull;
public class SlowListContainsAllInspectionTest extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new SlowListContainsAllInspection()
};
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/slowListContainsAll";
}
}

View File

@@ -1328,6 +1328,9 @@ inspection.move.field.assignment.to.initializer.display.name=Field assignment ca
inspection.frequently.used.inheritor.inspection.display.name=Class may extend a commonly used base class
inspection.slow.abstract.set.remove.all.description=Call to 'set.removeAll(list)' may work slowly
inspection.slow.abstract.set.remove.all.fix.family.name=Use 'Set.remove' instead of 'Set.removeAll'
inspection.slow.list.contains.all.description=Call to 'list.containsAll(collection)' may have poor performance
inspection.slow.list.contains.all.fix.family.name=Wrap in 'HashSet' constructor
inspection.slow.list.contains.all.fix.name=Wrap ''{0}'' in ''HashSet'' constructor
slice.filter.parse.error.null.filter.not.applicable.for.primitive.type=''null'' filter is not applicable for primitive type {0}
slice.filter.parse.error.not.null.filter.not.applicable.for.primitive.type=''!null'' filter is not applicable for primitive type {0}
slice.filter.parse.error.enum.constant.not.found=Enum constant not found: {0}