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