MethodCallInstruction cleanup

1. myContext & myCall merged to single field
2. ofNullable stuff moved to DataFlowInspectionBase
This commit is contained in:
Tagir Valeev
2017-08-23 10:16:40 +07:00
parent 65f8625d44
commit 5d245f2dae
4 changed files with 54 additions and 64 deletions
@@ -178,10 +178,11 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
private void analyzeNullLiteralMethodArguments(PsiMethod method, ProblemsHolder holder, boolean isOnTheFly) {
if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && isOnTheFly) {
for (PsiParameter parameter : NullParameterConstraintChecker.checkMethodParameters(method)) {
holder.registerProblem(parameter.getNameIdentifier(),
InspectionsBundle.message("dataflow.method.fails.with.null.argument"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
createNavigateToNullParameterUsagesFix(parameter));
PsiIdentifier name = parameter.getNameIdentifier();
if (name != null) {
holder.registerProblem(name, InspectionsBundle.message("dataflow.method.fails.with.null.argument"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createNavigateToNullParameterUsagesFix(parameter));
}
}
}
}
@@ -330,7 +331,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
reportNullableArgumentsPassedToNonAnnotated(visitor, holder, reportedAnchors);
}
reportOptionalOfNullableImprovements(holder, reportedAnchors, runner.getInstructions());
reportOptionalOfNullableImprovements(holder, reportedAnchors, visitor.getOfNullableCalls());
reportUncheckedOptionalGet(holder, visitor.getOptionalCalls(), visitor.getOptionalQualifiers());
@@ -463,27 +464,25 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, Set<PsiElement> reportedAnchors, Instruction[] instructions) {
for (Instruction instruction : instructions) {
if (instruction instanceof MethodCallInstruction) {
MethodCallInstruction methodCall = (MethodCallInstruction)instruction;
if (methodCall.getArgCount() != 1) continue;
final PsiElement arg = methodCall.getArgumentAnchor(0);
if (methodCall.isOptionalAlwaysNullProblem()) {
if (!reportedAnchors.add(arg)) continue;
holder.registerProblem(arg, "Passing <code>null</code> argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg));
private static void reportOptionalOfNullableImprovements(ProblemsHolder holder,
Set<PsiElement> reportedAnchors,
Map<MethodCallInstruction, ThreeState> nullArgs) {
nullArgs.forEach((call, nullArg) -> {
PsiElement arg = call.getArgumentAnchor(0);
if (reportedAnchors.add(arg)) {
switch (nullArg) {
case YES:
holder.registerProblem(arg, "Passing <code>null</code> argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg));
break;
case NO:
holder.registerProblem(arg, "Passing a non-null argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg));
break;
default:
}
else if (methodCall.isOptionalAlwaysNotNullProblem()) {
if (!reportedAnchors.add(arg)) continue;
holder.registerProblem(arg, "Passing a non-null argument to <code>Optional</code>",
DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg));
}
}
}
});
}
private static void reportConstantReferenceValues(ProblemsHolder holder, StandardInstructionVisitor visitor, Set<PsiElement> reportedAnchors) {
@@ -932,6 +931,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
private final Map<MethodCallInstruction, Boolean> myFailingCalls = new HashMap<>();
private final Map<PsiMethodCallExpression, ThreeState> myOptionalCalls = new HashMap<>();
private final Map<PsiMethodCallExpression, ThreeState> myBooleanCalls = new HashMap<>();
private final Map<MethodCallInstruction, ThreeState> myOfNullableCalls = new HashMap<>();
private final Map<PsiMethodReferenceExpression, DfaValue> myMethodReferenceResults = new HashMap<>();
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
private final List<PsiExpression> myOptionalQualifiers = new ArrayList<>();
@@ -956,6 +956,10 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
return myOptionalCalls;
}
Map<MethodCallInstruction, ThreeState> getOfNullableCalls() {
return myOfNullableCalls;
}
Map<PsiMethodCallExpression, ThreeState> getBooleanCalls() {
return myBooleanCalls;
}
@@ -1000,6 +1004,11 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
}
if (instruction.matches(DfaOptionalSupport.OPTIONAL_OF_NULLABLE)) {
DfaValue arg = memState.peek();
ThreeState nullArg = memState.isNull(arg) ? ThreeState.YES : memState.isNotNull(arg) ? ThreeState.NO : ThreeState.UNSURE;
myOfNullableCalls.merge(instruction, nullArg, ThreeState::merge);
}
DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
if (hasNonTrivialFailingContracts(instruction)) {
DfaConstValue fail = runner.getFactory().getConstFactory().getContractFail();
@@ -400,7 +400,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
forceNotNull(runner, memState, arg);
}
}
else if (!instruction.updateOfNullable(memState, arg) && requiredNullability == Nullness.UNKNOWN) {
else if (requiredNullability == Nullness.UNKNOWN) {
checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, anchor);
}
}
@@ -20,6 +20,7 @@ import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -31,7 +32,6 @@ import java.util.List;
public class MethodCallInstruction extends Instruction {
private static final Nullness[] EMPTY_NULLNESS_ARRAY = new Nullness[0];
@Nullable private final PsiCall myCall;
@Nullable private final PsiType myType;
private final int myArgCount;
private final boolean myShouldFlushFields;
@@ -40,11 +40,8 @@ public class MethodCallInstruction extends Instruction {
private final List<MethodContract> myContracts;
private final MethodType myMethodType;
@Nullable private final DfaValue myPrecalculatedReturnValue;
private final boolean myOfNullable;
private final boolean myVarArgCall;
private final Nullness[] myArgRequiredNullability;
private boolean myOnlyNullArgs = true;
private boolean myOnlyNotNullArgs = true;
private final Nullness myReturnNullability;
public enum MethodType {
@@ -55,14 +52,12 @@ public class MethodCallInstruction extends Instruction {
myContext = context;
myContracts = Collections.emptyList();
myMethodType = methodType;
myCall = null;
myArgCount = 0;
myType = resultType;
myShouldFlushFields = false;
myPrecalculatedReturnValue = null;
myTargetMethod = null;
myVarArgCall = false;
myOfNullable = false;
myArgRequiredNullability = EMPTY_NULLNESS_ARRAY;
myReturnNullability = Nullness.UNKNOWN;
}
@@ -72,7 +67,6 @@ public class MethodCallInstruction extends Instruction {
myMethodType = MethodType.METHOD_REFERENCE_CALL;
JavaResolveResult resolveResult = reference.advancedResolve(false);
myTargetMethod = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class);
myCall = null;
myContracts = Collections.unmodifiableList(contracts);
myArgCount = myTargetMethod == null ? 0 : myTargetMethod.getParameterList().getParametersCount();
if (myTargetMethod == null) {
@@ -93,7 +87,6 @@ public class MethodCallInstruction extends Instruction {
}
myVarArgCall = false; // vararg method reference calls are not supported now
myPrecalculatedReturnValue = null;
myOfNullable = DfaOptionalSupport.OPTIONAL_OF_NULLABLE.methodReferenceMatches(reference);
myArgRequiredNullability = myTargetMethod == null
? EMPTY_NULLNESS_ARRAY
: calcArgRequiredNullability(resolveResult.getSubstitutor(),
@@ -105,11 +98,10 @@ public class MethodCallInstruction extends Instruction {
myContext = call;
myContracts = Collections.unmodifiableList(contracts);
myMethodType = MethodType.REGULAR_METHOD_CALL;
myCall = call;
final PsiExpressionList argList = call.getArgumentList();
PsiExpression[] args = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY;
myArgCount = args.length;
myType = myCall instanceof PsiCallExpression ? ((PsiCallExpression)myCall).getType() : null;
myType = call instanceof PsiCallExpression ? ((PsiCallExpression)call).getType() : null;
JavaResolveResult result = call.resolveMethodGenerics();
myTargetMethod = (PsiMethod)result.getElement();
@@ -126,10 +118,20 @@ public class MethodCallInstruction extends Instruction {
myShouldFlushFields = !(call instanceof PsiNewExpression && myType != null && myType.getArrayDimensions() > 0) && !isPureCall();
myPrecalculatedReturnValue = precalculatedReturnValue;
myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.OPTIONAL_OF_NULLABLE.test((PsiMethodCallExpression)call);
myReturnNullability = call instanceof PsiNewExpression ? Nullness.NOT_NULL : DfaPsiUtil.getElementNullability(myType, myTargetMethod);
}
public boolean matches(CallMatcher matcher) {
switch (myMethodType) {
case REGULAR_METHOD_CALL:
return myContext instanceof PsiMethodCallExpression && matcher.test((PsiMethodCallExpression)myContext);
case METHOD_REFERENCE_CALL:
return matcher.methodReferenceMatches((PsiMethodReferenceExpression)myContext);
default:
return false;
}
}
/**
* Returns a PsiElement which at best represents an argument with given index
*
@@ -137,13 +139,13 @@ public class MethodCallInstruction extends Instruction {
* @return a PsiElement. Either argument expression or method reference if call is described by method reference
*/
public PsiElement getArgumentAnchor(int index) {
if (myCall != null) {
PsiExpressionList argumentList = myCall.getArgumentList();
if (myMethodType == MethodType.REGULAR_METHOD_CALL && myContext instanceof PsiCall) {
PsiExpressionList argumentList = ((PsiCall)myContext).getArgumentList();
if (argumentList != null) {
return argumentList.getExpressions()[index];
}
}
if (myContext instanceof PsiMethodReferenceExpression) {
if (myMethodType == MethodType.METHOD_REFERENCE_CALL && myContext instanceof PsiMethodReferenceExpression) {
return ((PsiMethodReferenceExpression)myContext).getReferenceNameElement();
}
return myContext;
@@ -243,7 +245,7 @@ public class MethodCallInstruction extends Instruction {
@Nullable
public PsiCall getCallExpression() {
return myCall;
return myMethodType == MethodType.REGULAR_METHOD_CALL && myContext instanceof PsiCall ? (PsiCall)myContext : null;
}
@NotNull
@@ -272,30 +274,9 @@ public class MethodCallInstruction extends Instruction {
case METHOD_REFERENCE_CALL:
return "CALL_METHOD_REFERENCE: " + myContext.getText();
case REGULAR_METHOD_CALL:
return "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText());
return "CALL_METHOD: " + myContext.getText();
default:
throw new IllegalStateException("Unexpected method type: " + myMethodType);
}
}
public boolean updateOfNullable(DfaMemoryState memState, DfaValue arg) {
if (!myOfNullable) return false;
if (!memState.isNotNull(arg)) {
myOnlyNotNullArgs = false;
}
if (!memState.isNull(arg)) {
myOnlyNullArgs = false;
}
return true;
}
public boolean isOptionalAlwaysNullProblem() {
return myOfNullable && myOnlyNullArgs;
}
public boolean isOptionalAlwaysNotNullProblem() {
return myOfNullable && myOnlyNotNullArgs;
}
}
@@ -129,7 +129,7 @@ public class OptionalInlining {
}
void testGuavaTransform(com.google.common.base.Optional<String> opt) {
String trimmed = com.google.common.base.Optional.fromNullable(nullableMethod()).transform(xx -> xx.trim()).or("");
String trimmed = com.google.common.base.Optional.fromNullable(<warning descr="Argument 'nullableMethod()' might be null but passed to non annotated parameter">nullableMethod()</warning>).transform(xx -> xx.trim()).or("");
if(<warning descr="Condition 'trimmed == null' is always 'false'">trimmed == null</warning>) {
System.out.println("impossible");
}
@@ -145,7 +145,7 @@ public class OptionalInlining {
void testToJavaUtil() {
String xyz = nullableMethod();
Object n = com.google.common.base.Optional.fromNullable(xyz).transform(String::trim).toJavaUtil().map(this::getObj).orElse(null);
Object n = com.google.common.base.Optional.fromNullable(<warning descr="Argument 'xyz' might be null but passed to non annotated parameter">xyz</warning>).transform(String::trim).toJavaUtil().map(this::getObj).orElse(null);
if(n instanceof Integer) {
// n instanceof Integer -> n is not null -> xyz was not null -> safe to dereference
System.out.println(xyz.trim());