Mutability tracking support in dataflow analysis

IDEA-182125 Mutability tracking support
IDEA-167940 Inspection to warn of modification of immutable collections
This commit is contained in:
Tagir Valeev
2017-11-27 16:07:21 +07:00
parent 5adec79f95
commit d65bab6112
23 changed files with 1105 additions and 70 deletions
@@ -4,25 +4,24 @@ package com.intellij.codeInsight;
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiMethodImpl;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.EntryStream;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.*;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_INFERRED;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_TYPE;
import static com.intellij.codeInsight.AnnotationUtil.*;
import static com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT;
public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
private static final Set<String> INFERRED_ANNOTATIONS =
ContainerUtil.set(AnnotationUtil.NOT_NULL, AnnotationUtil.NULLABLE, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
ContainerUtil.set(NOT_NULL, NULLABLE, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
private final Project myProject;
public InferredAnnotationsManagerImpl(Project project) {
@@ -54,7 +53,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
return fromBytecode;
}
if ((AnnotationUtil.NOT_NULL.equals(annotationFQN) || AnnotationUtil.NULLABLE.equals(annotationFQN))) {
if ((NOT_NULL.equals(annotationFQN) || NULLABLE.equals(annotationFQN))) {
PsiAnnotation anno = null;
if (listOwner instanceof PsiMethodImpl) {
anno = getInferredNullityAnnotation((PsiMethodImpl)listOwner);
@@ -86,9 +85,9 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && HardcodedContracts.hasHardcodedContracts(owner)) {
return true;
}
if (AnnotationUtil.NOT_NULL.equals(annotationFQN) && owner instanceof PsiParameter && owner.getParent() != null) {
if (NOT_NULL.equals(annotationFQN) && owner instanceof PsiParameter && owner.getParent() != null) {
List<String> annotations = NullableNotNullManager.getInstance(owner.getProject()).getNullables();
if (AnnotationUtil.isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_INFERRED | CHECK_TYPE)) {
if (isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_INFERRED | CHECK_TYPE)) {
return true;
}
if (HardcodedContracts.hasHardcodedContracts(owner)) {
@@ -110,7 +109,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
@Nullable
private PsiAnnotation getInferredNullityAnnotation(PsiMethodImpl method) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject);
if (AnnotationUtil.findAnnotation(method, manager.getNotNulls(), true) != null || AnnotationUtil.findAnnotation(method, manager.getNullables(), true) != null) {
if (findAnnotation(method, manager.getNotNulls(), true) != null || findAnnotation(method, manager.getNullables(), true) != null) {
return null;
}
@@ -155,21 +154,26 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
@Nullable
private PsiAnnotation createContractAnnotation(List<? extends MethodContract> contracts, boolean pure) {
return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "));
return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), "");
}
@Nullable
public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts) {
final String attrs;
if (!contracts.isEmpty() && pure) {
attrs = "value = " + "\"" + contracts + "\", pure = true";
} else if (pure) {
attrs = "pure = true";
} else if (!contracts.isEmpty()) {
attrs = "\"" + contracts + "\"";
} else {
public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts, String mutates) {
Map<String, String> attrMap = new LinkedHashMap<>();
if (!contracts.isEmpty()) {
attrMap.put("value", StringUtil.wrapWithDoubleQuote(contracts));
}
if (pure) {
attrMap.put("pure", "true");
}
else if (!mutates.trim().isEmpty()) {
attrMap.put("mutates", StringUtil.wrapWithDoubleQuote(mutates));
}
if (attrMap.isEmpty()) {
return null;
}
String attrs = attrMap.keySet().equals(Collections.singleton("value")) ?
attrMap.get("value") : EntryStream.of(attrMap).join(" = ").joining(", ");
return ProjectBytecodeAnalysis.getInstance(project).createContractAnnotation(attrs);
}
@@ -193,7 +197,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
ContainerUtil.addIfNotNull(result, getInferredContractAnnotation((PsiMethodImpl)listOwner));
}
if (!ignoreInference(listOwner, AnnotationUtil.NOT_NULL) || !ignoreInference(listOwner, AnnotationUtil.NULLABLE)) {
if (!ignoreInference(listOwner, NOT_NULL) || !ignoreInference(listOwner, NULLABLE)) {
PsiAnnotation annotation = getInferredNullityAnnotation((PsiMethodImpl)listOwner);
if (annotation != null && !ignoreInference(listOwner, annotation.getQualifiedName())) {
result.add(annotation);
@@ -202,7 +206,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
}
}
if (listOwner instanceof PsiParameter && !ignoreInference(listOwner, AnnotationUtil.NOT_NULL)) {
if (listOwner instanceof PsiParameter && !ignoreInference(listOwner, NOT_NULL)) {
ContainerUtil.addIfNotNull(result, getInferredNullityAnnotation((PsiParameter)listOwner));
}
@@ -64,6 +64,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private final ExceptionTransfer myError;
private final PsiType myAssertionError;
private InlinedBlockContext myInlinedBlockContext;
private final boolean myThisReadOnly;
ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions, boolean inlining) {
myInlining = inlining;
@@ -75,6 +76,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
myRuntimeException = new ExceptionTransfer(myFactory.createDfaType(createClassType(scope, JAVA_LANG_RUNTIME_EXCEPTION)));
myError = new ExceptionTransfer(myFactory.createDfaType(createClassType(scope, JAVA_LANG_ERROR)));
myAssertionError = createClassType(scope, JAVA_LANG_ASSERTION_ERROR);
PsiElement member = PsiTreeUtil.getParentOfType(codeFragment, PsiMember.class, PsiLambdaExpression.class);
myThisReadOnly = member instanceof PsiMethod && MutationSignature.fromMethod((PsiMethod)member).preservesThis();
}
private void buildClassInitializerFlow(PsiClass psiClass, boolean isStatic) {
@@ -1486,7 +1489,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (qualifierExpression != null) {
qualifierExpression.accept(this);
}
else {
else if (myThisReadOnly) {
addInstruction(new PushInstruction(myFactory.getFactValue(DfaFactType.MUTABLE, false), null));
} else {
pushUnknown();
}
@@ -1803,7 +1808,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitThisExpression(PsiThisExpression expression) {
startElement(expression);
addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL), null));
DfaValue value = myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL);
if (myThisReadOnly) {
value = myFactory.withFact(value, DfaFactType.MUTABLE, false);
}
addInstruction(new PushInstruction(value, null));
finishElement(expression);
}
@@ -281,6 +281,22 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
if (REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL && visitor.isAlwaysReturnsNotNull(runner.getInstructions())) {
reportAlwaysReturnsNotNull(holder, scope);
}
reportMutabilityViolations(holder, reportedAnchors, visitor.getMutabilityViolations(true),
InspectionsBundle.message("dataflow.message.immutable.modified"));
reportMutabilityViolations(holder, reportedAnchors, visitor.getMutabilityViolations(false),
InspectionsBundle.message("dataflow.message.immutable.passed"));
}
private static void reportMutabilityViolations(ProblemsHolder holder,
Set<PsiElement> reportedAnchors,
Set<PsiElement> violations,
String message) {
for (PsiElement violation : violations) {
if (reportedAnchors.add(violation)) {
holder.registerProblem(violation, message);
}
}
}
private void reportNullabilityProblems(ProblemsHolder holder,
@@ -15,6 +15,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -33,6 +34,8 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
private final List<PsiExpression> myOptionalQualifiers = new ArrayList<>();
private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet();
private final Set<PsiElement> myReceiverMutabilityViolation = new HashSet<>();
private final Set<PsiElement> myArgumentMutabilityViolation = new HashSet<>();
private boolean myAlwaysReturnsNotNull = true;
@Override
@@ -71,6 +74,10 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return myCCEInstructions;
}
Set<PsiElement> getMutabilityViolations(boolean receiver) {
return receiver ? myReceiverMutabilityViolation : myArgumentMutabilityViolation;
}
Stream<PsiArrayAccessExpression> outOfBoundsArrayAccesses() {
return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals);
}
@@ -231,6 +238,23 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return ok;
}
@Override
protected void reportMutabilityViolation(boolean receiver, @NotNull PsiElement anchor) {
if (receiver) {
if (anchor instanceof PsiMethodReferenceExpression) {
anchor = ((PsiMethodReferenceExpression)anchor).getReferenceNameElement();
} else if (anchor instanceof PsiMethodCallExpression) {
anchor = ((PsiMethodCallExpression)anchor).getMethodExpression().getReferenceNameElement();
}
if (anchor != null) {
myReceiverMutabilityViolation.add(anchor);
}
}
else {
myArgumentMutabilityViolation.add(anchor);
}
}
private static boolean shouldReportConstValue(Object value) {
return value == null || value instanceof Boolean;
}
@@ -40,7 +40,7 @@ public abstract class DfaFactType<T> extends Key<T> {
*/
public static final DfaFactType<Boolean> CAN_BE_NULL = new DfaFactType<Boolean>("Can be null") {
@Override
String toString(Boolean fact) {
String toString(@NotNull Boolean fact) {
return fact ? "Nullable" : "NotNull";
}
@@ -74,6 +74,20 @@ public abstract class DfaFactType<T> extends Key<T> {
}
};
public static final DfaFactType<Boolean> MUTABLE = new DfaFactType<Boolean>("Mutable") {
@Override
String toString(@NotNull Boolean fact) {
return fact ? "Mutable" : "ReadOnly";
}
@Nullable
@Override
Boolean calcFromVariable(@NotNull DfaVariableValue value) {
PsiModifierListOwner variable = value.getPsiVariable();
return MutationSignature.getMutabilityFact(variable);
}
};
/**
* This fact is applied to the Optional values (like {@link java.util.Optional} or Guava Optional).
* When its value is true, then optional is known to be present.
@@ -86,7 +100,7 @@ public abstract class DfaFactType<T> extends Key<T> {
}
@Override
String toString(Boolean fact) {
String toString(@NotNull Boolean fact) {
return fact ? "present Optional" : "absent Optional";
}
};
@@ -144,7 +158,7 @@ public abstract class DfaFactType<T> extends Key<T> {
}
@Override
String toString(LongRangeSet fact) {
String toString(@NotNull LongRangeSet fact) {
return fact.toString();
}
};
@@ -237,7 +251,7 @@ public abstract class DfaFactType<T> extends Key<T> {
return left.equals(right) ? left : null;
}
String toString(T fact) {
String toString(@NotNull T fact) {
return fact.toString();
}
@@ -1239,7 +1239,8 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
for (DfaVariableValue value : vars) {
if (value.isFlushableByCalls()) {
if (value.isFlushableByCalls() && (value.getQualifier() == null ||
!Boolean.FALSE.equals(getValueFact(value.getQualifier(), DfaFactType.MUTABLE)))) {
doFlush(value, shouldMarkUnknown(value));
}
}
@@ -0,0 +1,149 @@
// Copyright 2000-2017 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 org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
public class MutationSignature {
private static final String ATTR_MUTATES = "mutates";
private static final String CONTRACT_ANNOTATION = "org.jetbrains.annotations.Contract";
private static final MutationSignature UNKNOWN = new MutationSignature(false, new boolean[0]);
private static final MutationSignature PURE = new MutationSignature(false, new boolean[0]);
public static final String INVALID_TOKEN_MESSAGE = "Invalid token: %s; supported are 'this', 'arg1', 'arg2', etc.";
private final boolean myThis;
private final boolean[] myArgs;
private MutationSignature(boolean mutatesThis, boolean[] args) {
myThis = mutatesThis;
myArgs = args;
}
public boolean mutatesThis() {
return myThis;
}
public boolean mutatesArg(int n) {
return n < myArgs.length && myArgs[n];
}
public boolean preservesThis() {
return this != UNKNOWN && !myThis;
}
public boolean preservesArg(int n) {
return this != UNKNOWN && !mutatesArg(n);
}
/**
* @param signature to parse
* @return a parsed mutation signature
* @throws IllegalArgumentException if signature is invalid
*/
public static MutationSignature parse(String signature) {
boolean mutatesThis = false;
boolean[] args = {};
for (String part : signature.split(",")) {
part = part.trim();
if (part.equals("this")) {
mutatesThis = true;
}
else if (part.equals("arg")) {
if (args.length == 0) {
args = new boolean[] {true};
} else {
args[0] = true;
}
}
else if (part.startsWith("arg")) {
int argNum = Integer.parseInt(part.substring("arg".length()));
if (argNum < 0 || argNum > 255) {
throw new IllegalArgumentException(String.format(INVALID_TOKEN_MESSAGE, part));
}
if(args.length < argNum) {
args = Arrays.copyOf(args, argNum);
}
args[argNum-1] = true;
}
else if (!part.isEmpty()) {
throw new IllegalArgumentException(String.format(INVALID_TOKEN_MESSAGE, part));
}
}
return new MutationSignature(mutatesThis, args);
}
/**
* Checks the mutation signature
* @param signature signature to check
* @param method a method to apply the signature
* @return error message or null if signature is valid
*/
@Nullable
public static String checkSignature(@NotNull String signature, @NotNull PsiMethod method) {
try {
MutationSignature ms = parse(signature);
if (ms.myThis && method.hasModifierProperty(PsiModifier.STATIC)) {
return "Static method cannot mutate 'this'";
}
if (ms.myArgs.length > method.getParameterList().getParametersCount()) {
return "Reference to argument #" + ms.myArgs.length + " is invalid";
}
}
catch (IllegalArgumentException ex) {
return ex.getMessage();
}
return null;
}
@NotNull
public static MutationSignature fromMethod(@Nullable PsiMethod method) {
if (method == null) return UNKNOWN;
PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, CONTRACT_ANNOTATION);
if (annotation == null) return UNKNOWN;
PsiAnnotationMemberValue value = annotation.findAttributeValue(ATTR_MUTATES);
if (value instanceof PsiLiteralExpression) {
Object text = ((PsiLiteralExpression)value).getValue();
if (text instanceof String) {
try {
return parse((String)text);
}
catch (IllegalArgumentException ignored) { }
}
}
if(ControlFlowAnalyzer.isPure(method)) {
return PURE;
}
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;
}
}
@@ -340,8 +340,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState memState,
boolean contractOnly) {
DfaValue[] argValues = popCallArguments(instruction, runner, memState, contractOnly);
final DfaValue qualifier = popQualifier(instruction, memState);
PsiMethod method = instruction.getTargetMethod();
MutationSignature sig = MutationSignature.fromMethod(method);
DfaValue[] argValues = popCallArguments(instruction, runner, memState, contractOnly, sig);
final DfaValue qualifier = popQualifier(instruction, memState, sig);
return new DfaCallArguments(qualifier, argValues);
}
@@ -349,7 +351,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private DfaValue[] popCallArguments(MethodCallInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
boolean contractOnly) {
boolean contractOnly, MutationSignature sig) {
final int argCount = instruction.getArgCount();
PsiMethod method = instruction.getTargetMethod();
@@ -382,6 +384,12 @@ 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)) {
reportMutabilityViolation(false, anchor);
if (arg instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)arg, DfaFactType.MUTABLE, true);
}
}
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
argValues[paramIndex] = arg;
}
@@ -389,8 +397,20 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return argValues;
}
private DfaValue popQualifier(MethodCallInstruction instruction, DfaMemoryState memState) {
return dereference(memState, memState.pop(), instruction.getQualifierNullabilityProblem());
protected void reportMutabilityViolation(boolean receiver, @NotNull PsiElement anchor) {
}
private DfaValue popQualifier(MethodCallInstruction instruction,
DfaMemoryState memState,
MutationSignature sig) {
DfaValue value = dereference(memState, memState.pop(), instruction.getQualifierNullabilityProblem());
if (sig.mutatesThis() && !memState.applyFact(value, DfaFactType.MUTABLE, true)) {
reportMutabilityViolation(true, instruction.getContext());
if (value instanceof DfaVariableValue) {
memState.forceVariableFact((DfaVariableValue)value, DfaFactType.MUTABLE, true);
}
}
return value;
}
private static LinkedHashSet<DfaMemoryState> addContractResults(DfaCallArguments callArguments,
@@ -507,10 +527,13 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (type != null && !(type instanceof PsiPrimitiveType)) {
Nullness nullability = instruction.getReturnNullability();
PsiMethod targetMethod = instruction.getTargetMethod();
Boolean mutable = null;
if (targetMethod != null) {
mutable = MutationSignature.getMutabilityFact(targetMethod);
PsiMethod realMethod = findSpecificMethod(targetMethod, state, qualifierValue);
if (realMethod != targetMethod) {
nullability = DfaPsiUtil.getElementNullability(type, realMethod);
mutable = MutationSignature.getMutabilityFact(realMethod);
PsiType returnType = realMethod.getReturnType();
if (returnType != null && TypeConversionUtil.erasure(type).isAssignableFrom(returnType)) {
// possibly covariant return type
@@ -521,7 +544,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
nullability = factory.suggestNullabilityForNonAnnotatedMember(targetMethod);
}
}
return factory.createTypeValue(type, nullability);
DfaValue value = factory.createTypeValue(type, nullability);
return factory.withFact(value, DfaFactType.MUTABLE, mutable);
}
LongRangeSet range = LongRangeSet.fromType(type);
if (range != null) {
@@ -15,30 +15,38 @@
*/
package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInspection.dataFlow.CFGBuilder;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.SpecialField;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiVariable;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.MethodCallUtils;
import org.jetbrains.annotations.NotNull;
import static com.intellij.codeInspection.dataFlow.SpecialField.COLLECTION_SIZE;
import static com.intellij.codeInspection.dataFlow.SpecialField.MAP_SIZE;
import static com.intellij.psi.CommonClassNames.JAVA_UTIL_COLLECTIONS;
import static com.intellij.psi.CommonClassNames.*;
import static com.siyeh.ig.callMatcher.CallMatcher.anyOf;
import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
public class CollectionFactoryInliner implements CallInliner {
static final class FactoryInfo {
int mySize;
SpecialField mySizeField;
final boolean myNotNull;
final int mySize;
final SpecialField mySizeField;
public FactoryInfo(int size, SpecialField sizeField) {
this(size, sizeField, false);
}
public FactoryInfo(int size, SpecialField sizeField, boolean notNull) {
mySize = size;
mySizeField = sizeField;
myNotNull = notNull;
}
}
@@ -48,24 +56,63 @@ public class CollectionFactoryInliner implements CallInliner {
.register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyMap").parameterCount(0), new FactoryInfo(0, MAP_SIZE))
.register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonMap").parameterCount(2), new FactoryInfo(1, MAP_SIZE));
private static final CallMatcher JDK9_MAP_FACTORIES =
staticCall(JAVA_UTIL_MAP, "of", "ofEntries");
private static final CallMatcher JDK9_FACTORIES = anyOf(
staticCall(JAVA_UTIL_LIST, "of"),
staticCall(JAVA_UTIL_SET, "of")
);
private static final CallMatcher JDK9_ARRAY_FACTORIES = anyOf(
staticCall(JAVA_UTIL_LIST, "of").parameterTypes("E..."),
staticCall(JAVA_UTIL_SET, "of").parameterTypes("E...")
);
private static FactoryInfo getFactoryInfo(@NotNull PsiMethodCallExpression call) {
FactoryInfo info = STATIC_FACTORIES.mapFirst(call);
if (info != null) return info;
if (JDK9_FACTORIES.test(call)) {
int size =
JDK9_ARRAY_FACTORIES.test(call) && !MethodCallUtils.isVarArgCall(call) ? -1 : call.getArgumentList().getExpressions().length;
return new FactoryInfo(size, COLLECTION_SIZE, true);
}
if (JDK9_MAP_FACTORIES.test(call)) {
boolean ofEntries = "ofEntries".equals(call.getMethodExpression().getReferenceName());
int size =
ofEntries && !MethodCallUtils.isVarArgCall(call) ? -1 : call.getArgumentList().getExpressions().length / (ofEntries ? 1 : 2);
return new FactoryInfo(size, MAP_SIZE, true);
}
return null;
}
@Override
public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
FactoryInfo factoryInfo = STATIC_FACTORIES.mapFirst(call);
FactoryInfo factoryInfo = getFactoryInfo(call);
if (factoryInfo == null) return false;
PsiExpression[] args = call.getArgumentList().getExpressions();
for (PsiExpression arg : args) {
builder.pushExpression(arg).pop();
builder.pushExpression(arg);
if (factoryInfo.myNotNull) {
builder.checkNotNull(arg, NullabilityProblemKind.passingNullableToNotNullParameter);
}
builder.pop();
}
PsiVariable variable = builder.createTempVariable(call.getType());
DfaValueFactory factory = builder.getFactory();
DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false);
builder.pushVariable(variable) // tmpVar = <Value of collection type>
.push(factory.createTypeValue(call.getType(), Nullness.NOT_NULL))
.assign() // leave tmpVar on stack: it's result of method call
.push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = <size>
.push(factory.getInt(factoryInfo.mySize))
.assign()
.pop();
DfaValue result = factory.withFact(factory.createTypeValue(call.getType(), Nullness.NOT_NULL), DfaFactType.MUTABLE, false);
if (factoryInfo.mySize == -1) {
builder.push(result);
} else {
PsiVariable variable = builder.createTempVariable(call.getType());
DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false);
builder.pushVariable(variable) // tmpVar = <Value of collection type>
.push(result)
.assign() // leave tmpVar on stack: it's result of method call
.push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = <size>
.push(factory.getInt(factoryInfo.mySize))
.assign()
.pop();
}
return true;
}
}
@@ -83,6 +83,17 @@ public class DfaValueFactory {
return getFactFactory().createValue(facts);
}
@NotNull
public <T> DfaValue withFact(@NotNull DfaValue value, @NotNull DfaFactType<T> factType, @Nullable T factValue) {
if(value instanceof DfaUnknownValue) {
return getFactFactory().createValue(DfaFactMap.EMPTY.with(factType, factValue));
}
if(value instanceof DfaFactMapValue) {
return ((DfaFactMapValue)value).withFact(factType, factValue);
}
return DfaUnknownValue.getInstance();
}
@NotNull
public DfaPsiType createDfaType(@NotNull PsiType psiType) {
int dimensions = psiType.getArrayDimensions();
@@ -23,6 +23,7 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
import com.intellij.codeInsight.intention.LowPriorityAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
@@ -33,6 +34,7 @@ import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.NonFocusableCheckBox;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -56,7 +58,7 @@ public class EditContractIntention extends BaseIntentionAction implements LowPri
}
@Nullable
private static PsiMethod getTargetMethod(@NotNull Project project, Editor editor, PsiFile file) {
private static PsiMethod getTargetMethod(Editor editor, PsiFile file) {
final PsiModifierListOwner owner = AddAnnotationPsiFix.getContainer(file, editor.getCaretModel().getOffset());
if (owner instanceof PsiMethod && ExternalAnnotationsManagerImpl.areExternalAnnotationsApplicable(owner)) {
PsiElement original = owner.getOriginalElement();
@@ -67,7 +69,7 @@ public class EditContractIntention extends BaseIntentionAction implements LowPri
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
final PsiMethod method = getTargetMethod(project, editor, file);
final PsiMethod method = getTargetMethod(editor, file);
if (method != null) {
boolean hasContract = ControlFlowAnalyzer.findContractAnnotation(method) != null;
setText(hasContract ? "Edit method contract of '" + method.getName() + "'" : "Add method contract to '" + method.getName() + "'");
@@ -78,33 +80,95 @@ public class EditContractIntention extends BaseIntentionAction implements LowPri
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
final PsiMethod method = getTargetMethod(project, editor, file);
final PsiMethod method = getTargetMethod(editor, file);
assert method != null;
Contract existingAnno = AnnotationUtil.findAnnotationInHierarchy(method, Contract.class);
String oldContract = existingAnno == null ? null : existingAnno.value();
boolean oldPure = existingAnno != null && existingAnno.pure();
String oldMutates = existingAnno == null ? null : existingAnno.mutates();
JBTextField contractText = new JBTextField(oldContract);
JBTextField mutatesText = new JBTextField(oldMutates);
JCheckBox pureCB = createPureCheckBox(oldPure);
DialogBuilder builder = createDialog(project, contractText, pureCB);
contractText.getDocument().addDocumentListener(new DocumentAdapter() {
DialogBuilder builder = createDialog(project, contractText, pureCB, mutatesText);
DocumentAdapter validator = new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
String error = getErrorMessage(contractText.getText(), method);
builder.setOkActionEnabled(error == null);
builder.setErrorText(error, contractText);
String contractError = getContractErrorMessage(contractText.getText(), method);
if (contractError != null) {
builder.setOkActionEnabled(false);
builder.setErrorText(contractError, contractText);
}
else {
String mutatesError = getMutatesErrorMessage(mutatesText.getText(), method);
if (mutatesError != null) {
builder.setOkActionEnabled(false);
builder.setErrorText(mutatesError, mutatesText);
}
else {
builder.setOkActionEnabled(true);
builder.setErrorText(null);
}
}
}
});
};
Runnable updateControls = () -> {
if (pureCB.isSelected()) {
mutatesText.setText("");
mutatesText.setEnabled(false);
}
else {
mutatesText.setEnabled(true);
}
};
pureCB.addChangeListener(e -> updateControls.run());
contractText.getDocument().addDocumentListener(validator);
mutatesText.getDocument().addDocumentListener(validator);
updateControls.run();
if (builder.showAndGet()) {
updateContract(method, contractText.getText(), pureCB.isSelected());
updateContract(method, contractText.getText(), pureCB.isSelected(), mutatesText.getText());
}
}
private static DialogBuilder createDialog(@NotNull Project project, JBTextField contractText, JCheckBox pureCB) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(Messages.configureMessagePaneUi(new JTextPane(), ourPrompt), BorderLayout.NORTH);
panel.add(contractText, BorderLayout.CENTER);
panel.add(pureCB, BorderLayout.SOUTH);
private static DialogBuilder createDialog(@NotNull Project project,
JBTextField contractText,
JCheckBox pureCB,
JBTextField mutatesText) {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints constraints =
new GridBagConstraints(0, 0, 2, 1, 4.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, JBUI.insets(2), 0, 0);
panel.add(Messages.configureMessagePaneUi(new JTextPane(), ourPrompt), constraints);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.weightx = 1;
JLabel contractLabel = new JLabel("Contract:");
contractLabel.setDisplayedMnemonic('c');
contractLabel.setLabelFor(contractText);
panel.add(contractLabel, constraints);
constraints.gridx = 1;
constraints.weightx = 3;
panel.add(contractText, constraints);
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 2;
constraints.weightx = 4;
panel.add(pureCB, constraints);
panel.add(pureCB, constraints);
if (ApplicationManagerEx.getApplicationEx().isInternal()) {
constraints.gridx = 0;
constraints.gridy = 3;
constraints.weightx = 1;
constraints.gridwidth = 1;
JLabel mutatesLabel = new JLabel("Mutates:");
mutatesLabel.setDisplayedMnemonic('m');
mutatesLabel.setLabelFor(mutatesText);
panel.add(mutatesLabel, constraints);
constraints.gridx = 1;
constraints.weightx = 3;
panel.add(mutatesText, constraints);
}
DialogBuilder builder = new DialogBuilder(project).setNorthPanel(panel).title("Edit Method Contract");
builder.setPreferredFocusComponent(contractText);
@@ -118,11 +182,11 @@ public class EditContractIntention extends BaseIntentionAction implements LowPri
return pureCB;
}
private static void updateContract(PsiMethod method, String contract, boolean pure) {
private static void updateContract(PsiMethod method, String contract, boolean pure, String mutates) {
Project project = method.getProject();
ExternalAnnotationsManager manager = ExternalAnnotationsManager.getInstance(project);
manager.deannotate(method, ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
PsiAnnotation mockAnno = InferredAnnotationsManagerImpl.createContractAnnotation(project, pure, contract);
PsiAnnotation mockAnno = InferredAnnotationsManagerImpl.createContractAnnotation(project, pure, contract, mutates);
if (mockAnno != null) {
try {
manager.annotateExternally(method, ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT, method.getContainingFile(),
@@ -134,7 +198,12 @@ public class EditContractIntention extends BaseIntentionAction implements LowPri
}
@Nullable
private static String getErrorMessage(String contract, PsiMethod method) {
private static String getMutatesErrorMessage(String mutates, PsiMethod method) {
return StringUtil.isEmpty(mutates) ? null : MutationSignature.checkSignature(mutates, method);
}
@Nullable
private static String getContractErrorMessage(String contract, PsiMethod method) {
return StringUtil.isEmpty(contract) ? null : ContractInspection.checkContract(method, contract);
}
@@ -0,0 +1,77 @@
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.ReadOnly;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class MutabilityBasics {
@ReadOnly
static <T> List<T> emptyList() {
return Collections.emptyList();
}
@Contract(<error descr="Cannot resolve method 'mutates'">mutates</error> = "arg")
static <T extends Comparable<T>> void sort(List<T> collection) {
Collections.sort(collection);
}
@Contract(<error descr="Cannot resolve method 'mutates'">mutates</error> = "arg1")
static <T extends Comparable<T>> void addAll(Collection<T> collection, List<T> other) {
sort(<warning descr="Immutable object is passed where mutable is expected">other</warning>);
collection.addAll(other);
}
// Purity implies that no arguments should be changed
@Contract(pure = true)
static <T extends Comparable<T>> T min(List<T> list) {
sort(<warning descr="Immutable object is passed where mutable is expected">list</warning>);
return list.get(0);
}
interface Point {
int get();
@Contract(<error descr="Cannot resolve method 'mutates'">mutates</error> = "this")
void set(int x);
@Contract(pure = true)
default void setZero() {
// cannot modify itself (call mutating method), because declared as pure
<warning descr="Immutable object is modified">set</warning>(0);
}
}
@ReadOnly
static Point getZero() {
return new Point() {
@Override
public int get() {
return 0;
}
@Override
public void set(int x) {
throw new UnsupportedOperationException();
}
};
}
// Differs from getZero as getZero() is considered as getter with predefined value
@ReadOnly
static Point zero() {
return getZero();
}
@ReadOnly List<String> list = Arrays.asList("foo", "bar", "baz");
void test() {
List<String> collection = emptyList();
sort(<warning descr="Immutable object is passed where mutable is expected">collection</warning>);
sort(<warning descr="Immutable object is passed where mutable is expected">MutabilityBasics.<String>emptyList()</warning>);
getZero().<warning descr="Immutable object is modified">set</warning>(1);
zero().<warning descr="Immutable object is modified">set</warning>(1);
sort(<warning descr="Immutable object is passed where mutable is expected">list</warning>);
}
}
@@ -0,0 +1,62 @@
import java.util.*;
import java.io.*;
public class MutabilityJdk {
void testEmpty() {
List<String> list = Collections.emptyList();
Collections.sort(<warning descr="Immutable object is passed where mutable is expected">list</warning>);
}
void testUnmodifiable(Collection<String> collection) {
collection = Collections.unmodifiableCollection(collection);
collection.<warning descr="Immutable object is modified">add</warning>("foo");
}
void testBranch(boolean b) {
List<Object> list;
if(b) {
list = Collections.emptyList();
} else {
list = new ArrayList<>();
}
if(!b) {
list.add("foo");
}
list.<warning descr="Immutable object is modified">add</warning>("bar");
}
void testArrayGtZero(File configRoot) {
final File[] files = configRoot.listFiles();
final Map<String, File> templatesOnDisk = files != null && files.length > 0 ? new HashMap<>() : Collections.emptyMap();
if (files != null) {
for (File file : files) {
if (!file.isDirectory()) {
final String name = file.getName();
templatesOnDisk.put(name, file);
}
}
}
}
// Purity is inferred
static Runnable testLambda(List<String> list) {
return () -> list.add("foo");
}
interface X {
List<Object> get();
}
List<String> getList(X x) {
List<String> result = Collections.emptyList();
for (Object obj : x.get()) {
if (obj instanceof String) {
if (result.isEmpty()) result = new ArrayList<>();
result.add((String)obj);
}
}
return result;
}
}
@@ -0,0 +1,50 @@
import java.util.*;
public class MutabilityJdk9 {
void testList() {
List<Integer> list = List.of(1,2,3);
if(<warning descr="Condition 'list.size() > 5' is always 'false'">list.size() > 5</warning>) {
System.out.println("impossible");
}
list.<warning descr="Immutable object is modified">sort</warning>(null);
}
void testSet() {
Set<String> set = Set.of("foo", "bar", "baz", "qux");
if(<warning descr="Condition 'set.size() == 4' is always 'true'">set.size() == 4</warning>) {
System.out.println("always");
}
set.<warning descr="Immutable object is modified">add</warning>("oops");
}
void testMap() {
Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5);
if(<warning descr="Condition 'map.size() != 5' is always 'false'">map.size() != 5</warning>) {
System.out.println("never");
}
map.<warning descr="Immutable object is modified">put</warning>("foo", 6);
}
void testMapOfEntries() {
Map<String, Integer> map = Map.ofEntries(Map.entry("x", 1), Map.entry("y", 2));
if(<warning descr="Condition 'map.size() == 2' is always 'true'">map.size() == 2</warning>) {
System.out.println("you bet!");
}
map.<warning descr="Immutable object is modified">remove</warning>("x");
}
// IDEA-167940
void testImmutable(String a, String b){
List.of(a).<warning descr="Immutable object is modified">add</warning>(b); //java 9 collection literals do not accept modification
Collections.singletonList(a).<warning descr="Immutable object is modified">add</warning>(b); //singleton should not be modified
}
void testVarArg(String[] str) {
List<String> list = List.of(str);
if(str.length > 2) {
list.<warning descr="Immutable object is modified">add</warning>("foo");
}
}
}
@@ -0,0 +1,15 @@
import java.util.*;
public class NullabilityJdk9 {
void test() {
List<Integer> list = List.of(1,2,3,<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
Set<String> set = Set.of("foo", "bar", <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>, "baz");
Map<String, Integer> map = Map.of("x", <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>,
"y", <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>,
<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>, 3);
Map<String, Integer> map1 = Map.ofEntries(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>, <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
Integer[] array = null;
List<Integer> list1 = List.of(<warning descr="Argument 'array' might be null">array</warning>);
}
}
@@ -207,4 +207,13 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
}
public void testCastInstanceOf() { doTest(); }
public void testMutabilityBasics() {
myFixture.addClass("package org.jetbrains.annotations;public @interface ReadOnly {}");
doTest();
}
public void testMutabilityJdk() {
doTest();
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2017 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.codeInspection;
import com.intellij.JavaTestUtil;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
public class DataFlowInspection9Test extends DataFlowInspectionTestCase {
private static final DefaultLightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@Override
public Sdk getSdk() {
return PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk9());
}
};
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return PROJECT_DESCRIPTOR;
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/inspection/dataFlow/fixture/";
}
public void testNullabilityJdk9() { doTest();}
public void testMutabilityJdk9() { doTest();}
}
@@ -29,6 +29,7 @@ public class DataFlowInspectionTestSuite {
suite.addTestSuite(DataFlowInspectionTest.class);
suite.addTestSuite(DataFlowInspection8Test.class);
suite.addTestSuite(DataFlowInspection9Test.class);
suite.addTestSuite(DataFlowInspectionHeavyTest.class);
suite.addTestSuite(DataFlowInspectionAncientTest.class);
suite.addTestSuite(ContractCheckTest.class);
@@ -0,0 +1,25 @@
// Copyright 2000-2017 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.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.MutationSignature;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MutationSignatureTest {
@Test
public void testParse() {
MutationSignature sig = MutationSignature.parse("this");
assertTrue(sig.mutatesThis());
assertFalse(sig.mutatesArg(0));
sig = MutationSignature.parse("arg1 , arg2");
assertFalse(sig.mutatesThis());
assertTrue(sig.mutatesArg(0));
assertTrue(sig.mutatesArg(1));
assertFalse(sig.mutatesArg(2));
sig = MutationSignature.parse("");
assertFalse(sig.mutatesThis());
assertFalse(sig.mutatesArg(0));
}
}
@@ -601,10 +601,23 @@
<val name="sourceIsContainer" val="true"/>
<val name="targetIsContainer" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collection T[] toArray(T[]) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collection boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean addAll(java.util.Collection&lt;? extends E&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean addAll(java.util.Collection&lt;? extends E&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.intellij.lang.annotations.Flow'>
@@ -615,9 +628,29 @@
<item name="java.util.Collection boolean containsAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collection boolean remove(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean removeAll(java.util.Collection&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean removeAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collection boolean removeIf(java.util.function.Predicate&lt;? super E&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean retainAll(java.util.Collection&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collection boolean retainAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -631,6 +664,26 @@
<item name="java.util.Collection java.util.Iterator&lt;E&gt; iterator()">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collection java.util.Spliterator&lt;E&gt; spliterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collection java.util.stream.Stream&lt;E&gt; parallelStream()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collection java.util.stream.Stream&lt;E&gt; stream()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collection void clear()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Collections T max(java.util.Collection&lt;? extends T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -649,6 +702,11 @@
<item name="java.util.Collections T min(java.util.Collection&lt;? extends T&gt;, java.util.Comparator&lt;? super T&gt;) 1">
<annotation name="org.jetbrains.annotations.Nullable" />
</item>
<item name='java.util.Collections boolean addAll(java.util.Collection&lt;? super T&gt;, T...)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections boolean addAll(java.util.Collection&lt;? super T&gt;, T...) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -661,6 +719,11 @@
<item name="java.util.Collections boolean disjoint(java.util.Collection&lt;?&gt;, java.util.Collection&lt;?&gt;) 1">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collections boolean replaceAll(java.util.List&lt;T&gt;, T, T)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections boolean replaceAll(java.util.List&lt;T&gt;, T, T) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -679,6 +742,11 @@
<item name="java.util.Collections int binarySearch(java.util.List&lt;? extends java.lang.Comparable&lt;? super T&gt;&gt;, T) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collections int frequency(java.util.Collection&lt;?&gt;, java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.Collections int frequency(java.util.Collection&lt;?&gt;, java.lang.Object) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -709,6 +777,10 @@
<item name="java.util.Collections java.util.Collection&lt;T&gt; synchronizedCollection(java.util.Collection&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</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" />
</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" />
</item>
@@ -741,16 +813,23 @@
</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" />
</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" />
</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" />
</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" />
</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" />
</item>
@@ -768,13 +847,19 @@
</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" />
</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" />
</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" />
</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" />
</item>
@@ -793,18 +878,29 @@
<item name="java.util.Collections java.util.Set&lt;E&gt; checkedSet(java.util.Set&lt;E&gt;, java.lang.Class&lt;E&gt;) 1">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Collections java.util.Set&lt;E&gt; newSetFromMap(java.util.Map&lt;E,java.lang.Boolean&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.Collections java.util.Set&lt;E&gt; newSetFromMap(java.util.Map&lt;E,java.lang.Boolean&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</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" />
</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" />
</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" />
</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" />
</item>
@@ -820,6 +916,10 @@
<item name="java.util.Collections java.util.SortedMap&lt;K,V&gt; synchronizedSortedMap(java.util.SortedMap&lt;K,V&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</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" />
</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" />
</item>
@@ -831,46 +931,96 @@
</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" />
</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" />
</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" />
</item>
<item name='java.util.Collections void copy(java.util.List&lt;? super T&gt;, java.util.List&lt;? extends T&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void copy(java.util.List&lt;? super T&gt;, java.util.List&lt;? extends T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void copy(java.util.List&lt;? super T&gt;, java.util.List&lt;? extends T&gt;) 1">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void fill(java.util.List&lt;? super T&gt;, T)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void fill(java.util.List&lt;? super T&gt;, T) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void reverse(java.util.List&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void reverse(java.util.List&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void rotate(java.util.List&lt;?&gt;, int)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void rotate(java.util.List&lt;?&gt;, int) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void shuffle(java.util.List&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void shuffle(java.util.List&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void shuffle(java.util.List&lt;?&gt;, java.util.Random)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void shuffle(java.util.List&lt;?&gt;, java.util.Random) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void shuffle(java.util.List&lt;?&gt;, java.util.Random) 1">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void sort(java.util.List&lt;T&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void sort(java.util.List&lt;T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void sort(java.util.List&lt;T&gt;, java.util.Comparator&lt;? super T&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void sort(java.util.List&lt;T&gt;, java.util.Comparator&lt;? super T&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collections void sort(java.util.List&lt;T&gt;, java.util.Comparator&lt;? super T&gt;) 1">
<annotation name="org.jetbrains.annotations.Nullable" />
</item>
<item name="java.util.Collections void swap(java.util.List&lt;?&gt;, int, int)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.Collections void swap(java.util.List&lt;?&gt;, int, int) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -961,8 +1111,14 @@
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="sourceIsContainer" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.List E set(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="sourceIsContainer" val="true"/>
</annotation>
@@ -974,15 +1130,28 @@
</item>
<item name="java.util.List T[] toArray(T[])">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;arg1&quot;"/>
</annotation>
</item>
<item name="java.util.List T[] toArray(T[]) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.List boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.List boolean add(E) 0'>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="targetIsContainer" val="true"/>
</annotation>
</item>
<item name="java.util.List boolean addAll(int, java.util.Collection&lt;? extends E&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.List boolean addAll(int, java.util.Collection&lt;? extends E&gt;) 1">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.intellij.lang.annotations.Flow'>
@@ -990,6 +1159,11 @@
<val name="targetIsContainer" val="true"/>
</annotation>
</item>
<item name="java.util.List boolean addAll(java.util.Collection&lt;? extends E&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.List boolean addAll(java.util.Collection&lt;? extends E&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.intellij.lang.annotations.Flow'>
@@ -1000,9 +1174,24 @@
<item name="java.util.List boolean containsAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.List boolean remove(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.List boolean removeAll(java.util.Collection&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.List boolean removeAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.List boolean retainAll(java.util.Collection&lt;?&gt;)">
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.List boolean retainAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -1025,18 +1214,56 @@
<item name="java.util.List java.util.ListIterator&lt;E&gt; listIterator(int)">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.List void add(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.List void add(int, E) 1'>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="targetIsContainer" val="true"/>
</annotation>
</item>
<item name='java.util.List void replaceAll(java.util.function.UnaryOperator&lt;E&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.List void sort(java.util.Comparator&lt;? super E&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V compute(K, java.util.function.BiFunction&lt;? super K,? super V,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V computeIfAbsent(K, java.util.function.Function&lt;? super K,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V computeIfPresent(K, java.util.function.BiFunction&lt;? super K,? super V,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V get(java.lang.Object)'>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="source" val="&quot;this.values&quot;"/>
<val name="sourceIsContainer" val="true" />
</annotation>
</item>
<item name='java.util.Map V merge(K, V, java.util.function.BiFunction&lt;? super V,? super V,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V put(K, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="source" val="&quot;this.values&quot;"/>
<val name="sourceIsContainer" val="true" />
@@ -1054,12 +1281,35 @@
<val name="targetIsContainer" val="true" />
</annotation>
</item>
<item name='java.util.Map V putIfAbsent(K, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map V remove(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
<annotation name='org.intellij.lang.annotations.Flow'>
<val name="source" val="&quot;this.values&quot;"/>
<val name="sourceIsContainer" val="true" />
</annotation>
</item>
<item name='java.util.Map V replace(K, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map boolean remove(java.lang.Object, java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map boolean replace(K, V, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.Map java.util.Collection&lt;V&gt; values()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.intellij.lang.annotations.Flow'>
@@ -1067,6 +1317,9 @@
<val name="sourceIsContainer" val="true" />
<val name="targetIsContainer" val="true" />
</annotation>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.Map java.util.Set&lt;K&gt; keySet()">
<annotation name="org.jetbrains.annotations.NotNull" />
@@ -1075,13 +1328,39 @@
<val name="sourceIsContainer" val="true" />
<val name="targetIsContainer" val="true" />
</annotation>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.Map java.util.Set&lt;java.util.Map.Entry&lt;K,V&gt;&gt; entrySet()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.Map void putAll(java.util.Map&lt;? extends K,? extends V&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.util.Map void replaceAll(java.util.function.BiFunction&lt;? super K,? super V,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Map.Entry K getKey()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Map.Entry V getValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Map.Entry V setValue(V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.NavigableSet E ceiling(E)'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
@@ -1096,27 +1375,48 @@
</item>
<item name='java.util.NavigableSet E pollFirst()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.NavigableSet E pollLast()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name="java.util.NavigableSet java.util.Iterator&lt;E&gt; descendingIterator()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.NavigableSet java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name="java.util.NavigableSet java.util.NavigableSet&lt;E&gt; descendingSet()">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.NavigableSet java.util.NavigableSet&lt;E&gt; headSet(E, boolean)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.NavigableSet java.util.NavigableSet&lt;E&gt; subSet(E, boolean, E, boolean)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.NavigableSet java.util.NavigableSet&lt;E&gt; tailSet(E, boolean)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.NavigableSet java.util.SortedSet&lt;E&gt; headSet(E)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
@@ -1151,6 +1451,31 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Queue E element()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Queue E peek()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Queue E poll()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Queue E remove()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Queue boolean offer(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.ResourceBundle boolean containsKey(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -1326,6 +1651,11 @@
<item name='java.util.Set T[] toArray(T[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Set boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
</item>
<item name='java.util.Set boolean addAll(java.util.Collection&lt;? extends E&gt;) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -1355,15 +1685,27 @@
</item>
<item name="java.util.SortedSet java.util.Comparator&lt;? super E&gt; comparator()">
<annotation name="org.jetbrains.annotations.Nullable" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.SortedSet java.util.SortedSet&lt;E&gt; headSet(E)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.SortedSet java.util.SortedSet&lt;E&gt; subSet(E, E)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.SortedSet java.util.SortedSet&lt;E&gt; tailSet(E)">
<annotation name="org.jetbrains.annotations.NotNull" />
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name="java.util.TimeZone java.util.TimeZone getTimeZone(java.lang.String) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
@@ -64,4 +64,21 @@ public @interface Contract {
* to check that the method's return value is actually used in the call place.
*/
boolean pure() default false;
/**
* Contains a specifier which describes which method parameters can be mutated during the method call.
* <p>
* The following values are possible:
* <table>
* <tr><td>"this"</td>Method mutates the receiver object, and doesn't mutates any objects passed as arguments (cannot be applied for static method or constructor)</tr>
* <tr><td>"arg"</td>Method mutates the sole argument and doesn't mutate the receiver object (if applicable)</tr>
* <tr><td>"arg1", "arg2", ...</td>Method mutates the N-th argument</tr>
* <tr><td>"this,arg1"</td>Method mutates the receiver and first argument and doesn't mutate any other arguments</tr>
* </table>
* </p>
*
* @return a mutation specifier string
* Warning: This annotation parameter is experimental and may be changed or removed without further notice!
*/
String mutates() default "";
}
@@ -92,6 +92,8 @@ dataflow.method.fails.with.null.argument=Method will throw an exception when par
dataflow.message.optional.get.without.is.present=<code>{0}.#ref()</code> without ''isPresent()'' check
dataflow.message.constant.method.reference=Method reference result is always ''{0}''
dataflow.message.array.index.out.of.bounds=Array index is out of bounds
dataflow.message.immutable.modified=Immutable object is modified
dataflow.message.immutable.passed=Immutable object is passed where mutable is expected
#deprecated
inspection.deprecated.display.name=Deprecated API usage
@@ -0,0 +1,34 @@
// 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.LOCAL_VARIABLE})
@ApiStatus.Experimental
public @interface ReadOnly {
}