[java-dfa] Simplify Mutability computation using external annotations

GitOrigin-RevId: 6570f36a8fa94ca14f4341875b5b980c7124641a
This commit is contained in:
Tagir Valeev
2025-06-16 10:27:38 +02:00
committed by intellij-monorepo-bot
parent 9d683df66a
commit 73943d0839
6 changed files with 28 additions and 61 deletions

View File

@@ -10,7 +10,6 @@ import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.impl.source.PsiMethodImpl;
@@ -20,7 +19,6 @@ import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ClassUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
@@ -72,12 +70,6 @@ public enum Mutability {
public static final @NotNull String UNMODIFIABLE_ANNOTATION = UNMODIFIABLE.myAnnotation;
public static final @NotNull String UNMODIFIABLE_VIEW_ANNOTATION = UNMODIFIABLE_VIEW.myAnnotation;
private static final @NotNull CallMatcher STREAM_COLLECT = CallMatcher.instanceCall(
CommonClassNames.JAVA_UTIL_STREAM_STREAM, "collect").parameterTypes("java.util.stream.Collector");
private static final @NotNull CallMatcher STREAM_TO_LIST = CallMatcher.instanceCall(
CommonClassNames.JAVA_UTIL_STREAM_STREAM, "toList").withLanguageLevelAtLeast(LanguageLevel.JDK_16);
private static final @NotNull CallMatcher UNMODIFIABLE_COLLECTORS = CallMatcher.staticCall(
CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toUnmodifiableList", "toUnmodifiableSet", "toUnmodifiableMap");
private final @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String myResourceKey;
private final String myAnnotation;
private final Key<CachedValue<PsiAnnotation>> myKey;
@@ -199,18 +191,12 @@ public enum Mutability {
Mutability mutability = UNMODIFIABLE;
for (PsiExpression initializer : expressions) {
Mutability newMutability = UNKNOWN;
if (ClassUtils.isImmutable(initializer.getType())) {
PsiType type = initializer.getType();
if (ClassUtils.isImmutable(type) || (type != null && type.hasAnnotation(UNMODIFIABLE_ANNOTATION))) {
newMutability = UNMODIFIABLE;
} else if (initializer instanceof PsiMethodCallExpression call) {
if (STREAM_COLLECT.test(call)) {
PsiExpression collector = call.getArgumentList().getExpressions()[0];
newMutability = UNMODIFIABLE_COLLECTORS.matches(collector) ? UNMODIFIABLE : UNKNOWN;
} else if (STREAM_TO_LIST.test(call)) {
newMutability = UNMODIFIABLE;
} else {
PsiMethod method = call.resolveMethod();
newMutability = method == null ? UNKNOWN : getMutability(method);
}
PsiMethod method = call.resolveMethod();
newMutability = method == null ? UNKNOWN : getMutability(method);
}
mutability = mutability.join(newMutability);
if (!mutability.isUnmodifiable()) break;

View File

@@ -1,41 +0,0 @@
package <error descr="Package 'java.util.stream' exists in another module: java.base">java.util.stream</error>;
import java.util.List;
import java.util.Iterator;
import java.util.Spliterator;
//mock
class Stream<T> implements BaseStream<T, Stream<T>>{
public native List<T> toList();
public native Iterator<T> iterator();
public native Spliterator<T> spliterator();
public native boolean isParallel();
public native Stream<T> sequential();
public native Stream<T> parallel();
public native Stream<T> unordered();
public native Stream<T> onClose(Runnable var1);
public native void close();
}
public class MutabilityJdk16 {
private final List<Integer> list = new Stream<Integer>().toList();
void testFieldList(){
list.<warning descr="Immutable object is modified">add</warning>(4);
}
void testToList() {
List<Integer> l = new Stream<Integer>()
.toList();
l.<warning descr="Immutable object is modified">add</warning>(4);
}
}

View File

@@ -0,0 +1,17 @@
import java.util.List;
import java.util.stream.*;
public class MutabilityJdk21 {
private final List<Integer> list = Stream.of(1, 2, 3).toList();
void testFieldList(){
list.<warning descr="Immutable object is modified">add</warning>(4);
}
void testToList() {
List<Integer> l = Stream.of(1, 2, 3)
.toList();
l.<warning descr="Immutable object is modified">add</warning>(4);
}
}

View File

@@ -40,8 +40,6 @@ public class DataFlowInspection16Test extends DataFlowInspectionTestCase {
}
public void testStaticFieldInAnonymous() { doTest(); }
public void testMutabilityJdk16() { doTest(); }
public void testAccessorNullityUnderDefaultQualifier() {
addCheckerAnnotations(myFixture);
doTest();

View File

@@ -202,4 +202,7 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
public void testPassthroughGenericParameter() {
doTestWith((dfi, cvi) -> dfi.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true);
}
public void testMutabilityJdk21() { doTest(); }
}

View File

@@ -1034,6 +1034,10 @@
<item name='java.util.stream.Stream java.lang.Object[] toArray()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.stream.Stream java.util.List&lt;T&gt; toList()'>
<annotation name='org.jetbrains.annotations.Unmodifiable'/>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.stream.Stream java.util.Optional&lt;T&gt; findAny()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>