mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
DFA: branch-specific reporting (IDEA-203016)
This commit is contained in:
@@ -42,6 +42,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
@@ -448,28 +449,31 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
|
||||
private void reportNullabilityProblems(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
|
||||
Map<PsiExpression, ConstantResult> expressions = visitor.getConstantExpressions();
|
||||
visitor.problems().forEach(problem -> {
|
||||
List<NullabilityProblem<?>> problems = NullabilityProblemKind.postprocessNullabilityProblems(visitor.problems().toList());
|
||||
for (NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
Consumer<PsiExpression> standardReporter = expr -> reportNullabilityProblem(reporter, problem, expression, expressions);
|
||||
NullabilityProblemKind.innerClassNPE.ifMyProblem(problem, newExpression -> {
|
||||
List<LocalQuickFix> fixes = createNPEFixes(newExpression.getQualifier(), newExpression, reporter.isOnTheFly());
|
||||
reporter.registerProblem(getElementToHighlight(newExpression), problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
reporter
|
||||
.registerProblem(getElementToHighlight(newExpression), problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
});
|
||||
NullabilityProblemKind.callMethodRefNPE.ifMyProblem(problem, methodRef ->
|
||||
reporter.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"),
|
||||
createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY)));
|
||||
createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY)));
|
||||
NullabilityProblemKind.callNPE.ifMyProblem(problem, call -> reportCallMayProduceNpe(reporter, problem.getMessage(expressions), call));
|
||||
NullabilityProblemKind.passingToNotNullParameter.ifMyProblem(problem, expr -> {
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(expr);
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, expression, reporter.isOnTheFly());
|
||||
reporter.registerProblem(expr, problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
reporter.registerProblem(expression, problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
});
|
||||
NullabilityProblemKind.passingToNotNullMethodRefParameter.ifMyProblem(problem, methodRef -> {
|
||||
LocalQuickFix[] fixes = createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"), fixes);
|
||||
});
|
||||
NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, expression -> {
|
||||
NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, arrayAccess -> {
|
||||
LocalQuickFix[] fixes =
|
||||
createNPEFixes(expression.getArrayExpression(), expression, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(expression, problem.getMessage(expressions), fixes);
|
||||
createNPEFixes(arrayAccess.getArrayExpression(), arrayAccess, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
reporter.registerProblem(arrayAccess, problem.getMessage(expressions), fixes);
|
||||
});
|
||||
NullabilityProblemKind.fieldAccessNPE.ifMyProblem(problem, element -> {
|
||||
PsiElement parent = element.getParent();
|
||||
@@ -478,24 +482,25 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
reporter.registerProblem(element, problem.getMessage(expressions), fix);
|
||||
});
|
||||
NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> {
|
||||
if (element instanceof PsiTypeCastExpression && ((PsiTypeCastExpression)element).getType() instanceof PsiPrimitiveType) {
|
||||
element = Objects.requireNonNull(((PsiTypeCastExpression)element).getOperand());
|
||||
PsiExpression anchor = expression;
|
||||
if (anchor instanceof PsiTypeCastExpression && anchor.getType() instanceof PsiPrimitiveType) {
|
||||
anchor = Objects.requireNonNull(((PsiTypeCastExpression)anchor).getOperand());
|
||||
}
|
||||
reporter.registerProblem(element, problem.getMessage(expressions));
|
||||
reporter.registerProblem(anchor, problem.getMessage(expressions));
|
||||
});
|
||||
NullabilityProblemKind.nullableFunctionReturn.ifMyProblem(
|
||||
problem, expr -> reporter.registerProblem(expr, problem.getMessage(expressions)));
|
||||
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, expr -> reportNullabilityProblem(reporter, problem, expr, expressions));
|
||||
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, expr -> reportNullabilityProblem(reporter, problem, expr, expressions));
|
||||
problem, expr -> reporter.registerProblem(expression == null ? expr : expression, problem.getMessage(expressions)));
|
||||
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, standardReporter);
|
||||
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, standardReporter);
|
||||
if (SUGGEST_NULLABLE_ANNOTATIONS) {
|
||||
NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.ifMyProblem(
|
||||
problem, methodRef -> reporter.registerProblem(methodRef, problem.getMessage(expressions)));
|
||||
NullabilityProblemKind.passingToNonAnnotatedParameter.ifMyProblem(
|
||||
problem, expression -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(expressions), expression));
|
||||
problem, top -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(expressions), expression, top));
|
||||
NullabilityProblemKind.assigningToNonAnnotatedField.ifMyProblem(
|
||||
problem, expression -> reportNullableAssignedToNonAnnotatedField(reporter, expression, problem.getMessage(expressions)));
|
||||
problem, top -> reportNullableAssignedToNonAnnotatedField(reporter, top, expression, problem.getMessage(expressions)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void reportNullabilityProblem(ProblemReporter reporter,
|
||||
@@ -604,20 +609,25 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
});
|
||||
}
|
||||
|
||||
private void reportNullableArgumentsPassedToNonAnnotated(ProblemReporter reporter, String message, PsiExpression anchor) {
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(anchor);
|
||||
PsiParameter parameter = MethodCallUtils.getParameterForArgument(anchor);
|
||||
private void reportNullableArgumentsPassedToNonAnnotated(ProblemReporter reporter,
|
||||
String message,
|
||||
PsiExpression expression,
|
||||
PsiExpression top) {
|
||||
PsiParameter parameter = MethodCallUtils.getParameterForArgument(top);
|
||||
if (parameter != null && BaseIntentionAction.canModify(parameter) && AnnotationUtil.isAnnotatingApplicable(parameter)) {
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, expression, reporter.isOnTheFly());
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, top, reporter.isOnTheFly());
|
||||
fixes.add(AddAnnotationPsiFix.createAddNullableFix(parameter));
|
||||
reporter.registerProblem(anchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
}
|
||||
|
||||
private void reportNullableAssignedToNonAnnotatedField(ProblemReporter reporter, PsiExpression expression, String message) {
|
||||
PsiField field = getAssignedField(expression);
|
||||
private void reportNullableAssignedToNonAnnotatedField(ProblemReporter reporter,
|
||||
PsiExpression top,
|
||||
PsiExpression expression,
|
||||
String message) {
|
||||
PsiField field = getAssignedField(top);
|
||||
if (field != null) {
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, expression, reporter.isOnTheFly());
|
||||
List<LocalQuickFix> fixes = createNPEFixes(expression, top, reporter.isOnTheFly());
|
||||
fixes.add(AddAnnotationPsiFix.createAddNullableFix(field));
|
||||
reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
|
||||
|
||||
@Override
|
||||
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
|
||||
if (NullabilityProblemKind.nullableReturn.isMyProblem(problem) && !state.isNotNull(value)) {
|
||||
if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) {
|
||||
myAlwaysReturnsNotNull = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.ig.psiutils.ExpectedTypeUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
@@ -18,11 +17,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.codeInspection.InspectionsBundle.BUNDLE;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
/**
|
||||
* Represents a kind of nullability problem
|
||||
@@ -127,16 +127,6 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
return problem != null && problem.myKind == this ? (NullabilityProblem<T>)problem : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the kind of supplied problem is the same as this kind
|
||||
*
|
||||
* @param problem problem to check
|
||||
* @return true if the kind of supplied problem is the same as this kind
|
||||
*/
|
||||
public final boolean isMyProblem(@Nullable NullabilityProblem<?> problem) {
|
||||
return problem != null && problem.myKind == this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes given consumer if the supplied problem has the same kind as this kind
|
||||
*
|
||||
@@ -199,74 +189,18 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiAssignmentExpression) {
|
||||
PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
|
||||
IElementType tokenType = assignment.getOperationTokenType();
|
||||
if (assignment.getRExpression() == context) {
|
||||
PsiExpression lho = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression());
|
||||
if (lho != null) {
|
||||
PsiType type = lho.getType();
|
||||
if (tokenType.equals(JavaTokenType.PLUSEQ) && TypeUtils.isJavaLangString(type)) {
|
||||
return null;
|
||||
}
|
||||
if (type instanceof PsiPrimitiveType) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
Nullability nullability = Nullability.UNKNOWN;
|
||||
PsiVariable target = null;
|
||||
if (lho instanceof PsiReferenceExpression) {
|
||||
target = ObjectUtils.tryCast(((PsiReferenceExpression)lho).resolve(), PsiVariable.class);
|
||||
if (target != null) {
|
||||
nullability = DfaPsiUtil.getElementNullability(type, target);
|
||||
}
|
||||
} else {
|
||||
nullability = DfaPsiUtil.getTypeNullability(type);
|
||||
}
|
||||
boolean forceDeclaredNullity = !(target instanceof PsiParameter && target.getParent() instanceof PsiParameterList);
|
||||
if (forceDeclaredNullity && nullability == Nullability.NOT_NULL) {
|
||||
return (lho instanceof PsiArrayAccessExpression ? storingToNotNullArray : assigningToNotNull).problem(context, expression);
|
||||
}
|
||||
if (nullability == Nullability.UNKNOWN && lho instanceof PsiReferenceExpression) {
|
||||
PsiField field = ObjectUtils.tryCast(((PsiReferenceExpression)lho).resolve(), PsiField.class);
|
||||
if (field != null && !field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return assigningToNonAnnotatedField.problem(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return getAssignmentProblem((PsiAssignmentExpression)parent, expression, context);
|
||||
}
|
||||
if (parent instanceof PsiExpressionList) {
|
||||
if (parent.getParent() instanceof PsiSwitchLabelStatementBase) {
|
||||
return fieldAccessNPE.problem(context, expression);
|
||||
}
|
||||
PsiParameter parameter = MethodCallUtils.getParameterForArgument(context);
|
||||
if (parameter != null) {
|
||||
if (parameter.getType() instanceof PsiPrimitiveType) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
PsiElement grandParent = parent.getParent();
|
||||
if (grandParent instanceof PsiAnonymousClass) {
|
||||
grandParent = grandParent.getParent();
|
||||
}
|
||||
if (grandParent instanceof PsiCall) {
|
||||
PsiSubstitutor substitutor = ((PsiCall)grandParent).resolveMethodGenerics().getSubstitutor();
|
||||
Nullability nullability = DfaPsiUtil.getElementNullability(substitutor.substitute(parameter.getType()), parameter);
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
return passingToNotNullParameter.problem(context, expression);
|
||||
}
|
||||
if (nullability == Nullability.UNKNOWN) {
|
||||
return passingToNonAnnotatedParameter.problem(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return getExpressionListProblem((PsiExpressionList)parent, expression, context);
|
||||
}
|
||||
if (parent instanceof PsiIfStatement ||
|
||||
parent instanceof PsiWhileStatement ||
|
||||
parent instanceof PsiDoWhileStatement ||
|
||||
parent instanceof PsiUnaryExpression ||
|
||||
parent instanceof PsiConditionalExpression ||
|
||||
parent instanceof PsiTypeCastExpression ||
|
||||
parent instanceof PsiForStatement && ((PsiForStatement)parent).getCondition() == context ||
|
||||
parent instanceof PsiAssertStatement && ((PsiAssertStatement)parent).getAssertCondition() == context) {
|
||||
if (parent instanceof PsiArrayInitializerExpression) {
|
||||
return getArrayInitializerProblem((PsiArrayInitializerExpression)parent, expression, context);
|
||||
}
|
||||
if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiDoWhileStatement ||
|
||||
parent instanceof PsiUnaryExpression || parent instanceof PsiConditionalExpression || parent instanceof PsiTypeCastExpression ||
|
||||
(parent instanceof PsiForStatement && ((PsiForStatement)parent).getCondition() == context) ||
|
||||
(parent instanceof PsiAssertStatement && ((PsiAssertStatement)parent).getAssertCondition() == context)) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
if (parent instanceof PsiSwitchBlock) {
|
||||
@@ -290,27 +224,6 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiArrayInitializerExpression) {
|
||||
PsiType type = ((PsiArrayInitializerExpression)parent).getType();
|
||||
if (type instanceof PsiArrayType) {
|
||||
PsiType componentType = ((PsiArrayType)type).getComponentType();
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNull(componentType)) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
Nullability nullability = DfaPsiUtil.getTypeNullability(componentType);
|
||||
if (nullability == Nullability.UNKNOWN) {
|
||||
if (parent.getParent() instanceof PsiNewExpression) {
|
||||
PsiType expectedType = ExpectedTypeUtils.findExpectedType((PsiExpression)parent.getParent(), false);
|
||||
if (expectedType instanceof PsiArrayType) {
|
||||
nullability = DfaPsiUtil.getTypeNullability(((PsiArrayType)expectedType).getComponentType());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
return storingToNotNullArray.problem(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiArrayAccessExpression) {
|
||||
PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression)parent;
|
||||
if (arrayAccessExpression.getArrayExpression() == context) {
|
||||
@@ -321,6 +234,103 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static NullabilityProblem<?> getExpressionListProblem(@NotNull PsiExpressionList expressionList,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull PsiExpression context) {
|
||||
if (expressionList.getParent() instanceof PsiSwitchLabelStatementBase) {
|
||||
return fieldAccessNPE.problem(context, expression);
|
||||
}
|
||||
PsiParameter parameter = MethodCallUtils.getParameterForArgument(context);
|
||||
if (parameter != null) {
|
||||
if (parameter.getType() instanceof PsiPrimitiveType) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
PsiElement grandParent = expressionList.getParent();
|
||||
if (grandParent instanceof PsiAnonymousClass) {
|
||||
grandParent = grandParent.getParent();
|
||||
}
|
||||
if (grandParent instanceof PsiCall) {
|
||||
PsiSubstitutor substitutor = ((PsiCall)grandParent).resolveMethodGenerics().getSubstitutor();
|
||||
Nullability nullability = DfaPsiUtil.getElementNullability(substitutor.substitute(parameter.getType()), parameter);
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
return passingToNotNullParameter.problem(context, expression);
|
||||
}
|
||||
if (nullability == Nullability.UNKNOWN) {
|
||||
return passingToNonAnnotatedParameter.problem(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static NullabilityProblem<?> getArrayInitializerProblem(@NotNull PsiArrayInitializerExpression initializer,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull PsiExpression context) {
|
||||
PsiType type = initializer.getType();
|
||||
if (type instanceof PsiArrayType) {
|
||||
PsiType componentType = ((PsiArrayType)type).getComponentType();
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNull(componentType)) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
Nullability nullability = DfaPsiUtil.getTypeNullability(componentType);
|
||||
if (nullability == Nullability.UNKNOWN) {
|
||||
if (initializer.getParent() instanceof PsiNewExpression) {
|
||||
PsiType expectedType = ExpectedTypeUtils.findExpectedType((PsiExpression)initializer.getParent(), false);
|
||||
if (expectedType instanceof PsiArrayType) {
|
||||
nullability = DfaPsiUtil.getTypeNullability(((PsiArrayType)expectedType).getComponentType());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
return storingToNotNullArray.problem(context, expression);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static NullabilityProblem<?> getAssignmentProblem(@NotNull PsiAssignmentExpression assignment,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull PsiExpression context) {
|
||||
IElementType tokenType = assignment.getOperationTokenType();
|
||||
if (assignment.getRExpression() == context) {
|
||||
PsiExpression lho = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression());
|
||||
if (lho != null) {
|
||||
PsiType type = lho.getType();
|
||||
if (tokenType.equals(JavaTokenType.PLUSEQ) && TypeUtils.isJavaLangString(type)) {
|
||||
return null;
|
||||
}
|
||||
if (type instanceof PsiPrimitiveType) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
Nullability nullability = Nullability.UNKNOWN;
|
||||
PsiVariable target = null;
|
||||
if (lho instanceof PsiReferenceExpression) {
|
||||
target = tryCast(((PsiReferenceExpression)lho).resolve(), PsiVariable.class);
|
||||
if (target != null) {
|
||||
nullability = DfaPsiUtil.getElementNullability(type, target);
|
||||
}
|
||||
}
|
||||
else {
|
||||
nullability = DfaPsiUtil.getTypeNullability(type);
|
||||
}
|
||||
boolean forceDeclaredNullity = !(target instanceof PsiParameter && target.getParent() instanceof PsiParameterList);
|
||||
if (forceDeclaredNullity && nullability == Nullability.NOT_NULL) {
|
||||
return (lho instanceof PsiArrayAccessExpression ? storingToNotNullArray : assigningToNotNull).problem(context, expression);
|
||||
}
|
||||
if (nullability == Nullability.UNKNOWN && lho instanceof PsiReferenceExpression) {
|
||||
PsiField field = tryCast(((PsiReferenceExpression)lho).resolve(), PsiField.class);
|
||||
if (field != null && !field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return assigningToNonAnnotatedField.problem(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for top expression with the same nullability as given expression. That is: skips casts or conditionals, which don't unbox;
|
||||
* goes up from switch expression breaks or expression-branches.
|
||||
@@ -329,7 +339,7 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
* @return the top expression
|
||||
*/
|
||||
@NotNull
|
||||
private static PsiExpression findTopExpression(@NotNull PsiExpression expression) {
|
||||
static PsiExpression findTopExpression(@NotNull PsiExpression expression) {
|
||||
PsiExpression context = expression;
|
||||
while (true) {
|
||||
PsiElement parent = context.getParent();
|
||||
@@ -368,16 +378,90 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
return unboxingNullable.problem(context, expression);
|
||||
}
|
||||
|
||||
static List<NullabilityProblem<?>> postprocessNullabilityProblems(Collection<NullabilityProblem<?>> problems) {
|
||||
List<NullabilityProblem<?>> unchanged = new ArrayList<>();
|
||||
Map<PsiExpression, NullabilityProblem<?>> expressionToProblem = new HashMap<>();
|
||||
for (NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
NullabilityProblemKind<?> kind = problem.getKind();
|
||||
if (expression == null) {
|
||||
unchanged.add(problem);
|
||||
continue;
|
||||
}
|
||||
if (innerClassNPE == kind || callNPE == kind || arrayAccessNPE == kind || fieldAccessNPE == kind) {
|
||||
// Qualifier-problems are reported on top-expression level for now as it's rare case to have
|
||||
// something complex in qualifier and we highlight not the qualifier itself, but something else (e.g. called method name)
|
||||
unchanged.add(problem.withExpression(findTopExpression(expression)));
|
||||
continue;
|
||||
}
|
||||
// Merge ternary problems reported for both branches into single problem
|
||||
while (true) {
|
||||
PsiExpression top = skipParenthesesAndObjectCastsUp(expression);
|
||||
PsiConditionalExpression ternary = tryCast(top.getParent(), PsiConditionalExpression.class);
|
||||
if (ternary != null) {
|
||||
PsiExpression otherBranch = null;
|
||||
if (ternary.getThenExpression() == top) {
|
||||
otherBranch = ternary.getElseExpression();
|
||||
}
|
||||
else if (ternary.getElseExpression() == top) {
|
||||
otherBranch = ternary.getThenExpression();
|
||||
}
|
||||
if (otherBranch != null) {
|
||||
otherBranch = skipParenthesesAndObjectCastsDown(otherBranch);
|
||||
NullabilityProblem<?> otherBranchProblem = expressionToProblem.remove(otherBranch);
|
||||
if (otherBranchProblem != null) {
|
||||
expression = ternary;
|
||||
problem = problem.withExpression(ternary);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
expressionToProblem.put(expression, problem);
|
||||
}
|
||||
return StreamEx.of(unchanged, expressionToProblem.values()).toFlatList(Function.identity());
|
||||
}
|
||||
|
||||
private static PsiExpression skipParenthesesAndObjectCastsDown(PsiExpression expression) {
|
||||
while (true) {
|
||||
if (expression instanceof PsiParenthesizedExpression) {
|
||||
expression = ((PsiParenthesizedExpression)expression).getExpression();
|
||||
}
|
||||
else if (expression instanceof PsiTypeCastExpression && !(expression.getType() instanceof PsiPrimitiveType)) {
|
||||
expression = ((PsiTypeCastExpression)expression).getOperand();
|
||||
}
|
||||
else {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiExpression skipParenthesesAndObjectCastsUp(PsiExpression expression) {
|
||||
PsiExpression top = expression;
|
||||
while (true) {
|
||||
PsiElement parent = top.getParent();
|
||||
if (parent instanceof PsiParenthesizedExpression ||
|
||||
(parent instanceof PsiTypeCastExpression && !(((PsiTypeCastExpression)parent).getType() instanceof PsiPrimitiveType))) {
|
||||
top = (PsiExpression)parent;
|
||||
}
|
||||
else {
|
||||
return top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a concrete nullability problem on PSI which consists of PSI element (anchor) and {@link NullabilityProblemKind}.
|
||||
* @param <T> a type of anchor element
|
||||
*/
|
||||
public static final class NullabilityProblem<T extends PsiElement> {
|
||||
private final @NotNull NullabilityProblemKind<? super T> myKind;
|
||||
private final @NotNull NullabilityProblemKind<T> myKind;
|
||||
private final @NotNull T myAnchor;
|
||||
private final @Nullable PsiExpression myDereferencedExpression;
|
||||
|
||||
NullabilityProblem(@NotNull NullabilityProblemKind<? super T> kind, @NotNull T anchor, @Nullable PsiExpression dereferencedExpression) {
|
||||
NullabilityProblem(@NotNull NullabilityProblemKind<T> kind, @NotNull T anchor, @Nullable PsiExpression dereferencedExpression) {
|
||||
myKind = kind;
|
||||
myAnchor = anchor;
|
||||
myDereferencedExpression = dereferencedExpression;
|
||||
@@ -419,22 +503,32 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
return myKind.myNormalMessage;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NullabilityProblemKind<T> getKind() {
|
||||
return myKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof NullabilityProblem)) return false;
|
||||
NullabilityProblem<?> problem = (NullabilityProblem<?>)o;
|
||||
return myKind.equals(problem.myKind) && myAnchor.equals(problem.myAnchor);
|
||||
return myKind.equals(problem.myKind) && myAnchor.equals(problem.myAnchor) &&
|
||||
Objects.equals(myDereferencedExpression, problem.myDereferencedExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myKind, myAnchor);
|
||||
return Objects.hash(myKind, myAnchor, myDereferencedExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + myKind + "] " + myAnchor.getText();
|
||||
}
|
||||
|
||||
public NullabilityProblem<T> withExpression(PsiExpression expression) {
|
||||
return expression == myDereferencedExpression ? this : new NullabilityProblem<>(myKind, myAnchor, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
return ((DfaFactMapValue)value).withFact(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
|
||||
}
|
||||
if (ok) return value;
|
||||
if (memState.isNull(value) && NullabilityProblemKind.nullableFunctionReturn.isMyProblem(problem)) {
|
||||
if (memState.isNull(value) && problem != null && problem.getKind() == NullabilityProblemKind.nullableFunctionReturn) {
|
||||
return value.getFactory().getFactValue(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
|
||||
}
|
||||
if (value instanceof DfaVariableValue) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// "Replace with '(b ? null : "foo") != null ?:'" "true"
|
||||
// "Replace with '(b ? foo(1) : foo(2)) != null ?:'" "true"
|
||||
class A {
|
||||
void bar(String s) {}
|
||||
|
||||
void foo(boolean b){
|
||||
bar((b ? null : "foo") != null ? b ? null : "foo" : null);
|
||||
bar((b ? foo(1) : foo(2)) != null ? b ? foo(1) : foo(2) : null);
|
||||
}
|
||||
|
||||
static String foo(int x) {
|
||||
return x > 0 ? "pos" : x < 0 ? "neg" : null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
// "Replace with '(b ? null : "foo") != null ?:'" "true"
|
||||
// "Replace with '(b ? foo(1) : foo(2)) != null ?:'" "true"
|
||||
class A {
|
||||
void bar(String s) {}
|
||||
|
||||
void foo(boolean b){
|
||||
bar(b ? null<caret> : "foo");
|
||||
bar(b ? <caret>foo(1) : foo(2));
|
||||
}
|
||||
|
||||
static String foo(int x) {
|
||||
return x > 0 ? "pos" : x < 0 ? "neg" : null;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
// "Surround with 'if ((b ? null : "foo") != null)'" "true"
|
||||
// "Surround with 'if ((b ? foo(1) : foo(2)) != null)'" "true"
|
||||
class A {
|
||||
void bar(String s) {}
|
||||
|
||||
void foo(boolean b){
|
||||
if ((b ? null : "foo") != null) {
|
||||
bar(b ? null : "foo");
|
||||
if ((b ? foo(1) : foo(2)) != null) {
|
||||
bar(b ? foo(1) : foo(2));
|
||||
}
|
||||
}
|
||||
|
||||
static String foo(int x) {
|
||||
return x > 0 ? "pos" : x < 0 ? "neg" : null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
// "Surround with 'if ((b ? null : "foo") != null)'" "true"
|
||||
// "Surround with 'if ((b ? foo(1) : foo(2)) != null)'" "true"
|
||||
class A {
|
||||
void bar(String s) {}
|
||||
|
||||
void foo(boolean b){
|
||||
bar(b ? null<caret> : "foo");
|
||||
bar(b ? foo(1)<caret> : foo(2));
|
||||
}
|
||||
|
||||
static String foo(int x) {
|
||||
return x > 0 ? "pos" : x < 0 ? "neg" : null;
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,20 @@ class FooImpl implements Foo {
|
||||
return null;
|
||||
}
|
||||
else if (Math.random() > 0.5) {
|
||||
@NotNull Object @Nullable [] arr1 = new Object[]{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), <warning descr="'null' is stored to an array of @NotNull elements">Math.random() > 0.5 ? new Object() : null</warning>};
|
||||
@NotNull Object @Nullable [] arr2 = {<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), <warning descr="'null' is stored to an array of @NotNull elements">Math.random() > 0.5 ? new Object() : null</warning>};
|
||||
return new Object[]{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), <warning descr="'null' is stored to an array of @NotNull elements">Math.random() > 0.5 ? new Object() : null</warning>};
|
||||
@NotNull Object @Nullable [] arr1 = new Object[]{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), Math.random() > 0.5 ? new Object() : <warning descr="'null' is stored to an array of @NotNull elements">null</warning>, <warning descr="Expression 'foo()' might evaluate to null but is stored to an array of @NotNull elements">foo()</warning>};
|
||||
@NotNull Object @Nullable [] arr2 = {<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), Math.random() > 0.5 ? new Object() : <warning descr="'null' is stored to an array of @NotNull elements">null</warning>, <warning descr="Expression 'foo()' might evaluate to null but is stored to an array of @NotNull elements">foo()</warning>};
|
||||
return new Object[]{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), Math.random() > 0.5 ? new Object() : <warning descr="'null' is stored to an array of @NotNull elements">null</warning>, <warning descr="Expression 'foo()' might evaluate to null but is stored to an array of @NotNull elements">foo()</warning>};
|
||||
}
|
||||
return new @NotNull Object @Nullable []{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), <warning descr="'null' is stored to an array of @NotNull elements">Math.random() > 0.5 ? new Object() : null</warning>};
|
||||
return new @NotNull Object @Nullable []{<warning descr="'null' is stored to an array of @NotNull elements">null</warning>, new Object(), Math.random() > 0.5 ? new Object() : <warning descr="'null' is stored to an array of @NotNull elements">null</warning>, <warning descr="Expression 'foo()' might evaluate to null but is stored to an array of @NotNull elements">foo()</warning>};
|
||||
}
|
||||
|
||||
void test() {
|
||||
@NotNull Object @Nullable [] array = getNullableArrayOfNotNullObjects();
|
||||
assert array != null;
|
||||
array[0] = <warning descr="'null' is stored to an array of @NotNull elements">null</warning>;
|
||||
array[1] = <warning descr="'null' is stored to an array of @NotNull elements">Math.random() > 0.5 ? null : "foo"</warning>;
|
||||
array[1] = Math.random() > 0.5 ? <warning descr="'null' is stored to an array of @NotNull elements">null</warning> : "foo";
|
||||
array[3] = <warning descr="Expression 'foo()' might evaluate to null but is stored to an array of @NotNull elements">foo()</warning>;
|
||||
}
|
||||
|
||||
native @Nullable Object foo();
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public class OptionalInlining {
|
||||
System.out.println("impossible");
|
||||
}
|
||||
if(opt.isPresent()) {
|
||||
if(<warning descr="Condition 'opt.transform(x -> x.isEmpty() ? null : x).toJavaUtil().isPresent()' is always 'true'">opt.transform(x -> <warning descr="Function may return null, but it's not allowed here">x.isEmpty() ? null : x</warning>).toJavaUtil().isPresent()</warning>) {
|
||||
if(<warning descr="Condition 'opt.transform(x -> x.isEmpty() ? null : x).toJavaUtil().isPresent()' is always 'true'">opt.transform(x -> x.isEmpty() ? <warning descr="Function may return null, but it's not allowed here">null</warning> : x).toJavaUtil().isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
if(opt.toJavaUtil().map(x -> x.isEmpty() ? null : x).isPresent()) {
|
||||
@@ -157,7 +157,7 @@ public class OptionalInlining {
|
||||
opt.flatMap(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
|
||||
opt.flatMap(x -> <warning descr="Function may return null, but it's not allowed here">null</warning>);
|
||||
opt.flatMap(<warning descr="Function may return null, but it's not allowed here">this::nullableOptionalMethod</warning>);
|
||||
opt.flatMap(x -> <warning descr="Function may return null, but it's not allowed here">x.isEmpty() ? null : Optional.of(x)</warning>);
|
||||
opt.flatMap(x -> x.isEmpty() ? <warning descr="Function may return null, but it's not allowed here">null</warning> : Optional.of(x));
|
||||
opt.flatMap(x -> {
|
||||
if (x.isEmpty()) {
|
||||
return <warning descr="Function may return null, but it's not allowed here">null</warning>;
|
||||
|
||||
@@ -8,12 +8,12 @@ import java.util.stream.*;
|
||||
public class StreamTypeAnnoInlining {
|
||||
void testToArray() {
|
||||
@NotNull Object @NotNull[] foo0 = Stream.of("a", "b")
|
||||
.map(x-> <warning descr="Function may return null, but it's not allowed here">"a".equals(x) ? null : x.toUpperCase()</warning>)
|
||||
.map(x-> "a".equals(x) ? <warning descr="Function may return null, but it's not allowed here">null</warning> : x.toUpperCase())
|
||||
.toArray();
|
||||
|
||||
// IDEA-194697
|
||||
@NotNull String @NotNull[] foo = Stream.of("a", "b")
|
||||
.map(x-> <warning descr="Function may return null, but it's not allowed here">"a".equals(x) ? null : x.toUpperCase()</warning>)
|
||||
.map(x-> "a".equals(x) ? <warning descr="Function may return null, but it's not allowed here">null</warning> : x.toUpperCase())
|
||||
.toArray(String[]::new);
|
||||
|
||||
@NotNull String @NotNull[] foo1 = Stream.of("b", "c")
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
public class SwitchExpressionsNullabilityJava12 {
|
||||
void cons(@NotNull String str) {}
|
||||
|
||||
void test(@Nullable String a, @Nullable String b, int i, boolean f) {
|
||||
cons(((String)(switch(i) {
|
||||
case 1 -> <warning descr="Argument 'a' might be null">a</warning>;
|
||||
case 2 -> "foo";
|
||||
case 3 -> <warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>;
|
||||
case 4 -> {
|
||||
System.out.println("four!");
|
||||
break f ? i : <warning descr="Argument 'b' might be null">b</warning>;
|
||||
}
|
||||
default -> "bar";
|
||||
})));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
final class MyClass {
|
||||
void cons(Object x) {}
|
||||
|
||||
void test(@Nullable Object a, @Nullable Object b, @Nullable Object c, boolean f1, boolean f2) {
|
||||
cons(<warning descr="Argument 'f1 ? a : b' might be null but passed to non-annotated parameter">f1 ? a : b</warning>);
|
||||
cons(f1 ? <warning descr="Argument 'a' might be null but passed to non-annotated parameter">a</warning> : new Object());
|
||||
cons(f1 ? new Object() : <warning descr="Argument 'a' might be null but passed to non-annotated parameter">a</warning>);
|
||||
cons(f1 ? <warning descr="Argument 'f2 ? a : (Object)b' might be null but passed to non-annotated parameter">f2 ? a : (Object)b</warning> : new Object());
|
||||
cons(f1 ? f2 ? <warning descr="Argument 'a' might be null but passed to non-annotated parameter">a</warning> : new Object() : <warning descr="Argument 'b' might be null but passed to non-annotated parameter">b</warning>);
|
||||
cons(f1 ? f2 ? new Object() : <warning descr="Argument 'a' might be null but passed to non-annotated parameter">a</warning> : <warning descr="Argument 'b' might be null but passed to non-annotated parameter">b</warning>);
|
||||
cons(<warning descr="Argument 'f1 ? f2 ? a : b : ((Object)c)' might be null but passed to non-annotated parameter">f1 ? f2 ? a : b : ((Object)c)</warning>);
|
||||
cons(f1 ? <warning descr="Argument 'f2 ? a : b' might be null but passed to non-annotated parameter">f2 ? a : b</warning> : f2 ? <warning descr="Argument 'c' might be null but passed to non-annotated parameter">c</warning> : new Object());
|
||||
}
|
||||
}
|
||||
@@ -40,4 +40,5 @@ public class DataFlowInspection12Test extends DataFlowInspectionTestCase {
|
||||
|
||||
public void testSwitchStatementsJava12() { doTest(); }
|
||||
public void testSwitchExpressionsJava12() { doTest(); }
|
||||
public void testSwitchExpressionsNullabilityJava12() { doTest(); }
|
||||
}
|
||||
@@ -669,4 +669,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testAssertNullEphemeral() { doTest(); }
|
||||
public void testNotNullAnonymousConstructor() { doTest(); }
|
||||
public void testCaughtNPE() { doTest(); }
|
||||
public void testTernaryNullability() { doTest(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user