an index to detect nulls passed as arguments of method parameters + DataFlowInspection and NullableStuffInspection use it to detect method call failings when one of parameters is null

This commit is contained in:
Dmitry Batkovich
2016-08-18 12:21:51 +03:00
parent 17a7a27274
commit 8a9227543c
23 changed files with 907 additions and 47 deletions
@@ -35,15 +35,14 @@ import java.util.*;
class ContractChecker extends DataFlowRunner {
private final PsiMethod myMethod;
private final MethodContract myContract;
private final boolean myOnTheFly;
private final Set<PsiElement> myViolations = ContainerUtil.newHashSet();
private final Set<PsiElement> myNonViolations = ContainerUtil.newHashSet();
private final Set<PsiElement> myFailures = ContainerUtil.newHashSet();
private ContractChecker(PsiMethod method, MethodContract contract, final boolean onTheFly) {
super(false, true, onTheFly);
myMethod = method;
myContract = contract;
myOnTheFly = onTheFly;
}
static Map<PsiElement, String> checkContractClause(PsiMethod method,
@@ -72,12 +71,6 @@ class ContractChecker extends DataFlowRunner {
return checker.getErrors();
}
@Override
protected boolean shouldCheckTimeLimit() {
if (!myOnTheFly) return false;
return super.shouldCheckTimeLimit();
}
@NotNull
@Override
protected DfaInstructionState[] acceptInstruction(@NotNull InstructionVisitor visitor, @NotNull DfaInstructionState instructionState) {
@@ -70,6 +70,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE;
public boolean IGNORE_ASSERT_STATEMENTS;
public boolean REPORT_CONSTANT_REFERENCE_VALUES = true;
public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
@Override
public JComponent createOptionsPanel() {
@@ -103,6 +104,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
@Override
public void visitMethod(PsiMethod method) {
analyzeCodeBlock(method.getBody(), holder, isOnTheFly);
analyzeNullLiteralMethodArguments(method, holder, isOnTheFly);
}
@Override
@@ -137,20 +139,31 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
};
}
protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
return null;
}
private void analyzeNullLiteralMethodArguments(PsiMethod method, ProblemsHolder holder, boolean isOnTheFly) {
if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER) {
for (PsiParameter parameter : NullParameterConstraintChecker.checkMethodParameters(method, isOnTheFly)) {
final String name = parameter.getName();
holder.registerProblem(parameter.getNameIdentifier(),
InspectionsBundle.message("dataflow.method.fails.with.null.argument", name),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
NullableStuffInspectionBase.getWrappedUiDependentQuickFix(this::createNavigateToNullParameterUsagesFix, parameter, isOnTheFly));
}
}
}
private void analyzeCodeBlock(@Nullable final PsiElement scope, ProblemsHolder holder, final boolean onTheFly) {
if (scope == null) return;
PsiClass containingClass = PsiTreeUtil.getParentOfType(scope, PsiClass.class);
if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass) && !(containingClass instanceof PsiEnumConstantInitializer)) return;
final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, !isInsideConstructorOrInitializer(
scope)) {
@Override
protected boolean shouldCheckTimeLimit() {
if (!onTheFly) return false;
return super.shouldCheckTimeLimit();
}
};
final StandardDataFlowRunner dfaRunner =
new StandardDataFlowRunner(TREAT_UNKNOWN_MEMBERS_AS_NULLABLE,
!isInsideConstructorOrInitializer(scope), onTheFly);
analyzeDfaWithNestedClosures(scope, holder, dfaRunner, Collections.singletonList(dfaRunner.createMemoryState()), onTheFly);
}
@@ -53,16 +53,17 @@ public class DataFlowRunner {
private final MultiMap<PsiElement, DfaMemoryState> myNestedClosures = new MultiMap<>();
@NotNull
private final DfaValueFactory myValueFactory;
private final boolean myShouldCheckLimitTime;
// Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction
// is executed more than this limit times.
static final int MAX_STATES_PER_BRANCH = 300;
protected DataFlowRunner() {
this(false, true);
this(false, true, false);
}
protected DataFlowRunner(boolean unknownMembersAreNullable, boolean honorFieldInitializers) {
protected DataFlowRunner(boolean unknownMembersAreNullable, boolean honorFieldInitializers, boolean shouldCheckLimitTime) {
myShouldCheckLimitTime = shouldCheckLimitTime;
myValueFactory = new DfaValueFactory(honorFieldInitializers, unknownMembersAreNullable);
}
@@ -270,7 +271,7 @@ public class DataFlowRunner {
}
protected boolean shouldCheckTimeLimit() {
return !ApplicationManager.getApplication().isUnitTestMode();
return myShouldCheckLimitTime && !ApplicationManager.getApplication().isUnitTestMode();
}
@NotNull
@@ -150,7 +150,7 @@ public class DfaPsiUtil {
public Result<Set<PsiField>> compute() {
final PsiCodeBlock body = constructor.getBody();
final Map<PsiField, Boolean> map = ContainerUtil.newHashMap();
final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(false, false) {
final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(false, false, false) {
private boolean isCallExposingNonInitializedFields(Instruction instruction) {
if (!(instruction instanceof MethodCallInstruction) ||
@@ -0,0 +1,138 @@
/*
* Copyright 2000-2016 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.dataFlow;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInspection.dataFlow.instructions.AssignInstruction;
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import com.intellij.codeInspection.dataFlow.instructions.ReturnInstruction;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
import com.intellij.util.SmartList;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
/**
* This checker uses following idea:
* On the checker's start mark all parameters with null-argument usages as violated (i.e. the method fails if parameter is null).
* A parameter can be amnestied (excluded from violated) when one of following statements is true:
* 1. If at least one successful method execution ({@link ReturnInstruction#isViaException()} == false)
* doesn't require a not-null value for the parameter ({@link DfaMemoryState#isNotNull(DfaValue) == false});
* OR
* 2. If the parameter has a reassignment while one of any method execution.
*
* All remaining violated parameters is required to be not-null for successful method execution.
*/
class NullParameterConstraintChecker extends DataFlowRunner {
private final Set<PsiParameter> myPossiblyViolatedParameters;
private NullParameterConstraintChecker(Collection<PsiParameter> parameters, boolean isOnTheFly) {
super(false, true, isOnTheFly);
myPossiblyViolatedParameters = new THashSet<>(parameters);
}
@NotNull
static PsiParameter[] checkMethodParameters(PsiMethod method, boolean isOnTheFly) {
if (method.getBody() == null) return PsiParameter.EMPTY_ARRAY;
final Collection<PsiParameter> nullableParameters = new SmartList<>();
final PsiParameter[] parameters = method.getParameterList().getParameters();
for (int index = 0; index < parameters.length; index++) {
PsiParameter parameter = parameters[index];
if (!(parameter.getType() instanceof PsiPrimitiveType) &&
!NullableNotNullManager.isNotNull(parameter) &&
JavaNullMethodArgumentUtil.hasNullArgument(method, index)) {
nullableParameters.add(parameter);
}
}
if (nullableParameters.isEmpty()) return PsiParameter.EMPTY_ARRAY;
final NullParameterConstraintChecker checker = new NullParameterConstraintChecker(nullableParameters, isOnTheFly);
checker.analyzeMethod(method.getBody(), new StandardInstructionVisitor());
return checker.myPossiblyViolatedParameters.toArray(new PsiParameter[checker.myPossiblyViolatedParameters.size()]);
}
@NotNull
@Override
protected DfaInstructionState[] acceptInstruction(@NotNull InstructionVisitor visitor, @NotNull DfaInstructionState instructionState) {
DfaMemoryState memState = instructionState.getMemoryState();
if (memState.isEphemeral()) {
return DfaInstructionState.EMPTY_ARRAY;
}
Instruction instruction = instructionState.getInstruction();
if (instruction instanceof AssignInstruction) {
final DfaValue value = ((AssignInstruction)instruction).getAssignedValue();
if (value instanceof DfaVariableValue) {
final PsiModifierListOwner psiVariable = ((DfaVariableValue)value).getPsiVariable();
if (psiVariable instanceof PsiParameter) {
myPossiblyViolatedParameters.remove(psiVariable);
}
}
}
if (instruction instanceof ReturnInstruction && !((ReturnInstruction)instruction).isViaException()) {
for (PsiParameter parameter : myPossiblyViolatedParameters.toArray(new PsiParameter[myPossiblyViolatedParameters.size()])) {
final DfaVariableValue dfaVar = getFactory().getVarFactory().createVariableValue(parameter, false);
if (!memState.isNotNull(dfaVar)) {
myPossiblyViolatedParameters.remove(parameter);
}
}
}
return super.acceptInstruction(visitor, instructionState);
}
@NotNull
@Override
protected DfaMemoryState createMemoryState() {
return new MyDfaMemoryState(getFactory());
}
private class MyDfaMemoryState extends DfaMemoryStateImpl {
protected MyDfaMemoryState(DfaValueFactory factory) {
super(factory);
}
protected MyDfaMemoryState(MyDfaMemoryState toCopy) {
super(toCopy);
}
@Override
public void flushVariable(@NotNull DfaVariableValue variable) {
final PsiModifierListOwner psi = variable.getPsiVariable();
if (psi instanceof PsiParameter && myPossiblyViolatedParameters.contains(psi)) return;
super.flushVariable(variable);
}
@NotNull
@Override
public DfaMemoryStateImpl createCopy() {
return new MyDfaMemoryState(this);
}
}
}
@@ -27,9 +27,9 @@ package com.intellij.codeInspection.dataFlow;
public class StandardDataFlowRunner extends DataFlowRunner {
public StandardDataFlowRunner() {
this(false, true);
this(false, true, false);
}
public StandardDataFlowRunner(boolean unknownMembersAreNullable, boolean honorFieldInitializers) {
super(unknownMembersAreNullable, honorFieldInitializers);
public StandardDataFlowRunner(boolean unknownMembersAreNullable, boolean honorFieldInitializers, boolean shouldCheckLimitTime) {
super(unknownMembersAreNullable, honorFieldInitializers, shouldCheckLimitTime);
}
}
@@ -25,17 +25,16 @@ import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -44,6 +43,7 @@ import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionTool {
// deprecated fields remain to minimize changes to users inspection profiles (which are often located in version control).
@@ -58,6 +58,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
@Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true;
@Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test
@SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true;
public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.nullable.NullableStuffInspectionBase");
@@ -85,7 +86,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
return new JavaElementVisitor() {
@Override
public void visitMethod(PsiMethod method) {
checkNullableStuffForMethod(method, holder);
checkNullableStuffForMethod(method, holder, isOnTheFly);
}
@Override
@@ -148,6 +149,10 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
};
}
protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
return null;
}
private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) {
final Project project = file.getProject();
final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
@@ -378,7 +383,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
return "NullableProblems";
}
private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder) {
private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder, boolean isOnFly) {
Annotated annotated = check(method, holder, method.getReturnType());
List<PsiMethod> superMethods = ContainerUtil.map(
@@ -387,7 +392,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject());
checkSupers(method, holder, annotated, superMethods, nullableManager);
checkParameters(method, holder, superMethods, nullableManager);
checkParameters(method, holder, superMethods, nullableManager, isOnFly);
checkOverriders(method, holder, annotated, nullableManager);
}
@@ -419,14 +424,18 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
holder.registerProblem(method.getNameIdentifier(),
InspectionsBundle.message("inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
wrapFix(fix));
fix);
break;
}
}
}
}
private void checkParameters(PsiMethod method, ProblemsHolder holder, List<PsiMethod> superMethods, NullableNotNullManager nullableManager) {
private void checkParameters(PsiMethod method,
ProblemsHolder holder,
List<PsiMethod> superMethods,
NullableNotNullManager nullableManager,
boolean isOnFly) {
PsiParameter[] parameters = method.getParameterList().getParameters();
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
@@ -462,7 +471,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
holder.registerProblem(parameter.getNameIdentifier(),
InspectionsBundle.message("inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(superParameter)),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
wrapFix(fix));
fix);
break;
}
}
@@ -477,11 +486,32 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.not.annotated", getPresentableAnnoName(parameter)),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
wrapFix(fix));
fix);
break;
}
}
}
checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter);
}
}
private void checkNullLiteralArgumentOfNotNullParameterUsages(PsiMethod method,
ProblemsHolder holder,
NullableNotNullManager nullableManager,
boolean isOnFly,
int parameterIdx,
PsiParameter parameter) {
if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && isNotNullNotInferred(parameter, false, false)) {
PsiAnnotation notNullAnnotation = nullableManager.getNotNullAnnotation(parameter, false);
if (JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) {
LocalQuickFix[] fixes = getWrappedUiDependentQuickFix(this::createNavigateToNullParameterUsagesFix, parameter, isOnFly);
boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.receives.null.literal", getPresentableAnnoName(parameter)),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
fixes);
}
}
}
@@ -533,7 +563,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
}
holder.registerProblem(psiElement, InspectionsBundle.message("nullable.stuff.problems.overridden.methods.are.not.annotated"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
wrapFix(fix));
fix);
methodQuickFixSuggested = true;
}
if (hasAnnotatedParameter) {
@@ -552,10 +582,10 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
holder.registerProblem(psiElement,
InspectionsBundle.message("nullable.stuff.problems.overridden.method.parameters.are.not.annotated"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
wrapFix(!applicable
!applicable
? createChangeDefaultNotNullFix(nullableManager, parameters[i])
: new AnnotateOverriddenMethodParameterFix(defaultNotNull,
nullableManager.getDefaultNullable())));
nullableManager.getDefaultNullable()));
parameterQuickFixSuggested[i] = true;
}
}
@@ -565,6 +595,13 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
}
}
@NotNull
public static LocalQuickFix[] getWrappedUiDependentQuickFix(Function<PsiParameter, LocalQuickFix> fixSupplier, PsiParameter parameter, boolean isOnTheFly) {
if (!isOnTheFly) return LocalQuickFix.EMPTY_ARRAY;
final LocalQuickFix fix = fixSupplier.apply(parameter);
return fix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[]{fix};
}
private static boolean isNotNullNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) {
Project project = owner.getProject();
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
@@ -585,12 +622,6 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
return !(anno != null && AnnotationUtil.isInferredAnnotation(anno));
}
@NotNull
private static LocalQuickFix[] wrapFix(LocalQuickFix fix) {
if (fix == null) return LocalQuickFix.EMPTY_ARRAY;
return new LocalQuickFix[]{fix};
}
private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) {
final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls());
if (annotation != null) {
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.NullableNotNullDialog;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.nullable.NullableStuffInspection;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
@@ -75,12 +76,18 @@ public class DataFlowInspection extends DataFlowInspectionBase {
};
}
@Override
protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
return new NullableStuffInspection.NavigateToNullLiteralArguments(parameter);
}
private class OptionsPanel extends JPanel {
private final JCheckBox myIgnoreAssertions;
private final JCheckBox myReportConstantReferences;
private final JCheckBox mySuggestNullables;
private final JCheckBox myDontReportTrueAsserts;
private final JCheckBox myTreatUnknownMembersAsNullable;
private final JCheckBox myReportNullArguments;
private OptionsPanel() {
super(new GridBagLayout());
@@ -138,6 +145,15 @@ public class DataFlowInspection extends DataFlowInspectionBase {
}
});
myReportNullArguments = new JCheckBox("Report 'null' literals passed to not-null required parameter");
myTreatUnknownMembersAsNullable.setSelected(REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER);
myTreatUnknownMembersAsNullable.getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = myTreatUnknownMembersAsNullable.isSelected();
}
});
gc.insets = JBUI.emptyInsets();
gc.gridy = 0;
add(mySuggestNullables, gc);
@@ -172,6 +188,9 @@ public class DataFlowInspection extends DataFlowInspectionBase {
gc.gridy++;
add(myTreatUnknownMembersAsNullable, gc);
gc.gridy++;
add(myReportNullArguments, gc);
}
}
@@ -16,10 +16,28 @@
package com.intellij.codeInspection.nullable;
import com.intellij.codeInsight.NullableNotNullDialog;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
@@ -27,6 +45,11 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class NullableStuffInspection extends NullableStuffInspectionBase {
@Override
protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
return new NavigateToNullLiteralArguments(parameter);
}
@Override
public JComponent createOptionsPanel() {
return new OptionsPanel();
@@ -41,6 +64,7 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
private JCheckBox myIgnoreExternalSuperNotNull;
private JCheckBox myNNParameterOverridesNA;
private JCheckBox myRequireNNFieldsInitialized;
private JBCheckBox myReportNullLiteralsPassedNotNullParameter;
private OptionsPanel() {
super(new BorderLayout());
@@ -58,6 +82,7 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
myReportNotAnnotatedGetter.addActionListener(actionListener);
myIgnoreExternalSuperNotNull.addActionListener(actionListener);
myRequireNNFieldsInitialized.addActionListener(actionListener);
myReportNullLiteralsPassedNotNullParameter.addActionListener(actionListener);
myConfigureAnnotationsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@@ -77,6 +102,7 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
myIgnoreExternalSuperNotNull.setSelected(IGNORE_EXTERNAL_SUPER_NOTNULL);
myNNParameterOverridesNA.setSelected(REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED);
myRequireNNFieldsInitialized.setSelected(REQUIRE_NOTNULL_FIELDS_INITIALIZED);
myReportNullLiteralsPassedNotNullParameter.setSelected(REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER);
myIgnoreExternalSuperNotNull.setEnabled(myNAMethodOverridesNN.isSelected());
}
@@ -88,9 +114,52 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
IGNORE_EXTERNAL_SUPER_NOTNULL = myIgnoreExternalSuperNotNull.isSelected();
REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = myNNParameterOverridesNA.isSelected();
REQUIRE_NOTNULL_FIELDS_INITIALIZED = myRequireNNFieldsInitialized.isSelected();
REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = myReportNullLiteralsPassedNotNullParameter.isSelected();
REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL;
myIgnoreExternalSuperNotNull.setEnabled(myNAMethodOverridesNN.isSelected());
}
}
public static class NavigateToNullLiteralArguments extends LocalQuickFixOnPsiElement {
public NavigateToNullLiteralArguments(@NotNull PsiParameter element) {
super(element);
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionsBundle.message("nullable.stuff.inspection.navigate.null.argument.usages.fix.family.name");
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) {
PsiParameter p = (PsiParameter)startElement;
final PsiMethod method = PsiTreeUtil.getParentOfType(p, PsiMethod.class);
if (method == null) return;
final int parameterIdx = ArrayUtil.find(method.getParameterList().getParameters(), p);
if (parameterIdx < 0) return;
UsageViewPresentation presentation = new UsageViewPresentation();
String title = InspectionsBundle.message("nullable.stuff.inspection.navigate.null.argument.usages.view.name", p.getName());
presentation.setUsagesString(title);
presentation.setTabName(title);
presentation.setTabText(title);
UsageViewManager.getInstance(project).searchAndShowUsages(
new UsageTarget[]{new PsiElement2UsageTargetAdapter(method.getParameterList().getParameters()[parameterIdx])},
() -> new UsageSearcher() {
@Override
public void generate(@NotNull final Processor<Usage> processor) {
ReadAction.run(() -> JavaNullMethodArgumentUtil.searchNullArgument(method, parameterIdx, (arg) -> processor.process(new UsageInfo2UsageAdapter(new UsageInfo(arg)))));
}
}, false, false, presentation, null);
}
};
}
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.codeInspection.nullable.NullableStuffInspection.OptionsPanel">
<grid id="cc1c9" binding="myPanel" layout-manager="GridLayoutManager" row-count="8" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="cc1c9" binding="myPanel" layout-manager="GridLayoutManager" row-count="9" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="69" y="57" width="634" height="239"/>
<xy x="69" y="57" width="634" height="277"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<vspacer id="c3eef">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="2f304" class="javax.swing.JCheckBox" binding="myReportNotAnnotatedGetter">
@@ -39,7 +39,7 @@
</component>
<component id="ef852" class="javax.swing.JButton" binding="myConfigureAnnotationsButton" default-binding="true">
<constraints>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/InspectionsBundle" key="configure.annotations.option"/>
@@ -69,6 +69,14 @@
<text value="Require @NotNull fields to be initialized explicitly"/>
</properties>
</component>
<component id="9b307" class="com.intellij.ui.components.JBCheckBox" binding="myReportNullLiteralsPassedNotNullParameter" default-binding="true">
<constraints>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Report 'null' literals passed to @NotNull method parameters"/>
</properties>
</component>
</children>
</grid>
</form>
@@ -0,0 +1,238 @@
/*
* Copyright 2000-2016 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.psi.impl.search;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.lang.LighterASTTokenNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.impl.cache.RecordUtil;
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.LightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.IntArrayList;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JavaNullMethodArgumentIndex extends ScalarIndexExtension<JavaNullMethodArgumentIndex.MethodCallData> implements PsiDependentIndex {
private static final Logger LOG = Logger.getInstance(JavaNullMethodArgumentIndex.class);
public static final ID<MethodCallData, Void> INDEX_ID = ID.create("java.null.method.argument");
@NotNull
@Override
public ID<MethodCallData, Void> getName() {
return INDEX_ID;
}
@NotNull
@Override
public DataIndexer<MethodCallData, Void, FileContent> getIndexer() {
return inputData -> {
final CharSequence contentAsText = inputData.getContentAsText();
if (!JavaStubElementTypes.JAVA_FILE.shouldBuildStubFor(inputData.getFile())) {
return Collections.emptyMap();
}
if (!StringUtil.contains(contentAsText, PsiKeyword.NULL)) {
return Collections.emptyMap();
}
Map<MethodCallData, Void> result = new THashMap<>();
final LighterAST lighterAst = ((FileContentImpl)inputData).getLighterASTForPsiDependentIndex();
new RecursiveLighterASTNodeWalkingVisitor(lighterAst) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
if (element.getTokenType() == JavaElementType.METHOD_CALL_EXPRESSION ||
element.getTokenType() == JavaElementType.NEW_EXPRESSION ||
element.getTokenType() == JavaElementType.ANONYMOUS_CLASS) {
final IntArrayList indices = getNullParameterIndices(element);
if (indices != null) {
final String name = getMethodName(element, element.getTokenType());
if (name != null) {
for (int i = 0; i < indices.size(); i++) {
final int nullParameterIndex = indices.get(i);
result.put(new MethodCallData(name, nullParameterIndex), null);
}
}
}
}
super.visitNode(element);
}
@Nullable
private IntArrayList getNullParameterIndices(@NotNull LighterASTNode methodCall) {
final LighterASTNode node = LightTreeUtil.firstChildOfType(lighterAst, methodCall, JavaElementType.EXPRESSION_LIST);
if (node == null) return null;
final List<LighterASTNode> parameters = LightTreeUtil.getChildrenOfType(lighterAst, node, ElementType.EXPRESSION_BIT_SET);
IntArrayList indices = null;
for (int idx = 0; idx < parameters.size(); idx++) {
LighterASTNode parameter = parameters.get(idx);
if (parameter.getTokenType() == JavaElementType.LITERAL_EXPRESSION) {
final CharSequence literal = ((LighterASTTokenNode) lighterAst.getChildren(parameter).get(0)).getText();
if (StringUtil.equals(literal, PsiKeyword.NULL)) {
if (indices == null) {
indices = new IntArrayList(1);
}
indices.add(idx);
}
}
}
return indices;
}
@Nullable
private String getMethodName(@NotNull LighterASTNode call, IElementType elementType) {
if (elementType == JavaElementType.NEW_EXPRESSION || elementType == JavaElementType.ANONYMOUS_CLASS) {
final List<LighterASTNode> refs = LightTreeUtil.getChildrenOfType(lighterAst, call, JavaElementType.JAVA_CODE_REFERENCE);
if (refs.isEmpty()) return null;
final LighterASTNode lastRef = refs.get(refs.size() - 1);
return getLastIdentifierText(lastRef);
} else {
LOG.assertTrue(elementType == JavaElementType.METHOD_CALL_EXPRESSION);
final LighterASTNode methodReference = lighterAst.getChildren(call).get(0);
if (methodReference.getTokenType() == JavaElementType.REFERENCE_EXPRESSION) {
return getLastIdentifierText(methodReference);
}
}
return null;
}
@Nullable
private String getLastIdentifierText(LighterASTNode lastRef) {
final List<LighterASTNode> identifiers = LightTreeUtil.getChildrenOfType(lighterAst, lastRef, JavaTokenType.IDENTIFIER);
if (identifiers.isEmpty()) return null;
final LighterASTNode methodNameIdentifier = identifiers.get(identifiers.size() - 1);
return RecordUtil.intern(lighterAst.getCharTable(), methodNameIdentifier);
}
}.visitNode(lighterAst.getRoot());
return result;
};
}
@NotNull
@Override
public KeyDescriptor<MethodCallData> getKeyDescriptor() {
return new KeyDescriptor<MethodCallData>() {
@Override
public int getHashCode(MethodCallData value) {
return value.hashCode();
}
@Override
public boolean isEqual(MethodCallData val1, MethodCallData val2) {
return val1.equals(val2);
}
@Override
public void save(@NotNull DataOutput out, MethodCallData value) throws IOException {
EnumeratorStringDescriptor.INSTANCE.save(out, value.getMethodName());
DataInputOutputUtil.writeINT(out, value.getNullParameterIndex());
}
@Override
public MethodCallData read(@NotNull DataInput in) throws IOException {
return new MethodCallData(EnumeratorStringDescriptor.INSTANCE.read(in),
DataInputOutputUtil.readINT(in));
}
};
}
@Override
public int getVersion() {
return 0;
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return new DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE);
}
@Override
public boolean dependsOnFileContent() {
return true;
}
public static final class MethodCallData {
@NotNull
private final String myMethodName;
private final int myNullParameterIndex;
public MethodCallData(@NotNull String name, int index) {
myMethodName = name;
myNullParameterIndex = index;
}
@NotNull
public String getMethodName() {
return myMethodName;
}
public int getNullParameterIndex() {
return myNullParameterIndex;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MethodCallData data = (MethodCallData)o;
if (myNullParameterIndex != data.myNullParameterIndex) return false;
if (!myMethodName.equals(data.myMethodName)) return false;
return true;
}
@Override
public int hashCode() {
int result = myMethodName.hashCode();
result = 31 * result + myNullParameterIndex;
return result;
}
@Override
public String toString() {
return "MethodCallData{" +
"myMethodName='" + myMethodName + '\'' +
", myNullParameterIndex=" + myNullParameterIndex +
'}';
}
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2000-2016 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.psi.impl.search;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopeUtil;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class JavaNullMethodArgumentUtil {
public static boolean hasNullArgument(@NotNull PsiMethod method, final int argumentIdx) {
final boolean[] result = {false};
searchNullArgument(method, argumentIdx, expression -> {
result[0] = true;
return false;
});
return result[0];
}
public static void searchNullArgument(@NotNull PsiMethod method, final int argumentIdx, @NotNull Processor<PsiExpression> nullArgumentProcessor) {
final GlobalSearchScope scope = findScopeWhereNullArgumentCanPass(method, argumentIdx);
if (scope == null) return;
MethodReferencesSearch.search(method, scope, true).forEach(ref -> {
final PsiElement psi = ref.getElement();
if (psi != null) {
final PsiElement parent = psi.getParent();
PsiExpressionList argumentList = null;
if (parent instanceof PsiCallExpression) {
argumentList = ((PsiCallExpression)parent).getArgumentList();
}
else if (parent instanceof PsiAnonymousClass) {
argumentList = ((PsiAnonymousClass)parent).getArgumentList();
}
if (argumentList != null) {
final PsiExpression[] arguments = argumentList.getExpressions();
final PsiExpression argument = arguments[argumentIdx];
if (argument instanceof PsiLiteralExpression && PsiKeyword.NULL.equals(argument.getText())) {
return nullArgumentProcessor.process(argument);
}
}
}
return true;
});
}
@Nullable
private static GlobalSearchScope findScopeWhereNullArgumentCanPass(@NotNull PsiMethod method, int parameterIndex) {
final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
final CommonProcessors.CollectProcessor<VirtualFile> collector = new CommonProcessors.CollectProcessor<>(new ArrayList<>());
fileBasedIndex.getFilesWithKey(JavaNullMethodArgumentIndex.INDEX_ID,
Collections.singleton(new JavaNullMethodArgumentIndex.MethodCallData(method.getName(), parameterIndex)),
collector,
GlobalSearchScopeUtil.toGlobalSearchScope(method.getUseScope(), method.getProject()));
final Collection<VirtualFile> candidateFiles = collector.getResults();
return candidateFiles.isEmpty() ? null : GlobalSearchScope.filesScope(method.getProject(), candidateFiles);
}
}
@@ -17,7 +17,7 @@ class Test {
}
@NotNull
private static <T> T notNull(@Nullable T value) {
private static <T> T notNull(@Nullable T <warning descr="Method fails when parameter 'value' is 'null'">value</warning>) {
if (value == null) {
throw new RuntimeException("null");
@@ -0,0 +1,17 @@
import java.util.Objects;
class Test {
void dangerousMethod(String reassginedParameter) {
if (reassginedParameter == null) {
reassginedParameter = "default value";
}
Objects.requireNonNull(reassginedParameter);
System.out.println(reassginedParameter);
}
void usage() {
dangerousMethod(<warning descr="Passing 'null' argument to non annotated parameter">null</warning>);
}
}
@@ -0,0 +1,29 @@
import java.util.Objects;
class Test {
private static void testMethod(Object <warning descr="Method fails when parameter 'o' is 'null'">o</warning>, Object o2, Object <warning descr="Method fails when parameter 'o3' is 'null'">o3</warning>, Object o4, int i) {
Objects.requireNonNull(o, "o is not null");
if (o3 != null) {
System.out.println(o3.hashCode());
} else {
throw new NullPointerException();
}
System.out.println(o);
for (int j = 0; j < i; j++) {
System.out.println(j);
Objects.requireNonNull(o4);
}
Objects.requireNonNull(o3);
if (o4 == null) {
System.out.println("o4 is null");
} else {
Objects.requireNonNull(o4);
}
}
public static void main(String[] args) {
testMethod(<warning descr="Passing 'null' argument to non annotated parameter">null</warning>, <warning descr="Passing 'null' argument to non annotated parameter">null</warning>, <warning descr="Passing 'null' argument to non annotated parameter">null</warning>, <warning descr="Passing 'null' argument to non annotated parameter">null</warning>, 10);
}
}
@@ -0,0 +1,26 @@
import java.util.Objects;
class Test {
private static void testMethod(Object o, Object o2, Object o3, Object o4, int i) {
Objects.requireNonNull(o2, "no usages with null literal argument");
if (i > 2016) {
Objects.requireNonNull(o, "o is not null");
}
System.out.println(o);
for (int j = 0; j < i; j++) {
System.out.println(j);
Objects.requireNonNull(o4);
}
if (o4 == null) {
System.out.println("o4 is null");
} else {
Objects.requireNonNull(o4);
}
}
public static void main(String[] args) {
testMethod(<warning descr="Passing 'null' argument to non annotated parameter">null</warning>, "I am not null", <warning descr="Passing 'null' argument to non annotated parameter">null</warning>, <warning descr="Passing 'null' argument to non annotated parameter">null</warning>, 10);
}
}
@@ -0,0 +1,43 @@
import org.jetbrains.annotations.NotNull;
class Main111 {
Main111(<warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object o) {
}
static class SubClass {
SubClass(<warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object o) {
}
}
static class SubClass2 {
SubClass2(<warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object o) {
}
}
static void main() {
new Main111(null);
new Main111.SubClass(null);
new SubClass2(null);
new ParamerizedRunnable(null) {
@Override
void run() {
}
};
}
abstract static class ParamerizedRunnable {
private Object parameter;
public ParamerizedRunnable(<warning descr="Parameter annotated @NotNull should not receive null as an argument"><warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning></warning> Object parameter) {
this.parameter = parameter;
}
abstract void run();
}
}
@@ -0,0 +1,24 @@
import org.jetbrains.annotations.NotNull;
class Test {
void someMethod(<warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object warn, <warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object o2, <warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object warn1) {
}
static void someStaticMethod(Object notAnnotated, <warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object o2, <warning descr="Parameter annotated @NotNull should not receive null as an argument">@NotNull</warning> Object warn2) {
}
public static void main(String[] args) {
someStaticMethod(null, "", "");
Test.someStaticMethod("", null, null);
new Test().someMethod("", null, null);
Test m = new Test();
m.someMethod(null, "", "");
m.someMethod(null, "", "");
}
}
@@ -50,6 +50,18 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testPrimitiveInVoidLambda() { doTest(); }
public void testNotNullLambdaParameter() { doTest(); }
public void testNullArgumentIsFailingMethodCall() {
doTest();
}
public void testNullArgumentIsNotFailingMethodCall() {
doTest();
}
public void testNullArgumentButParameterIsReassigned() {
doTest();
}
public void testNullableArrayComponent() {
setupCustomAnnotations();
DataFlowInspection inspection = new DataFlowInspection();
@@ -163,6 +163,14 @@ public class NullableStuffInspectionTest extends LightCodeInsightFixtureTestCase
myFixture.checkHighlighting(true, false, true);
}
public void testNullPassedToNotNullParameter() {
doTest();
}
public void testNullPassedToNotNullConstructorParameter() {
doTest();
}
public void testHonorParameterDefaultInSetters() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
@@ -0,0 +1,103 @@
/*
* Copyright 2000-2016 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.psi.impl.search
import com.intellij.openapi.fileTypes.StdFileTypes
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import com.intellij.util.indexing.FileContentImpl
import com.intellij.util.indexing.IndexingDataKeys
import org.intellij.lang.annotations.Language
class JavaNullMethodArgumentIndexTest : LightPlatformCodeInsightFixtureTestCase() {
fun testIndex() {
@Language("JAVA")
val file = myFixture.configureByText(StdFileTypes.JAVA, """
package org.some;
class Main111 {
Main111(Object o) {
}
void someMethod(Object o, Object o2, Object o3) {
}
static void staticMethod(Object o, Object o2, Object o3) {
}
public static void main(String[] args) {
staticMethod(null, "", "");
org.some.Main111.staticMethod("", "", null);
new Main111(null).someMethod("", "", null);
Main111 m = new Main111(null);
m.someMethod(null, "", "");
}
static class SubClass {
SubClass(Object o) {
}
}
static class SubClass2 {
SubClass2(Object o) {
}
}
static void main() {
new org.some.Main111(null);
new org.some.Main111.SubClass(null);
new SubClass2(null);
new ParametrizedRunnable(null) {
@Override
void run() {
}};
}
abstract class ParametrizedRunnable {
Object parameter;
ParametrizedRunnable(Object parameter){
this.parameter = parameter;
}
abstract void run();
}
}
""").virtualFile
val content = FileContentImpl.createByFile(file)
content.putUserData(IndexingDataKeys.PROJECT, project)
val data = JavaNullMethodArgumentIndex().indexer.map(content).keys
assertSize(8, data)
assertContainsElements(data,
JavaNullMethodArgumentIndex.MethodCallData("staticMethod", 0),
JavaNullMethodArgumentIndex.MethodCallData("staticMethod", 2),
JavaNullMethodArgumentIndex.MethodCallData("someMethod", 0),
JavaNullMethodArgumentIndex.MethodCallData("someMethod", 2),
JavaNullMethodArgumentIndex.MethodCallData("Main111", 0),
JavaNullMethodArgumentIndex.MethodCallData("SubClass", 0),
JavaNullMethodArgumentIndex.MethodCallData("SubClass2", 0),
JavaNullMethodArgumentIndex.MethodCallData("ParametrizedRunnable", 0))
}
}
@@ -91,6 +91,7 @@ dataflow.message.return.nullable.from.notnullable=Expression <code>#ref</code> m
dataflow.message.unboxing=Unboxing of <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.unboxing.method.reference=Use of <code>#ref</code> #loc would need unboxing which may produce <code>java.lang.NullPointerException</code>
dataflow.too.complex=Method <code>#ref</code> is too complex to analyze by data flow algorithm
dataflow.method.fails.with.null.argument=Method fails when parameter ''{0}'' is <code>null<code>
#deprecated
inspection.deprecated.display.name=Deprecated API usage
@@ -153,6 +154,7 @@ inspection.nullable.problems.NotNull.parameter.overrides.Nullable=Parameter anno
inspection.nullable.problems.NotNull.parameter.overrides.not.annotated=Parameter annotated @{0} should not override non-annotated parameter
inspection.nullable.problems.parameter.overrides.NotNull=Not annotated parameter overrides @{0} parameter
inspection.nullable.problems.primitive.type.annotation=Primitive type members cannot be annotated
inspection.nullable.problems.NotNull.parameter.receives.null.literal=Parameter annotated @{0} should not receive 'null' as an argument
inspection.test.only.problems.display.name=Test-only class or method call in production code
inspection.test.only.problems.test.only.method.call=Test-only method is called in production code
@@ -724,3 +726,5 @@ inspection.annotate.method.quickfix.family.name=Annotate method
inspection.tool.window.dialog.title=Inspection Tool Window
inspection.tool.window.dialog.no.options=Inspection ''{0}'' has no configurable options
inspection.tool.window.inspection.dialog.title=Inspection ''{0}'' options
nullable.stuff.inspection.navigate.null.argument.usages.fix.family.name=Navigate to 'null' argument usages
nullable.stuff.inspection.navigate.null.argument.usages.view.name=''null'' argument usages for parameter {0}
+2
View File
@@ -1762,6 +1762,8 @@
implementationClass="com.intellij.refactoring.introduceparameterobject.JavaIntroduceParameterObjectDelegate"/>
<refactoring.pushDown language="JAVA" implementationClass="com.intellij.refactoring.memberPushDown.JavaPushDownDelegate" id="java"/>
<library.javaSourceRootDetector implementation="com.intellij.openapi.roots.ui.configuration.LibraryJavaSourceRootDetector"/>
<fileBasedIndex implementation="com.intellij.psi.impl.search.JavaNullMethodArgumentIndex"/>
</extensions>
<actions>