From 69081779fe7fcbd60fdbc40e770e4ee1a18b4ae6 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 24 Jun 2015 16:35:03 +0200 Subject: [PATCH] dfa, Optional.ofNullable: suggest improvements only if in all states the arg was null or non-null (IDEA-141843) --- .../dataFlow/DataFlowInspectionBase.java | 34 +++++++++++------- .../dataFlow/DfaOptionalSupport.java | 6 ++-- .../dataFlow/NullabilityProblem.java | 4 +-- .../dataFlow/StandardInstructionVisitor.java | 22 ++---------- .../instructions/MethodCallInstruction.java | 26 ++++++++++++++ .../dataFlow/fixture/OptionalOfNullable.java | 24 +++++++++++++ .../DataFlowInspection8Test.java | 21 +++++++---- java/mockJDK-1.8/jre/lib/annotations.jar | Bin 0 -> 24037 bytes 8 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/OptionalOfNullable.java create mode 100644 java/mockJDK-1.8/jre/lib/annotations.jar 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 3ef6d6aa6285..2ac602d69d54 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 @@ -270,7 +270,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { reportNullableArgumentsPassedToNonAnnotated(visitor, holder, reportedAnchors); } - reportOptionalOfNullableImprovements(holder, visitor, reportedAnchors); + reportOptionalOfNullableImprovements(holder, reportedAnchors, runner.getInstructions()); if (REPORT_CONSTANT_REFERENCE_VALUES) { @@ -278,18 +278,26 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, - DataFlowInstructionVisitor visitor, - HashSet reportedAnchors) { - for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullToOptional)) { - if (!reportedAnchors.add(expr)) continue; - holder.registerProblem(expr, "Passing null argument to Optional", - DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(expr)); - } - for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNotNullToOptional)) { - if (!reportedAnchors.add(expr)) continue; - holder.registerProblem(expr, "Passing a non-null argument to Optional", - DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix()); + private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, Set reportedAnchors, Instruction[] instructions) { + for (Instruction instruction : instructions) { + if (instruction instanceof MethodCallInstruction) { + final PsiExpression[] args = ((MethodCallInstruction)instruction).getArgs(); + if (args.length != 1) continue; + + final PsiExpression expr = args[0]; + + if (((MethodCallInstruction)instruction).isOptionalAlwaysNullProblem()) { + if (!reportedAnchors.add(expr)) continue; + holder.registerProblem(expr, "Passing null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(expr)); + } + else if (((MethodCallInstruction)instruction).isOptionalAlwaysNotNullProblem()) { + if (!reportedAnchors.add(expr)) continue; + holder.registerProblem(expr, "Passing a non-null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix()); + } + + } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java index 12c0d3ca6f3f..d880c023bf73 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java @@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable; /** * @author anet, peter */ -class DfaOptionalSupport { +public class DfaOptionalSupport { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DfaOptionalSupport"); private static final String GUAVA_OPTIONAL = "com.google.common.base.Optional"; @@ -77,8 +77,8 @@ class DfaOptionalSupport { } @Nullable - static PsiMethod resolveOfNullable(PsiCallExpression expression) { - String name = ((PsiMethodCallExpression)expression).getMethodExpression().getReferenceName(); + public static PsiMethod resolveOfNullable(@NotNull PsiMethodCallExpression expression) { + String name = expression.getMethodExpression().getReferenceName(); if ("ofNullable".equals(name) || "fromNullable".equals(name)) { PsiMethod method = expression.resolveMethod(); PsiClass psiClass = method == null ? null : method.getContainingClass(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java index 2810ffdb2e34..9240f5d3383e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java @@ -10,7 +10,5 @@ public enum NullabilityProblem { assigningToNotNull, nullableReturn, passingNullableToNotNullParameter, - passingNullableArgumentToNonAnnotatedParameter, - passingNullToOptional, - passingNotNullToOptional + passingNullableArgumentToNonAnnotatedParameter } 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 66893c0c0c1b..9f2c106b576a 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 @@ -57,15 +57,6 @@ public class StandardInstructionVisitor extends InstructionVisitor { return callExpression != null ? DfaPsiUtil.getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null; } }; - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private final FactoryMap myOptionOfNullable = new FactoryMap() { - @Nullable - @Override - protected Boolean create(MethodCallInstruction key) { - PsiCallExpression expression = key.getCallExpression(); - return expression instanceof PsiMethodCallExpression && DfaOptionalSupport.resolveOfNullable(expression) != null; - } - }; @Override public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { @@ -240,11 +231,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { forceNotNull(runner, memState, arg); } } - else if (myOptionOfNullable.get(instruction)) { - checkNotNullable(memState, arg, NullabilityProblem.passingNotNullToOptional, expr); - checkNotNullable(memState, arg, NullabilityProblem.passingNullToOptional, expr); - } - else if (requiredNullability == Nullness.UNKNOWN) { + else if (!instruction.updateOfNullable(memState, arg) && requiredNullability == Nullness.UNKNOWN) { checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr); } } @@ -384,14 +371,9 @@ public class StandardInstructionVisitor extends InstructionVisitor { protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) { - if (problem == NullabilityProblem.passingNotNullToOptional) { - return !state.isNotNull(value); - } - boolean notNullable = state.checkNotNullable(value); if (notNullable && - problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter && - problem != NullabilityProblem.passingNullToOptional) { + problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) { DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory(); state.applyCondition(factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), NE, false)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java index f7aa1da08598..3b38cefb99ca 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -46,8 +46,11 @@ public class MethodCallInstruction extends Instruction { private final List myContracts; private final MethodType myMethodType; @Nullable private final DfaValue myPrecalculatedReturnValue; + private final boolean myOfNullable; private final boolean myVarArgCall; private final Map myArgRequiredNullability; + private boolean myOnlyNullArgs = true; + private boolean myOnlyNotNullArgs = true; public enum MethodType { BOXING, UNBOXING, REGULAR_METHOD_CALL, CAST @@ -64,6 +67,7 @@ public class MethodCallInstruction extends Instruction { myPrecalculatedReturnValue = null; myTargetMethod = null; myVarArgCall = false; + myOfNullable = false; myArgRequiredNullability = Collections.emptyMap(); } @@ -91,6 +95,7 @@ public class MethodCallInstruction extends Instruction { myShouldFlushFields = !(call instanceof PsiNewExpression && myType != null && myType.getArrayDimensions() > 0) && !isPureCall(); myPrecalculatedReturnValue = precalculatedReturnValue; + myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.resolveOfNullable((PsiMethodCallExpression)call) != null; } private Map calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) { @@ -191,4 +196,25 @@ public class MethodCallInstruction extends Instruction { ? "BOX" : "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText()); } + + public boolean updateOfNullable(DfaMemoryState memState, DfaValue arg) { + if (!myOfNullable) return false; + + if (!memState.isNotNull(arg)) { + myOnlyNotNullArgs = false; + } + if (!memState.isNull(arg)) { + myOnlyNullArgs = false; + } + return true; + } + + public boolean isOptionalAlwaysNullProblem() { + return myOfNullable && myOnlyNullArgs; + } + + public boolean isOptionalAlwaysNotNullProblem() { + return myOfNullable && myOnlyNotNullArgs; + } + } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalOfNullable.java b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalOfNullable.java new file mode 100644 index 000000000000..bd5756d4a6e2 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalOfNullable.java @@ -0,0 +1,24 @@ +import java.util.List; +import java.util.Optional; + +class Test { + Optional getName(List numbers) { + return Optional.ofNullable( + numbers.isEmpty() ? + null : + numbers.get(0)); + } + + Optional getName2(List numbers) { + return Optional.ofNullable( + numbers.isEmpty() ? + "2" : + numbers.get(0)); + } + + Optional getName3() { + return Optional.ofNullable(null); + } + +} + diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java index dcffac411bfc..90f864e137d2 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java @@ -18,14 +18,20 @@ package com.intellij.codeInspection; import com.intellij.JavaTestUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.DataFlowInspection; -import com.intellij.openapi.Disposable; import com.intellij.openapi.util.Disposer; +import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; /** * @author peter */ public class DataFlowInspection8Test extends LightCodeInsightFixtureTestCase { + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_8; + } @Override protected String getTestDataPath() { @@ -67,12 +73,9 @@ public class DataFlowInspection8Test extends LightCodeInsightFixtureTestCase { final NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); nnnManager.setNotNulls("foo.NotNull"); nnnManager.setNullables("foo.Nullable"); - Disposer.register(myTestRootDisposable, new Disposable() { - @Override - public void dispose() { - nnnManager.setNotNulls(); - nnnManager.setNullables(); - } + Disposer.register(myTestRootDisposable, () -> { + nnnManager.setNotNulls(); + nnnManager.setNullables(); }); } @@ -91,4 +94,8 @@ public class DataFlowInspection8Test extends LightCodeInsightFixtureTestCase { myFixture.enableInspections(inspection); myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); } + + + public void testOptionalOfNullable() { doTest(); } + } diff --git a/java/mockJDK-1.8/jre/lib/annotations.jar b/java/mockJDK-1.8/jre/lib/annotations.jar new file mode 100644 index 0000000000000000000000000000000000000000..b78e2de180d4dda13f4350cb13237982fece9189 GIT binary patch literal 24037 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 DHLDCK literal 0 HcmV?d00001