ContractInspection: warn about mutability violations (part of IDEA-182125)

This commit is contained in:
Tagir Valeev
2018-02-09 15:52:51 +07:00
parent 9e0c212432
commit d6f04c1725
4 changed files with 78 additions and 19 deletions
@@ -50,6 +50,25 @@ public class ContractInspection extends AbstractBaseJavaLocalInspectionTool {
holder.registerProblem(value, error);
}
}
checkMutationContract(annotation, method);
}
private void checkMutationContract(PsiAnnotation annotation, PsiMethod method) {
String mutationContract = AnnotationUtil.getStringAttributeValue(annotation, MutationSignature.ATTR_MUTATES);
if (StringUtil.isNotEmpty(mutationContract)) {
boolean pure = Boolean.TRUE.equals(AnnotationUtil.getBooleanAttributeValue(annotation, "pure"));
String error;
if (pure) {
error = "Pure method cannot have mutation contract";
} else {
error = MutationSignature.checkSignature(mutationContract, method);
}
if (error != null) {
PsiAnnotationMemberValue value = annotation.findAttributeValue(MutationSignature.ATTR_MUTATES);
assert value != null;
holder.registerProblem(value, error);
}
}
}
};
}
@@ -1,15 +1,16 @@
// 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.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.psi.*;
import com.siyeh.ig.psiutils.ClassUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
public class MutationSignature {
private static final String ATTR_MUTATES = "mutates";
public 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]);
@@ -91,8 +92,17 @@ public class MutationSignature {
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";
PsiParameter[] parameters = method.getParameterList().getParameters();
if (ms.myArgs.length > parameters.length) {
return "Reference to parameter #" + ms.myArgs.length + " is invalid";
}
for (int i = 0; i < ms.myArgs.length; i++) {
if (ms.myArgs[i]) {
PsiType type = parameters[i].getType();
if (ClassUtils.isImmutable(type)) {
return "Parameter #" + (i + 1) + " has immutable type '" + type.getPresentableText() + "'";
}
}
}
}
catch (IllegalArgumentException ex) {
@@ -0,0 +1,43 @@
package org.jetbrains.annotations;
import java.lang.annotation.*;
import java.util.*;
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface Contract {
String value() default "";
boolean pure() default false;
String mutates() default "";
}
class Test {
@Contract(mutates = <warning descr="Static method cannot mutate 'this'">"this"</warning>)
public static void test1(List<String> list) {}
@Contract(mutates = <warning descr="Reference to parameter #3 is invalid">"arg3"</warning>)
public static void test2(List<String> list) {}
@Contract(mutates = <warning descr="Invalid token: blahblahblah; supported are 'this', 'arg1', 'arg2', etc.">"blahblahblah"</warning>)
public static void test3(List<String> list) {}
@Contract(mutates = "arg")
public static void test4(List<String> list) {}
@Contract(mutates = <warning descr="Pure method cannot have mutation contract">"arg"</warning>, pure = true)
public static void test5(List<String> list) {}
@Contract(mutates = "arg", pure = false)
public static void test6(List<String> list) {}
@Contract(mutates = "", pure = true)
public static void test7(List<String> list) {}
@Contract(mutates = <warning descr="Parameter #1 has immutable type 'String'">"arg1"</warning>)
public static void test8(String s, int i, List<String> list) {}
@Contract(mutates = <warning descr="Parameter #2 has immutable type 'int'">"arg2"</warning>)
public static void test9(String s, int i, List<String> list) {}
@Contract(mutates = "arg3")
public static void test10(String s, int i, List<String> list) {}
}
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInspection;
import com.intellij.JavaTestUtil;
@@ -64,4 +50,5 @@ public class ContractCheckTest extends LightCodeInsightFixtureTestCase {
public void testPassingVarargsToDelegate() { doTest(); }
public void testUnknownIfCondition() { doTest(); }
public void testCallingNotNullMethod() { doTest(); }
public void testMutationSignatureProblems() { doTest(); }
}