ObviousNullCheckInspection: support assertNull-like methods

Fixes IDEA-174782 "Useless null checks": highlight always-(not-)null arguments to assertNull/assertNotNull
This commit is contained in:
Tagir Valeev
2017-06-27 08:25:19 +03:00
parent f7ce43e1f4
commit 9301835979
3 changed files with 53 additions and 27 deletions
@@ -48,16 +48,20 @@ public class ObviousNullCheckInspection extends BaseJavaBatchLocalInspectionTool
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
Integer nullIndex = getNullParameterIndex(call);
if (nullIndex == null) return;
NullCheckParameter nullCheckParameter = NullCheckParameter.fromCall(call);
if (nullCheckParameter == null) return;
if (!(call.getParent() instanceof PsiExpressionStatement) && !REQUIRE_NON_NULL_METHOD.test(call)) return;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length <= nullIndex) return;
PsiExpression nullArg = PsiUtil.skipParenthesizedExprDown(args[nullIndex]);
if (args.length <= nullCheckParameter.myIndex) return;
PsiExpression nullArg = PsiUtil.skipParenthesizedExprDown(args[nullCheckParameter.myIndex]);
String explanation = getObviouslyNonNullExplanation(nullArg);
if (explanation == null) return;
holder.registerProblem(nullArg, InspectionsBundle.message("inspection.useless.null.check.message", explanation),
new RemoveNullCheckFix());
if(nullCheckParameter.myNull) {
holder.registerProblem(nullArg, InspectionsBundle.message("inspection.useless.null.check.always.fail.message", explanation));
} else {
holder.registerProblem(nullArg, InspectionsBundle.message("inspection.useless.null.check.message", explanation),
new RemoveNullCheckFix());
}
}
};
}
@@ -74,28 +78,40 @@ public class ObviousNullCheckInspection extends BaseJavaBatchLocalInspectionTool
return null;
}
@Nullable
private static Integer getNullParameterIndex(PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method == null) return null;
if (!ControlFlowAnalyzer.isPure(method)) return null;
List<? extends MethodContract> contracts = ControlFlowAnalyzer.getMethodCallContracts(method, call);
if (contracts.size() != 1) return null;
StandardMethodContract contract = ObjectUtils.tryCast(contracts.get(0), StandardMethodContract.class);
if (contract == null || contract.getReturnValue() != MethodContract.ValueConstraint.THROW_EXCEPTION) return null;
MethodContract.ValueConstraint[] arguments = contract.arguments;
Integer nullIndex = null;
for (int i = 0; i < arguments.length; i++) {
MethodContract.ValueConstraint argument = arguments[i];
if (argument == MethodContract.ValueConstraint.NULL_VALUE) {
if (nullIndex != null) return null;
nullIndex = i;
static class NullCheckParameter {
int myIndex;
boolean myNull;
public NullCheckParameter(int index, boolean aNull) {
myIndex = index;
myNull = aNull;
}
@Nullable
static NullCheckParameter fromCall(PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method == null) return null;
if (!ControlFlowAnalyzer.isPure(method)) return null;
List<? extends MethodContract> contracts = ControlFlowAnalyzer.getMethodCallContracts(method, call);
if (contracts.size() != 1) return null;
StandardMethodContract contract = ObjectUtils.tryCast(contracts.get(0), StandardMethodContract.class);
if (contract == null || contract.getReturnValue() != MethodContract.ValueConstraint.THROW_EXCEPTION) return null;
MethodContract.ValueConstraint[] arguments = contract.arguments;
Integer nullIndex = null;
boolean isNull = false;
for (int i = 0; i < arguments.length; i++) {
MethodContract.ValueConstraint argument = arguments[i];
if (argument == MethodContract.ValueConstraint.NULL_VALUE || argument == MethodContract.ValueConstraint.NOT_NULL_VALUE) {
if (nullIndex != null) return null;
nullIndex = i;
isNull = argument == MethodContract.ValueConstraint.NOT_NULL_VALUE;
}
else if (argument != MethodContract.ValueConstraint.ANY_VALUE) {
return null;
}
}
else if (argument != MethodContract.ValueConstraint.ANY_VALUE) {
return null;
}
return nullIndex == null ? null : new NullCheckParameter(nullIndex, isNull);
}
return nullIndex;
}
public static class RemoveNullCheckFix implements LocalQuickFix {
@@ -6,9 +6,12 @@ abstract class ObviousNullCheck {
abstract String getBar();
void test() {
void test(String param) {
assertNotNull(<warning descr="Useless null-check: a value of primitive type is never null">5 + 6</warning>);
assertNull("Null!", param);
assertNull(param, <warning descr="Null-check will always fail: literal is never null">"Null!"</warning>);
Objects.requireNonNull(null);
Objects.requireNonNull(<warning descr="Useless null-check: literal is never null">"xyz"</warning>, "xyz");
Objects.requireNonNull((<warning descr="Useless null-check: concatenation is never null">getFoo() + getBar()</warning>));
@@ -27,4 +30,10 @@ abstract class ObviousNullCheck {
static void assertNotNull(Object obj) {
if(obj == null) throw new NullPointerException();
}
@Contract(value="_,!null -> fail", pure=true)
static void assertNull(String msg, Object obj) {
if(obj != null) throw new NullPointerException(msg);
}
}
@@ -870,6 +870,7 @@ inspection.replace.with.trivial.lambda.fix.family.name=Replace with trivial lamb
inspection.replace.with.trivial.lambda.fix.name=Replace with lambda returning ''{0}''
inspection.useless.null.check.message=Useless null-check: {0} is never null
inspection.useless.null.check.always.fail.message=Null-check will always fail: {0} is never null
inspection.useless.null.check.fix.family.name=Remove useless null-check
inspection.comparator.result.comparison.display.name=Suspicious usage of compare method