ReadOnly -> Unmodifiable; introduced UnmodifiableView distinguish between unmodifiable and unmodifiable view

This commit is contained in:
Tagir Valeev
2018-01-08 12:12:26 +07:00
parent cbf693deac
commit 34ae3706ab
14 changed files with 151 additions and 101 deletions
@@ -1473,7 +1473,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
qualifierExpression.accept(this);
}
else if (myThisReadOnly) {
addInstruction(new PushInstruction(myFactory.getFactValue(DfaFactType.MUTABLE, false), null));
addInstruction(new PushInstruction(myFactory.getFactValue(DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE), null));
} else {
pushUnknown();
}
@@ -1553,7 +1553,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
}
return CachedValueProvider.Result
.create(Collections.<StandardMethodContract>emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
.create(Collections.emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@@ -1768,7 +1768,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
startElement(expression);
DfaValue value = myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL);
if (myThisReadOnly) {
value = myFactory.withFact(value, DfaFactType.MUTABLE, false);
value = myFactory.withFact(value, DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE);
}
addInstruction(new PushInstruction(value, expression));
finishElement(expression);
@@ -74,17 +74,17 @@ public abstract class DfaFactType<T> extends Key<T> {
}
};
public static final DfaFactType<Boolean> MUTABLE = new DfaFactType<Boolean>("Mutable") {
public static final DfaFactType<Mutability> MUTABILITY = new DfaFactType<Mutability>("Mutable") {
@Override
String toString(@NotNull Boolean fact) {
return fact ? "Mutable" : "ReadOnly";
boolean isUnknown(@NotNull Mutability fact) {
return fact == Mutability.UNKNOWN;
}
@Nullable
@NotNull
@Override
Boolean calcFromVariable(@NotNull DfaVariableValue value) {
Mutability calcFromVariable(@NotNull DfaVariableValue value) {
PsiModifierListOwner variable = value.getPsiVariable();
return MutationSignature.getMutabilityFact(variable);
return Mutability.getMutability(variable);
}
};
@@ -1222,7 +1222,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
for (DfaVariableValue value : vars) {
if (value.isFlushableByCalls() && (value.getQualifier() == null ||
!Boolean.FALSE.equals(getValueFact(value.getQualifier(), DfaFactType.MUTABLE)))) {
getValueFact(value.getQualifier(), DfaFactType.MUTABILITY) != Mutability.UNMODIFIABLE)) {
doFlush(value, shouldMarkUnknown(value));
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2018 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.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
public enum Mutability {
UNKNOWN, MUTABLE, UNMODIFIABLE, UNMODIFIABLE_VIEW;
@NotNull
static Mutability getMutability(PsiModifierListOwner owner) {
if (owner instanceof PsiParameter && owner.getParent() instanceof PsiParameterList) {
PsiParameterList list = (PsiParameterList)owner.getParent();
PsiMethod method = ObjectUtils.tryCast(list.getParent(), PsiMethod.class);
if (method != null) {
int index = list.getParameterIndex((PsiParameter)owner);
MutationSignature signature = MutationSignature.fromMethod(method);
if (signature.mutatesArg(index)) {
return MUTABLE;
} else if (signature.preservesArg(index) &&
PsiTreeUtil.findChildOfAnyType(method.getBody(), PsiLambdaExpression.class, PsiClass.class) == null) {
// If method preserves argument, it still may return a lambda which captures an argument and changes it
// TODO: more precise check (at least differentiate parameters which are captured by lambdas or not)
return UNMODIFIABLE_VIEW;
}
return UNKNOWN;
}
}
if (AnnotationUtil.isAnnotated(owner, Collections.singleton("org.jetbrains.annotations.Unmodifiable"),
AnnotationUtil.CHECK_HIERARCHY |
AnnotationUtil.CHECK_EXTERNAL |
AnnotationUtil.CHECK_INFERRED)) {
return UNMODIFIABLE;
}
if (AnnotationUtil.isAnnotated(owner, Collections.singleton("org.jetbrains.annotations.UnmodifiableView"),
AnnotationUtil.CHECK_HIERARCHY |
AnnotationUtil.CHECK_EXTERNAL |
AnnotationUtil.CHECK_INFERRED)) {
return UNMODIFIABLE_VIEW;
}
return UNKNOWN;
}
}
@@ -3,13 +3,10 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
public class MutationSignature {
private static final String ATTR_MUTATES = "mutates";
@@ -121,29 +118,4 @@ public class MutationSignature {
}
return UNKNOWN;
}
@Nullable
static Boolean getMutabilityFact(PsiModifierListOwner owner) {
if (owner instanceof PsiParameter && owner.getParent() instanceof PsiParameterList) {
PsiParameterList list = (PsiParameterList)owner.getParent();
PsiMethod method = ObjectUtils.tryCast(list.getParent(), PsiMethod.class);
if (method != null) {
int index = list.getParameterIndex((PsiParameter)owner);
MutationSignature signature = fromMethod(method);
if (signature.mutatesArg(index)) {
return Boolean.TRUE;
} else if (signature.preservesArg(index) &&
PsiTreeUtil.findChildOfAnyType(method.getBody(), PsiLambdaExpression.class, PsiClass.class) == null) {
// If method preserves argument, it still may return a lambda which captures an argument and changes it
// TODO: more precise check (at least differentiate parameters which are captured by lambdas or not)
return Boolean.FALSE;
}
return null;
}
}
return AnnotationUtil.isAnnotated(owner, Collections.singleton("org.jetbrains.annotations.ReadOnly"),
AnnotationUtil.CHECK_HIERARCHY |
AnnotationUtil.CHECK_EXTERNAL |
AnnotationUtil.CHECK_INFERRED) ? Boolean.FALSE : null;
}
}
@@ -368,10 +368,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
else if (requiredNullability == Nullness.UNKNOWN) {
checkNotNullable(memState, arg, NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter.problem(anchor));
}
if (sig.mutatesArg(paramIndex) && !memState.applyFact(arg, DfaFactType.MUTABLE, true)) {
if (sig.mutatesArg(paramIndex) && !memState.applyFact(arg, DfaFactType.MUTABILITY, Mutability.MUTABLE)) {
reportMutabilityViolation(false, anchor);
if (arg instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)arg, DfaFactType.MUTABLE, true);
memState.forceVariableFact((DfaVariableValue)arg, DfaFactType.MUTABILITY, Mutability.MUTABLE);
}
}
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
@@ -388,10 +388,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaMemoryState memState,
MutationSignature sig) {
DfaValue value = dereference(memState, memState.pop(), instruction.getQualifierNullabilityProblem());
if (sig.mutatesThis() && !memState.applyFact(value, DfaFactType.MUTABLE, true)) {
if (sig.mutatesThis() && !memState.applyFact(value, DfaFactType.MUTABILITY, Mutability.MUTABLE)) {
reportMutabilityViolation(true, instruction.getContext());
if (value instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)value, DfaFactType.MUTABLE, true);
memState.forceVariableFact((DfaVariableValue)value, DfaFactType.MUTABILITY, Mutability.MUTABLE);
}
}
return value;
@@ -511,13 +511,13 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (type != null && !(type instanceof PsiPrimitiveType)) {
Nullness nullability = instruction.getReturnNullability();
PsiMethod targetMethod = instruction.getTargetMethod();
Boolean mutable = null;
Mutability mutable = Mutability.UNKNOWN;
if (targetMethod != null) {
mutable = MutationSignature.getMutabilityFact(targetMethod);
mutable = Mutability.getMutability(targetMethod);
PsiMethod realMethod = findSpecificMethod(targetMethod, state, qualifierValue);
if (realMethod != targetMethod) {
nullability = DfaPsiUtil.getElementNullability(type, realMethod);
mutable = MutationSignature.getMutabilityFact(realMethod);
mutable = Mutability.getMutability(realMethod);
PsiType returnType = realMethod.getReturnType();
if (returnType != null && TypeConversionUtil.erasure(type).isAssignableFrom(returnType)) {
// possibly covariant return type
@@ -529,7 +529,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
DfaValue value = factory.createTypeValue(type, nullability);
return factory.withFact(value, DfaFactType.MUTABLE, mutable);
return factory.withFact(value, DfaFactType.MUTABILITY, mutable);
}
LongRangeSet range = LongRangeSet.fromType(type);
if (range != null) {
@@ -99,7 +99,8 @@ public class CollectionFactoryInliner implements CallInliner {
builder.pop();
}
DfaValueFactory factory = builder.getFactory();
DfaValue result = factory.withFact(factory.createTypeValue(call.getType(), Nullness.NOT_NULL), DfaFactType.MUTABLE, false);
DfaValue result =
factory.withFact(factory.createTypeValue(call.getType(), Nullness.NOT_NULL), DfaFactType.MUTABILITY, Mutability.UNMODIFIABLE);
if (factoryInfo.mySize == -1) {
builder.push(result);
} else {
@@ -1,5 +1,5 @@
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.ReadOnly;
import org.jetbrains.annotations.Unmodifiable;
import java.util.Arrays;
import java.util.Collection;
@@ -7,7 +7,7 @@ import java.util.Collections;
import java.util.List;
public class MutabilityBasics {
@ReadOnly
@Unmodifiable
static <T> List<T> emptyList() {
return Collections.emptyList();
}
@@ -43,7 +43,7 @@ public class MutabilityBasics {
}
}
@ReadOnly
@Unmodifiable
static Point getZero() {
return new Point() {
@Override
@@ -59,12 +59,12 @@ public class MutabilityBasics {
}
// Differs from getZero as getZero() is considered as getter with predefined value
@ReadOnly
@Unmodifiable
static Point zero() {
return getZero();
}
@ReadOnly List<String> list = Arrays.asList("foo", "bar", "baz");
@Unmodifiable List<String> list = Arrays.asList("foo", "bar", "baz");
void test() {
List<String> collection = emptyList();
@@ -59,4 +59,18 @@ public class MutabilityJdk {
}
return result;
}
void testNoFlush() {
List<String> list1 = Collections.emptyList();
List<String> list2 = new ArrayList<>();
List<String> list3 = Collections.unmodifiableList(list2);
if(<warning descr="Condition 'list1.isEmpty()' is always 'true'">list1.isEmpty()</warning>) System.out.println("ok");
if(!list3.isEmpty()) return;
if(<warning descr="Condition '!list3.isEmpty()' is always 'false'">!<warning descr="Condition 'list3.isEmpty()' is always 'true'">list3.isEmpty()</warning></warning>) return;
list2.add("foo");
// list1 size is not flushed (UNMODIFIABLE)
if(<warning descr="Condition 'list1.isEmpty()' is always 'true'">list1.isEmpty()</warning>) System.out.println("ok");
// list3 size is flushed (UNMODIFIABLE_VIEW)
if(!list3.isEmpty()) return;
}
}
@@ -209,7 +209,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testCastInstanceOf() { doTest(); }
public void testMutabilityBasics() {
myFixture.addClass("package org.jetbrains.annotations;public @interface ReadOnly {}");
myFixture.addClass("package org.jetbrains.annotations;public @interface Unmodifiable {}");
doTest();
}
+14 -14
View File
@@ -1126,7 +1126,7 @@
</item>
<item name="java.util.Collections java.util.Collection&lt;T&gt; unmodifiableCollection(java.util.Collection&lt;? extends T&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.Collection&lt;T&gt; unmodifiableCollection(java.util.Collection&lt;? extends T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1160,22 +1160,22 @@
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; emptyList()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; nCopies(int, T)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; singletonList(T)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; synchronizedList(java.util.List&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; unmodifiableList(java.util.List&lt;? extends T&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.List&lt;T&gt; unmodifiableList(java.util.List&lt;? extends T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1194,18 +1194,18 @@
</item>
<item name="java.util.Collections java.util.Map&lt;K,V&gt; emptyMap()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.Map&lt;K,V&gt; singletonMap(K, V)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.Map&lt;K,V&gt; synchronizedMap(java.util.Map&lt;K,V&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections java.util.Map&lt;K,V&gt; unmodifiableMap(java.util.Map&lt;? extends K,? extends V&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.Map&lt;K,V&gt; unmodifiableMap(java.util.Map&lt;? extends K,? extends V&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1235,18 +1235,18 @@
</item>
<item name="java.util.Collections java.util.Set&lt;T&gt; emptySet()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.Set&lt;T&gt; singleton(T)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.Set&lt;T&gt; synchronizedSet(java.util.Set&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections java.util.Set&lt;T&gt; unmodifiableSet(java.util.Set&lt;? extends T&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.Set&lt;T&gt; unmodifiableSet(java.util.Set&lt;? extends T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1265,7 +1265,7 @@
</item>
<item name="java.util.Collections java.util.SortedMap&lt;K,V&gt; unmodifiableSortedMap(java.util.SortedMap&lt;K,? extends V&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.SortedMap&lt;K,V&gt; unmodifiableSortedMap(java.util.SortedMap&lt;K,? extends V&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1278,14 +1278,14 @@
</item>
<item name="java.util.Collections java.util.SortedSet&lt;E&gt; emptySortedSet()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.Unmodifiable" />
</item>
<item name="java.util.Collections java.util.SortedSet&lt;T&gt; synchronizedSortedSet(java.util.SortedSet&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections java.util.SortedSet&lt;T&gt; unmodifiableSortedSet(java.util.SortedSet&lt;T&gt;)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name="org.jetbrains.annotations.ReadOnly" />
<annotation name="org.jetbrains.annotations.UnmodifiableView" />
</item>
<item name="java.util.Collections java.util.SortedSet&lt;T&gt; unmodifiableSortedSet(java.util.SortedSet&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1,34 +0,0 @@
// 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 org.jetbrains.annotations;
import java.lang.annotation.*;
/**
* An annotation which depicts that method returns a read-only value or a variable
* contains a read-only value. Read-only value means that calling methods which may
* mutate this value (alter visible behavior) either don't have any effect or throw
* an exception. This does not mean that value cannot be altered at all. For example,
* a value could be a read-only wrapper over a mutable value.
* <p>
* This annotation is experimental and may be changed/removed in future
* without additional notice!
* </p>
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@ApiStatus.Experimental
public @interface ReadOnly {
}
@@ -0,0 +1,23 @@
/*
* Copyright 2000-2018 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 org.jetbrains.annotations;
import java.lang.annotation.*;
/**
* An annotation which depicts that method returns an unmodifiable value or a variable
* contains an unmodifiable value. Unmodifiable value means that calling methods which may
* mutate this value (alter visible behavior) either don't have any effect or throw
* an exception.
* <p>
* This annotation is experimental and may be changed/removed in future
* without additional notice!
* </p>
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@ApiStatus.Experimental
public @interface Unmodifiable {
}
@@ -0,0 +1,24 @@
/*
* Copyright 2000-2018 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 org.jetbrains.annotations;
import java.lang.annotation.*;
/**
* An annotation which depicts that method returns an unmodifiable value or a variable
* contains an unmodifiable view. Unmodifiable view means that calling methods which may
* mutate this value (alter visible behavior) either don't have any effect or throw
* an exception. However this value could be modified by third-party, thus methods reading
* the object content might return different result.
* <p>
* This annotation is experimental and may be changed/removed in future
* without additional notice!
* </p>
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@ApiStatus.Experimental
public @interface UnmodifiableView {
}