2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com
.intellij
.codeInspection
.dataFlow
;
27 import com
.intellij
.codeInsight
.AnnotationUtil
;
28 import com
.intellij
.codeInsight
.daemon
.GroupNames
;
29 import com
.intellij
.codeInsight
.daemon
.impl
.quickfix
.SimplifyBooleanExpressionFix
;
30 import com
.intellij
.codeInspection
.*;
31 import com
.intellij
.codeInspection
.dataFlow
.instructions
.*;
32 import com
.intellij
.codeInspection
.ex
.BaseLocalInspectionTool
;
33 import com
.intellij
.openapi
.application
.ApplicationNamesInfo
;
34 import com
.intellij
.openapi
.diagnostic
.Logger
;
35 import com
.intellij
.openapi
.project
.Project
;
36 import com
.intellij
.openapi
.util
.Pair
;
37 import com
.intellij
.openapi
.vfs
.ReadonlyStatusHandler
;
38 import com
.intellij
.psi
.*;
39 import com
.intellij
.psi
.util
.PsiUtil
;
40 import com
.intellij
.psi
.util
.PsiUtilBase
;
41 import com
.intellij
.util
.IncorrectOperationException
;
42 import com
.intellij
.util
.SmartList
;
43 import org
.jetbrains
.annotations
.NonNls
;
44 import org
.jetbrains
.annotations
.NotNull
;
45 import org
.jetbrains
.annotations
.Nullable
;
48 import javax
.swing
.event
.ChangeEvent
;
49 import javax
.swing
.event
.ChangeListener
;
52 import java
.util
.List
;
54 public class DataFlowInspection
extends BaseLocalInspectionTool
{
55 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowInspection");
56 @NonNls private static final String SHORT_NAME
= "ConstantConditions";
57 public boolean SUGGEST_NULLABLE_ANNOTATIONS
= false;
58 public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS
= false;
60 public JComponent
createOptionsPanel() {
61 return new OptionsPanel();
65 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, boolean isOnTheFly
) {
66 return new JavaElementVisitor() {
67 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {}
70 @Override public void visitField(PsiField field
) {
71 if (isNullLiteralExpression(field
.getInitializer()) && AnnotationUtil
.isNotNull(field
)) {
72 holder
.registerProblem(field
.getInitializer(), InspectionsBundle
.message("dataflow.message.initializing.field.with.null"));
76 @Override public void visitMethod(PsiMethod method
) {
77 analyzeCodeBlock(method
.getBody(), holder
);
80 @Override public void visitClassInitializer(PsiClassInitializer initializer
) {
81 analyzeCodeBlock(initializer
.getBody(), holder
);
86 private void analyzeCodeBlock(final PsiCodeBlock body
, ProblemsHolder holder
) {
87 if (body
== null) return;
88 final StandardDataFlowRunner dfaRunner
= new StandardDataFlowRunner(SUGGEST_NULLABLE_ANNOTATIONS
);
89 final StandardInstructionVisitor visitor
= new DataFlowInstructionVisitor();
90 final RunnerResult rc
= dfaRunner
.analyzeMethod(body
, visitor
);
91 if (rc
== RunnerResult
.OK
) {
92 if (dfaRunner
.problemsDetected(visitor
)) {
93 createDescription(dfaRunner
, holder
, visitor
);
96 else if (rc
== RunnerResult
.TOO_COMPLEX
) {
97 if (body
.getParent() instanceof PsiMethod
) {
98 PsiMethod method
= (PsiMethod
)body
.getParent();
99 final PsiIdentifier name
= method
.getNameIdentifier();
100 if (name
!= null) { // Might be null for synthetic methods like JSP page.
101 holder
.registerProblem(name
, InspectionsBundle
.message("dataflow.too.complex"), ProblemHighlightType
.INFO
);
108 private static LocalQuickFix
[] createNPEFixes(PsiExpression qualifier
) {
109 if (qualifier
!= null &&
110 !(qualifier
instanceof PsiMethodCallExpression
) &&
111 !(qualifier
instanceof PsiLiteralExpression
&& ((PsiLiteralExpression
)qualifier
).getValue() == null)) {
113 PsiBinaryExpression binary
= (PsiBinaryExpression
)JavaPsiFacade
.getInstance(qualifier
.getProject()).getElementFactory()
114 .createExpressionFromText("a != null",
116 binary
.getLOperand().replace(qualifier
);
117 List
<LocalQuickFix
> fixes
= new SmartList
<LocalQuickFix
>();
119 if (PsiUtil
.getLanguageLevel(qualifier
).hasAssertKeyword()) {
120 fixes
.add(new AddAssertStatementFix(binary
));
122 SurroundWithIfFix ifFix
= new SurroundWithIfFix(qualifier
);
123 if (ifFix
.isAvailable()) {
126 return fixes
.toArray(new LocalQuickFix
[fixes
.size()]);
128 catch (IncorrectOperationException e
) {
136 private void createDescription(StandardDataFlowRunner runner
, ProblemsHolder holder
, StandardInstructionVisitor visitor
) {
137 Pair
<Set
<Instruction
>,Set
<Instruction
>> constConditions
= runner
.getConstConditionalExpressions();
138 Set
<Instruction
> trueSet
= constConditions
.getFirst();
139 Set
<Instruction
> falseSet
= constConditions
.getSecond();
140 Set
<Instruction
> npeSet
= runner
.getNPEInstructions();
141 Set
<Instruction
> cceSet
= runner
.getCCEInstructions();
142 Set
<Instruction
> redundantInstanceofs
= StandardDataFlowRunner
.getRedundantInstanceofs(runner
, visitor
);
144 ArrayList
<Instruction
> allProblems
= new ArrayList
<Instruction
>();
145 allProblems
.addAll(trueSet
);
146 allProblems
.addAll(falseSet
);
147 allProblems
.addAll(npeSet
);
148 allProblems
.addAll(cceSet
);
149 allProblems
.addAll(redundantInstanceofs
);
151 Collections
.sort(allProblems
, new Comparator
<Instruction
>() {
152 public int compare(Instruction i1
, Instruction i2
) {
153 return i1
.getIndex() - i2
.getIndex();
157 HashSet
<PsiElement
> reportedAnchors
= new HashSet
<PsiElement
>();
159 for (Instruction instruction
: allProblems
) {
160 if (instruction
instanceof MethodCallInstruction
) {
161 MethodCallInstruction mcInstruction
= (MethodCallInstruction
)instruction
;
162 if (mcInstruction
.getCallExpression() instanceof PsiMethodCallExpression
) {
163 PsiMethodCallExpression callExpression
= (PsiMethodCallExpression
)mcInstruction
.getCallExpression();
164 LocalQuickFix
[] fix
= createNPEFixes(callExpression
.getMethodExpression().getQualifierExpression());
166 holder
.registerProblem(callExpression
,
167 InspectionsBundle
.message("dataflow.message.npe.method.invocation"),
171 else if (instruction
instanceof FieldReferenceInstruction
) {
172 FieldReferenceInstruction frInstruction
= (FieldReferenceInstruction
)instruction
;
173 PsiElement elementToAssert
= frInstruction
.getElementToAssert();
174 PsiExpression expression
= frInstruction
.getExpression();
175 if (expression
instanceof PsiArrayAccessExpression
) {
176 LocalQuickFix
[] fix
= createNPEFixes((PsiExpression
)elementToAssert
);
177 holder
.registerProblem(expression
,
178 InspectionsBundle
.message("dataflow.message.npe.array.access"),
182 LocalQuickFix
[] fix
= createNPEFixes((PsiExpression
)elementToAssert
);
183 holder
.registerProblem(elementToAssert
,
184 InspectionsBundle
.message("dataflow.message.npe.field.access"),
188 else if (instruction
instanceof TypeCastInstruction
) {
189 TypeCastInstruction tcInstruction
= (TypeCastInstruction
)instruction
;
190 PsiTypeCastExpression typeCast
= tcInstruction
.getCastExpression();
191 holder
.registerProblem(typeCast
.getCastType(),
192 InspectionsBundle
.message("dataflow.message.cce", typeCast
.getOperand().getText()));
194 else if (instruction
instanceof BranchingInstruction
) {
195 PsiElement psiAnchor
= ((BranchingInstruction
)instruction
).getPsiAnchor();
196 if (instruction
instanceof InstanceofInstruction
&& visitor
.isInstanceofRedundant((InstanceofInstruction
)instruction
)) {
197 if (visitor
.canBeNull((BinopInstruction
)instruction
)) {
198 holder
.registerProblem(psiAnchor
,
199 InspectionsBundle
.message("dataflow.message.redundant.instanceof"),
200 new RedundantInstanceofFix());
203 final LocalQuickFix localQuickFix
= createSimplifyBooleanExpressionFix(psiAnchor
, true);
204 holder
.registerProblem(psiAnchor
,
205 InspectionsBundle
.message("dataflow.message.constant.condition", Boolean
.toString(true)),
206 localQuickFix
==null?
null:new LocalQuickFix
[]{localQuickFix
});
209 else if (psiAnchor
instanceof PsiSwitchLabelStatement
) {
210 if (falseSet
.contains(instruction
)) {
211 holder
.registerProblem(psiAnchor
,
212 InspectionsBundle
.message("dataflow.message.unreachable.switch.label"));
215 else if (psiAnchor
!= null && !reportedAnchors
.contains(psiAnchor
) && !isCompileConstantInIfCondition(psiAnchor
)) {
216 boolean evaluatesToTrue
= trueSet
.contains(instruction
);
217 if (onTheLeftSideOfConditionalAssignemnt(psiAnchor
)) {
218 holder
.registerProblem(psiAnchor
, InspectionsBundle
.message("dataflow.message.pointless.assignment.expression",
219 Boolean
.toString(evaluatesToTrue
)));
222 boolean report
= !(psiAnchor
.getParent() instanceof PsiAssertStatement
) || !DONT_REPORT_TRUE_ASSERT_STATEMENTS
|| !evaluatesToTrue
;
224 final LocalQuickFix localQuickFix
= createSimplifyBooleanExpressionFix(psiAnchor
, evaluatesToTrue
);
225 holder
.registerProblem(psiAnchor
, InspectionsBundle
.message("dataflow.message.constant.condition",
226 Boolean
.toString(evaluatesToTrue
)),
227 localQuickFix
== null ?
null : new LocalQuickFix
[]{localQuickFix
});
230 reportedAnchors
.add(psiAnchor
);
235 Set
<PsiExpression
> exprs
= runner
.getNullableArguments();
236 for (PsiExpression expr
: exprs
) {
237 final String text
= isNullLiteralExpression(expr
)
238 ? InspectionsBundle
.message("dataflow.message.passing.null.argument")
239 : InspectionsBundle
.message("dataflow.message.passing.nullable.argument");
240 LocalQuickFix
[] fixes
= createNPEFixes(expr
);
241 holder
.registerProblem(expr
, text
, fixes
);
244 exprs
= runner
.getNullableAssignments();
245 for (PsiExpression expr
: exprs
) {
246 final String text
= isNullLiteralExpression(expr
)
247 ? InspectionsBundle
.message("dataflow.message.assigning.null")
248 : InspectionsBundle
.message("dataflow.message.assigning.nullable");
249 holder
.registerProblem(expr
, text
);
252 exprs
= runner
.getUnboxedNullables();
253 for (PsiExpression expr
: exprs
) {
254 holder
.registerProblem(expr
, InspectionsBundle
.message("dataflow.message.unboxing"));
257 final Set
<PsiReturnStatement
> statements
= runner
.getNullableReturns();
258 for (PsiReturnStatement statement
: statements
) {
259 final PsiExpression expr
= statement
.getReturnValue();
260 if (runner
.isInNotNullMethod()) {
261 final String text
= isNullLiteralExpression(expr
)
262 ? InspectionsBundle
.message("dataflow.message.return.null.from.notnull")
263 : InspectionsBundle
.message("dataflow.message.return.nullable.from.notnull");
264 holder
.registerProblem(expr
, text
);
266 else if (AnnotationUtil
.isAnnotatingApplicable(statement
)) {
267 final String text
= isNullLiteralExpression(expr
)
268 ? InspectionsBundle
.message("dataflow.message.return.null.from.notnullable")
269 : InspectionsBundle
.message("dataflow.message.return.nullable.from.notnullable");
270 holder
.registerProblem(expr
, text
, new AnnotateMethodFix(AnnotationUtil
.NULLABLE
, AnnotationUtil
.NOT_NULL
));
276 private static boolean isCompileConstantInIfCondition(PsiElement element
) {
277 if (!(element
instanceof PsiReferenceExpression
)) return false;
278 PsiElement resolved
= ((PsiReferenceExpression
)element
).resolve();
279 if (!(resolved
instanceof PsiField
)) return false;
280 PsiField field
= (PsiField
)resolved
;
282 if (!field
.hasModifierProperty(PsiModifier
.FINAL
)) return false;
284 PsiElement parent
= element
.getParent();
285 if (parent
instanceof PsiPrefixExpression
&& ((PsiPrefixExpression
)parent
).getOperationSign().getTokenType() == JavaTokenType
.EXCL
) {
287 parent
= parent
.getParent();
289 return parent
instanceof PsiIfStatement
&& ((PsiIfStatement
)parent
).getCondition() == element
;
292 private static boolean isNullLiteralExpression(PsiExpression expr
) {
293 if (expr
instanceof PsiLiteralExpression
) {
294 final PsiLiteralExpression literalExpression
= (PsiLiteralExpression
)expr
;
295 return PsiType
.NULL
.equals(literalExpression
.getType());
300 private static boolean onTheLeftSideOfConditionalAssignemnt(final PsiElement psiAnchor
) {
301 final PsiElement parent
= psiAnchor
.getParent();
302 if (parent
instanceof PsiAssignmentExpression
) {
303 final PsiAssignmentExpression expression
= (PsiAssignmentExpression
)parent
;
304 if (expression
.getLExpression() == psiAnchor
) return true;
310 private static LocalQuickFix
createSimplifyBooleanExpressionFix(PsiElement element
, final boolean value
) {
311 if (!(element
instanceof PsiExpression
)) return null;
312 final PsiExpression expression
= (PsiExpression
)element
;
313 while (element
.getParent() instanceof PsiExpression
) {
314 element
= element
.getParent();
316 final SimplifyBooleanExpressionFix fix
= new SimplifyBooleanExpressionFix(expression
, value
);
317 // simplify intention already active
318 if (!fix
.isAvailable(element
.getProject(), null, element
.getContainingFile()) ||
319 SimplifyBooleanExpressionFix
.canBeSimplified((PsiExpression
)element
)) {
322 return new LocalQuickFix() {
323 @NotNull public String
getName() {
324 return fix
.getText();
327 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {
328 final PsiElement psiElement
= descriptor
.getPsiElement();
330 LOG
.assertTrue(psiElement
.isValid());
331 fix
.invoke(project
, null, psiElement
.getContainingFile());
333 catch (IncorrectOperationException e
) {
339 public String
getFamilyName() {
340 return InspectionsBundle
.message("inspection.data.flow.simplify.boolean.expression.quickfix");
345 private static class RedundantInstanceofFix
implements LocalQuickFix
{
347 public String
getName() {
348 return InspectionsBundle
.message("inspection.data.flow.redundant.instanceof.quickfix");
351 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {
352 if (ReadonlyStatusHandler
.getInstance(project
)
353 .ensureFilesWritable(PsiUtilBase
.getVirtualFile(descriptor
.getPsiElement())).hasReadonlyFiles()) return;
354 final PsiElement psiElement
= descriptor
.getPsiElement();
355 if (psiElement
instanceof PsiInstanceOfExpression
) {
357 final PsiExpression compareToNull
= JavaPsiFacade
.getInstance(psiElement
.getProject()).getElementFactory().
358 createExpressionFromText(((PsiInstanceOfExpression
)psiElement
).getOperand().getText() + " != null",
359 psiElement
.getParent());
360 psiElement
.replace(compareToNull
);
362 catch (IncorrectOperationException e
) {
369 public String
getFamilyName() {
376 public String
getDisplayName() {
377 return InspectionsBundle
.message("inspection.data.flow.display.name");
381 public String
getGroupDisplayName() {
382 return GroupNames
.BUGS_GROUP_NAME
;
386 public String
getShortName() {
390 private class OptionsPanel
extends JPanel
{
391 private final JCheckBox mySuggestNullables
;
392 private final JCheckBox myDontReportTrueAsserts
;
394 private OptionsPanel() {
395 super(new GridBagLayout());
397 GridBagConstraints gc
= new GridBagConstraints();
400 gc
.fill
= GridBagConstraints
.HORIZONTAL
;
401 gc
.anchor
= GridBagConstraints
.NORTHWEST
;
403 //mySuggestNullables = new JCheckBox("Suggest @Nullable annotation for method possibly return null.\n Requires JDK5.0 and annotations.jar from IDEA distribution");
404 mySuggestNullables
= new JCheckBox(
405 InspectionsBundle
.message("inspection.data.flow.nullable.quickfix.option", ApplicationNamesInfo
.getInstance().getProductName()));
406 mySuggestNullables
.setSelected(SUGGEST_NULLABLE_ANNOTATIONS
);
407 mySuggestNullables
.getModel().addChangeListener(new ChangeListener() {
408 public void stateChanged(ChangeEvent e
) {
409 SUGGEST_NULLABLE_ANNOTATIONS
= mySuggestNullables
.isSelected();
413 myDontReportTrueAsserts
= new JCheckBox(
414 InspectionsBundle
.message("inspection.data.flow.true.asserts.option", ApplicationNamesInfo
.getInstance().getProductName()));
415 myDontReportTrueAsserts
.setSelected(DONT_REPORT_TRUE_ASSERT_STATEMENTS
);
416 myDontReportTrueAsserts
.getModel().addChangeListener(new ChangeListener() {
417 public void stateChanged(ChangeEvent e
) {
418 DONT_REPORT_TRUE_ASSERT_STATEMENTS
= myDontReportTrueAsserts
.isSelected();
422 gc
.insets
= new Insets(0, 0, 15, 0);
424 add(mySuggestNullables
, gc
);
427 add(myDontReportTrueAsserts
, gc
);
431 private static class DataFlowInstructionVisitor
extends StandardInstructionVisitor
{
433 protected void onAssigningToNotNullableVariable(AssignInstruction instruction
, DataFlowRunner runner
) {
434 ((StandardDataFlowRunner
)runner
).onAssigningToNotNullableVariable(instruction
.getRExpression());
437 protected void onNullableReturn(CheckReturnValueInstruction instruction
, DataFlowRunner runner
) {
438 ((StandardDataFlowRunner
)runner
).onNullableReturn(instruction
.getReturn());
441 protected void onInstructionProducesNPE(FieldReferenceInstruction instruction
, DataFlowRunner runner
) {
442 ((StandardDataFlowRunner
)runner
).onInstructionProducesNPE(instruction
);
445 protected void onInstructionProducesCCE(TypeCastInstruction instruction
, DataFlowRunner runner
) {
446 ((StandardDataFlowRunner
)runner
).onInstructionProducesCCE(instruction
);
449 protected void onInstructionProducesNPE(MethodCallInstruction instruction
, DataFlowRunner runner
) {
450 ((StandardDataFlowRunner
) runner
).onInstructionProducesNPE(instruction
);
453 protected void onUnboxingNullable(MethodCallInstruction instruction
, DataFlowRunner runner
) {
454 ((StandardDataFlowRunner
) runner
).onUnboxingNullable(instruction
.getContext());
457 protected void onPassingNullParameter(DataFlowRunner runner
, PsiExpression arg
) {
458 ((StandardDataFlowRunner
) runner
).onPassingNullParameter(arg
); // Parameters on stack are reverted.