Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5070270

Browse files
committed
C#: Fix CFG for nested finally blocks
1 parent b9fa837 commit 5070270

8 files changed

Lines changed: 581 additions & 238 deletions

File tree

csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll

Lines changed: 118 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ private module Cached {
2727
cached
2828
newtype TSplitKind =
2929
TInitializerSplitKind() or
30-
TFinallySplitKind() or
30+
TFinallySplitKind(int nestLevel) { nestLevel = FinallySplitting::nestLevel(_) } or
3131
TExceptionHandlerSplitKind() or
3232
TBooleanSplitKind(BooleanSplitting::BooleanSplitSubKind kind) { kind.startsSplit(_) } or
3333
TLoopUnrollingSplitKind(LoopUnrollingSplitting::UnrollableLoopStmt loop)
3434

3535
cached
3636
newtype TSplit =
3737
TInitializerSplit(Constructor c) { InitializerSplitting::constructorInitializes(c, _) } or
38-
TFinallySplit(FinallySplitting::FinallySplitType type) or
38+
TFinallySplit(FinallySplitting::FinallySplitType type, int nestLevel) {
39+
nestLevel = FinallySplitting::nestLevel(_)
40+
} or
3941
TExceptionHandlerSplit(ExceptionClass ec) or
4042
TBooleanSplit(BooleanSplitting::BooleanSplitSubKind kind, boolean branch) {
4143
kind.startsSplit(_) and
@@ -427,23 +429,31 @@ module FinallySplitting {
427429
)
428430
}
429431

432+
/**
433+
* Holds if `innerTry` has a `finally` block and is immediately nested inside the
434+
* `finally` block of `outerTry`.
435+
*/
436+
private predicate nestedFinally(TryStmt outerTry, TryStmt innerTry) {
437+
exists(ControlFlowElement innerFinally |
438+
innerFinally = getAChild(getAFinallyDescendant(outerTry), outerTry.getEnclosingCallable()) and
439+
innerFinally = innerTry.getFinally()
440+
)
441+
}
442+
443+
/** Gets the nesting level of the `finally` block for `try`. */
444+
int nestLevel(TryStmt try) { result = strictcount(TryStmt outer | nestedFinally*(outer, try)) }
445+
430446
/** A control flow element that belongs to a `finally` block. */
431447
private class FinallyControlFlowElement extends ControlFlowElement {
432-
FinallyControlFlowElement() { this = getAFinallyDescendant(_) }
448+
private TryStmt try;
433449

434-
/** Holds if this node is the entry node in the `finally` block it belongs to. */
435-
predicate isEntryNode() {
436-
exists(TryStmt try | this = getAFinallyDescendant(try) | this = first(try.getFinally()))
437-
}
450+
FinallyControlFlowElement() { this = getAFinallyDescendant(try) }
438451

439-
/**
440-
* Holds if this node is a last element in the `finally` block belonging to
441-
* `try` statement `try`, with completion `c`.
442-
*/
443-
predicate isExitNode(TryStmt try, Completion c) {
444-
this = getAFinallyDescendant(try) and
445-
this = last(try.getFinally(), c)
446-
}
452+
/** Gets the immediate `try` block that this node belongs to. */
453+
TryStmt getTryStmt() { result = try }
454+
455+
/** Holds if this node is the entry node in the `finally` block it belongs to. */
456+
predicate isEntryNode() { this = first(try.getFinally()) }
447457
}
448458

449459
/** A control flow element that does not belong to a `finally` block. */
@@ -474,44 +484,60 @@ module FinallySplitting {
474484
class FinallySplitImpl extends SplitImpl, TFinallySplit {
475485
private FinallySplitType type;
476486

477-
FinallySplitImpl() { this = TFinallySplit(type) }
487+
private int nestLevel;
488+
489+
FinallySplitImpl() { this = TFinallySplit(type, nestLevel) }
478490

479491
/**
480492
* Gets the type of this `finally` split, that is, how to continue execution after the
481493
* `finally` block.
482494
*/
483495
FinallySplitType getType() { result = type }
484496

497+
/** Gets the `finally` nesting level. */
498+
int getNestLevel() { result = nestLevel }
499+
485500
override string toString() {
486501
if type instanceof NormalSuccessor
487502
then result = ""
488-
else result = "finally: " + type.toString()
503+
else
504+
if nestLevel > 1
505+
then result = "finally(" + nestLevel + "): " + type.toString()
506+
else result = "finally: " + type.toString()
489507
}
490508
}
491509

492-
private class FinallySplitKind extends SplitKind, TFinallySplitKind {
493-
override int getListOrder() { result = InitializerSplitting::getNextListOrder() }
510+
private int getListOrder(FinallySplitKind kind) {
511+
result = InitializerSplitting::getNextListOrder() + kind.getNestLevel()
512+
}
494513

495-
override string toString() { result = "Finally" }
514+
int getNextListOrder() {
515+
result = max(int i | i = getListOrder(_) + 1 or i = InitializerSplitting::getNextListOrder())
496516
}
497517

498-
int getNextListOrder() { result = InitializerSplitting::getNextListOrder() + 1 }
518+
private class FinallySplitKind extends SplitKind, TFinallySplitKind {
519+
private int nestLevel;
520+
521+
FinallySplitKind() { this = TFinallySplitKind(nestLevel) }
522+
523+
/** Gets the `finally` nesting level. */
524+
int getNestLevel() { result = nestLevel }
525+
526+
override int getListOrder() { result = getListOrder(this) }
527+
528+
override string toString() { result = "Finally (" + nestLevel + ")" }
529+
}
499530

500531
private class FinallySplitInternal extends SplitInternal, FinallySplitImpl {
501-
override FinallySplitKind getKind() { any() }
532+
override FinallySplitKind getKind() { result.getNestLevel() = this.getNestLevel() }
502533

503534
override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
504-
succ.(FinallyControlFlowElement).isEntryNode() and
535+
succ = any(FinallyControlFlowElement entry |
536+
entry.isEntryNode() and
537+
this.getNestLevel() = nestLevel(entry.getTryStmt())
538+
) and
505539
succ = succ(pred, c) and
506-
this.getType().isSplitForEntryCompletion(c) and
507-
(
508-
// Abnormal entry must enter the correct splitting
509-
not c instanceof NormalCompletion
510-
or
511-
// Normal entry only when not entering a nested `finally` block; in that case,
512-
// the outer split must be maintained (see `hasSuccessor()`)
513-
pred instanceof NonFinallyControlFlowElement
514-
)
540+
this.getType().isSplitForEntryCompletion(c)
515541
}
516542

517543
override predicate hasEntry(Callable c, ControlFlowElement succ) { none() }
@@ -525,53 +551,90 @@ module FinallySplitting {
525551
(exists(succ(pred, _)) or exists(succExit(pred, _)))
526552
}
527553

528-
/** Holds if `pred` may exit this split with completion `c`. */
529-
private predicate exit(ControlFlowElement pred, Completion c) {
554+
/**
555+
* Holds if `pred` may exit this split with completion `c`. The Boolean
556+
* `derived` indicates whether `c` is a derived completion from a `try`/
557+
* `catch` block.
558+
*/
559+
private predicate exit(ControlFlowElement pred, Completion c, boolean derived) {
530560
this.appliesToPredecessor(pred) and
531561
exists(TryStmt try, FinallySplitType type |
532562
type = this.getType() and
563+
nestLevel(try) = this.getNestLevel() and
533564
pred = last(try, c)
534565
|
535-
if pred.(FinallyControlFlowElement).isExitNode(try, c)
536-
then (
566+
if pred = last(try.getFinally(), c)
567+
then
537568
// Finally block can itself exit with completion `c`: either `c` must
538569
// match this split, `c` must be an abnormal completion, or this split
539570
// does not require another completion to be recovered
540-
type.matchesCompletion(c)
541-
or
542-
not c instanceof NormalCompletion
543-
or
544-
type instanceof NormalSuccessor
545-
) else (
571+
derived = false and
572+
(
573+
type.matchesCompletion(c)
574+
or
575+
not c instanceof NormalCompletion
576+
or
577+
type instanceof NormalSuccessor
578+
)
579+
else (
546580
// Finally block can exit with completion `c` derived from try/catch
547581
// block: must match this split
582+
derived = true and
548583
type.matchesCompletion(c) and
549584
not type instanceof NormalSuccessor
550585
)
551586
)
587+
or
588+
// If this split is normal, and an outer split can exit based on a derived
589+
// completion, we need to exit this split as well. For example, in
590+
//
591+
// ```
592+
// bool done;
593+
// try
594+
// {
595+
// if (b1) throw new ExceptionA();
596+
// }
597+
// finally
598+
// {
599+
// try
600+
// {
601+
// if (b2) throw new ExceptionB();
602+
// }
603+
// finally
604+
// {
605+
// done = true;
606+
// }
607+
// }
608+
// ```
609+
//
610+
// if the outer split for `done = true` is `ExceptionA` and the inner split
611+
// is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner
612+
// split must be able to exit with an `ExceptionA` completion.
613+
this.appliesToPredecessor(pred) and
614+
exists(FinallySplitInternal outer |
615+
outer.getNestLevel() = this.getNestLevel() - 1 and
616+
outer.exit(pred, c, derived) and
617+
this.getType() instanceof NormalSuccessor and
618+
derived = true
619+
)
552620
}
553621

554622
override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
555-
this.appliesToPredecessor(pred) and
556623
succ = succ(pred, c) and
557624
(
558-
// Entering a nested `finally` block abnormally means that we should exit this split
559-
succ.(FinallyControlFlowElement).isEntryNode() and
560-
not c instanceof NormalCompletion
561-
or
562-
exit(pred, c)
625+
exit(pred, c, _)
563626
or
564-
exit(pred, any(BreakCompletion bc)) and
627+
exit(pred, any(BreakCompletion bc), _) and
565628
c instanceof BreakNormalCompletion
566629
)
567630
}
568631

569632
override Callable hasExit(ControlFlowElement pred, Completion c) {
570633
result = succExit(pred, c) and
571634
(
572-
exit(pred, c)
635+
exit(pred, c, _)
573636
or
574-
exit(pred, any(BreakCompletion bc)) and
637+
exit(pred, any(BreakCompletion bc), _) and
575638
c instanceof BreakNormalCompletion
576639
)
577640
}
@@ -582,11 +645,11 @@ module FinallySplitting {
582645
succ = any(FinallyControlFlowElement fcfe |
583646
if fcfe.isEntryNode()
584647
then
585-
// Entering a nested `finally` block normally must remember the outer split
586-
c instanceof NormalCompletion
648+
// entering a nested `finally` block
649+
nestLevel(fcfe.getTryStmt()) > this.getNestLevel()
587650
else
588-
// Staying in the same `finally` block should maintain this split
589-
not this.hasEntry(pred, succ, c)
651+
// staying in the same (possibly nested) `finally` block as `pred`
652+
nestLevel(fcfe.getTryStmt()) >= this.getNestLevel()
590653
)
591654
}
592655
}

csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,12 @@
249249
| Finally.cs:93:31:93:45 | [finally: continue] object creation of type Exception | Finally.cs:93:31:93:45 | [finally: continue] object creation of type Exception | 1 |
250250
| Finally.cs:93:31:93:45 | [finally: return] object creation of type Exception | Finally.cs:93:31:93:45 | [finally: return] object creation of type Exception | 1 |
251251
| Finally.cs:93:31:93:45 | object creation of type Exception | Finally.cs:93:31:93:45 | object creation of type Exception | 1 |
252+
| Finally.cs:96:17:98:17 | [finally(2): exception(Exception)] {...} | Finally.cs:97:21:97:23 | [finally(2): exception(Exception)] ...-- | 4 |
253+
| Finally.cs:96:17:98:17 | [finally: break, finally(2): exception(Exception)] {...} | Finally.cs:97:21:97:23 | [finally: break, finally(2): exception(Exception)] ...-- | 4 |
252254
| Finally.cs:96:17:98:17 | [finally: break] {...} | Finally.cs:97:21:97:23 | [finally: break] ...-- | 4 |
255+
| Finally.cs:96:17:98:17 | [finally: continue, finally(2): exception(Exception)] {...} | Finally.cs:97:21:97:23 | [finally: continue, finally(2): exception(Exception)] ...-- | 4 |
253256
| Finally.cs:96:17:98:17 | [finally: continue] {...} | Finally.cs:97:21:97:23 | [finally: continue] ...-- | 4 |
254-
| Finally.cs:96:17:98:17 | [finally: exception(Exception)] {...} | Finally.cs:97:21:97:23 | [finally: exception(Exception)] ...-- | 4 |
257+
| Finally.cs:96:17:98:17 | [finally: return, finally(2): exception(Exception)] {...} | Finally.cs:97:21:97:23 | [finally: return, finally(2): exception(Exception)] ...-- | 4 |
255258
| Finally.cs:96:17:98:17 | [finally: return] {...} | Finally.cs:97:21:97:23 | [finally: return] ...-- | 4 |
256259
| Finally.cs:96:17:98:17 | {...} | Finally.cs:97:21:97:23 | ...-- | 4 |
257260
| Finally.cs:103:10:103:11 | enter M5 | Finally.cs:107:17:107:21 | access to field Field | 7 |
@@ -343,26 +346,30 @@
343346
| Finally.cs:199:27:199:42 | object creation of type ExceptionA | Finally.cs:199:27:199:42 | object creation of type ExceptionA | 1 |
344347
| Finally.cs:202:9:212:9 | [finally: exception(Exception)] {...} | Finally.cs:205:21:205:22 | [finally: exception(Exception)] access to parameter b2 | 5 |
345348
| Finally.cs:202:9:212:9 | {...} | Finally.cs:205:21:205:22 | access to parameter b2 | 5 |
346-
| Finally.cs:205:25:205:47 | [finally: exception(Exception)] throw ...; | Finally.cs:205:25:205:47 | [finally: exception(Exception)] throw ...; | 1 |
347-
| Finally.cs:205:25:205:47 | [finally: exception(ExceptionA)] throw ...; | Finally.cs:205:25:205:47 | [finally: exception(ExceptionA)] throw ...; | 1 |
348-
| Finally.cs:205:25:205:47 | throw ...; | Finally.cs:205:25:205:47 | throw ...; | 1 |
349+
| Finally.cs:205:25:205:47 | [finally: exception(Exception)] throw ...; | Finally.cs:209:21:209:22 | [finally: exception(Exception), finally(2): exception(ExceptionB)] access to parameter b3 | 4 |
350+
| Finally.cs:205:25:205:47 | [finally: exception(ExceptionA)] throw ...; | Finally.cs:209:21:209:22 | [finally: exception(ExceptionA), finally(2): exception(ExceptionB)] access to parameter b3 | 4 |
351+
| Finally.cs:205:25:205:47 | throw ...; | Finally.cs:209:21:209:22 | [finally(2): exception(ExceptionB)] access to parameter b3 | 4 |
349352
| Finally.cs:205:31:205:46 | [finally: exception(Exception)] object creation of type ExceptionB | Finally.cs:205:31:205:46 | [finally: exception(Exception)] object creation of type ExceptionB | 1 |
350353
| Finally.cs:205:31:205:46 | [finally: exception(ExceptionA)] object creation of type ExceptionB | Finally.cs:205:31:205:46 | [finally: exception(ExceptionA)] object creation of type ExceptionB | 1 |
351354
| Finally.cs:205:31:205:46 | object creation of type ExceptionB | Finally.cs:205:31:205:46 | object creation of type ExceptionB | 1 |
355+
| Finally.cs:208:13:210:13 | [finally(2): exception(Exception)] {...} | Finally.cs:209:21:209:22 | [finally(2): exception(Exception)] access to parameter b3 | 3 |
356+
| Finally.cs:208:13:210:13 | [finally: exception(Exception), finally(2): exception(Exception)] {...} | Finally.cs:209:21:209:22 | [finally: exception(Exception), finally(2): exception(Exception)] access to parameter b3 | 3 |
352357
| Finally.cs:208:13:210:13 | [finally: exception(Exception)] {...} | Finally.cs:209:21:209:22 | [finally: exception(Exception)] access to parameter b3 | 3 |
358+
| Finally.cs:208:13:210:13 | [finally: exception(ExceptionA), finally(2): exception(Exception)] {...} | Finally.cs:209:21:209:22 | [finally: exception(ExceptionA), finally(2): exception(Exception)] access to parameter b3 | 3 |
353359
| Finally.cs:208:13:210:13 | [finally: exception(ExceptionA)] {...} | Finally.cs:209:21:209:22 | [finally: exception(ExceptionA)] access to parameter b3 | 3 |
354-
| Finally.cs:208:13:210:13 | [finally: exception(ExceptionB)] {...} | Finally.cs:209:21:209:22 | [finally: exception(ExceptionB)] access to parameter b3 | 3 |
355360
| Finally.cs:208:13:210:13 | {...} | Finally.cs:209:21:209:22 | access to parameter b3 | 3 |
361+
| Finally.cs:209:31:209:46 | [finally(2): exception(Exception)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally(2): exception(Exception)] throw ...; | 2 |
362+
| Finally.cs:209:31:209:46 | [finally(2): exception(ExceptionB)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally(2): exception(ExceptionB)] throw ...; | 2 |
363+
| Finally.cs:209:31:209:46 | [finally: exception(Exception), finally(2): exception(Exception)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(Exception), finally(2): exception(Exception)] throw ...; | 2 |
364+
| Finally.cs:209:31:209:46 | [finally: exception(Exception), finally(2): exception(ExceptionB)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(Exception), finally(2): exception(ExceptionB)] throw ...; | 2 |
356365
| Finally.cs:209:31:209:46 | [finally: exception(Exception)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(Exception)] throw ...; | 2 |
366+
| Finally.cs:209:31:209:46 | [finally: exception(ExceptionA), finally(2): exception(Exception)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(ExceptionA), finally(2): exception(Exception)] throw ...; | 2 |
367+
| Finally.cs:209:31:209:46 | [finally: exception(ExceptionA), finally(2): exception(ExceptionB)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(ExceptionA), finally(2): exception(ExceptionB)] throw ...; | 2 |
357368
| Finally.cs:209:31:209:46 | [finally: exception(ExceptionA)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(ExceptionA)] throw ...; | 2 |
358-
| Finally.cs:209:31:209:46 | [finally: exception(ExceptionB)] object creation of type ExceptionC | Finally.cs:209:25:209:47 | [finally: exception(ExceptionB)] throw ...; | 2 |
359369
| Finally.cs:209:31:209:46 | object creation of type ExceptionC | Finally.cs:209:25:209:47 | throw ...; | 2 |
360-
| Finally.cs:211:13:211:29 | ...; | Finally.cs:211:13:211:28 | ... = ... | 4 |
361-
| Finally.cs:211:13:211:29 | ...; | Finally.cs:211:13:211:28 | ... = ... | 4 |
370+
| Finally.cs:211:13:211:29 | ...; | Finally.cs:213:9:213:24 | ... = ... | 8 |
362371
| Finally.cs:211:13:211:29 | [finally: exception(Exception)] ...; | Finally.cs:211:13:211:28 | [finally: exception(Exception)] ... = ... | 4 |
363372
| Finally.cs:211:13:211:29 | [finally: exception(ExceptionA)] ...; | Finally.cs:211:13:211:28 | [finally: exception(ExceptionA)] ... = ... | 4 |
364-
| Finally.cs:211:13:211:29 | [finally: exception(ExceptionB)] ...; | Finally.cs:211:13:211:28 | [finally: exception(ExceptionB)] ... = ... | 4 |
365-
| Finally.cs:213:9:213:25 | ...; | Finally.cs:213:9:213:24 | ... = ... | 4 |
366373
| Foreach.cs:6:10:6:11 | enter M1 | Foreach.cs:8:29:8:32 | access to parameter args | 3 |
367374
| Foreach.cs:6:10:6:11 | exit M1 | Foreach.cs:6:10:6:11 | exit M1 | 1 |
368375
| Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | 1 |

0 commit comments

Comments
 (0)