[java, inspection, memory] IDEA-270638 add handling for immutable collections in static collection inspection

GitOrigin-RevId: 97168a2d412b9f3227f5f76086e110f9c0f66311
This commit is contained in:
Aleksey Dobrynin
2025-11-26 16:51:49 +00:00
committed by intellij-monorepo-bot
parent 9cb03803e1
commit a367eaef63
6 changed files with 129 additions and 12 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.Nullability;
@@ -230,6 +230,15 @@ public final class CustomMethodHandlers {
"newConcurrentHashMap", "newTreeMap").parameterCount(0)),
toValue((arguments, state, factory, method) -> COLLECTION_SIZE.asDfType(intValue(0)).meet(LOCAL_OBJECT)));
private static final CallMatcher IMMUTABLE_CALLS = anyOf(
staticCall(JAVA_UTIL_COLLECTIONS, "emptyList", "emptySet", "emptyMap").parameterCount(0),
staticCall(JAVA_UTIL_COLLECTIONS, "singleton", "singletonList", "singletonMap"),
staticCall(JAVA_UTIL_LIST, "of", "copyOf"),
staticCall(JAVA_UTIL_SET, "of", "copyOf"),
staticCall(JAVA_UTIL_MAP, "of", "ofEntries", "copyOf"),
staticCall(JAVA_UTIL_ARRAYS, "asList")
);
public static CustomMethodHandler find(PsiMethod method) {
CustomMethodHandler handler = null;
if (isConstantCall(method)) {
@@ -248,6 +257,15 @@ public final class CustomMethodHandlers {
return CONSTANT_CALLS.methodMatches(method);
}
/**
* @param method method to check
* @return true if the method returns an immutable result, false otherwise
*/
@Contract("null -> false")
public static boolean isImmutableCall(PsiMethod method) {
return IMMUTABLE_CALLS.methodMatches(method);
}
private static @NotNull DfType handleConstantCall(DfaCallArguments arguments, DfaMemoryState state, PsiMethod method) {
PsiType returnType = method.getReturnType();
if (returnType == null) return DfType.TOP;
@@ -15,10 +15,13 @@
*/
package com.siyeh.ig.memory;
import com.intellij.codeInspection.dataFlow.CustomMethodHandlers;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
@@ -67,6 +70,13 @@ public final class StaticCollectionInspection extends BaseInspection {
if (m_ignoreWeakCollections && CollectionUtils.isWeakCollectionClass(type)) {
return;
}
// Ignore final fields initialized with immutable, fixed-size collections
if (field.hasModifierProperty(PsiModifier.FINAL) &&
PsiUtil.skipParenthesizedExprDown(field.getInitializer()) instanceof PsiMethodCallExpression call &&
CustomMethodHandlers.isImmutableCall(call.resolveMethod())){
return;
}
registerFieldError(field);
}
}
@@ -10,6 +10,11 @@ therefore prohibited by some coding standards.
}
</code></pre>
<p>
The inspection ignores <code>final</code> fields initialized with immutable, fixed-size collections
(e.g., <code>Map.of()</code>, <code>List.of()</code>, <code>Collections.emptyList()</code>, <code>Collections.singletonMap()</code>).
These collections cannot grow and therefore cannot cause memory leaks.
</p>
<!-- tooltip end -->
<p>
Configure the inspection:
@@ -0,0 +1,37 @@
import java.util.*;
public class StaticCollectionInspection {
// with warning
private static final Map <warning descr="Static collection 's_map1'">s_map1</warning> = new HashMap(10);
private static final List <warning descr="Static collection 's_list2'">s_list2</warning> = new ArrayList(10);
private static final Set <warning descr="Static collection 's_set3'">s_set3</warning> = new HashSet<>();
private static /* final */ Map <warning descr="Static collection 's_map4'">s_map4</warning> = Map.of("key", "value");
private static /* final */ List <warning descr="Static collection 's_list5'">s_list5</warning> = List.of("a", "b");
private static final Map <warning descr="Static collection 's_map6'">s_map6</warning>;
static {
s_map6 = new HashMap<>();
}
// without warning
private static final Map<String, String> s_map7 = Map.of("a", "A", "b", "B", "c", "C");
private static final List<String> s_list8 = List.of("a", "b", "c");
private static final Set<Integer> s_set9 = Set.of(1, 2, 3);
private static final Map<String, String> s_map10 = Map.copyOf(Collections.emptyMap());
private static final List<String> s_list11 = List.copyOf(Collections.emptyList());
private static final Set<String> s_set12 = Set.copyOf(Collections.emptySet());
private static final List<String> s_list13 = Collections.emptyList();
private static final Set<String> s_set14 = Collections.emptySet();
private static final Map s_map15 = Collections.emptyMap();
private static final List<String> s_list16 = Collections.singletonList("only");
private static final Set<String> s_set17 = Collections.singleton("only");
private static final Map<String, String> s_map18 = Collections.singletonMap("key", "value");
private static final List<String> s_list19 = Arrays.asList("a", "b", "c");
private static final Map<String, String> s_map20 = Map.ofEntries(
Map.entry("key1", "value1"),
Map.entry("key2", "value2")
);
private StaticCollectionInspection() {
}
}
@@ -1,17 +1,39 @@
package com.siyeh.igtest.performance;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
public class StaticCollectionInspection
{
private static final Map s_map1 = new HashMap(10);
private static final List s_map2 = new ArrayList(10);
public class StaticCollectionInspection {
// with warning
private static final Map s_map1 = new HashMap(10);
private static final List s_list2 = new ArrayList(10);
private static final Set s_set3 = new HashSet<>();
private static /* final */ Map s_map4 = Map.of("key", "value");
private static /* final */ List s_list5 = List.of("a", "b");
private StaticCollectionInspection()
{
}
private static final Map s_map6;
static {
s_map6 = new HashMap<>();
}
// without warning
private static final Map<String, String> s_map7 = Map.of("a", "A", "b", "B", "c", "C");
private static final List<String> s_list8 = List.of("a", "b", "c");
private static final Set<Integer> s_set9 = Set.of(1, 2, 3);
private static final Map<String, String> s_map10 = Map.copyOf(Collections.emptyMap());
private static final List<String> s_list11 = List.copyOf(Collections.emptyList());
private static final Set<String> s_set12 = Set.copyOf(Collections.emptySet());
private static final List<String> s_list13 = Collections.emptyList();
private static final Set<String> s_set14 = Collections.emptySet();
private static final Map s_map15 = Collections.emptyMap();
private static final List<String> s_list16 = Collections.singletonList("only");
private static final Set<String> s_set17 = Collections.singleton("only");
private static final Map<String, String> s_map18 = Collections.singletonMap("key", "value");
private static final List<String> s_list19 = Arrays.asList("a", "b", "c");
private static final Map<String, String> s_map20 = Map.ofEntries(
Map.entry("key1", "value1"),
Map.entry("key2", "value2")
);
private StaticCollectionInspection() {
}
}
@@ -0,0 +1,25 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.memory;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StaticCollectionInspectionTest extends LightJavaInspectionTestCase {
public void testStaticCollectionInspection() {
doTest();
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_11;
}
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new StaticCollectionInspection();
}
}