diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index d66d63bf85c3..6572fd309b78 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -64,6 +64,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { @NonNls private static final String SHORT_NAME = "ConstantConditions"; public boolean SUGGEST_NULLABLE_ANNOTATIONS = false; public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS = false; + public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = false; public boolean IGNORE_ASSERT_STATEMENTS = false; public boolean REPORT_CONSTANT_REFERENCE_VALUES = true; @@ -82,6 +83,9 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { if (!REPORT_CONSTANT_REFERENCE_VALUES) { node.addContent(new Element("option").setAttribute("name", "REPORT_CONSTANT_REFERENCE_VALUES").setAttribute("value", "false")); } + if (TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) { + node.addContent(new Element("option").setAttribute("name", "TREAT_UNKNOWN_MEMBERS_AS_NULLABLE").setAttribute("value", "true")); + } } @Override @@ -121,7 +125,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { PsiClass containingClass = PsiTreeUtil.getParentOfType(scope, PsiClass.class); if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass)) return; - final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(scope) { + final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(scope, TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) { @Override protected boolean shouldCheckTimeLimit() { if (!onTheFly) return false; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index 07e5fcc5480d..10e0a0f4acd3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -57,13 +57,17 @@ public class DataFlowRunner { public static final int MAX_STATES_PER_BRANCH = 300; protected DataFlowRunner(PsiElement block) { + this(block, false); + } + + protected DataFlowRunner(PsiElement block, boolean unknownMembersAreNullable) { PsiElement parentConstructor = PsiTreeUtil.findFirstParent(block, new Condition() { @Override public boolean value(PsiElement psiElement) { return psiElement instanceof PsiMethod && ((PsiMethod)psiElement).isConstructor(); } }); - myValueFactory = new DfaValueFactory(parentConstructor == null); + myValueFactory = new DfaValueFactory(parentConstructor == null, unknownMembersAreNullable); } public DfaValueFactory getFactory() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java index 2652c6ff19f6..68e881c97f14 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java @@ -41,7 +41,10 @@ public class StandardDataFlowRunner extends DataFlowRunner { private boolean myIsInMethod = false; public StandardDataFlowRunner(PsiElement block) { - super(block); + this (block, false); + } + public StandardDataFlowRunner(PsiElement block, boolean unknownMembersAreNullable) { + super(block, unknownMembersAreNullable); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 96102b53f5d4..57ace52e9b10 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -343,7 +343,11 @@ public class StandardInstructionVisitor extends InstructionVisitor { } if (type != null && (type instanceof PsiClassType || type.getArrayDimensions() > 0)) { - return factory.createTypeValue(type, myReturnTypeNullability.get(instruction)); + Nullness nullability = myReturnTypeNullability.get(instruction); + if (nullability == Nullness.UNKNOWN && factory.isUnknownMembersAreNullable()) { + nullability = Nullness.NULLABLE; + } + return factory.createTypeValue(type, nullability); } return DfaUnknownValue.getInstance(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index da1d2d952634..4a260d7c827b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -42,9 +42,11 @@ public class DfaValueFactory { private final Map, Boolean> myConvertibleCache = ContainerUtil.newHashMap(); private final Map myDfaTypes = ContainerUtil.newHashMap(); private final boolean myHonorFieldInitializers; + private final boolean myUnknownMembersAreNullable; - public DfaValueFactory(boolean honorFieldInitializers) { + public DfaValueFactory(boolean honorFieldInitializers, boolean unknownMembersAreNullable) { myHonorFieldInitializers = honorFieldInitializers; + myUnknownMembersAreNullable = unknownMembersAreNullable; myValues.add(null); myVarFactory = new DfaVariableValue.Factory(this); myConstFactory = new DfaConstValue.Factory(this); @@ -58,6 +60,10 @@ public class DfaValueFactory { return myHonorFieldInitializers; } + public boolean isUnknownMembersAreNullable() { + return myUnknownMembersAreNullable; + } + public DfaValue createTypeValue(@Nullable PsiType type, Nullness nullability) { type = TypeConversionUtil.erasure(type); if (type == null) return DfaUnknownValue.getInstance(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index 840103f5c993..76d0be269059 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -165,6 +165,8 @@ public class DfaVariableValue extends DfaValue { return nullability; } + Nullness defaultNullability = var instanceof PsiMember && myFactory.isUnknownMembersAreNullable() ? Nullness.NULLABLE : Nullness.UNKNOWN; + if (var instanceof PsiParameter && var.getParent() instanceof PsiForeachStatement) { PsiExpression iteratedValue = ((PsiForeachStatement)var.getParent()).getIteratedValue(); if (iteratedValue != null) { @@ -178,7 +180,7 @@ public class DfaVariableValue extends DfaValue { if (var instanceof PsiField && DfaPsiUtil.isFinalField((PsiVariable)var) && myFactory.isHonorFieldInitializers()) { List initializers = DfaPsiUtil.findAllConstructorInitializers((PsiField)var); if (initializers.isEmpty()) { - return Nullness.UNKNOWN; + return defaultNullability; } boolean hasUnknowns = false; @@ -204,13 +206,13 @@ public class DfaVariableValue extends DfaValue { if (DfaPsiUtil.isInitializedNotNull((PsiField)var)) { return Nullness.NOT_NULL; } - return Nullness.UNKNOWN; + return defaultNullability; } return Nullness.NOT_NULL; } - return Nullness.UNKNOWN; + return defaultNullability; } public boolean isFlushableByCalls() { diff --git a/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java b/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java index 73d8848d4d17..e383b6a4e89a 100644 --- a/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspection.java @@ -68,6 +68,7 @@ public class DataFlowInspection extends DataFlowInspectionBase { private final JCheckBox myReportConstantReferences; private final JCheckBox mySuggestNullables; private final JCheckBox myDontReportTrueAsserts; + private final JCheckBox myTreatUnknownMembersAsNullable; private OptionsPanel() { super(new GridBagLayout()); @@ -116,6 +117,15 @@ public class DataFlowInspection extends DataFlowInspectionBase { } }); + myTreatUnknownMembersAsNullable = new JCheckBox("Treat non-annotated methods and fields as @Nullable"); + myTreatUnknownMembersAsNullable.setSelected(TREAT_UNKNOWN_MEMBERS_AS_NULLABLE); + myTreatUnknownMembersAsNullable.getModel().addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = myTreatUnknownMembersAsNullable.isSelected(); + } + }); + gc.insets = new Insets(0, 0, 0, 0); gc.gridy = 0; add(mySuggestNullables, gc); @@ -167,6 +177,9 @@ public class DataFlowInspection extends DataFlowInspectionBase { gc.gridy++; add(myReportConstantReferences, gc); + + gc.gridy++; + add(myTreatUnknownMembersAsNullable, gc); } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java b/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java new file mode 100644 index 000000000000..96448a4d0d95 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ParanoidMode.java @@ -0,0 +1,22 @@ +import org.jetbrains.annotations.NotNull; + +class Test { + Object o; + + void field() { + o.hashCode(); + } + + void callUnknownMethod() { + unknownObject().hashCode(); + } + + void callNotNullMethod() { + knownObject().hashCode(); + } + + native Object unknownObject(); + + @NotNull + native Object knownObject(); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java index 490ded30353c..92465cf858bc 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java @@ -137,6 +137,13 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase { myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); } + public void testParanoidMode() { + final DataFlowInspection inspection = new DataFlowInspection(); + inspection.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true; + myFixture.enableInspections(inspection); + myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); + } + public void testReportConstantReferences() { doTestReportConstantReferences(); myFixture.launchAction(myFixture.findSingleIntention("Replace with 'null'"));