mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-74934 "Constant conditions & exceptions": take type parameters into consideration, take 1 (methods)
This commit is contained in:
+94
-62
@@ -22,7 +22,6 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import gnu.trove.THashSet;
|
||||
@@ -30,6 +29,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -40,44 +41,14 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<BinopInstruction>();
|
||||
private final Set<PsiElement> myNotToReportReachability = new THashSet<PsiElement>();
|
||||
private final Set<InstanceofInstruction> myUsefulInstanceofs = new THashSet<InstanceofInstruction>();
|
||||
private final FactoryMap<MethodCallInstruction, boolean[]> myParametersNotNull = new FactoryMap<MethodCallInstruction, boolean[]>() {
|
||||
private final FactoryMap<MethodCallInstruction, Map<PsiExpression, Boolean>> myParametersNullability = new FactoryMap<MethodCallInstruction, Map<PsiExpression, Boolean>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected boolean[] create(MethodCallInstruction key) {
|
||||
final PsiCallExpression callExpression = key.getCallExpression();
|
||||
final PsiMethod callee = callExpression == null ? null : callExpression.resolveMethod();
|
||||
if (callee != null) {
|
||||
final PsiParameter[] params = callee.getParameterList().getParameters();
|
||||
boolean[] result = new boolean[params.length];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
result[i] = NullableNotNullManager.getInstance(params[i].getProject()).isNotNull(params[i], false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return ArrayUtil.EMPTY_BOOLEAN_ARRAY;
|
||||
}
|
||||
protected Map<PsiExpression, Boolean> create(MethodCallInstruction key) {
|
||||
return calcParameterNullability(key.getCallExpression());
|
||||
}
|
||||
};
|
||||
private final FactoryMap<MethodCallInstruction, boolean[]> myParametersNonAnnotated = new FactoryMap<MethodCallInstruction, boolean[]>() {
|
||||
@Override
|
||||
protected boolean[] create(MethodCallInstruction key) {
|
||||
final PsiCallExpression callExpression = key.getCallExpression();
|
||||
final PsiMethod callee = callExpression == null ? null : callExpression.resolveMethod();
|
||||
if (callee != null) {
|
||||
final PsiParameter[] params = callee.getParameterList().getParameters();
|
||||
boolean[] result = new boolean[params.length];
|
||||
final NullableNotNullManager notNullManager = NullableNotNullManager.getInstance(callee.getProject());
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
result[i] = !notNullManager.isNotNull(params[i], false) && !notNullManager.isNullable(params[i], false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return ArrayUtil.EMPTY_BOOLEAN_ARRAY;
|
||||
}
|
||||
}
|
||||
};
|
||||
private final FactoryMap<MethodCallInstruction, Boolean> myCalleeNullability = new FactoryMap<MethodCallInstruction, Boolean>() {
|
||||
private final FactoryMap<MethodCallInstruction, Boolean> myReturnTypeNullability = new FactoryMap<MethodCallInstruction, Boolean>() {
|
||||
@Override
|
||||
protected Boolean create(MethodCallInstruction key) {
|
||||
final PsiCallExpression callExpression = key.getCallExpression();
|
||||
@@ -85,21 +56,85 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
if (callExpression != null) {
|
||||
final PsiMethod callee = callExpression.resolveMethod();
|
||||
if (callee != null) {
|
||||
if (NullableNotNullManager.isNullable(callee)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (NullableNotNullManager.isNotNull(callee)) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return callExpression != null ? getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null;
|
||||
}
|
||||
};
|
||||
|
||||
private static Map<PsiExpression, Boolean> calcParameterNullability(@Nullable PsiCallExpression callExpression) {
|
||||
PsiExpressionList argumentList = callExpression == null ? null : callExpression.getArgumentList();
|
||||
if (argumentList != null) {
|
||||
JavaResolveResult result = callExpression.resolveMethodGenerics();
|
||||
PsiMethod method = (PsiMethod)result.getElement();
|
||||
if (method != null) {
|
||||
PsiSubstitutor substitutor = result.getSubstitutor();
|
||||
PsiExpression[] args = argumentList.getExpressions();
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
|
||||
boolean varArg = isVarArgCall(method, substitutor, args, parameters);
|
||||
int checkedCount = Math.min(args.length, parameters.length) - (varArg ? 1 : 0);
|
||||
|
||||
Map<PsiExpression, Boolean> map = ContainerUtil.newHashMap();
|
||||
for (int i = 0; i < checkedCount; i++) {
|
||||
map.put(args[i], getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i]));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static boolean isVarArgCall(PsiMethod method, PsiSubstitutor substitutor, PsiExpression[] args, PsiParameter[] parameters) {
|
||||
if (!method.isVarArgs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int argCount = args.length;
|
||||
int paramCount = parameters.length;
|
||||
if (argCount > paramCount) {
|
||||
return true;
|
||||
}
|
||||
else if (paramCount > 0) {
|
||||
if (argCount == paramCount) {
|
||||
PsiType lastArgType = args[argCount - 1].getType();
|
||||
if (lastArgType != null &&
|
||||
!substitutor.substitute(parameters[paramCount - 1].getType()).isAssignableFrom(lastArgType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static Boolean getElementNullability(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) {
|
||||
if (owner == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (NullableNotNullManager.isNullable(owner)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (NullableNotNullManager.isNotNull(owner)) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
if (resultType != null) {
|
||||
NullableNotNullManager nnn = NullableNotNullManager.getInstance(owner.getProject());
|
||||
for (PsiAnnotation annotation : resultType.getAnnotations()) {
|
||||
String qualifiedName = annotation.getQualifiedName();
|
||||
if (nnn.getNullables().contains(qualifiedName)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (nnn.getNotNulls().contains(qualifiedName)) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
DfaValue dfaSource = memState.pop();
|
||||
@@ -181,24 +216,21 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
@Override
|
||||
public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
final PsiExpression[] args = instruction.getArgs();
|
||||
final boolean[] parametersNotNull = myParametersNotNull.get(instruction);
|
||||
final boolean[] nonAnnotated = myParametersNonAnnotated.get(instruction);
|
||||
Map<PsiExpression, Boolean> map = myParametersNullability.get(instruction);
|
||||
final DfaNotNullValue.Factory factory = runner.getFactory().getNotNullFactory();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
final DfaValue arg = memState.pop();
|
||||
final int revIdx = args.length - i - 1;
|
||||
if (args.length <= parametersNotNull.length && revIdx < parametersNotNull.length && !(i == args.length - 1 && instruction.isVarargCall())) {
|
||||
if (parametersNotNull[revIdx]) {
|
||||
if (!memState.applyNotNull(arg)) {
|
||||
onPassingNullParameter(runner, args[revIdx]);
|
||||
if (arg instanceof DfaVariableValue) {
|
||||
memState.setVarValue((DfaVariableValue)arg, factory.create(((DfaVariableValue)arg).getVariableType()));
|
||||
}
|
||||
PsiExpression expr = args[(args.length - i - 1)];
|
||||
if (map.get(expr) == Boolean.FALSE) {
|
||||
if (!memState.applyNotNull(arg)) {
|
||||
onPassingNullParameter(runner, expr);
|
||||
if (arg instanceof DfaVariableValue) {
|
||||
memState.setVarValue((DfaVariableValue)arg, factory.create(((DfaVariableValue)arg).getVariableType()));
|
||||
}
|
||||
}
|
||||
else if (nonAnnotated[revIdx] && !memState.checkNotNullable(arg)) {
|
||||
onPassingNullParameterToNonAnnotated(runner, args[revIdx]);
|
||||
}
|
||||
}
|
||||
else if (map.containsKey(expr) && map.get(expr) == null && !memState.checkNotNullable(arg)) {
|
||||
onPassingNullParameterToNonAnnotated(runner, expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +268,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
final PsiType type = instruction.getResultType();
|
||||
final MethodCallInstruction.MethodType methodType = instruction.getMethodType();
|
||||
if (type != null && (type instanceof PsiClassType || type.getArrayDimensions() > 0)) {
|
||||
@Nullable final Boolean nullability = myCalleeNullability.get(instruction);
|
||||
@Nullable final Boolean nullability = myReturnTypeNullability.get(instruction);
|
||||
return nullability == Boolean.FALSE ? factory.getNotNullFactory().create(type) : factory.getTypeFactory().create(type, nullability == Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
-30
@@ -42,7 +42,6 @@ public class MethodCallInstruction extends Instruction {
|
||||
@NotNull private final PsiExpression myContext;
|
||||
private final MethodType myMethodType;
|
||||
@Nullable private DfaValue myPrecalculatedReturnValue;
|
||||
private boolean myVarargCall;
|
||||
public enum MethodType {
|
||||
BOXING, UNBOXING, REGULAR_METHOD_CALL, CAST
|
||||
}
|
||||
@@ -50,7 +49,6 @@ public class MethodCallInstruction extends Instruction {
|
||||
public MethodCallInstruction(@NotNull PsiCallExpression callExpression, @Nullable DfaValue precalculatedReturnValue) {
|
||||
this(callExpression, MethodType.REGULAR_METHOD_CALL);
|
||||
myPrecalculatedReturnValue = precalculatedReturnValue;
|
||||
myVarargCall = calcIsVarargCall(callExpression);
|
||||
}
|
||||
|
||||
public MethodCallInstruction(@NotNull PsiExpression context, MethodType methodType, @Nullable PsiType resultType) {
|
||||
@@ -79,34 +77,6 @@ public class MethodCallInstruction extends Instruction {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public boolean isVarargCall() {
|
||||
return myVarargCall;
|
||||
}
|
||||
|
||||
private static boolean calcIsVarargCall(PsiCallExpression callExpression) {
|
||||
PsiExpressionList argumentList = callExpression.getArgumentList();
|
||||
if (argumentList != null) {
|
||||
JavaResolveResult result = callExpression.resolveMethodGenerics();
|
||||
PsiMethod method = (PsiMethod)result.getElement();
|
||||
if (method != null && method.isVarArgs()) {
|
||||
PsiType[] argTypes = argumentList.getExpressionTypes();
|
||||
int argCount = argTypes.length;
|
||||
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
int paramCount = parameters.length;
|
||||
|
||||
if (argCount > paramCount) {
|
||||
return true;
|
||||
}
|
||||
PsiType lastParam = parameters[paramCount - 1].getType();
|
||||
if (argCount == paramCount && !result.getSubstitutor().substitute(lastParam).isAssignableFrom(argTypes[argCount - 1])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiExpression[] getArgs() {
|
||||
return myArgs;
|
||||
|
||||
+2
-1
@@ -34,12 +34,13 @@ import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PushInstruction extends Instruction {
|
||||
private final DfaValue myValue;
|
||||
private final PsiExpression myPlace;
|
||||
|
||||
public PushInstruction(DfaValue value, PsiExpression place) {
|
||||
public PushInstruction(@Nullable DfaValue value, PsiExpression place) {
|
||||
myValue = value != null ? value : DfaUnknownValue.getInstance();
|
||||
myPlace = place;
|
||||
}
|
||||
|
||||
@@ -68,13 +68,13 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
|
||||
}
|
||||
}
|
||||
|
||||
public void setNotNulls(String[] annotations) {
|
||||
public void setNotNulls(String... annotations) {
|
||||
myNotNulls.clear();
|
||||
addAllIfNotPresent(myNotNulls, DEFAULT_NOT_NULLS);
|
||||
addAllIfNotPresent(myNotNulls, annotations);
|
||||
}
|
||||
|
||||
public void setNullables(String[] annotations) {
|
||||
public void setNullables(String... annotations) {
|
||||
myNullables.clear();
|
||||
addAllIfNotPresent(myNullables, DEFAULT_NULLABLES);
|
||||
addAllIfNotPresent(myNullables, annotations);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import foo.NotNull;
|
||||
import foo.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Test {
|
||||
private static void test(List<@NotNull Object> list) {
|
||||
if (<warning descr="Condition 'list.get(0) == null' is always 'false'">list.get(0) == null</warning>) {
|
||||
return;
|
||||
}
|
||||
list.add(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
|
||||
}
|
||||
private static void test2(List<@Nullable Object> list) {
|
||||
if (list.get(0) == null) {
|
||||
return;
|
||||
}
|
||||
list.add(null);
|
||||
}
|
||||
|
||||
private static void test3(Ref<@NotNull Object> ref) {
|
||||
if (ref.value == null) {
|
||||
return;
|
||||
}
|
||||
ref.value = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Ref<T> {
|
||||
T value;
|
||||
}
|
||||
+16
@@ -16,6 +16,7 @@
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
|
||||
@@ -103,4 +104,19 @@ public class DataFlowInspectionFixtureTest extends JavaCodeInsightFixtureTestCas
|
||||
public void testPassingNullableIntoVararg() throws Throwable { doTest(); }
|
||||
public void testEqualsImpliesNotNull() throws Throwable { doTest(); }
|
||||
|
||||
public void testAnnotatedTypeParameters() throws Throwable {
|
||||
myFixture.addClass("package foo; public @interface Nullable {}");
|
||||
myFixture.addClass("package foo; public @interface NotNull {}");
|
||||
NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject());
|
||||
nnnManager.setNotNulls("foo.NotNull");
|
||||
nnnManager.setNullables("foo.Nullable");
|
||||
try {
|
||||
doTest();
|
||||
}
|
||||
finally {
|
||||
nnnManager.setNotNulls();
|
||||
nnnManager.setNullables();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user