mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
add @Contract annotation (IDEA-93372)
This commit is contained in:
+55
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user