allow dfa inspection to treat all non-annotated members as nullable (IDEA-129898)

This commit is contained in:
peter
2014-09-16 13:50:45 +02:00
parent e2ee62224c
commit 984af0337e
9 changed files with 73 additions and 8 deletions
@@ -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;
@@ -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<PsiElement>() {
@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() {
@@ -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
@@ -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();
}
@@ -42,9 +42,11 @@ public class DfaValueFactory {
private final Map<Pair<DfaPsiType, DfaPsiType>, Boolean> myConvertibleCache = ContainerUtil.newHashMap();
private final Map<PsiType, DfaPsiType> 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();
@@ -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<PsiExpression> 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() {
@@ -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);
}
}
@@ -0,0 +1,22 @@
import org.jetbrains.annotations.NotNull;
class Test {
Object o;
void field() {
<warning descr="Method invocation 'o.hashCode()' may produce 'java.lang.NullPointerException'">o.hashCode()</warning>;
}
void callUnknownMethod() {
<warning descr="Method invocation 'unknownObject().hashCode()' may produce 'java.lang.NullPointerException'">unknownObject().hashCode()</warning>;
}
void callNotNullMethod() {
knownObject().hashCode();
}
native Object unknownObject();
@NotNull
native Object knownObject();
}
@@ -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'"));