mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
+26
-22
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -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,
|
||||
|
||||
+24
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+149
@@ -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;
|
||||
}
|
||||
}
|
||||
+30
-6
@@ -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) {
|
||||
|
||||
+64
-17
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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();
|
||||
|
||||
+87
-18
@@ -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();}
|
||||
}
|
||||
+1
@@ -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);
|
||||
|
||||
+25
@@ -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=""arg1""/>
|
||||
</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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean addAll(java.util.Collection<? extends E>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean addAll(java.util.Collection<? extends E>) 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<?>) 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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean removeAll(java.util.Collection<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean removeAll(java.util.Collection<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collection boolean removeIf(java.util.function.Predicate<? super E>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean retainAll(java.util.Collection<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collection boolean retainAll(java.util.Collection<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -631,6 +664,26 @@
|
||||
<item name="java.util.Collection java.util.Iterator<E> iterator()">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collection java.util.Spliterator<E> spliterator()'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collection java.util.stream.Stream<E> parallelStream()'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collection java.util.stream.Stream<E> 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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections T max(java.util.Collection<? extends T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -649,6 +702,11 @@
|
||||
<item name="java.util.Collections T min(java.util.Collection<? extends T>, java.util.Comparator<? super T>) 1">
|
||||
<annotation name="org.jetbrains.annotations.Nullable" />
|
||||
</item>
|
||||
<item name='java.util.Collections boolean addAll(java.util.Collection<? super T>, T...)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections boolean addAll(java.util.Collection<? super T>, T...) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -661,6 +719,11 @@
|
||||
<item name="java.util.Collections boolean disjoint(java.util.Collection<?>, java.util.Collection<?>) 1">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collections boolean replaceAll(java.util.List<T>, T, T)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections boolean replaceAll(java.util.List<T>, T, T) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -679,6 +742,11 @@
|
||||
<item name="java.util.Collections int binarySearch(java.util.List<? extends java.lang.Comparable<? super T>>, T) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collections int frequency(java.util.Collection<?>, 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<?>, java.lang.Object) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -709,6 +777,10 @@
|
||||
<item name="java.util.Collections java.util.Collection<T> synchronizedCollection(java.util.Collection<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -741,16 +813,23 @@
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> emptyList()">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> nCopies(int, T)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> singletonList(T)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> synchronizedList(java.util.List<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> unmodifiableList(java.util.List<? extends T>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.List<T> unmodifiableList(java.util.List<? extends T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -768,13 +847,19 @@
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Map<K,V> emptyMap()">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Map<K,V> singletonMap(K, V)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Map<K,V> synchronizedMap(java.util.Map<K,V>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Map<K,V> unmodifiableMap(java.util.Map<? extends K,? extends V>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Map<K,V> unmodifiableMap(java.util.Map<? extends K,? extends V>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -793,18 +878,29 @@
|
||||
<item name="java.util.Collections java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>) 1">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collections java.util.Set<E> newSetFromMap(java.util.Map<E,java.lang.Boolean>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<E> newSetFromMap(java.util.Map<E,java.lang.Boolean>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<T> emptySet()">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<T> singleton(T)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<T> synchronizedSet(java.util.Set<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -820,6 +916,10 @@
|
||||
<item name="java.util.Collections java.util.SortedMap<K,V> synchronizedSortedMap(java.util.SortedMap<K,V>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedMap<K,V> unmodifiableSortedMap(java.util.SortedMap<K,? extends V>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedMap<K,V> unmodifiableSortedMap(java.util.SortedMap<K,? extends V>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -831,46 +931,96 @@
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedSet<E> emptySortedSet()">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>)">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
<annotation name="org.jetbrains.annotations.ReadOnly" />
|
||||
</item>
|
||||
<item name="java.util.Collections java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Collections void copy(java.util.List<? super T>, java.util.List<? extends T>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void copy(java.util.List<? super T>, java.util.List<? extends T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void copy(java.util.List<? super T>, java.util.List<? extends T>) 1">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void fill(java.util.List<? super T>, T)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void fill(java.util.List<? super T>, T) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void reverse(java.util.List<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void reverse(java.util.List<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void rotate(java.util.List<?>, int)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void rotate(java.util.List<?>, int) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void shuffle(java.util.List<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void shuffle(java.util.List<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void shuffle(java.util.List<?>, java.util.Random)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void shuffle(java.util.List<?>, java.util.Random) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void shuffle(java.util.List<?>, java.util.Random) 1">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void sort(java.util.List<T>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void sort(java.util.List<T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void sort(java.util.List<T>, java.util.Comparator<? super T>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void sort(java.util.List<T>, java.util.Comparator<? super T>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.Collections void sort(java.util.List<T>, java.util.Comparator<? super T>) 1">
|
||||
<annotation name="org.jetbrains.annotations.Nullable" />
|
||||
</item>
|
||||
<item name="java.util.Collections void swap(java.util.List<?>, int, int)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""arg1""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Collections void swap(java.util.List<?>, 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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.List E set(int, E)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</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=""arg1""/>
|
||||
</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=""this""/>
|
||||
</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<? extends E>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.List boolean addAll(int, java.util.Collection<? extends E>) 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<? extends E>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.List boolean addAll(java.util.Collection<? extends E>) 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<?>) 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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.List boolean removeAll(java.util.Collection<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.List boolean removeAll(java.util.Collection<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name="java.util.List boolean retainAll(java.util.Collection<?>)">
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.List boolean retainAll(java.util.Collection<?>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
@@ -1025,18 +1214,56 @@
|
||||
<item name="java.util.List java.util.ListIterator<E> 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=""this""/>
|
||||
</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<E>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.List void sort(java.util.Comparator<? super E>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V compute(K, java.util.function.BiFunction<? super K,? super V,? extends V>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V computeIfAbsent(K, java.util.function.Function<? super K,? extends V>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V computeIfPresent(K, java.util.function.BiFunction<? super K,? super V,? extends V>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V get(java.lang.Object)'>
|
||||
<annotation name='org.intellij.lang.annotations.Flow'>
|
||||
<val name="source" val=""this.values""/>
|
||||
<val name="sourceIsContainer" val="true" />
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V merge(K, V, java.util.function.BiFunction<? super V,? super V,? extends V>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V put(K, V)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
<annotation name='org.intellij.lang.annotations.Flow'>
|
||||
<val name="source" val=""this.values""/>
|
||||
<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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map V remove(java.lang.Object)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
<annotation name='org.intellij.lang.annotations.Flow'>
|
||||
<val name="source" val=""this.values""/>
|
||||
<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=""this""/>
|
||||
</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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Map boolean replace(K, V, V)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.Map java.util.Collection<V> 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<K> 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<java.util.Map.Entry<K,V>> 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<? extends K,? extends V>) 0">
|
||||
<annotation name="org.jetbrains.annotations.NotNull" />
|
||||
</item>
|
||||
<item name='java.util.Map void replaceAll(java.util.function.BiFunction<? super K,? super V,? extends V>)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</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=""this""/>
|
||||
</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=""this""/>
|
||||
</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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="java.util.NavigableSet java.util.Iterator<E> 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<E> iterator()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name="java.util.NavigableSet java.util.NavigableSet<E> 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<E> 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<E> 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<E> 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<E> 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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Queue E remove()'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Queue boolean offer(E)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="mutates" val=""this""/>
|
||||
</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=""this""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Set boolean addAll(java.util.Collection<? extends E>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -1355,15 +1685,27 @@
|
||||
</item>
|
||||
<item name="java.util.SortedSet java.util.Comparator<? super E> 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<E> 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<E> 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<E> 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 {
|
||||
}
|
||||
Reference in New Issue
Block a user