IG: hide strict throw from finally mode behind option (IDEA-154184)
[idea/community.git] / plugins / InspectionGadgets / InspectionGadgetsAnalysis / src / com / siyeh / ig / errorhandling / ThrowFromFinallyBlockInspection.java
1 /*
2  * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.siyeh.ig.errorhandling;
17
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
20 import com.intellij.psi.*;
21 import com.intellij.psi.util.PsiTreeUtil;
22 import com.siyeh.InspectionGadgetsBundle;
23 import com.siyeh.ig.BaseInspection;
24 import com.siyeh.ig.BaseInspectionVisitor;
25 import com.siyeh.ig.psiutils.ExpressionUtils;
26 import com.siyeh.ig.psiutils.ParenthesesUtils;
27 import com.siyeh.ig.psiutils.VariableAccessUtils;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import javax.swing.*;
32 import java.util.List;
33
34 public class ThrowFromFinallyBlockInspection extends BaseInspection {
35
36   @SuppressWarnings("PublicField")
37   public boolean warnOnAllExceptions = false;
38
39   @Override
40   @NotNull
41   public String getDisplayName() {
42     return InspectionGadgetsBundle.message(
43       "throw.from.finally.block.display.name");
44   }
45
46   @Override
47   public boolean isEnabledByDefault() {
48     return true;
49   }
50
51   @Nullable
52   @Override
53   public JComponent createOptionsPanel() {
54     return new SingleCheckboxOptionsPanel(InspectionGadgetsBundle.message("throw,from.finally.block.everywhere.option"),
55                                           this, "warnOnAllExceptions");
56   }
57
58   @Override
59   @NotNull
60   protected String buildErrorString(Object... infos) {
61     if (infos.length == 0) {
62       return InspectionGadgetsBundle.message("throw.from.finally.block.problem.descriptor");
63     }
64     else {
65       final PsiClassType type = (PsiClassType)infos[0];
66       return InspectionGadgetsBundle.message("possible.throw.from.finally.block.problem.descriptor", type.getPresentableText());
67     }
68   }
69
70   @Override
71   public BaseInspectionVisitor buildVisitor() {
72     return new ThrowFromFinallyBlockVisitor();
73   }
74
75   private class ThrowFromFinallyBlockVisitor extends BaseInspectionVisitor {
76
77     @Override
78     public void visitCallExpression(PsiCallExpression expression) {
79       super.visitCallExpression(expression);
80       if (!warnOnAllExceptions) {
81         return;
82       }
83       final List<PsiClassType> exceptions = ExceptionUtil.getThrownExceptions(expression);
84       if (exceptions.isEmpty()) {
85         return;
86       }
87       for (PsiClassType exception : exceptions) {
88         final PsiCodeBlock finallyBlock = getContainingFinallyBlock(expression, exception);
89         if (finallyBlock != null && isHidingOfPreviousException(finallyBlock, expression)) {
90           if (expression instanceof PsiMethodCallExpression) {
91             registerMethodCallError((PsiMethodCallExpression)expression, exception);
92           }
93           else if (expression instanceof PsiNewExpression) {
94             registerNewExpressionError((PsiNewExpression)expression, exception);
95           }
96           return;
97         }
98       }
99     }
100
101     @Override
102     public void visitThrowStatement(PsiThrowStatement statement) {
103       super.visitThrowStatement(statement);
104       final PsiExpression exception = ParenthesesUtils.stripParentheses(statement.getException());
105       if (exception == null) {
106         return;
107       }
108       final PsiType type = exception.getType();
109       if (type == null) {
110         return;
111       }
112       final PsiCodeBlock finallyBlock = getContainingFinallyBlock(statement, type);
113       if (finallyBlock == null) {
114         return;
115       }
116       if (exception instanceof PsiReferenceExpression) {
117         final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)exception;
118         final PsiElement target = referenceExpression.resolve();
119         if (target == null || !PsiTreeUtil.isAncestor(finallyBlock, target, true)) {
120           // variable from outside finally block is thrown
121           return;
122         }
123       }
124       if (isHidingOfPreviousException(finallyBlock, statement)) {
125         registerStatementError(statement);
126       }
127     }
128
129     private boolean isHidingOfPreviousException(PsiCodeBlock finallyBlock, PsiElement throwElement) {
130       final PsiElement parent = finallyBlock.getParent();
131       if (!(parent instanceof PsiTryStatement)) {
132         // never reached
133         return false;
134       }
135       final PsiTryStatement tryStatement = (PsiTryStatement)parent;
136       final PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks();
137       if (catchBlocks.length == 0) {
138         return true;
139       }
140       final PsiIfStatement ifStatement = getParentOfType(throwElement, PsiIfStatement.class, finallyBlock);
141       if (ifStatement == null) {
142         return true;
143       }
144       final boolean inThenBranch = PsiTreeUtil.isAncestor(ifStatement.getThenBranch(), throwElement, false);
145       final boolean inElseBranch = PsiTreeUtil.isAncestor(ifStatement.getElseBranch(), throwElement, false);
146       if (!inThenBranch && !inElseBranch) {
147         return true;
148       }
149       final PsiExpression condition = ifStatement.getCondition();
150       final PsiVariable variable = ExpressionUtils.getVariableFromNullComparison(condition, inThenBranch);
151       if (variable == null) {
152         return true;
153       }
154       boolean assigned = true;
155       for (PsiCodeBlock catchBlock : catchBlocks) {
156         assigned &= VariableAccessUtils.variableIsAssigned(variable, catchBlock);
157       }
158       return !assigned;
159     }
160
161     @Nullable
162     public <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass, @NotNull PsiElement stopAt) {
163       if (element == null || element instanceof PsiFile) return null;
164       element = element.getParent();
165
166       while (element != null && !aClass.isInstance(element)) {
167         if (element == stopAt || element instanceof PsiFile) return null;
168         element = element.getParent();
169       }
170       //noinspection unchecked
171       return (T)element;
172     }
173   }
174
175   private static PsiCodeBlock getContainingFinallyBlock(@NotNull PsiElement element, @NotNull PsiType thrownType) {
176     PsiElement currentElement = element;
177     while (true) {
178       final PsiTryStatement tryStatement = PsiTreeUtil
179         .getParentOfType(currentElement, PsiTryStatement.class, true, PsiClass.class, PsiLambdaExpression.class);
180       if (tryStatement == null) {
181         return null;
182       }
183       final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
184       if (PsiTreeUtil.isAncestor(finallyBlock, currentElement, true)) {
185         return finallyBlock;
186       }
187       if (PsiTreeUtil.isAncestor(tryStatement.getTryBlock(), currentElement, true) && isCaught(tryStatement, thrownType)) {
188         return null;
189       }
190       currentElement = tryStatement;
191     }
192   }
193
194   private static boolean isCaught(PsiTryStatement tryStatement, PsiType exceptionType) {
195     for (PsiParameter parameter : tryStatement.getCatchBlockParameters()) {
196       final PsiType type = parameter.getType();
197       if (type.isAssignableFrom(exceptionType)) return true;
198     }
199     return false;
200   }
201 }