update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / dataFlow / DataFlowInspection.java
bloba7d27a3131d4ad2238eb384226e9caafee032570
1 /*
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.
19 * User: max
20 * Date: Dec 24, 2001
21 * Time: 2:46:32 PM
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;
47 import javax.swing.*;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import java.awt.*;
51 import java.util.*;
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();
64 @NotNull
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);
107 @Nullable
108 private static LocalQuickFix[] createNPEFixes(PsiExpression qualifier) {
109 if (qualifier != null &&
110 !(qualifier instanceof PsiMethodCallExpression) &&
111 !(qualifier instanceof PsiLiteralExpression && ((PsiLiteralExpression)qualifier).getValue() == null)) {
112 try {
113 PsiBinaryExpression binary = (PsiBinaryExpression)JavaPsiFacade.getInstance(qualifier.getProject()).getElementFactory()
114 .createExpressionFromText("a != null",
115 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()) {
124 fixes.add(ifFix);
126 return fixes.toArray(new LocalQuickFix[fixes.size()]);
128 catch (IncorrectOperationException e) {
129 LOG.error(e);
130 return null;
133 return null;
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"),
168 fix);
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"),
179 fix);
181 else {
182 LocalQuickFix[] fix = createNPEFixes((PsiExpression)elementToAssert);
183 holder.registerProblem(elementToAssert,
184 InspectionsBundle.message("dataflow.message.npe.field.access"),
185 fix);
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());
202 else {
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)));
221 else {
222 boolean report = !(psiAnchor.getParent() instanceof PsiAssertStatement) || !DONT_REPORT_TRUE_ASSERT_STATEMENTS || !evaluatesToTrue;
223 if (report) {
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) {
286 element = parent;
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());
297 return false;
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;
306 return false;
309 @Nullable
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)) {
320 return null;
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();
329 try {
330 LOG.assertTrue(psiElement.isValid());
331 fix.invoke(project, null, psiElement.getContainingFile());
333 catch (IncorrectOperationException e) {
334 LOG.error(e);
338 @NotNull
339 public String getFamilyName() {
340 return InspectionsBundle.message("inspection.data.flow.simplify.boolean.expression.quickfix");
345 private static class RedundantInstanceofFix implements LocalQuickFix {
346 @NotNull
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) {
356 try {
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) {
363 LOG.error(e);
368 @NotNull
369 public String getFamilyName() {
370 return getName();
375 @NotNull
376 public String getDisplayName() {
377 return InspectionsBundle.message("inspection.data.flow.display.name");
380 @NotNull
381 public String getGroupDisplayName() {
382 return GroupNames.BUGS_GROUP_NAME;
385 @NotNull
386 public String getShortName() {
387 return SHORT_NAME;
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();
398 gc.weighty = 0;
399 gc.weightx = 1;
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);
423 gc.gridy = 0;
424 add(mySuggestNullables, gc);
426 gc.gridy++;
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.