blob: 7ce5e3f361835c08000eecf239ba007aafae465d [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.inferNullity;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.intention.AddAnnotationFix;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Query;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class NullityInferrer {
private static final int MAX_PASSES = 10;
public static final String NOTHING_FOUND_TO_INFER = "Nothing found to infer";
private int numAnnotationsAdded = 0;
private final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNotNullSet = new ArrayList<SmartPsiElementPointer<? extends PsiModifierListOwner>>();
private final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNullableSet = new ArrayList<SmartPsiElementPointer<? extends PsiModifierListOwner>>();
private final boolean myAnnotateLocalVariables;
private final SmartPointerManager myPointerManager;
public NullityInferrer(boolean annotateLocalVariables, Project project) {
myAnnotateLocalVariables = annotateLocalVariables;
myPointerManager = SmartPointerManager.getInstance(project);
}
private boolean expressionIsNeverNull(@Nullable PsiExpression expression) {
if (expression == null) {
return false;
}
final ExpressionIsNeverNullVisitor visitor = new ExpressionIsNeverNullVisitor();
expression.accept(visitor);
return visitor.isNeverNull();
}
protected boolean expressionIsSometimesNull(@Nullable PsiExpression expression) {
if (expression == null) {
return false;
}
final ExpressionIsSometimesNullVisitor visitor = new ExpressionIsSometimesNullVisitor();
expression.accept(visitor);
return visitor.isSometimesNull();
}
private boolean methodNeverReturnsNull(@NotNull PsiMethod method) {
final MethodNeverReturnsNullVisitor visitor = new MethodNeverReturnsNullVisitor();
method.accept(visitor);
return visitor.getNeverReturnsNull();
}
private boolean variableNeverAssignedNull(@NotNull PsiVariable variable) {
final PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
if (!expressionIsNeverNull(initializer)) {
return false;
}
}
else if (!variable.hasModifierProperty(PsiModifier.FINAL)) {
return false;
}
final Query<PsiReference> references = ReferencesSearch.search(variable);
for (final PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) {
continue;
}
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiAssignmentExpression)) {
continue;
}
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
if (assignment.getLExpression().equals(element) &&
!expressionIsNeverNull(assignment.getRExpression())) {
return false;
}
}
return true;
}
private boolean variableSometimesAssignedNull(@NotNull PsiVariable variable) {
final PsiExpression initializer = variable.getInitializer();
if (initializer != null && expressionIsSometimesNull(initializer)) {
return true;
}
final Query<PsiReference> references = ReferencesSearch.search(variable);
for (final PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) {
continue;
}
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiAssignmentExpression)) {
continue;
}
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
if (assignment.getLExpression().equals(element) && expressionIsSometimesNull(assignment.getRExpression())) {
return true;
}
}
return false;
}
public void collect(@NotNull PsiFile file) {
int prevNumAnnotationsAdded;
int pass = 0;
do {
final NullityInferrerVisitor visitor = new NullityInferrerVisitor();
prevNumAnnotationsAdded = numAnnotationsAdded;
file.accept(visitor);
pass++;
}
while (prevNumAnnotationsAdded < numAnnotationsAdded && pass < MAX_PASSES);
}
@TestOnly
public void apply(final Project project) {
final NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
for (SmartPsiElementPointer<? extends PsiModifierListOwner> pointer : myNullableSet) {
annotateNullable(project, manager, pointer.getElement());
}
for (SmartPsiElementPointer<? extends PsiModifierListOwner> pointer : myNotNullSet) {
annotateNotNull(project, manager, pointer.getElement());
}
if (myNullableSet.isEmpty() && myNotNullSet.isEmpty()) {
throw new RuntimeException(NOTHING_FOUND_TO_INFER);
}
}
public static void nothingFoundMessage(final Project project) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Messages.showInfoMessage(project, "No places found to infer @Nullable/@NotNull", "Infer Nullity Results");
}
});
}
private static void annotateNotNull(Project project,
NullableNotNullManager manager,
final PsiModifierListOwner element) {
if (element != null) {
if (element instanceof PsiField && ((PsiField)element).hasInitializer() && element.hasModifierProperty(PsiModifier.FINAL)) return;
invoke(project, element, manager.getDefaultNotNull(), manager.getDefaultNullable());
}
}
private static void annotateNullable(Project project,
NullableNotNullManager manager,
final PsiModifierListOwner element) {
if (element != null) {
invoke(project, element, manager.getDefaultNullable(), manager.getDefaultNotNull());
}
}
private static void invoke(final Project project,
final PsiModifierListOwner element,
final String fqn, final String toRemove) {
WriteCommandAction.runWriteCommandAction(project, new Runnable() {
@Override
public void run() {
new AddAnnotationFix(fqn, element, toRemove).invoke(project, null, element.getContainingFile());
}
});
}
public int getCount() {
return myNotNullSet.size() + myNullableSet.size();
}
public static void apply(Project project, NullableNotNullManager manager, UsageInfo info) {
if (info instanceof NullableUsageInfo) {
annotateNullable(project, manager, (PsiModifierListOwner)info.getElement());
} else if (info instanceof NotNullUsageInfo) {
annotateNotNull(project, manager, (PsiModifierListOwner)info.getElement());
}
}
private boolean shouldIgnore(PsiModifierListOwner element) {
if (!myAnnotateLocalVariables){
if (element instanceof PsiLocalVariable) return true;
if (element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiForeachStatement) return true;
}
return false;
}
private void registerNullableAnnotation(@NotNull PsiModifierListOwner method) {
registerAnnotation(method, true);
}
private void registerNotNullAnnotation(@NotNull PsiModifierListOwner method) {
registerAnnotation(method, false);
}
private void registerAnnotation(@NotNull PsiModifierListOwner method, boolean isNullable) {
final SmartPsiElementPointer<PsiModifierListOwner> methodPointer = myPointerManager.createSmartPsiElementPointer(method);
if (isNullable) {
myNullableSet.add(methodPointer);
}
else {
myNotNullSet.add(methodPointer);
}
numAnnotationsAdded++;
}
private static class NullableUsageInfo extends UsageInfo {
public NullableUsageInfo(@NotNull PsiElement element) {
super(element);
}
}
private static class NotNullUsageInfo extends UsageInfo {
private NotNullUsageInfo(@NotNull PsiElement element) {
super(element);
}
}
public void collect(List<UsageInfo> usages) {
collect(usages, true);
collect(usages, false);
}
private void collect(List<UsageInfo> usages, boolean nullable) {
final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> set = nullable ? myNullableSet : myNotNullSet;
for (SmartPsiElementPointer<? extends PsiModifierListOwner> elementPointer : set) {
final PsiModifierListOwner element = elementPointer.getElement();
if (element != null && !shouldIgnore(element)) {
usages.add(nullable ? new NullableUsageInfo(element) : new NotNullUsageInfo(element));
}
}
}
private class ExpressionIsNeverNullVisitor extends JavaElementVisitor {
private boolean neverNull = true;
@Override
public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
neverNull = !"null".equals(expression.getText());
}
@Override
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
neverNull = expressionIsNeverNull(expression.getRExpression());
}
@Override
public void visitAssertStatement(PsiAssertStatement statement) {
}
@Override
public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) {
final PsiExpression condition = expression.getCondition();
final PsiExpression thenExpression = expression.getThenExpression();
final PsiExpression elseExpression = expression.getElseExpression();
if (canTrunkImpossibleBrunch(condition, elseExpression)) {
neverNull = expressionIsNeverNull(thenExpression);
return;
}
neverNull = expressionIsNeverNull(thenExpression) ||
expressionIsNeverNull(elseExpression);
}
@Override
public void visitParenthesizedExpression(@NotNull PsiParenthesizedExpression expression) {
neverNull = expressionIsNeverNull(expression.getExpression());
}
@Override
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
neverNull = false;
}
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
final PsiElement referent = expression.resolve();
if (referent instanceof PsiVariable) {
final PsiVariable var = (PsiVariable)referent;
if (var instanceof PsiEnumConstant || isNotNull(var)) {
neverNull = true;
return;
}
}
neverNull = false;
}
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
final PsiMethod method = expression.resolveMethod();
neverNull = method != null && isNotNull(method);
}
private boolean isNeverNull() {
return neverNull;
}
}
private static boolean trunkImpossibleBrunch(PsiExpression condition,
PsiExpression elseExpression,
PsiExpression rOperand,
PsiExpression lOperand) {
if (rOperand instanceof PsiLiteralExpression && "null".equals(rOperand.getText()) && lOperand instanceof PsiReferenceExpression) {
final PsiElement resolve = ((PsiReferenceExpression)lOperand).resolve();
if (resolve instanceof PsiVariable &&
((PsiBinaryExpression)condition).getOperationTokenType() == JavaTokenType.EQEQ &&
elseExpression instanceof PsiReferenceExpression &&
((PsiReferenceExpression)elseExpression).resolve() == resolve) {
return true;
}
}
return false;
}
private static boolean canTrunkImpossibleBrunch(PsiExpression condition, PsiExpression elseExpression) {
if (condition instanceof PsiBinaryExpression) {
final PsiExpression rOperand = ((PsiBinaryExpression)condition).getROperand();
final PsiExpression lOperand = ((PsiBinaryExpression)condition).getLOperand();
if (trunkImpossibleBrunch(condition, elseExpression, rOperand, lOperand) ||
trunkImpossibleBrunch(condition, elseExpression, lOperand, rOperand)) {
return true;
}
}
return false;
}
private class ExpressionIsSometimesNullVisitor extends JavaRecursiveElementWalkingVisitor{
private boolean sometimesNull = false;
@Override
public void visitElement(PsiElement element) {
if (sometimesNull) return;
super.visitElement(element);
}
@Override
public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
sometimesNull = "null".equals(expression.getText());
}
@Override
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
sometimesNull = expressionIsSometimesNull(expression.getRExpression());
}
@Override
public void visitAssertStatement(PsiAssertStatement statement) {
}
@Override
public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) {
final PsiExpression condition = expression.getCondition();
final PsiExpression thenExpression = expression.getThenExpression();
final PsiExpression elseExpression = expression.getElseExpression();
if (canTrunkImpossibleBrunch(condition, elseExpression)) {
sometimesNull = expressionIsSometimesNull(thenExpression);
return;
}
sometimesNull = expressionIsSometimesNull(thenExpression) ||
expressionIsSometimesNull(elseExpression);
}
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
final PsiElement referent = expression.resolve();
if (referent instanceof PsiVariable) {
final PsiVariable var = (PsiVariable)referent;
if (isNullable(var)) {
sometimesNull = true;
}
}
}
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
final PsiMethod method = expression.resolveMethod();
if (method != null) {
sometimesNull = isNullable(method);
}
}
private boolean isSometimesNull() {
return sometimesNull;
}
}
private class MethodNeverReturnsNullVisitor extends JavaRecursiveElementWalkingVisitor {
private boolean neverReturnsNull = true;
@Override
public void visitClass(PsiClass aClass) {
//so as not to drill into anonymous classes
}
@Override
public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
super.visitReturnStatement(statement);
final PsiExpression value = statement.getReturnValue();
if (expressionIsNeverNull(value)) {
return;
}
if (value instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)value;
//if it's a recursive call, don't throw the red flag
final PsiMethod method = methodCallExpression.resolveMethod();
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(value, PsiMethod.class);
if (method != null && method.equals(containingMethod)) {
return;
}
}
neverReturnsNull = false;
}
private boolean getNeverReturnsNull() {
return neverReturnsNull;
}
}
private boolean isNotNull(PsiModifierListOwner owner) {
if (NullableNotNullManager.isNotNull(owner)) {
return true;
}
final SmartPsiElementPointer<PsiModifierListOwner> pointer = myPointerManager.createSmartPsiElementPointer(owner);
return myNotNullSet.contains(pointer);
}
private boolean isNullable(PsiModifierListOwner owner) {
if (NullableNotNullManager.isNullable(owner)) {
return true;
}
final SmartPsiElementPointer<PsiModifierListOwner> pointer = myPointerManager.createSmartPsiElementPointer(owner);
return myNullableSet.contains(pointer);
}
private class NullityInferrerVisitor extends JavaRecursiveElementVisitor{
@Override
public void visitMethod(@NotNull PsiMethod method) {
super.visitMethod(method);
if (method.isConstructor() || method.getReturnType() instanceof PsiPrimitiveType) {
return;
}
final Collection<PsiMethod> overridingMethods = OverridingMethodsSearch.search(method).findAll();
for (final PsiMethod overridingMethod : overridingMethods) {
if (isNullable(overridingMethod)) {
registerNullableAnnotation(method);
return;
}
}
final NullableNotNullManager manager = NullableNotNullManager.getInstance(method.getProject());
if (!manager.isNotNull(method, false) && manager.isNotNull(method, true)) {
registerNotNullAnnotation(method);
return;
}
if (isNotNull(method) || isNullable(method)) {
return;
}
final PsiCodeBlock body = method.getBody();
if (body != null) {
final boolean[] sometimesReturnsNull = new boolean[1];
body.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(PsiClass aClass) {
}
@Override
public void visitElement(PsiElement element) {
if (sometimesReturnsNull[0]) return;
super.visitElement(element);
}
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
super.visitReturnStatement(statement);
final PsiExpression value = statement.getReturnValue();
if (expressionIsSometimesNull(value)) {
sometimesReturnsNull[0] = true;
}
}
});
if (sometimesReturnsNull[0]) {
registerNullableAnnotation(method);
return;
}
}
if (methodNeverReturnsNull(method)) {
for (final PsiMethod overridingMethod : overridingMethods) {
if (!isNotNull(overridingMethod)) {
return;
}
}
//and check that all of the submethods are not nullable
registerNotNullAnnotation(method);
}
}
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
super.visitLocalVariable(variable);
if (variable.getType() instanceof PsiPrimitiveType ||
isNotNull(variable) || isNullable(variable)) {
return;
}
if (variableNeverAssignedNull(variable)) {
registerNotNullAnnotation(variable);
}
if (variableSometimesAssignedNull(variable)) {
registerNullableAnnotation(variable);
}
}
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
super.visitParameter(parameter);
if (parameter.getType() instanceof PsiPrimitiveType ||
isNotNull(parameter) || isNullable(parameter)) {
return;
}
final PsiElement grandParent = parameter.getDeclarationScope();
if (grandParent instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)grandParent;
if (method.getBody() != null) {
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(method))) {
final PsiElement place = reference.getElement();
if (place instanceof PsiReferenceExpression) {
final PsiReferenceExpression expr = (PsiReferenceExpression)place;
final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
if (processParameter(parameter, expr, parent)) return;
if (isNotNull(method)) {
PsiElement toReturn = parent;
if (parent instanceof PsiConditionalExpression &&
((PsiConditionalExpression)parent).getCondition() != expr) { //todo check conditional operations
toReturn = parent.getParent();
}
if (toReturn instanceof PsiReturnStatement) {
registerNotNullAnnotation(parameter);
return;
}
}
}
}
}
}
else if (grandParent instanceof PsiForeachStatement) {
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(grandParent))) {
final PsiElement place = reference.getElement();
if (place instanceof PsiReferenceExpression) {
final PsiReferenceExpression expr = (PsiReferenceExpression)place;
final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
if (processParameter(parameter, expr, parent)) return;
}
}
}
else {
if (variableNeverAssignedNull(parameter)) {
registerNotNullAnnotation(parameter);
}
if (variableSometimesAssignedNull(parameter)) {
registerNullableAnnotation(parameter);
}
}
}
private boolean processParameter(PsiParameter parameter, PsiReferenceExpression expr, PsiElement parent) {
if (PsiUtil.isAccessedForWriting(expr)) return true;
if (parent instanceof PsiBinaryExpression) { //todo check if comparison operation
PsiExpression opposite = null;
final PsiExpression lOperand = ((PsiBinaryExpression)parent).getLOperand();
final PsiExpression rOperand = ((PsiBinaryExpression)parent).getROperand();
if (lOperand == expr) {
opposite = rOperand;
}
else if (rOperand == expr) {
opposite = lOperand;
}
if (opposite != null && opposite.getType() == PsiType.NULL) {
if (parent.getParent() instanceof PsiAssertStatement &&
((PsiBinaryExpression)parent).getOperationTokenType() == JavaTokenType.NE) {
registerNotNullAnnotation(parameter);
return true;
}
registerNullableAnnotation(parameter);
return true;
}
}
else if (parent instanceof PsiInstanceOfExpression) {
return true;
}
else if (parent instanceof PsiReferenceExpression) {
final PsiExpression qualifierExpression = ((PsiReferenceExpression)parent).getQualifierExpression();
if (qualifierExpression == expr) {
registerNotNullAnnotation(parameter);
return true;
}
else {
PsiElement exprParent = expr.getParent();
while (exprParent instanceof PsiTypeCastExpression || exprParent instanceof PsiParenthesizedExpression) {
if (qualifierExpression == exprParent) {
registerNotNullAnnotation(parameter);
return true;
}
exprParent = exprParent.getParent();
}
}
}
else if (parent instanceof PsiAssignmentExpression) {
if (((PsiAssignmentExpression)parent).getRExpression() == expr) {
final PsiExpression expression = ((PsiAssignmentExpression)parent).getLExpression();
if (expression instanceof PsiReferenceExpression) {
final PsiElement resolve = ((PsiReferenceExpression)expression).resolve();
if (resolve instanceof PsiVariable) {
final PsiVariable localVar = (PsiVariable)resolve;
if (isNotNull(localVar)) {
registerNotNullAnnotation(parameter);
return true;
}
}
}
}
} else if (parent instanceof PsiForeachStatement) {
if (((PsiForeachStatement)parent).getIteratedValue() == expr) {
registerNotNullAnnotation(parameter);
return true;
}
}
else if (parent instanceof PsiSwitchStatement && ((PsiSwitchStatement)parent).getExpression() == expr) {
registerNotNullAnnotation(parameter);
return true;
}
final PsiCall call = PsiTreeUtil.getParentOfType(expr, PsiCall.class);
if (call != null) {
final PsiExpressionList argumentList = call.getArgumentList();
if (argumentList != null) {
final PsiExpression[] args = argumentList.getExpressions();
int idx = ArrayUtil.find(args, expr);
if (idx >= 0) {
final PsiMethod resolvedMethod = call.resolveMethod();
if (resolvedMethod != null) {
final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters();
if (idx < parameters.length) { //not vararg
final PsiParameter resolvedToParam = parameters[idx];
if (isNotNull(resolvedToParam) && !resolvedToParam.isVarArgs()) {
registerNotNullAnnotation(parameter);
return true;
}
}
}
}
}
}
return false;
}
@Override
public void visitField(@NotNull PsiField field) {
super.visitField(field);
if (field instanceof PsiEnumConstant) {
return;
}
if (field.getType() instanceof PsiPrimitiveType ||
isNotNull(field) || isNullable(field)) {
return;
}
if (variableNeverAssignedNull(field)) {
registerNotNullAnnotation(field);
}
if (variableSometimesAssignedNull(field)) {
registerNullableAnnotation(field);
}
}
}
}