Skip to content

Commit f39dda9

Browse files
committed
Java: Add support for data flow through thrown exceptions.
1 parent ebf04d3 commit f39dda9

File tree

4 files changed

+221
-21
lines changed

4 files changed

+221
-21
lines changed

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ private predicate deadcode(Expr e) {
2929
module SsaFlow {
3030
module Impl = SsaImpl::DataFlowIntegration;
3131

32-
private predicate ssaDefAssigns(SsaExplicitWrite def, Expr value) {
32+
private predicate ssaDefAssigns(SsaExplicitWrite def, Node value) {
3333
exists(VariableUpdate upd | upd = def.getDefiningExpr() |
34-
value = upd.(VariableAssign).getSource() or
35-
value = upd.(AssignOp) or
36-
value = upd.(RecordBindingVariableExpr)
34+
value.asExpr() = upd.(VariableAssign).getSource() or
35+
value.asExpr() = upd.(AssignOp) or
36+
value.asExpr() = upd.(RecordBindingVariableExpr) or
37+
value.(CatchParameterNode).getVariable() = upd
3738
)
3839
}
3940

@@ -49,7 +50,7 @@ module SsaFlow {
4950
result.(Impl::WriteDefSourceNode).getDefinition().(SsaParameterInit).getParameter() = p
5051
)
5152
or
52-
ssaDefAssigns(result.(Impl::WriteDefSourceNode).getDefinition(), n.asExpr())
53+
ssaDefAssigns(result.(Impl::WriteDefSourceNode).getDefinition(), n)
5354
}
5455

5556
predicate localFlowStep(SsaSourceVariable v, Node nodeFrom, Node nodeTo, boolean isUseStep) {
@@ -99,6 +100,11 @@ private module Cached {
99100
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
100101
TFieldValueNode(Field f) or
101102
TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) or
103+
TExceptionOutNode(DataFlowCall call) or
104+
TExceptionReturnNode(DataFlowCallable callable) or
105+
TCatchTypeTestNode(CatchClause catch) or
106+
TCatchParameterNode(CatchClause catch) or
107+
TUncaughtNode(TryStmt try) { ExceptionFlow::tryCatch(try, _) } or
102108
TAdditionalNode(Expr e, string id) { any(AdditionalDataFlowNode adfn).nodeAt(e, id) }
103109

104110
cached
@@ -177,6 +183,14 @@ module Public {
177183
or
178184
result = this.(FieldValueNode).getField().getType()
179185
or
186+
result instanceof TypeException and this instanceof ExceptionOutNode
187+
or
188+
result instanceof TypeException and this instanceof CatchTypeTestNode
189+
or
190+
result = this.(CatchParameterNode).getVariable().getType()
191+
or
192+
result instanceof TypeException and this instanceof UncaughtNode
193+
or
180194
result instanceof TypeObject and this instanceof AdditionalNode
181195
or
182196
result = this.(SsaNode).getTypeImpl()
@@ -383,6 +397,27 @@ module Public {
383397
predicate isOwnInstanceAccess() { this.getInstanceAccess().isOwnInstanceAccess() }
384398
}
385399

400+
/**
401+
* A node representing a thrown exception as the result of a call.
402+
*/
403+
class ExceptionOutNode extends Node, TExceptionOutNode {
404+
override string toString() { result = "Exception out: " + this.getCall().toString() }
405+
406+
override Location getLocation() { result = this.getCall().getLocation() }
407+
408+
/** Gets the associated call. */
409+
DataFlowCall getCall() { this = TExceptionOutNode(result) }
410+
}
411+
412+
/**
413+
* A node representing a thrown exception being returned from a callable.
414+
*/
415+
class ExceptionReturnNode extends Node, TExceptionReturnNode {
416+
override string toString() { result = "Exception return" }
417+
418+
override Location getLocation() { result = this.getEnclosingCallable().getLocation() }
419+
}
420+
386421
/** A node introduced by an extension of `AdditionalDataFlowNode`. */
387422
class AdditionalNode extends Node, TAdditionalNode {
388423
Expr e_;
@@ -455,6 +490,11 @@ module Private {
455490
result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or
456491
result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or
457492
result.asFieldScope() = n.(FieldValueNode).getField() or
493+
result = n.(ExceptionOutNode).getCall().getEnclosingCallable() or
494+
n = TExceptionReturnNode(result) or
495+
result.asCallable() = n.(CatchTypeTestNode).getCatch().getEnclosingCallable() or
496+
result.asCallable() = n.(CatchParameterNode).getCatch().getEnclosingCallable() or
497+
result.asCallable() = n.(UncaughtNode).getTry().getEnclosingCallable() or
458498
result.asCallable() = any(Expr e | n.(AdditionalNode).nodeAt(e, _)).getEnclosingCallable() or
459499
result.asCallable() = n.(SsaNode).getBasicBlock().getEnclosingCallable()
460500
}
@@ -507,15 +547,23 @@ module Private {
507547
DataFlowCall getCall() { this.argumentOf(result, _) }
508548
}
509549

510-
/** A data flow node that occurs as the result of a `ReturnStmt`. */
550+
/**
551+
* A data flow node that occurs as the result of a `ReturnStmt` or an
552+
* exception being returned.
553+
*/
511554
class ReturnNode extends Node {
512555
ReturnNode() {
513556
exists(ReturnStmt ret | this.asExpr() = ret.getResult()) or
514-
this.(FlowSummaryNode).isReturn()
557+
this.(FlowSummaryNode).isReturn() or
558+
this instanceof ExceptionReturnNode
515559
}
516560

517561
/** Gets the kind of this returned value. */
518-
ReturnKind getKind() { any() }
562+
ReturnKind getKind() {
563+
if this instanceof ExceptionReturnNode
564+
then result instanceof ExceptionReturnKind
565+
else result instanceof NormalReturnKind
566+
}
519567
}
520568

521569
/** A data flow node that represents the output of a call. */
@@ -524,13 +572,24 @@ module Private {
524572
this.asExpr() instanceof MethodCall
525573
or
526574
this.(FlowSummaryNode).isOut(_)
575+
or
576+
this instanceof ExceptionOutNode
527577
}
528578

529579
/** Gets the underlying call. */
530580
DataFlowCall getCall() {
531581
result.asCall() = this.asExpr()
532582
or
533583
this.(FlowSummaryNode).isOut(result)
584+
or
585+
result = this.(ExceptionOutNode).getCall()
586+
}
587+
588+
/** Gets the kind of this returned value. */
589+
ReturnKind getKind() {
590+
if this instanceof ExceptionOutNode
591+
then result instanceof ExceptionReturnKind
592+
else result instanceof NormalReturnKind
534593
}
535594
}
536595

@@ -597,6 +656,67 @@ module Private {
597656
cn.isInstanceAccess() and result = cn.getEnclosingCallable().getDeclaringType()
598657
}
599658
}
659+
660+
/**
661+
* A data flow node that carries an exception and tests if it is caught in a
662+
* given catch clause.
663+
*/
664+
class CatchTypeTestNode extends Node, TCatchTypeTestNode {
665+
override string toString() { result = this.getCatch().toString() }
666+
667+
override Location getLocation() { result = this.getCatch().getLocation() }
668+
669+
/** Gets the catch clause associated with this node. */
670+
CatchClause getCatch() { this = TCatchTypeTestNode(result) }
671+
672+
Node getSuccessor(boolean match) {
673+
match = true and
674+
this.getCatch() = result.(CatchParameterNode).getCatch()
675+
or
676+
match = false and
677+
exists(TryStmt try, int i, CatchClause cc |
678+
cc = this.getCatch() and
679+
cc = try.getCatchClause(i) and
680+
// A catch-all does not allow for uncaught exceptions.
681+
not cc.getACaughtType() instanceof TypeThrowable and
682+
not cc.getACaughtType() instanceof TypeException
683+
|
684+
result.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1)
685+
or
686+
not exists(try.getCatchClause(i + 1)) and
687+
result.(UncaughtNode).getTry() = try
688+
)
689+
}
690+
}
691+
692+
/**
693+
* A data flow node that holds the value of a variable defined in a catch
694+
* clause.
695+
*/
696+
class CatchParameterNode extends Node, TCatchParameterNode {
697+
override string toString() { result = this.getVariable().toString() }
698+
699+
override Location getLocation() { result = this.getVariable().getLocation() }
700+
701+
/** Gets the catch clause associated with this node. */
702+
CatchClause getCatch() { this = TCatchParameterNode(result) }
703+
704+
/** Gets the variable declaration associated with this node. */
705+
LocalVariableDeclExpr getVariable() { result = this.getCatch().getVariable() }
706+
}
707+
708+
/**
709+
* A data flow node that carries an exception that is uncaught by a try-catch
710+
* statement.
711+
*/
712+
class UncaughtNode extends Node, TUncaughtNode {
713+
override string toString() { result = "Uncaught exception" }
714+
715+
override Location getLocation() { result = this.getTry().getLocation() }
716+
717+
/** Gets the try statement associated with this node. */
718+
TryStmt getTry() { this = TUncaughtNode(result) }
719+
}
600720
}
601721

602722
private import Private

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,28 @@ private import DataFlowNodes
1717
private import codeql.dataflow.VariableCapture as VariableCapture
1818
import DataFlowNodes::Private
1919

20-
private newtype TReturnKind = TNormalReturnKind()
20+
private newtype TReturnKind =
21+
TNormalReturnKind() or
22+
TExceptionReturnKind()
2123

2224
/**
23-
* A return kind. A return kind describes how a value can be returned
24-
* from a callable. For Java, this is simply a method return.
25+
* A return kind. A return kind describes how a value can be returned from a
26+
* callable. For Java, this is either a normal method return or an exception
27+
* being returned.
2528
*/
2629
class ReturnKind extends TReturnKind {
2730
/** Gets a textual representation of this return kind. */
28-
string toString() { result = "return" }
31+
string toString() { none() }
32+
}
33+
34+
/** A return kind indicating normal method return. */
35+
class NormalReturnKind extends ReturnKind, TNormalReturnKind {
36+
override string toString() { result = "return" }
37+
}
38+
39+
/** A return kind indicating exceptional method return. */
40+
class ExceptionReturnKind extends ReturnKind, TExceptionReturnKind {
41+
override string toString() { result = "exception return" }
2942
}
3043

3144
/**
@@ -34,7 +47,7 @@ class ReturnKind extends TReturnKind {
3447
*/
3548
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
3649
result.getCall() = call and
37-
kind = TNormalReturnKind()
50+
result.getKind() = kind
3851
}
3952

4053
/**
@@ -170,6 +183,8 @@ private CaptureFlow::ClosureNode asClosureNode(Node n) {
170183
n.asExpr() = write.(VariableAssign).getSource()
171184
or
172185
n.asExpr() = write.(AssignOp)
186+
or
187+
n.(CatchParameterNode).getVariable() = write
173188
)
174189
}
175190

@@ -203,6 +218,59 @@ predicate jumpStep(Node node1, Node node2) {
203218
node2.(FlowSummaryNode).getSummaryNode())
204219
}
205220

221+
module ExceptionFlow {
222+
/**
223+
* Holds if `try` has at least one catch clause and `body` is either the main
224+
* body of the `try` or one of its resource declarations.
225+
*/
226+
predicate tryCatch(TryStmt try, Stmt body) {
227+
exists(try.getACatchClause()) and
228+
(
229+
body = try.getBlock() or
230+
body = try.getAResourceDecl()
231+
)
232+
}
233+
234+
/**
235+
* Holds if `s2` is the enclosing statement of `s1` and `s1` is not directly
236+
* wrapped in a try-catch.
237+
*/
238+
private predicate excStep(Stmt s1, Stmt s2) {
239+
s1.getEnclosingStmt() = s2 and
240+
not tryCatch(_, s1)
241+
}
242+
243+
pragma[nomagic]
244+
private DataFlowCallable excReturnGetCallable(ExceptionReturnNode n) {
245+
result = nodeGetEnclosingCallable(n)
246+
}
247+
248+
/** Holds if a thrown exception can flow locally from `node1` to `node2`. */
249+
predicate localStep(Node node1, Node node2) {
250+
node1.(ExceptionOutNode).getCall().(SummaryCall).getEnclosingCallable() =
251+
excReturnGetCallable(node2)
252+
or
253+
exists(Stmt exc |
254+
node1.asExpr() = exc.(ThrowStmt).getExpr() or
255+
node1.(ExceptionOutNode).getCall().asCall().getEnclosingStmt() = exc or
256+
node1.(UncaughtNode).getTry() = exc
257+
|
258+
exists(TryStmt try, Stmt body |
259+
excStep+(exc, body) and
260+
tryCatch(try, body) and
261+
node2.(CatchTypeTestNode).getCatch() = try.getCatchClause(0)
262+
)
263+
or
264+
exists(Callable callable |
265+
excStep+(exc, callable.getBody()) and
266+
excReturnGetCallable(node2).asCallable() = callable
267+
)
268+
)
269+
or
270+
node1.(CatchTypeTestNode).getSuccessor(_) = node2
271+
}
272+
}
273+
206274
/**
207275
* Holds if `fa` is an access to an instance field that occurs as the
208276
* destination of an assignment of the value `src`.
@@ -391,9 +459,9 @@ pragma[nomagic]
391459
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { erasedHaveIntersection(t1, t2) }
392460

393461
/** A node that performs a type cast. */
394-
class CastNode extends ExprNode {
462+
class CastNode extends Node {
395463
CastNode() {
396-
this.getExpr() instanceof CastingExpr
464+
this.asExpr() instanceof CastingExpr
397465
or
398466
exists(SsaExplicitWrite upd |
399467
upd.getDefiningExpr().(VariableAssign).getSource() =
@@ -403,6 +471,8 @@ class CastNode extends ExprNode {
403471
] and
404472
this.asExpr() = ssaGetAFirstUse(upd)
405473
)
474+
or
475+
this instanceof CatchParameterNode
406476
}
407477
}
408478

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2, string model) {
234234
simpleAstFlowStep(node1.asExpr(), node2.asExpr())
235235
or
236236
captureValueStep(node1, node2)
237+
or
238+
ExceptionFlow::localStep(node1, node2)
237239
) and
238240
model = ""
239241
or

0 commit comments

Comments
 (0)