-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathPartiallyMaskedCatch.ql
More file actions
111 lines (104 loc) · 3.71 KB
/
PartiallyMaskedCatch.ql
File metadata and controls
111 lines (104 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* @name Unreachable catch clause
* @description An unreachable 'catch' clause may indicate a mistake in exception handling or may
* be unnecessary.
* @kind problem
* @problem.severity warning
* @precision high
* @id java/unreachable-catch-clause
* @tags reliability
* correctness
* exceptions
* external/cwe/cwe-561
*/
import java
/**
* Exceptions of type `rt` thrown from within statement `s` are caught by an inner try block
* and are therefore not propagated to the outer try block `t`.
*/
private predicate caughtInside(TryStmt t, Stmt s, RefType rt) {
exists(TryStmt innerTry | innerTry.getEnclosingStmt+() = t.getBlock() |
s.getEnclosingStmt+() = innerTry.getBlock() and
caughtType(innerTry, _).hasSubtype*(rt)
)
}
/**
* Returns an exception type thrown from within the try block of `t`
* that is relevant to the catch clauses of `t` (i.e. not already
* caught by an inner try-catch).
*/
private RefType getAThrownExceptionType(TryStmt t) {
exists(Method m, Exception e |
(
m = t.getAResourceDecl().getAVariable().getType().(RefType).getAMethod() or
m = t.getAResourceExpr().getType().(RefType).getAMethod()
) and
m.hasName("close") and
m.hasNoParameters() and
m.getAnException() = e and
result = e.getType()
)
or
exists(Call call, Exception e |
t.getBlock() = call.getEnclosingStmt().getEnclosingStmt*() or
t.getAResourceDecl() = call.getEnclosingStmt()
|
(
call.getCallee().getAnException() = e or
call.(GenericCall).getATypeArgument(call.getCallee().getAnException().getType()) = e.getType()
) and
not caughtInside(t, call.getEnclosingStmt(), e.getType()) and
result = e.getType()
)
or
exists(ThrowStmt ts |
t.getBlock() = ts.getEnclosingStmt*() and
not caughtInside(t, ts, ts.getExpr().getType()) and
result = ts.getExpr().getType()
)
}
private RefType caughtType(TryStmt try, int index) {
exists(CatchClause cc | cc = try.getCatchClause(index) |
if cc.isMultiCatch()
then result = cc.getVariable().getTypeAccess().(UnionTypeAccess).getAnAlternative().getType()
else result = cc.getVariable().getType()
)
}
private predicate maybeUnchecked(RefType t) {
t.getAnAncestor().hasQualifiedName("java.lang", "RuntimeException") or
t.getAnAncestor().hasQualifiedName("java.lang", "Error") or
t.hasQualifiedName("java.lang", "Exception") or
t.hasQualifiedName("java.lang", "Throwable")
}
predicate overlappingExceptions(RefType e1, RefType e2) {
exists(RefType throwable | throwable.hasQualifiedName("java.lang", "Throwable") |
throwable.hasSubtype*(e1) and
throwable.hasSubtype*(e2) and
e1.getADescendant() = e2.getADescendant()
)
}
from TryStmt try, int first, int second, RefType masking, RefType masked, string multiCatchMsg
where
try.getFile().isJavaSourceFile() and
masking = caughtType(try, first) and
masking.getAStrictAncestor() = masked and
masked = caughtType(try, second) and
forall(RefType thrownType |
thrownType = getAThrownExceptionType(try) and
// If there's any overlap in the types, this catch block may be relevant.
overlappingExceptions(thrownType, masked)
|
exists(RefType priorCaughtType, int priorIdx |
priorIdx < second and
priorCaughtType = caughtType(try, priorIdx) and
thrownType.hasSupertype*(priorCaughtType)
)
) and
not maybeUnchecked(masked) and
if try.getCatchClause(second).isMultiCatch()
then multiCatchMsg = " for type " + masked.getName()
else multiCatchMsg = ""
select try.getCatchClause(second),
"This catch-clause is unreachable" + multiCatchMsg + "; it is masked $@.",
try.getCatchClause(first),
"by a previous catch-clause for exceptions of type '" + masking.getName() + "'"