/* * Copyright 2000-2015 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.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; private final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNotNullSet = new ArrayList<>(); private final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNullableSet = new ArrayList<>(); 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(); } private 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( () -> 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) { 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 { private 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; @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 visitLambdaExpression(PsiLambdaExpression expression) {} @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 JavaRecursiveElementWalkingVisitor{ @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 visitLambdaExpression(PsiLambdaExpression expression) {} @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); } } } }