[java-dfa] Do not report array as immutable if it's passed to pure methods that return unmodifiable object

The object may be only shallowly unmodifiable

GitOrigin-RevId: 41c0a0b46fc80169ff334d6337d9f8708e689448
This commit is contained in:
Tagir Valeev
2024-06-10 11:43:36 +02:00
committed by intellij-monorepo-bot
parent a7a884760f
commit a26d25a99f
5 changed files with 54 additions and 22 deletions

View File

@@ -7,7 +7,6 @@ import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.codeInspection.dataFlow.Mutability;
import com.intellij.codeInspection.dataFlow.MutationSignature;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
@@ -1290,10 +1289,17 @@ public final class ExpressionUtils {
if (array instanceof PsiField && !(array.hasModifierProperty(PsiModifier.PRIVATE) && array.hasModifierProperty(PsiModifier.STATIC))) {
return null;
}
Boolean isConstantArray = CachedValuesManager.<Boolean>getCachedValue(array, () -> CachedValueProvider.Result
.create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT));
if (!isConstantContent(array)) return null;
Arrays.asList(initializers).replaceAll(expr -> isIllegalReference(array, expr) ? null : expr);
return Boolean.TRUE.equals(isConstantArray) ? initializers : null;
return initializers;
}
private static boolean isConstantContent(PsiVariable array) {
Boolean isConstantArray = CachedValuesManager.<Boolean>getCachedValue(array, () -> CachedValueProvider.Result
.create(!ContainerUtil.exists(VariableAccessUtils.getVariableReferences(array),
ExpressionUtils::canBeMutated),
PsiModificationTracker.MODIFICATION_COUNT));
return Boolean.TRUE.equals(isConstantArray);
}
private static boolean isIllegalReference(PsiVariable array, PsiExpression expr) {
@@ -1305,13 +1311,6 @@ public final class ExpressionUtils {
}) != null;
}
private static boolean isConstantArray(PsiVariable array) {
PsiElement scope = PsiTreeUtil.getParentOfType(array, array instanceof PsiField ? PsiClass.class : PsiCodeBlock.class);
if (scope == null) return false;
return PsiTreeUtil.processElements(
scope, e -> !(e instanceof PsiReferenceExpression ref && ref.isReferenceTo(array) && canBeMutated(ref)));
}
private static boolean canBeMutated(@NotNull PsiExpression expr) {
while (expr.getParent() instanceof PsiParenthesizedExpression parent) {
expr = parent;
@@ -1334,17 +1333,13 @@ public final class ExpressionUtils {
PsiMethod method = call.resolveMethod();
if (method == null) return true;
MutationSignature signature = MutationSignature.fromCall(call);
if (signature == MutationSignature.unknown()) return true;
if (!signature.isPure()) {
PsiParameter parameter = MethodCallUtils.getParameterForArgument(expr);
if (parameter == null || !(parameter.getParent() instanceof PsiParameterList paramList)) return true;
int index = paramList.getParameterIndex(parameter);
if (signature.mutatesArg(index)) return true;
}
if (!signature.isPure()) return true;
PsiType type = method.getReturnType();
boolean okReturnType = type instanceof PsiPrimitiveType || TypeUtils.isJavaLangString(type)
|| Mutability.getMutability(method).isUnmodifiable();
return !okReturnType && canBeMutated(call);
if (type instanceof PsiPrimitiveType || TypeUtils.isJavaLangString(type)) return false;
return canBeMutated(call);
}
if (parent instanceof PsiLocalVariable variable) {
return !isConstantContent(variable);
}
if (parent instanceof PsiArrayAccessExpression arrayAccess) {
return PsiUtil.isAccessedForWriting(arrayAccess);

View File

@@ -0,0 +1,17 @@
import java.util.*;
public class ArrayAddedIntoCollection {
public static void main(String[] args) {
int[] data = {-1};
List<int[]> wrapped = new ArrayList<>();
wrapped.add(wrap(data));
wrapped.get(0)[0] = 0;
if (data[0] == 0) {
System.out.println("oops");
}
}
static int[] wrap(int[] data) {
return data;
}
}

View File

@@ -0,0 +1,16 @@
import java.util.*;
public class ArrayElementWrappedInPureMethod {
public static void main(String[] args) {
int[] data = {-1};
List<int[]> wrapped = wrap(data);
wrapped.get(0)[0] = 0;
if (data[0] == 0) {
System.out.println("oops");
}
}
static List<int[]> wrap(int[] data) {
return List.of(data);
}
}

View File

@@ -5,7 +5,8 @@ class Test {
int[] array = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(array));
System.arraycopy(array, 0, dest, 0, 5);
if (<warning descr="Condition 'array[3] == 4' is always 'true'">array[3] == 4</warning>) {
// Analysis cannot see this, unfortunately
if (array[3] == 4) {
}
}

View File

@@ -127,4 +127,7 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
}""");
doTest();
}
public void testArrayElementWrappedInPureMethod() { doTest(); }
public void testArrayAddedIntoCollection() { doTest(); }
}