diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index d927d8cb78a4..bf0fc63be2a2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -1485,7 +1485,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { return Collections.emptyList(); } - private static List parseContract(String text) throws ParseException { + public static List parseContract(String text) throws ParseException { List result = ContainerUtil.newArrayList(); for (String clause : StringUtil.replace(text, " ", "").split(";")) { String arrow = "->"; @@ -1516,7 +1516,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { throw new ParseException("Constraint should be one of: null, !null, true, false, exit, fail, _. Found: " + name); } - private static class ParseException extends Exception { + public static class ParseException extends Exception { private ParseException(String message) { super(message); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index fc58969f5122..7edeb3ac30b7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -48,6 +48,7 @@ import com.intellij.util.ArrayUtilRt; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; import org.jdom.Element; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,6 +99,35 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { public void visitClassInitializer(PsiClassInitializer initializer) { analyzeCodeBlock(initializer.getBody(), holder); } + + @Override + public void visitAnnotation(PsiAnnotation annotation) { + if (!Contract.class.getName().equals(annotation.getQualifiedName())) return; + + PsiMethod method = PsiTreeUtil.getParentOfType(annotation, PsiMethod.class); + if (method == null) return; + + PsiAnnotationMemberValue value = annotation.findAttributeValue(null); + Object text = JavaPsiFacade.getInstance(annotation.getProject()).getConstantEvaluationHelper().computeConstantExpression(value); + if (!(text instanceof String)) return; + + List contracts; + try { + contracts = ControlFlowAnalyzer.parseContract((String)text); + } + catch (ControlFlowAnalyzer.ParseException e) { + holder.registerProblem(value, e.getMessage()); + return; + } + int paramCount = method.getParameterList().getParametersCount(); + for (int i = 0; i < contracts.size(); i++) { + MethodContract contract = contracts.get(i); + if (contract.arguments.length != paramCount) { + holder.registerProblem(value, "Method takes " + paramCount + " parameters, while contract clause " + i + " expects " + contract.arguments.length); + return; + } + } + } }; } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiConstantEvaluationHelper.java b/java/java-psi-api/src/com/intellij/psi/PsiConstantEvaluationHelper.java index 3c377f7da610..290d1bf11b70 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiConstantEvaluationHelper.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiConstantEvaluationHelper.java @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.util.concurrent.ConcurrentMap; @@ -33,6 +34,7 @@ public abstract class PsiConstantEvaluationHelper { * @return the result of the evaluation, or null if the expression is not a constant expression. */ @Nullable + @Contract("null -> null") public Object computeConstantExpression(PsiElement expression) { return computeConstantExpression(expression, false); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ContractAnnotation.java b/java/java-tests/testData/inspection/dataFlow/fixture/ContractAnnotation.java index f86d79733c73..64bfd011fed6 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ContractAnnotation.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ContractAnnotation.java @@ -32,4 +32,10 @@ public class AssertIsNotNull { } Object call() {return new Object();} + + @Contract("a") + void malformedContract() {} + + @Contract("null -> _") + void wrongParameterCount(Object a, boolean b) {} } diff --git a/resources-en/src/inspectionDescriptions/ConstantConditions.html b/resources-en/src/inspectionDescriptions/ConstantConditions.html index 351ad1491158..1e2dd7644de7 100644 --- a/resources-en/src/inspectionDescriptions/ConstantConditions.html +++ b/resources-en/src/inspectionDescriptions/ConstantConditions.html @@ -1,11 +1,16 @@ -This inspection reports those conditions in the specified inspection scope that are -always true or false, -as well as points out where a RuntimeException may be thrown, -based on data flow analysis of the code.
-This inspection also reports Nullable/NotNull contract violations. -Annotations to support the contract can be configured (by default @Nullable/@NotNull annotations from annotations.jar will be used) -
+This inspection analyzes method control and data flow to report possible conditions that are always true or false, +expressions whose value is statically proven to be constant, and situations that can lead to nullability contract violations.

+ +Variables, method parameters and return values marked as @Nullable or @NotNull are treated as nullable (or not-null, respectively) +and used during the analysis to check nullability contracts, e.g. report possible NullPointerException errors.

+ +More complex contracts can be defined using @Contract annotation, for example:

+@Contract("_, null -> null") — method returns null if its second argument is null
+@Contract("_, null -> null; _, !null -> !null") — method returns null if its second argument is null and not-null otherwise
+@Contract("true -> fail") — a typical assertFalse method which throws an exception if true is passed to it
+

+The inspection can be configured to use custom @Nullable/@NotNull annotations (by default the ones from annotations.jar will be used)