IDEA-153564 @Contract annotation for constructors; report non-trivial always failing calls

This commit is contained in:
peter
2016-09-28 16:46:40 +02:00
parent 821b19e875
commit 135a5d5c48
9 changed files with 105 additions and 8 deletions

View File

@@ -95,6 +95,9 @@ public class ContractInspection extends BaseJavaBatchLocalInspectionTool {
if (returnType != null && !InferenceFromSourceUtil.isReturnTypeCompatible(returnType, contract.returnValue)) {
return "Method returns " + returnType.getPresentableText() + " but the contract specifies " + contract.returnValue;
}
if (method.isConstructor() && contract.returnValue != MethodContract.ValueConstraint.THROW_EXCEPTION) {
return "Invalid contract return value for a constructor: " + contract.returnValue;
}
}
return null;
}

View File

@@ -1586,7 +1586,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiMethod constructor = pushConstructorArguments(expression);
addConditionalRuntimeThrow();
addInstruction(new MethodCallInstruction(expression, null, Collections.emptyList()));
addInstruction(new MethodCallInstruction(expression, null, constructor == null ? Collections.emptyList() : getMethodContracts(constructor)));
if (!myCatchStack.isEmpty()) {
addMethodThrows(constructor, expression);
@@ -1597,6 +1597,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(expression);
}
@Nullable
private PsiMethod pushConstructorArguments(PsiConstructorCall call) {
PsiExpressionList args = call.getArgumentList();
PsiMethod ctr = call.resolveConstructor();

View File

@@ -48,12 +48,10 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.extractMethod.ExtractMethodUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.SmartList;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -319,6 +317,8 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
reportAlwaysFailingCalls(holder, visitor, reportedAnchors);
reportConstantPushes(runner, holder, visitor, reportedAnchors);
reportNullableArguments(visitor, holder, reportedAnchors);
@@ -337,6 +337,35 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
private static void reportAlwaysFailingCalls(ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
HashSet<PsiElement> reportedAnchors) {
for (PsiCall call : visitor.getAlwaysFailingCalls()) {
PsiMethod method = call.resolveMethod();
if (method != null && reportedAnchors.add(call)) {
holder.registerProblem(getElementToHighlight(call), "The call to #ref always fails, according to its method contracts");
}
}
}
@NotNull private static PsiElement getElementToHighlight(@NotNull PsiCall call) {
PsiJavaCodeReferenceElement ref;
if (call instanceof PsiNewExpression) {
ref = ((PsiNewExpression)call).getClassReference();
}
else if (call instanceof PsiMethodCallExpression) {
ref = ((PsiMethodCallExpression)call).getMethodExpression();
}
else {
return call;
}
if (ref != null) {
PsiElement name = ref.getReferenceNameElement();
return name != null ? name : ref;
}
return call;
}
private void reportConstantPushes(StandardDataFlowRunner runner,
ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
@@ -465,8 +494,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
List<LocalQuickFix> fixes = createNPEFixes(methodExpression.getQualifierExpression(), callExpression, onTheFly);
ContainerUtil.addIfNotNull(fixes, ReplaceWithObjectsEqualsFix.createFix(callExpression, methodExpression));
PsiElement toHighlight = methodExpression.getReferenceNameElement();
if (toHighlight == null) toHighlight = methodExpression;
PsiElement toHighlight = getElementToHighlight(callExpression);
holder.registerProblem(toHighlight,
InspectionsBundle.message("dataflow.message.npe.method.invocation"),
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
@@ -855,6 +883,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
private final MultiMap<NullabilityProblem, PsiElement> myProblems = new MultiMap<>();
private final Map<Pair<NullabilityProblem, PsiElement>, StateInfo> myStateInfos = ContainerUtil.newHashMap();
private final Set<Instruction> myCCEInstructions = ContainerUtil.newHashSet();
private final Map<MethodCallInstruction, Boolean> myFailingCalls = new HashMap<>();
@Override
protected void onInstructionProducesCCE(TypeCastInstruction instruction) {
@@ -871,6 +900,36 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
});
}
Collection<PsiCall> getAlwaysFailingCalls() {
return StreamEx.of(myFailingCalls.keySet()).filter(this::isAlwaysFailing).map(MethodCallInstruction::getCallExpression).toList();
}
@Override
public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState) {
DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
if (hasNonTrivialFailingContracts(instruction)) {
boolean allFail = Arrays.stream(states).allMatch(s -> s.getMemoryState().peek() == runner.getFactory().getConstFactory().getContractFail());
myFailingCalls.put(instruction, allFail && isAlwaysFailing(instruction));
}
return states;
}
private static boolean hasNonTrivialFailingContracts(MethodCallInstruction instruction) {
List<MethodContract> contracts = instruction.getContracts();
return !contracts.isEmpty() && contracts.stream().allMatch(DataFlowInstructionVisitor::isNonTrivialFailingContract);
}
private static boolean isNonTrivialFailingContract(MethodContract contract) {
return contract.returnValue == MethodContract.ValueConstraint.THROW_EXCEPTION &&
Arrays.stream(contract.arguments).anyMatch(v -> v != MethodContract.ValueConstraint.ANY_VALUE);
}
private boolean isAlwaysFailing(MethodCallInstruction instruction) {
return !Boolean.FALSE.equals(myFailingCalls.get(instruction));
}
@Override
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) {
boolean ok = super.checkNotNullable(state, value, problem, anchor);

View File

@@ -0,0 +1,15 @@
import org.jetbrains.annotations.Contract;
class Zoo {
@Contract(<warning descr="Invalid contract return value for a constructor: null">"null->null"</warning> )
Zoo(Object o) {}
@Contract("_->fail" )
<warning descr="Contract clause '_ -> fail' is violated: no exception is thrown">Zoo</warning>(int o) {}
@Contract("true->fail" )
Zoo(boolean o) {
if (o) throw new RuntimeException();
}
}

View File

@@ -0,0 +1,17 @@
import org.jetbrains.annotations.*;
class CandidateInfo<T> {
@Contract("null, true, _ -> fail")
private CandidateInfo(@Nullable Object candidate, boolean applicable, boolean fullySubstituted) {
assert !applicable || candidate != null;
}
static void test() {
new <warning descr="The call to CandidateInfo always fails, according to its method contracts">CandidateInfo</warning>(null, true, false);
new <warning descr="The call to CandidateInfo always fails, according to its method contracts">CandidateInfo</warning>(null, true, true);
new CandidateInfo(null, false, true);
new CandidateInfo(new Object(), true, true);
new CandidateInfo(new Object(), false, true);
}
}

View File

@@ -8,7 +8,7 @@ class Test {
String a = notNull(getNullable());
@NotNull
String b = notNull(null);
String b = <warning descr="The call to notNull always fails, according to its method contracts">notNull</warning>(null);
}
@Nullable

View File

@@ -44,6 +44,7 @@ public class ContractCheckTest extends LightCodeInsightFixtureTestCase {
public void testVarargInferred() { doTest(); }
public void testDoubleParameter() { doTest(); }
public void testReturnPrimitiveArray() { doTest(); }
public void testCheckConstructorContracts() { doTest(); }
public void testPassingVarargsToDelegate() { doTest(); }
}

View File

@@ -237,6 +237,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testContractPreservesUnknownMethodNullability() { doTest(); }
public void testContractSeveralClauses() { doTest(); }
public void testContractVarargs() { doTest(); }
public void testContractConstructor() { doTest(); }
public void testBoxingImpliesNotNull() { doTest(); }
public void testLargeIntegersAreNotEqualWhenBoxed() { doTest(); }