add @Contract annotation (IDEA-93372)

This commit is contained in:
peter
2013-07-23 12:04:54 +02:00
parent 36647b9d0e
commit fd800a7537
4 changed files with 134 additions and 0 deletions
@@ -15,6 +15,7 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ConditionCheckManager;
import com.intellij.codeInsight.ConditionChecker;
import com.intellij.codeInsight.ExceptionUtil;
@@ -22,6 +23,8 @@ import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
@@ -1373,6 +1376,27 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
private static MethodContract getCallContract(PsiMethodCallExpression expression) {
PsiMethod resolved = expression.resolveMethod();
if (resolved != null) {
final PsiAnnotation contractAnno = AnnotationUtil.findAnnotation(resolved, "org.jetbrains.annotations.Contract");
if (contractAnno != null) {
final Project project = expression.getProject();
return CachedValuesManager.getManager(project).getCachedValue(contractAnno, new CachedValueProvider<MethodContract>() {
@Nullable
@Override
public Result<MethodContract> compute() {
PsiAnnotationMemberValue value = contractAnno.findAttributeValue(null);
Object text = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper().computeConstantExpression(value);
if (text instanceof String) {
try {
return Result.create(parseContract((String)text), contractAnno);
}
catch (Exception ignored) {
}
}
return Result.create(null, contractAnno);
}
});
}
@NonNls String methodName = resolved.getName();
PsiExpression[] params = expression.getArgumentList().getExpressions();
@@ -1438,6 +1462,37 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
return null;
}
private static MethodContract parseContract(String text) throws ParseException {
text = StringUtil.replace(text, " ", "");
String arrow = "->";
int arrowIndex = text.indexOf(arrow);
if (arrowIndex < 0) throw new ParseException("A contract must be in form arg1, ..., argN -> return-value");
String[] argStrings = text.substring(0, arrowIndex).split(",");
ValueConstraint[] args = new ValueConstraint[argStrings.length];
for (int i = 0; i < args.length; i++) {
args[i] = parseConstraint(argStrings[i]);
}
return new MethodContract(args, parseConstraint(text.substring(arrowIndex + arrow.length())));
}
private static ValueConstraint parseConstraint(String name) throws ParseException {
if (StringUtil.isEmpty(name)) throw new ParseException("Constraint should not be empty");
if ("null".equals(name)) return ValueConstraint.NULL_VALUE;
if ("!null".equals(name)) return ValueConstraint.NOT_NULL_VALUE;
if ("true".equals(name)) return ValueConstraint.TRUE_VALUE;
if ("false".equals(name)) return ValueConstraint.FALSE_VALUE;
if ("exit".equals(name)) return ValueConstraint.SYSTEM_EXIT;
if ("fail".equals(name)) return ValueConstraint.THROW_EXCEPTION;
if ("any".equals(name)) return ValueConstraint.ANY_VALUE;
throw new ParseException("Constraint should be one of: null, !null, true, false, exit, fail, any. Found: " + name);
}
private static class ParseException extends Exception {
private ParseException(String message) {
super(message);
}
}
private static ValueConstraint[] getAnyArgConstraints(PsiExpression[] params) {
ValueConstraint[] args = new ValueConstraint[params.length];
for (int i = 0; i < args.length; i++) {
@@ -0,0 +1,21 @@
import org.jetbrains.annotations.Contract;
import java.lang.*;
import java.lang.IllegalArgumentException;
public class AssertIsNotNull {
void bar() {
final Object o = call();
assertIsNotNull(o);
if(<warning descr="Condition 'o == null' is always 'false'">o == null</warning>) {}
}
@Contract("null -> fail")
static void assertIsNotNull(Object o) {
if (o == null) {
throw new IllegalArgumentException();
}
}
Object call() {return new Object();}
}
@@ -31,6 +31,11 @@ import java.io.IOException;
* @author peter
*/
public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.addClass("package org.jetbrains.annotations; public @interface Contract { String value(); }");
}
@Override
protected String getTestDataPath() {
@@ -266,4 +271,6 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
myFixture.enableInspections(inspection);
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
}
public void testContractAnnotation() { doTest(); }
}
@@ -0,0 +1,51 @@
/*
* Copyright 2000-2013 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.*;
/**
* Specifies some aspects of the method behavior depending on the arguments. Can be used by tools for advanced data flow analysis.<p>
*
* Method contract has the following syntax:<br/>
* contract ::= args '->' effect<br/>
* args ::= ((arg ,)* arg )?<br/>
* arg ::= value-constraint<br/>
* value-constraint ::= 'any' | 'null' | '!null' | 'false' | 'true'<br/>
* effect ::= value-constraint | 'fail' | 'exit'<p/>
*
* The constraints denote the following:<br/>
* <ul>
* <li> any - any value
* <li> null - null value
* <li> !null - a value statically proved to be not-null
* <li> true - true boolean value
* <li> false - false boolean value
* <li> fail - the method throws exception, if the arguments satisfy argument constraints
* <li> exit - the method terminates the current process, if the arguments satisfy argument constraints
* </ul>
* Examples:<p/>
* <code>@Contract("any, null -> null")</code> - method returns null if its second argument is null<br/>
* <code>@Contract("true -> fail")</code> - a typical assertFalse method which throws an exception if <code>true</code> is passed to it<br/>
*
* @author peter
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Contract {
String value();
}