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.
16 package com
.intellij
.codeInsight
;
18 import com
.intellij
.openapi
.application
.ApplicationManager
;
19 import com
.intellij
.openapi
.util
.Computable
;
20 import com
.intellij
.psi
.*;
21 import com
.intellij
.psi
.controlFlow
.*;
22 import com
.intellij
.psi
.search
.GlobalSearchScope
;
23 import com
.intellij
.psi
.util
.InheritanceUtil
;
24 import com
.intellij
.psi
.util
.TypeConversionUtil
;
25 import gnu
.trove
.THashSet
;
26 import org
.jetbrains
.annotations
.NonNls
;
27 import org
.jetbrains
.annotations
.NotNull
;
28 import org
.jetbrains
.annotations
.Nullable
;
35 public class ExceptionUtil
{
36 @NonNls private static final String CLONE_METHOD_NAME
= "clone";
38 private ExceptionUtil() {}
41 public static List
<PsiClassType
> getThrownExceptions(@NotNull PsiElement
[] elements
) {
42 List
<PsiClassType
> array
= new ArrayList
<PsiClassType
>();
43 for (PsiElement element
: elements
) {
44 List
<PsiClassType
> exceptions
= getThrownExceptions(element
);
45 addExceptions(array
, exceptions
);
52 public static List
<PsiClassType
> getThrownCheckedExceptions(@NotNull PsiElement
[] elements
) {
53 List
<PsiClassType
> exceptions
= getThrownExceptions(elements
);
54 if (exceptions
.isEmpty()) return exceptions
;
55 exceptions
= filterOutUncheckedExceptions(exceptions
);
60 private static List
<PsiClassType
> filterOutUncheckedExceptions(List
<PsiClassType
> exceptions
) {
61 List
<PsiClassType
> array
= new ArrayList
<PsiClassType
>();
62 for (PsiClassType exception
: exceptions
) {
63 if (!isUncheckedException(exception
)) array
.add(exception
);
69 private static List
<PsiClassType
> getThrownExceptions(@NotNull PsiElement element
) {
70 if (element
instanceof PsiClass
) {
71 if (element
instanceof PsiAnonymousClass
) {
72 final PsiExpressionList argumentList
= ((PsiAnonymousClass
)element
).getArgumentList();
73 if (argumentList
!= null){
74 return getThrownExceptions(argumentList
);
77 // filter class declaration in code
78 return Collections
.emptyList();
80 else if (element
instanceof PsiMethodCallExpression
) {
81 PsiReferenceExpression methodRef
= ((PsiMethodCallExpression
)element
).getMethodExpression();
82 JavaResolveResult result
= methodRef
.advancedResolve(false);
83 return getExceptionsByMethodAndChildren(element
, result
);
85 else if (element
instanceof PsiNewExpression
) {
86 JavaResolveResult result
= ((PsiNewExpression
)element
).resolveMethodGenerics();
87 return getExceptionsByMethodAndChildren(element
, result
);
89 else if (element
instanceof PsiThrowStatement
) {
90 PsiExpression expr
= ((PsiThrowStatement
)element
).getException();
91 if (expr
== null) return Collections
.emptyList();
92 PsiType exception
= expr
.getType();
93 List
<PsiClassType
> array
= new ArrayList
<PsiClassType
>();
94 if (exception
instanceof PsiClassType
) {
95 array
.add((PsiClassType
)exception
);
97 addExceptions(array
, getThrownExceptions(expr
));
100 else if (element
instanceof PsiTryStatement
) {
101 PsiTryStatement tryStatement
= (PsiTryStatement
)element
;
102 final PsiCodeBlock tryBlock
= tryStatement
.getTryBlock();
103 List
<PsiClassType
> array
= new ArrayList
<PsiClassType
>();
104 if (tryBlock
!= null) {
105 List
<PsiClassType
> exceptions
= getThrownExceptions(tryBlock
);
106 array
.addAll(exceptions
);
109 PsiParameter
[] parameters
= tryStatement
.getCatchBlockParameters();
110 for (PsiParameter parm
: parameters
) {
111 PsiType exception
= parm
.getType();
112 for (int j
= array
.size() - 1; j
>= 0; j
--) {
113 PsiClassType exception1
= array
.get(j
);
114 if (exception
.isAssignableFrom(exception1
)) {
115 array
.remove(exception1
);
120 PsiCodeBlock
[] catchBlocks
= tryStatement
.getCatchBlocks();
121 for (PsiCodeBlock catchBlock
: catchBlocks
) {
122 addExceptions(array
, getThrownExceptions(catchBlock
));
125 PsiCodeBlock finallyBlock
= tryStatement
.getFinallyBlock();
126 if (finallyBlock
!= null) {
127 // if finally block completes normally, exception not catched
128 // if finally block completes abruptly, exception gets lost
130 ControlFlow flow
= ControlFlowFactory
.getInstance(finallyBlock
.getProject()).getControlFlow(finallyBlock
, LocalsOrMyInstanceFieldsControlFlowPolicy
.getInstance(), false);
131 int completionReasons
= ControlFlowUtil
.getCompletionReasons(flow
, 0, flow
.getSize());
132 List
<PsiClassType
> thrownExceptions
= getThrownExceptions(finallyBlock
);
133 if ((completionReasons
& ControlFlowUtil
.NORMAL_COMPLETION_REASON
) == 0) {
134 array
= new ArrayList
<PsiClassType
>(thrownExceptions
);
137 addExceptions(array
, thrownExceptions
);
140 catch (AnalysisCanceledException e
) {
147 return getThrownExceptions(element
.getChildren());
151 private static List
<PsiClassType
> getExceptionsByMethodAndChildren(PsiElement element
, JavaResolveResult resolveResult
) {
152 PsiMethod method
= (PsiMethod
)resolveResult
.getElement();
153 List
<PsiClassType
> result
= new ArrayList
<PsiClassType
>();
154 if (method
!= null) {
155 PsiSubstitutor substitutor
= resolveResult
.getSubstitutor();
156 final PsiClassType
[] referenceTypes
= method
.getThrowsList().getReferencedTypes();
157 for (PsiType type
: referenceTypes
) {
158 type
= substitutor
.substitute(type
);
159 if (type
instanceof PsiClassType
) {
160 result
.add((PsiClassType
)type
);
165 PsiElement
[] children
= element
.getChildren();
166 for (PsiElement child
: children
) {
167 addExceptions(result
, getThrownExceptions(child
));
172 private static void addExceptions(List
<PsiClassType
> array
, Collection
<PsiClassType
> exceptions
) {
173 for (PsiClassType exception
: exceptions
) {
174 addException(array
, exception
);
178 private static void addException(List
<PsiClassType
> array
, PsiClassType exception
) {
179 if (exception
== null) return ;
180 for (int i
= array
.size()-1; i
>=0; i
--) {
181 PsiClassType exception1
= array
.get(i
);
182 if (exception1
.isAssignableFrom(exception
)) return;
183 if (exception
.isAssignableFrom(exception1
)) {
187 array
.add(exception
);
191 public static Collection
<PsiClassType
> collectUnhandledExceptions(@NotNull PsiElement element
, PsiElement topElement
) {
192 final Set
<PsiClassType
> set
= collectUnhandledExceptions(element
, topElement
, null);
193 return set
== null ? Collections
.<PsiClassType
>emptyList() : set
;
197 private static Set
<PsiClassType
> collectUnhandledExceptions(@NotNull PsiElement element
, PsiElement topElement
, Set
<PsiClassType
> foundExceptions
) {
198 Collection
<PsiClassType
> unhandledExceptions
= null;
199 if (element
instanceof PsiCallExpression
) {
200 PsiCallExpression expression
= (PsiCallExpression
)element
;
201 unhandledExceptions
= getUnhandledExceptions(expression
, topElement
);
203 else if (element
instanceof PsiThrowStatement
) {
204 PsiThrowStatement statement
= (PsiThrowStatement
)element
;
205 PsiClassType exception
= getUnhandledException(statement
, topElement
);
206 unhandledExceptions
= exception
== null ? Collections
.<PsiClassType
>emptyList() : Collections
.singletonList(exception
);
208 else if (element
instanceof PsiCodeBlock
209 && element
.getParent() instanceof PsiMethod
210 && ((PsiMethod
)element
.getParent()).isConstructor()
211 && !firstStatementIsConstructorCall((PsiCodeBlock
)element
)) {
212 // there is implicit parent constructor call
213 final PsiMethod constructor
= (PsiMethod
)element
.getParent();
214 final PsiClass aClass
= constructor
.getContainingClass();
215 final PsiClass superClass
= aClass
== null ?
null : aClass
.getSuperClass();
216 final PsiMethod
[] superConstructors
= superClass
== null ? PsiMethod
.EMPTY_ARRAY
: superClass
.getConstructors();
217 Set
<PsiClassType
> unhandled
= new HashSet
<PsiClassType
>();
218 for (PsiMethod superConstructor
: superConstructors
) {
219 if (!superConstructor
.hasModifierProperty(PsiModifier
.PRIVATE
) && superConstructor
.getParameterList().getParametersCount() == 0) {
220 final PsiClassType
[] exceptionTypes
= superConstructor
.getThrowsList().getReferencedTypes();
221 for (PsiClassType exceptionType
: exceptionTypes
) {
222 if (!isUncheckedException(exceptionType
) && !isHandled(element
, exceptionType
, topElement
)) {
223 unhandled
.add(exceptionType
);
230 // plus all exceptions thrown in instance class initializers
231 if (aClass
!= null) {
232 final PsiClassInitializer
[] initializers
= aClass
.getInitializers();
233 final Set
<PsiClassType
> thrownByInitializer
= new THashSet
<PsiClassType
>();
234 for (PsiClassInitializer initializer
: initializers
) {
235 if (initializer
.hasModifierProperty(PsiModifier
.STATIC
)) continue;
236 thrownByInitializer
.clear();
237 collectUnhandledExceptions(initializer
.getBody(), initializer
, thrownByInitializer
);
238 for (PsiClassType thrown
: thrownByInitializer
) {
239 if (!isHandled(constructor
.getBody(), thrown
, topElement
)) {
240 unhandled
.add(thrown
);
245 unhandledExceptions
= unhandled
;
248 if (unhandledExceptions
!= null) {
249 if (foundExceptions
== null) {
250 foundExceptions
= new THashSet
<PsiClassType
>();
252 foundExceptions
.addAll(unhandledExceptions
);
255 for (PsiElement child
= element
.getFirstChild(); child
!= null; child
= child
.getNextSibling()) {
256 foundExceptions
= collectUnhandledExceptions(child
, topElement
, foundExceptions
);
259 return foundExceptions
;
262 private static boolean firstStatementIsConstructorCall(PsiCodeBlock constructorBody
) {
263 final PsiStatement
[] statements
= constructorBody
.getStatements();
264 if (statements
.length
== 0) return false;
265 if (!(statements
[0] instanceof PsiExpressionStatement
)) return false;
267 final PsiExpression expression
= ((PsiExpressionStatement
)statements
[0]).getExpression();
268 if (!(expression
instanceof PsiMethodCallExpression
)) return false;
269 final PsiMethod method
= (PsiMethod
)((PsiMethodCallExpression
)expression
).getMethodExpression().resolve();
270 return method
!= null && method
.isConstructor();
274 public static List
<PsiClassType
> getUnhandledExceptions(PsiElement
[] elements
) {
275 final List
<PsiClassType
> array
= new ArrayList
<PsiClassType
>();
276 PsiElementVisitor visitor
= new JavaRecursiveElementWalkingVisitor() {
277 @Override public void visitCallExpression(PsiCallExpression expression
) {
278 addExceptions(array
, getUnhandledExceptions(expression
, null));
279 visitElement(expression
);
282 @Override public void visitThrowStatement(PsiThrowStatement statement
) {
283 addException(array
, getUnhandledException(statement
, null));
284 visitElement(statement
);
288 for (PsiElement element
: elements
) {
289 element
.accept(visitor
);
296 public static List
<PsiClassType
> getUnhandledExceptions(PsiElement element
) {
297 if (element
instanceof PsiCallExpression
) {
298 PsiCallExpression expression
= (PsiCallExpression
)element
;
299 return getUnhandledExceptions(expression
, null);
301 else if (element
instanceof PsiThrowStatement
) {
302 PsiThrowStatement throwStatement
= (PsiThrowStatement
)element
;
303 PsiClassType exception
= getUnhandledException(throwStatement
, null);
304 if (exception
!= null) return Collections
.singletonList(exception
);
307 return Collections
.emptyList();
311 public static List
<PsiClassType
> getUnhandledExceptions(PsiCallExpression methodCall
, PsiElement topElement
) {
312 final JavaResolveResult result
= methodCall
.resolveMethodGenerics();
313 PsiMethod method
= (PsiMethod
)result
.getElement();
314 return getUnhandledExceptions(method
, methodCall
, topElement
,
315 ApplicationManager
.getApplication().runReadAction(new Computable
<PsiSubstitutor
>() {
316 public PsiSubstitutor
compute() {
317 return result
.getSubstitutor();
323 public static PsiClassType
getUnhandledException(PsiThrowStatement throwStatement
, PsiElement topElement
){
324 final PsiExpression exception
= throwStatement
.getException();
325 if (exception
!= null) {
326 final PsiType type
= exception
.getType();
327 if (type
instanceof PsiClassType
) {
328 PsiClassType classType
= (PsiClassType
)type
;
329 if (!isUncheckedException(classType
) && !isHandled(throwStatement
, classType
, topElement
)) {
339 private static List
<PsiClassType
> getUnhandledExceptions(PsiMethod method
,
341 PsiElement topElement
,
342 PsiSubstitutor substitutor
) {
343 if (method
== null || isArrayClone(method
, element
)) {
344 return Collections
.emptyList();
346 final PsiClassType
[] referencedTypes
= method
.getThrowsList().getReferencedTypes();
347 if (referencedTypes
.length
> 0) {
348 List
<PsiClassType
> result
= new ArrayList
<PsiClassType
>();
350 for (PsiClassType referencedType
: referencedTypes
) {
351 final PsiType type
= substitutor
.substitute(referencedType
);
352 if (!(type
instanceof PsiClassType
)) continue;
353 PsiClassType classType
= (PsiClassType
)type
;
354 PsiClass exceptionClass
= ((PsiClassType
)type
).resolve();
355 if (exceptionClass
== null) continue;
357 if (isUncheckedException(classType
)) continue;
358 if (isHandled(element
, classType
, topElement
)) continue;
360 result
.add((PsiClassType
)type
);
365 return Collections
.emptyList();
368 private static boolean isArrayClone(PsiMethod method
, PsiElement element
) {
369 if (!(element
instanceof PsiMethodCallExpression
)) return false;
370 if (!method
.getName().equals(CLONE_METHOD_NAME
)) return false;
371 PsiClass containingClass
= method
.getContainingClass();
372 if (containingClass
== null || !"java.lang.Object".equals(containingClass
.getQualifiedName())) {
376 PsiMethodCallExpression methodCallExpression
= (PsiMethodCallExpression
)element
;
377 final PsiExpression qualifierExpression
= methodCallExpression
.getMethodExpression().getQualifierExpression();
378 return qualifierExpression
!= null && qualifierExpression
.getType() instanceof PsiArrayType
;
381 public static boolean isUncheckedException(PsiClassType type
) {
382 final GlobalSearchScope searchScope
= type
.getResolveScope();
383 final PsiClass aClass
= type
.resolve();
384 if (aClass
== null) return false;
385 final PsiClass runtimeExceptionClass
= ApplicationManager
.getApplication().runReadAction(
386 new Computable
<PsiClass
>() {
387 public PsiClass
compute() {
388 return JavaPsiFacade
.getInstance(aClass
.getProject()).findClass("java.lang.RuntimeException", searchScope
);
392 if (runtimeExceptionClass
!= null &&
393 InheritanceUtil
.isInheritorOrSelf(aClass
, runtimeExceptionClass
, true)) {
397 final PsiClass errorClass
= ApplicationManager
.getApplication().runReadAction(
398 new Computable
<PsiClass
>() {
399 public PsiClass
compute() {
400 return JavaPsiFacade
.getInstance(aClass
.getProject()).findClass("java.lang.Error", searchScope
);
404 return errorClass
!= null && InheritanceUtil
.isInheritorOrSelf(aClass
, errorClass
, true);
407 public static boolean isUncheckedExceptionOrSuperclass(@NotNull PsiClassType type
) {
408 String canonicalText
= type
.getCanonicalText();
409 return "java.lang.Throwable".equals(canonicalText
) ||
410 "java.lang.Exception".equals(canonicalText
) ||
411 isUncheckedException(type
);
414 public static boolean isHandled(PsiClassType exceptionType
, PsiElement throwPlace
) {
415 return isHandled(throwPlace
, exceptionType
, throwPlace
.getContainingFile());
418 private static boolean isHandled(PsiElement element
, PsiClassType exceptionType
, PsiElement topElement
) {
419 if (element
== null || element
.getParent() == topElement
|| element
.getParent() == null) return false;
421 final PsiElement parent
= element
.getParent();
423 if (parent
instanceof PsiMethod
) {
424 PsiMethod method
= (PsiMethod
)parent
;
425 return isHandledByMethodThrowsClause(method
, exceptionType
);
427 else if (parent
instanceof PsiClass
) {
428 // arguments to anon class constructor should be handled higher
429 // like in void f() throws XXX { new AA(methodThrowingXXX()) { ... }; }
430 return parent
instanceof PsiAnonymousClass
&& isHandled(parent
, exceptionType
, topElement
);
432 else if (parent
instanceof PsiClassInitializer
) {
433 if (((PsiClassInitializer
)parent
).hasModifierProperty(PsiModifier
.STATIC
)) return false;
434 // anonymous class initializers can throw any exceptions
435 if (!(parent
.getParent() instanceof PsiAnonymousClass
)) {
436 // exception thrown from within class instance initializer must be handled in every class constructor
437 // check each constructor throws exception or superclass (there must be at least one)
438 final PsiClass aClass
= ((PsiClassInitializer
)parent
).getContainingClass();
439 return areAllConstructorsThrow(aClass
, exceptionType
);
442 else if (parent
instanceof PsiTryStatement
) {
443 PsiTryStatement tryStatement
= (PsiTryStatement
)parent
;
444 if (tryStatement
.getTryBlock() == element
&& isCatched(tryStatement
, exceptionType
)) {
447 PsiCodeBlock finallyBlock
= tryStatement
.getFinallyBlock();
448 if (element
instanceof PsiCatchSection
&& finallyBlock
!= null && blockCompletesAbruptly(finallyBlock
)) {
449 // exception swallowed
453 else if (parent
instanceof JavaCodeFragment
) {
454 JavaCodeFragment codeFragment
= (JavaCodeFragment
)parent
;
455 JavaCodeFragment
.ExceptionHandler exceptionHandler
= codeFragment
.getExceptionHandler();
456 return exceptionHandler
!= null && exceptionHandler
.isHandledException(exceptionType
);
458 else if (JspPsiUtil
.isInJspFile(parent
) && parent
instanceof PsiFile
) {
461 else if (parent
instanceof PsiFile
) {
464 else if (parent
instanceof PsiField
&& ((PsiField
)parent
).getInitializer() == element
) {
465 final PsiClass aClass
= ((PsiField
)parent
).getContainingClass();
466 if (aClass
!= null && !(aClass
instanceof PsiAnonymousClass
) && !((PsiField
)parent
).hasModifierProperty(PsiModifier
.STATIC
)) {
467 // exceptions thrown in field initalizers should be thrown in all class constructors
468 return areAllConstructorsThrow(aClass
, exceptionType
);
471 return isHandled(parent
, exceptionType
, topElement
);
474 private static boolean areAllConstructorsThrow(final PsiClass aClass
, PsiClassType exceptionType
) {
475 if (aClass
== null) return false;
476 final PsiMethod
[] constructors
= aClass
.getConstructors();
477 boolean thrown
= constructors
.length
!= 0;
478 for (PsiMethod constructor
: constructors
) {
479 if (!isHandledByMethodThrowsClause(constructor
, exceptionType
)) {
487 private static boolean isCatched(PsiTryStatement tryStatement
, PsiClassType exceptionType
) {
488 PsiCodeBlock finallyBlock
= tryStatement
.getFinallyBlock();
489 if (finallyBlock
!= null) {
490 List
<PsiClassType
> exceptions
= getUnhandledExceptions(finallyBlock
);
491 if (exceptions
.contains(exceptionType
)) return false;
492 // if finally block completes normally, exception not catched
493 // if finally block completes abruptly, exception gets lost
494 if (blockCompletesAbruptly(finallyBlock
)) return true;
497 final PsiParameter
[] catchBlockParameters
= tryStatement
.getCatchBlockParameters();
498 for (PsiParameter parameter
: catchBlockParameters
) {
499 PsiType paramType
= parameter
.getType();
500 if (paramType
.isAssignableFrom(exceptionType
)) return true;
506 private static boolean blockCompletesAbruptly(final PsiCodeBlock finallyBlock
) {
508 ControlFlow flow
= ControlFlowFactory
.getInstance(finallyBlock
.getProject()).getControlFlow(finallyBlock
, LocalsOrMyInstanceFieldsControlFlowPolicy
.getInstance(), false);
509 int completionReasons
= ControlFlowUtil
.getCompletionReasons(flow
, 0, flow
.getSize());
510 if ((completionReasons
& ControlFlowUtil
.NORMAL_COMPLETION_REASON
) == 0) return true;
512 catch (AnalysisCanceledException e
) {
518 private static boolean isHandledByMethodThrowsClause(PsiMethod method
, PsiClassType exceptionType
) {
519 final PsiClassType
[] referencedTypes
= method
.getThrowsList().getReferencedTypes();
520 return isHandledBy(exceptionType
, referencedTypes
);
523 public static boolean isHandledBy(PsiClassType exceptionType
, @NotNull PsiClassType
[] referencedTypes
) {
524 for (PsiClassType classType
: referencedTypes
) {
525 if (classType
.isAssignableFrom(exceptionType
)) return true;
530 public static void sortExceptionsByHierarchy(List
<PsiClassType
> exceptions
) {
531 if (exceptions
.size() <= 1) return;
532 sortExceptionsByHierarchy(exceptions
.subList(1, exceptions
.size()));
533 for (int i
=0; i
<exceptions
.size()-1;i
++) {
534 if (TypeConversionUtil
.isAssignable(exceptions
.get(i
), exceptions
.get(i
+1))) {
535 Collections
.swap(exceptions
, i
,i
+1);