mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 06:05:01 +07:00
IDEA-112222 Validate @Contract annotation is related to the code
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2000-2014 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 com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.instructions.CheckReturnValueInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.ReturnInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
|
||||
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.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class ContractChecker extends DataFlowRunner {
|
||||
private final PsiMethod myMethod;
|
||||
private final MethodContract myContract;
|
||||
private final boolean myOnTheFly;
|
||||
private final Set<PsiElement> myViolations = ContainerUtil.newHashSet();
|
||||
private final Set<PsiElement> myNonViolations = ContainerUtil.newHashSet();
|
||||
private final Set<PsiElement> myFailures = ContainerUtil.newHashSet();
|
||||
|
||||
ContractChecker(PsiMethod method, PsiCodeBlock body, MethodContract contract, final boolean onTheFly) {
|
||||
super(body);
|
||||
myMethod = method;
|
||||
myContract = contract;
|
||||
myOnTheFly = onTheFly;
|
||||
}
|
||||
|
||||
static Map<PsiElement, String> checkContractClause(PsiMethod method,
|
||||
MethodContract contract,
|
||||
boolean ignoreAssertions, final boolean onTheFly) {
|
||||
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body == null) return Collections.emptyMap();
|
||||
|
||||
ContractChecker checker = new ContractChecker(method, body, contract, onTheFly);
|
||||
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
final DfaMemoryState initialState = checker.createMemoryState();
|
||||
final DfaValueFactory factory = checker.getFactory();
|
||||
for (int i = 0; i < contract.arguments.length; i++) {
|
||||
MethodContract.ValueConstraint constraint = contract.arguments[i];
|
||||
DfaConstValue comparisonValue = constraint.getComparisonValue(factory);
|
||||
if (comparisonValue != null) {
|
||||
boolean negated = constraint.shouldUseNonEqComparison();
|
||||
DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i], false);
|
||||
initialState.applyCondition(factory.getRelationFactory().createRelation(dfaParam, comparisonValue, JavaTokenType.EQEQ, negated));
|
||||
}
|
||||
}
|
||||
|
||||
checker.analyzeMethod(body, new StandardInstructionVisitor(), ignoreAssertions, Arrays.asList(initialState));
|
||||
return checker.getErrors();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldCheckTimeLimit() {
|
||||
if (!myOnTheFly) return false;
|
||||
return super.shouldCheckTimeLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DfaInstructionState[] acceptInstruction(InstructionVisitor visitor, DfaInstructionState instructionState) {
|
||||
DfaMemoryState memState = instructionState.getMemoryState();
|
||||
if (memState.isEphemeral()) {
|
||||
return DfaInstructionState.EMPTY_ARRAY;
|
||||
}
|
||||
Instruction instruction = instructionState.getInstruction();
|
||||
if (instruction instanceof CheckReturnValueInstruction) {
|
||||
PsiElement anchor = ((CheckReturnValueInstruction)instruction).getReturn();
|
||||
DfaValue retValue = memState.pop();
|
||||
if (breaksContract(retValue, myContract.returnValue, memState)) {
|
||||
myViolations.add(anchor);
|
||||
} else {
|
||||
myNonViolations.add(anchor);
|
||||
}
|
||||
return InstructionVisitor.nextInstruction(instruction, this, memState);
|
||||
|
||||
}
|
||||
|
||||
if (instruction instanceof ReturnInstruction) {
|
||||
if (((ReturnInstruction)instruction).isViaException()) {
|
||||
ContainerUtil.addIfNotNull(myFailures, ((ReturnInstruction)instruction).getAnchor());
|
||||
}
|
||||
}
|
||||
|
||||
return super.acceptInstruction(visitor, instructionState);
|
||||
}
|
||||
|
||||
|
||||
private Map<PsiElement, String> getErrors() {
|
||||
HashMap<PsiElement, String> errors = ContainerUtil.newHashMap();
|
||||
for (PsiElement element : myViolations) {
|
||||
if (!myNonViolations.contains(element)) {
|
||||
errors.put(element, "Contract clause '" + myContract + "' is violated");
|
||||
}
|
||||
}
|
||||
|
||||
if (myContract.returnValue != MethodContract.ValueConstraint.THROW_EXCEPTION) {
|
||||
for (PsiElement element : myFailures) {
|
||||
errors.put(element, "Contract clause '" + myContract + "' is violated: exception might be thrown instead of returning " + myContract.returnValue);
|
||||
}
|
||||
} else if (myFailures.isEmpty() && errors.isEmpty()) {
|
||||
PsiIdentifier nameIdentifier = myMethod.getNameIdentifier();
|
||||
errors.put(nameIdentifier != null ? nameIdentifier : myMethod,
|
||||
"Contract clause '" + myContract + "' is violated: no exception is thrown");
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private boolean breaksContract(DfaValue retValue, MethodContract.ValueConstraint constraint, DfaMemoryState state) {
|
||||
switch (constraint) {
|
||||
case NULL_VALUE: return state.isNotNull(retValue);
|
||||
case NOT_NULL_VALUE: return state.isNull(retValue);
|
||||
case TRUE_VALUE: return isEquivalentTo(retValue, getFactory().getConstFactory().getFalse(), state);
|
||||
case FALSE_VALUE: return isEquivalentTo(retValue, getFactory().getConstFactory().getTrue(), state);
|
||||
case THROW_EXCEPTION: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEquivalentTo(DfaValue val, DfaConstValue constValue, DfaMemoryState state) {
|
||||
return val == constValue || val instanceof DfaVariableValue && constValue == state.getConstantValue((DfaVariableValue)val);
|
||||
}
|
||||
|
||||
}
|
||||
+80
-121
@@ -21,7 +21,6 @@ import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
@@ -114,7 +113,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new CheckReturnValueInstruction(codeFragment));
|
||||
}
|
||||
|
||||
addInstruction(new ReturnInstruction(false));
|
||||
addInstruction(new ReturnInstruction(false, null));
|
||||
|
||||
return myCurrentFlow;
|
||||
}
|
||||
@@ -239,7 +238,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
initException(myAssertionError);
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, statement);
|
||||
}
|
||||
finishElement(statement);
|
||||
}
|
||||
@@ -569,11 +568,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new CheckReturnValueInstruction(returnValue));
|
||||
}
|
||||
|
||||
returnCheckingFinally();
|
||||
returnCheckingFinally(false, statement);
|
||||
finishElement(statement);
|
||||
}
|
||||
|
||||
private void returnCheckingFinally() {
|
||||
private void returnCheckingFinally(boolean viaException, @NotNull PsiElement anchor) {
|
||||
ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset();
|
||||
if (finallyOffset != null) {
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
@@ -583,7 +582,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
addInstruction(new GotoInstruction(finallyOffset));
|
||||
} else {
|
||||
addInstruction(new ReturnInstruction(false));
|
||||
addInstruction(new ReturnInstruction(viaException, anchor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +697,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
if (exception != null) {
|
||||
exception.accept(this);
|
||||
if (myCatchStack.isEmpty()) {
|
||||
addInstruction(new ReturnInstruction(true));
|
||||
addInstruction(new ReturnInstruction(true, statement));
|
||||
finishElement(statement);
|
||||
return;
|
||||
}
|
||||
@@ -712,14 +711,14 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
addInstruction(new PopInstruction());
|
||||
initException(myNpe);
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, statement);
|
||||
|
||||
gotoInstruction.setOffset(myCurrentFlow.getInstructionCount());
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new SwapInstruction());
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, statement);
|
||||
}
|
||||
|
||||
finishElement(statement);
|
||||
@@ -747,7 +746,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, null);
|
||||
|
||||
ifNoException.setOffset(myCurrentFlow.getInstructionCount());
|
||||
}
|
||||
@@ -765,9 +764,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
// the exception object should be in $exception$ variable
|
||||
private void addThrowCode(boolean catchRethrow) {
|
||||
private void addThrowCode(boolean catchRethrow, @Nullable PsiElement explicitThrower) {
|
||||
if (myCatchStack.isEmpty()) {
|
||||
addInstruction(new ReturnInstruction(true));
|
||||
addInstruction(new ReturnInstruction(true, explicitThrower));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -780,7 +779,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
i--;
|
||||
}
|
||||
if (i < 0) {
|
||||
addInstruction(new ReturnInstruction(true));
|
||||
addInstruction(new ReturnInstruction(true, explicitThrower));
|
||||
return;
|
||||
}
|
||||
cd = myCatchStack.get(i);
|
||||
@@ -922,7 +921,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null));
|
||||
|
||||
// else throw $exception$
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, null);
|
||||
}
|
||||
|
||||
finishElement(statement);
|
||||
@@ -947,7 +946,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
// not assignable => rethrow
|
||||
addThrowCode(true);
|
||||
addThrowCode(true, null);
|
||||
|
||||
// e = $exception$
|
||||
addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(section.getParameter(), false), null));
|
||||
@@ -971,7 +970,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
PsiMethod closer = PsiUtil.getResourceCloserMethod(variable);
|
||||
if (closer != null) {
|
||||
addMethodThrows(closer);
|
||||
addMethodThrows(closer, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1336,7 +1335,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
finishElement(expression);
|
||||
}
|
||||
|
||||
private void addMethodThrows(PsiMethod method) {
|
||||
private void addMethodThrows(PsiMethod method, @Nullable PsiElement explicitCall) {
|
||||
if (method != null) {
|
||||
PsiClassType[] refs = method.getThrowsList().getReferencedTypes();
|
||||
for (PsiClassType ref : refs) {
|
||||
@@ -1345,7 +1344,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(cond);
|
||||
addInstruction(new EmptyStackInstruction());
|
||||
initException(ref);
|
||||
addThrowCode(false);
|
||||
addThrowCode(false, explicitCall);
|
||||
cond.setOffset(myCurrentFlow.getInstructionCount());
|
||||
}
|
||||
}
|
||||
@@ -1397,7 +1396,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
addConditionalRuntimeThrow();
|
||||
List<MethodContract> contracts = getCallContracts(expression);
|
||||
List<MethodContract> contracts = method instanceof PsiMethod ? getMethodContracts((PsiMethod)method) : Collections.<MethodContract>emptyList();
|
||||
addInstruction(new MethodCallInstruction(expression, createChainedVariableValue(expression), contracts));
|
||||
if (!contracts.isEmpty()) {
|
||||
// if a contract resulted in 'fail', handle it
|
||||
@@ -1406,12 +1405,12 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject()));
|
||||
ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null);
|
||||
addInstruction(ifNotFail);
|
||||
returnCheckingFinally();
|
||||
returnCheckingFinally(true, expression);
|
||||
ifNotFail.setOffset(myCurrentFlow.getInstructionCount());
|
||||
}
|
||||
|
||||
if (!myCatchStack.isEmpty()) {
|
||||
addMethodThrows(expression.resolveMethod());
|
||||
addMethodThrows(expression.resolveMethod(), expression);
|
||||
}
|
||||
|
||||
if (isEqualsCall) {
|
||||
@@ -1432,74 +1431,70 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
finishElement(expression);
|
||||
}
|
||||
|
||||
private static List<MethodContract> getCallContracts(PsiMethodCallExpression expression) {
|
||||
final PsiMethod resolved = expression.resolveMethod();
|
||||
if (resolved != null) {
|
||||
final PsiAnnotation contractAnno = findContractAnnotation(resolved);
|
||||
if (contractAnno != null) {
|
||||
return CachedValuesManager.getCachedValue(contractAnno, new CachedValueProvider<List<MethodContract>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<List<MethodContract>> compute() {
|
||||
String text = AnnotationUtil.getStringAttributeValue(contractAnno, null);
|
||||
if (text != null) {
|
||||
try {
|
||||
List<MethodContract> applicable = ContainerUtil.filter(parseContract(text), new Condition<MethodContract>() {
|
||||
@Override
|
||||
public boolean value(MethodContract contract) {
|
||||
return contract.arguments.length == resolved.getParameterList().getParametersCount();
|
||||
}
|
||||
});
|
||||
return Result.create(applicable, contractAnno);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
static List<MethodContract> getMethodContracts(@NotNull final PsiMethod method) {
|
||||
final PsiAnnotation contractAnno = findContractAnnotation(method);
|
||||
final int paramCount = method.getParameterList().getParametersCount();
|
||||
if (contractAnno != null) {
|
||||
return CachedValuesManager.getCachedValue(contractAnno, new CachedValueProvider<List<MethodContract>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<List<MethodContract>> compute() {
|
||||
String text = AnnotationUtil.getStringAttributeValue(contractAnno, null);
|
||||
if (text != null) {
|
||||
try {
|
||||
List<MethodContract> applicable = ContainerUtil.filter(MethodContract.parseContract(text), new Condition<MethodContract>() {
|
||||
@Override
|
||||
public boolean value(MethodContract contract) {
|
||||
return contract.arguments.length == paramCount;
|
||||
}
|
||||
});
|
||||
return Result.create(applicable, contractAnno);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
return Result.create(Collections.<MethodContract>emptyList(), contractAnno);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNls String methodName = resolved.getName();
|
||||
|
||||
PsiExpression[] params = expression.getArgumentList().getExpressions();
|
||||
PsiClass owner = resolved.getContainingClass();
|
||||
if (owner != null) {
|
||||
final String className = owner.getQualifiedName();
|
||||
if ("java.lang.System".equals(className)) {
|
||||
if ("exit".equals(methodName)) {
|
||||
return Collections.singletonList(new MethodContract(getAnyArgConstraints(params), ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
return Result.create(Collections.<MethodContract>emptyList(), contractAnno);
|
||||
}
|
||||
else if ("junit.framework.Assert".equals(className) || "org.junit.Assert".equals(className) ||
|
||||
"junit.framework.TestCase".equals(className) || "org.testng.Assert".equals(className) || "org.testng.AssertJUnit".equals(className)) {
|
||||
boolean testng = className.startsWith("org.testng.");
|
||||
if ("fail".equals(methodName)) {
|
||||
return Collections.singletonList(new MethodContract(getAnyArgConstraints(params), ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int checkedParam = testng ? 0 : params.length - 1;
|
||||
ValueConstraint[] constraints = getAnyArgConstraints(params);
|
||||
if ("assertTrue".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.FALSE_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertFalse".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.TRUE_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertNull".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.NOT_NULL_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertNotNull".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.NULL_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
@NonNls String methodName = method.getName();
|
||||
|
||||
PsiClass owner = method.getContainingClass();
|
||||
if (owner != null) {
|
||||
final String className = owner.getQualifiedName();
|
||||
if ("java.lang.System".equals(className)) {
|
||||
if ("exit".equals(methodName)) {
|
||||
return Collections.singletonList(new MethodContract(getAnyArgConstraints(paramCount), ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
}
|
||||
else if ("junit.framework.Assert".equals(className) || "org.junit.Assert".equals(className) ||
|
||||
"junit.framework.TestCase".equals(className) || "org.testng.Assert".equals(className) || "org.testng.AssertJUnit".equals(className)) {
|
||||
boolean testng = className.startsWith("org.testng.");
|
||||
if ("fail".equals(methodName)) {
|
||||
return Collections.singletonList(new MethodContract(getAnyArgConstraints(paramCount), ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
|
||||
int checkedParam = testng ? 0 : paramCount - 1;
|
||||
ValueConstraint[] constraints = getAnyArgConstraints(paramCount);
|
||||
if ("assertTrue".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.FALSE_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertFalse".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.TRUE_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertNull".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.NOT_NULL_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
if ("assertNotNull".equals(methodName)) {
|
||||
constraints[checkedParam] = ValueConstraint.NULL_VALUE;
|
||||
return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
@@ -1510,44 +1505,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
return AnnotationUtil.findAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
}
|
||||
|
||||
public static List<MethodContract> parseContract(String text) throws ParseException {
|
||||
List<MethodContract> result = ContainerUtil.newArrayList();
|
||||
for (String clause : StringUtil.replace(text, " ", "").split(";")) {
|
||||
String arrow = "->";
|
||||
int arrowIndex = clause.indexOf(arrow);
|
||||
if (arrowIndex < 0) {
|
||||
throw new ParseException("A contract clause must be in form arg1, ..., argN -> return-value");
|
||||
}
|
||||
|
||||
String[] argStrings = clause.substring(0, arrowIndex).split(",");
|
||||
ValueConstraint[] args = new ValueConstraint[argStrings.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = parseConstraint(argStrings[i]);
|
||||
}
|
||||
result.add(new MethodContract(args, parseConstraint(clause.substring(arrowIndex + arrow.length()))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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 ("fail".equals(name)) return ValueConstraint.THROW_EXCEPTION;
|
||||
if ("_".equals(name)) return ValueConstraint.ANY_VALUE;
|
||||
throw new ParseException("Constraint should be one of: null, !null, true, false, fail, _. Found: " + name);
|
||||
}
|
||||
|
||||
public static class ParseException extends Exception {
|
||||
private ParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueConstraint[] getAnyArgConstraints(PsiExpression[] params) {
|
||||
ValueConstraint[] args = new ValueConstraint[params.length];
|
||||
private static ValueConstraint[] getAnyArgConstraints(int paramCount) {
|
||||
ValueConstraint[] args = new ValueConstraint[paramCount];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = ValueConstraint.ANY_VALUE;
|
||||
}
|
||||
@@ -1610,7 +1569,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new MethodCallInstruction(expression, null, Collections.<MethodContract>emptyList()));
|
||||
|
||||
if (!myCatchStack.isEmpty()) {
|
||||
addMethodThrows(ctr);
|
||||
addMethodThrows(ctr, expression);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-4
@@ -32,8 +32,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFi
|
||||
import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.*;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
@@ -96,6 +95,13 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
|
||||
@Override
|
||||
public void visitMethod(PsiMethod method) {
|
||||
analyzeCodeBlock(method.getBody(), holder, isOnTheFly);
|
||||
|
||||
for (MethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) {
|
||||
Map<PsiElement, String> errors = ContractChecker.checkContractClause(method, contract, IGNORE_ASSERT_STATEMENTS, isOnTheFly);
|
||||
for (Map.Entry<PsiElement, String> entry : errors.entrySet()) {
|
||||
holder.registerProblem(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,9 +151,9 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
|
||||
public static String checkContract(PsiMethod method, String text) {
|
||||
List<MethodContract> contracts;
|
||||
try {
|
||||
contracts = ControlFlowAnalyzer.parseContract(text);
|
||||
contracts = MethodContract.parseContract(text);
|
||||
}
|
||||
catch (ControlFlowAnalyzer.ParseException e) {
|
||||
catch (MethodContract.ParseException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
int paramCount = method.getParameterList().getParametersCount();
|
||||
|
||||
+3
@@ -474,6 +474,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
if (dfaVar instanceof DfaConstValue && ((DfaConstValue)dfaVar).getValue() != null) {
|
||||
return true;
|
||||
}
|
||||
if (dfaVar instanceof DfaTypeValue && ((DfaTypeValue)dfaVar).isNotNull()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
|
||||
int c1Index = getEqClassIndex(dfaVar);
|
||||
|
||||
+76
-2
@@ -15,6 +15,15 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@@ -27,7 +36,72 @@ public class MethodContract {
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
|
||||
public enum ValueConstraint {
|
||||
ANY_VALUE, NULL_VALUE, NOT_NULL_VALUE, TRUE_VALUE, FALSE_VALUE, THROW_EXCEPTION
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.join(arguments, new Function<ValueConstraint, String>() {
|
||||
@Override
|
||||
public String fun(ValueConstraint constraint) {
|
||||
return constraint.toString();
|
||||
}
|
||||
}, ", ") + " -> " + returnValue;
|
||||
}
|
||||
|
||||
public enum ValueConstraint {
|
||||
ANY_VALUE("_"), NULL_VALUE("null"), NOT_NULL_VALUE("!null"), TRUE_VALUE("true"), FALSE_VALUE("false"), THROW_EXCEPTION("fail");
|
||||
private final String myPresentableName;
|
||||
|
||||
ValueConstraint(String presentableName) {
|
||||
myPresentableName = presentableName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
DfaConstValue getComparisonValue(DfaValueFactory factory) {
|
||||
if (this == NULL_VALUE || this == NOT_NULL_VALUE) return factory.getConstFactory().getNull();
|
||||
if (this == TRUE_VALUE || this == FALSE_VALUE) return factory.getConstFactory().getTrue();
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean shouldUseNonEqComparison() {
|
||||
return this == NOT_NULL_VALUE || this == FALSE_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myPresentableName;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<MethodContract> parseContract(String text) throws ParseException {
|
||||
List<MethodContract> result = ContainerUtil.newArrayList();
|
||||
for (String clause : StringUtil.replace(text, " ", "").split(";")) {
|
||||
String arrow = "->";
|
||||
int arrowIndex = clause.indexOf(arrow);
|
||||
if (arrowIndex < 0) {
|
||||
throw new ParseException("A contract clause must be in form arg1, ..., argN -> return-value");
|
||||
}
|
||||
|
||||
String[] argStrings = clause.substring(0, arrowIndex).split(",");
|
||||
ValueConstraint[] args = new ValueConstraint[argStrings.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = parseConstraint(argStrings[i]);
|
||||
}
|
||||
result.add(new MethodContract(args, parseConstraint(clause.substring(arrowIndex + arrow.length()))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ValueConstraint parseConstraint(String name) throws ParseException {
|
||||
if (StringUtil.isEmpty(name)) throw new ParseException("Constraint should not be empty");
|
||||
for (ValueConstraint constraint : ValueConstraint.values()) {
|
||||
if (constraint.toString().equals(name)) return constraint;
|
||||
}
|
||||
throw new ParseException("Constraint should be one of: null, !null, true, false, fail, _. Found: " + name);
|
||||
}
|
||||
|
||||
public static class ParseException extends Exception {
|
||||
private ParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-16
@@ -30,12 +30,12 @@ import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*;
|
||||
import static com.intellij.psi.JavaTokenType.*;
|
||||
import static com.intellij.psi.JavaTokenType.EQEQ;
|
||||
import static com.intellij.psi.JavaTokenType.NE;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
@@ -247,20 +247,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
for (int i = 0; i < argValues.length; i++) {
|
||||
DfaValue argValue = argValues[i];
|
||||
MethodContract.ValueConstraint constraint = contract.arguments[i];
|
||||
DfaConstValue expectedValue = constraint == NULL_VALUE || constraint == NOT_NULL_VALUE ? constFactory.getNull() :
|
||||
constraint == FALSE_VALUE ? constFactory.getFalse() :
|
||||
constraint == TRUE_VALUE ? constFactory.getTrue() :
|
||||
null;
|
||||
DfaConstValue expectedValue = constraint.getComparisonValue(factory);
|
||||
if (expectedValue == null) continue;
|
||||
|
||||
boolean invertCondition = constraint == NOT_NULL_VALUE;
|
||||
boolean invertCondition = constraint.shouldUseNonEqComparison();
|
||||
DfaValue condition = factory.getRelationFactory().createRelation(argValue, expectedValue, EQEQ, invertCondition);
|
||||
if (condition == null) {
|
||||
if (!(argValue instanceof DfaConstValue)) {
|
||||
falseStates.addAll(states);
|
||||
continue;
|
||||
}
|
||||
condition = constFactory.createFromValue(argValue == expectedValue, PsiType.BOOLEAN, null);
|
||||
condition = constFactory.createFromValue((argValue == expectedValue) != invertCondition, PsiType.BOOLEAN, null);
|
||||
}
|
||||
|
||||
List<DfaMemoryState> nextStates = ContainerUtil.newArrayList();
|
||||
@@ -469,16 +466,21 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
return handleConstantComparison(instruction, runner, memState, dfaLeft, dfaRight, DfaRelationValue.getSymmetricOperation(opSign));
|
||||
}
|
||||
|
||||
if (EQEQ != opSign && NE != opSign ||
|
||||
!(dfaLeft instanceof DfaConstValue) || !(dfaRight instanceof DfaConstValue)) {
|
||||
if (EQEQ != opSign && NE != opSign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean negated = (NE == opSign) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
|
||||
if (dfaLeft == dfaRight ^ negated) {
|
||||
return alwaysTrue(instruction, runner, memState);
|
||||
if (dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue ||
|
||||
dfaLeft == runner.getFactory().getConstFactory().getContractFail() ||
|
||||
dfaRight == runner.getFactory().getConstFactory().getContractFail()) {
|
||||
boolean negated = (NE == opSign) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
|
||||
if (dfaLeft == dfaRight ^ negated) {
|
||||
return alwaysTrue(instruction, runner, memState);
|
||||
}
|
||||
return alwaysFalse(instruction, runner, memState);
|
||||
}
|
||||
return alwaysFalse(instruction, runner, memState);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DfaInstructionState[] checkTypeRanges(BinopInstruction instruction,
|
||||
|
||||
+10
-1
@@ -25,12 +25,21 @@
|
||||
package com.intellij.codeInspection.dataFlow.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ReturnInstruction extends Instruction {
|
||||
private final boolean isViaException;
|
||||
private final PsiElement myAnchor;
|
||||
|
||||
public ReturnInstruction(boolean isViaException) {
|
||||
public ReturnInstruction(boolean isViaException, @Nullable PsiElement anchor) {
|
||||
this.isViaException = isViaException;
|
||||
myAnchor = anchor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiElement getAnchor() {
|
||||
return myAnchor;
|
||||
}
|
||||
|
||||
public boolean isViaException() {
|
||||
|
||||
Reference in New Issue
Block a user