Skip to content

Commit 249eea9

Browse files
authored
Merge pull request #4780 from hvitved/csharp/cfg/nested-finally
C#: Add missing CFG edges for nested `finally` blocks
2 parents ca80f04 + 3531dde commit 249eea9

File tree

13 files changed

+1439
-673
lines changed

13 files changed

+1439
-673
lines changed

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

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import csharp
2323
private import semmle.code.csharp.commons.Assertions
2424
private import semmle.code.csharp.commons.Constants
2525
private import semmle.code.csharp.frameworks.System
26+
private import ControlFlowGraphImpl
2627
private import NonReturning
2728
private import SuccessorType
2829
private import SuccessorTypes
2930

30-
// Internal representation of completions
3131
private newtype TCompletion =
3232
TSimpleCompletion() or
3333
TBooleanCompletion(boolean b) { b = true or b = false } or
@@ -40,26 +40,33 @@ private newtype TCompletion =
4040
TGotoCompletion(string label) { label = any(GotoStmt gs).getLabel() } or
4141
TThrowCompletion(ExceptionClass ec) or
4242
TExitCompletion() or
43-
TNestedCompletion(Completion inner, Completion outer) {
44-
inner instanceof NormalCompletion and
45-
(
46-
outer = TReturnCompletion()
47-
or
48-
outer = TBreakCompletion()
49-
or
50-
outer = TContinueCompletion()
51-
or
52-
outer = TGotoCompletion(_)
53-
or
54-
outer = TThrowCompletion(_)
55-
or
56-
outer = TExitCompletion()
57-
)
58-
or
43+
TNestedCompletion(Completion inner, Completion outer, int nestLevel) {
5944
inner = TBreakCompletion() and
60-
outer instanceof NonNestedNormalCompletion
45+
outer instanceof NonNestedNormalCompletion and
46+
nestLevel = 0
47+
or
48+
inner instanceof NormalCompletion and
49+
nestedFinallyCompletion(outer, nestLevel)
6150
}
6251

52+
pragma[noinline]
53+
private predicate nestedFinallyCompletion(Completion outer, int nestLevel) {
54+
(
55+
outer = TReturnCompletion()
56+
or
57+
outer = TBreakCompletion()
58+
or
59+
outer = TContinueCompletion()
60+
or
61+
outer = TGotoCompletion(_)
62+
or
63+
outer = TThrowCompletion(_)
64+
or
65+
outer = TExitCompletion()
66+
) and
67+
nestLevel = any(Statements::TryStmtTree t).nestLevel()
68+
}
69+
6370
pragma[noinline]
6471
private predicate completionIsValidForStmt(Stmt s, Completion c) {
6572
s instanceof BreakStmt and
@@ -674,21 +681,25 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
674681
class NestedCompletion extends Completion, TNestedCompletion {
675682
Completion inner;
676683
Completion outer;
684+
int nestLevel;
677685

678-
NestedCompletion() { this = TNestedCompletion(inner, outer) }
686+
NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) }
679687

680688
/** Gets a completion that is compatible with the inner completion. */
681689
Completion getAnInnerCompatibleCompletion() {
682690
result.getOuterCompletion() = this.getInnerCompletion()
683691
}
684692

693+
/** Gets the level of this nested completion. */
694+
int getNestLevel() { result = nestLevel }
695+
685696
override Completion getInnerCompletion() { result = inner }
686697

687698
override Completion getOuterCompletion() { result = outer }
688699

689700
override SuccessorType getAMatchingSuccessorType() { none() }
690701

691-
override string toString() { result = outer + " [" + inner + "]" }
702+
override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" }
692703
}
693704

694705
/**
@@ -725,13 +736,13 @@ class NestedBreakCompletion extends NormalCompletion, NestedCompletion {
725736

726737
override BreakCompletion getInnerCompletion() { result = inner }
727738

728-
override SimpleCompletion getOuterCompletion() { result = outer }
739+
override NonNestedNormalCompletion getOuterCompletion() { result = outer }
729740

730741
override Completion getAnInnerCompatibleCompletion() {
731742
result = inner and
732743
outer = TSimpleCompletion()
733744
or
734-
result = TNestedCompletion(outer, inner)
745+
result = TNestedCompletion(outer, inner, _)
735746
}
736747

737748
override SuccessorType getAMatchingSuccessorType() {
@@ -749,7 +760,7 @@ class NestedBreakCompletion extends NormalCompletion, NestedCompletion {
749760
class ReturnCompletion extends Completion {
750761
ReturnCompletion() {
751762
this = TReturnCompletion() or
752-
this = TNestedCompletion(_, TReturnCompletion())
763+
this = TNestedCompletion(_, TReturnCompletion(), _)
753764
}
754765

755766
override ReturnSuccessor getAMatchingSuccessorType() { any() }
@@ -768,7 +779,7 @@ class ReturnCompletion extends Completion {
768779
class BreakCompletion extends Completion {
769780
BreakCompletion() {
770781
this = TBreakCompletion() or
771-
this = TNestedCompletion(_, TBreakCompletion())
782+
this = TNestedCompletion(_, TBreakCompletion(), _)
772783
}
773784

774785
override BreakSuccessor getAMatchingSuccessorType() { any() }
@@ -787,7 +798,7 @@ class BreakCompletion extends Completion {
787798
class ContinueCompletion extends Completion {
788799
ContinueCompletion() {
789800
this = TContinueCompletion() or
790-
this = TNestedCompletion(_, TContinueCompletion())
801+
this = TNestedCompletion(_, TContinueCompletion(), _)
791802
}
792803

793804
override ContinueSuccessor getAMatchingSuccessorType() { any() }
@@ -807,7 +818,7 @@ class GotoCompletion extends Completion {
807818

808819
GotoCompletion() {
809820
this = TGotoCompletion(label) or
810-
this = TNestedCompletion(_, TGotoCompletion(label))
821+
this = TNestedCompletion(_, TGotoCompletion(label), _)
811822
}
812823

813824
/** Gets the label of the `goto` completion. */
@@ -830,7 +841,7 @@ class ThrowCompletion extends Completion {
830841

831842
ThrowCompletion() {
832843
this = TThrowCompletion(ec) or
833-
this = TNestedCompletion(_, TThrowCompletion(ec))
844+
this = TNestedCompletion(_, TThrowCompletion(ec), _)
834845
}
835846

836847
/** Gets the type of the exception being thrown. */
@@ -856,7 +867,7 @@ class ThrowCompletion extends Completion {
856867
class ExitCompletion extends Completion {
857868
ExitCompletion() {
858869
this = TExitCompletion() or
859-
this = TNestedCompletion(_, TExitCompletion())
870+
this = TNestedCompletion(_, TExitCompletion(), _)
860871
}
861872

862873
override ExitSuccessor getAMatchingSuccessorType() { any() }

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

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ module Expressions {
858858
(cc.(MatchingCompletion).isNonMatch() or cc instanceof FalseCompletion) and
859859
c =
860860
any(NestedCompletion nc |
861+
nc.getNestLevel() = 0 and
861862
nc.getInnerCompletion() = cc and
862863
nc.getOuterCompletion()
863864
.(ThrowCompletion)
@@ -1300,13 +1301,53 @@ module Statements {
13001301
last(cc.getBlock(), result, c)
13011302
}
13021303

1304+
/** Gets a child of `cfe` that is in CFG scope `scope`. */
1305+
pragma[noinline]
1306+
private ControlFlowElement getAChildInScope(ControlFlowElement cfe, Callable scope) {
1307+
result = cfe.getAChild() and
1308+
scope = result.getEnclosingCallable()
1309+
}
1310+
13031311
class TryStmtTree extends PreOrderTree, TryStmt {
13041312
ControlFlowTree body;
13051313

13061314
final override predicate propagatesAbnormal(ControlFlowElement child) {
13071315
child = this.getFinally()
13081316
}
13091317

1318+
/**
1319+
* Gets a descendant that belongs to the `finally` block of this try statement.
1320+
*/
1321+
ControlFlowElement getAFinallyDescendant() {
1322+
result = this.getFinally()
1323+
or
1324+
exists(ControlFlowElement mid |
1325+
mid = this.getAFinallyDescendant() and
1326+
result = getAChildInScope(mid, mid.getEnclosingCallable()) and
1327+
not exists(TryStmtTree nestedTry |
1328+
result = nestedTry.getFinally() and
1329+
nestedTry != this
1330+
)
1331+
)
1332+
}
1333+
1334+
/**
1335+
* Holds if `innerTry` has a `finally` block and is immediately nested inside the
1336+
* `finally` block of this `try` statement.
1337+
*/
1338+
private predicate nestedFinally(TryStmtTree innerTry) {
1339+
exists(ControlFlowElement innerFinally |
1340+
innerFinally = getAChildInScope(this.getAFinallyDescendant(), this.getEnclosingCallable()) and
1341+
innerFinally = innerTry.getFinally()
1342+
)
1343+
}
1344+
1345+
/**
1346+
* Gets the `finally`-nesting level of this `try` statement. That is, the number of
1347+
* `finally` blocks that this `try` statement is nested under.
1348+
*/
1349+
int nestLevel() { result = count(TryStmtTree outer | outer.nestedFinally+(this)) }
1350+
13101351
/** Holds if `last` is a last element of the block of this `try` statement. */
13111352
pragma[nomagic]
13121353
predicate lastBlock(ControlFlowElement last, Completion c) { last(this.getBlock(), last, c) }
@@ -1343,10 +1384,11 @@ module Statements {
13431384

13441385
pragma[nomagic]
13451386
private predicate lastFinally(
1346-
ControlFlowElement last, NormalCompletion finally, Completion outer
1387+
ControlFlowElement last, NormalCompletion finally, Completion outer, int nestLevel
13471388
) {
13481389
this.lastFinally0(last, finally) and
1349-
exists(this.getBlockOrCatchFinallyPred(any(Completion c0 | outer = c0.getOuterCompletion())))
1390+
exists(this.getBlockOrCatchFinallyPred(any(Completion c0 | outer = c0.getOuterCompletion()))) and
1391+
nestLevel = this.nestLevel()
13501392
}
13511393

13521394
final override predicate last(ControlFlowElement last, Completion c) {
@@ -1360,13 +1402,14 @@ module Statements {
13601402
c instanceof ExitCompletion
13611403
)
13621404
or
1363-
this.lastFinally(last, c, any(NormalCompletion nc))
1405+
this.lastFinally(last, c, any(NormalCompletion nc), _)
13641406
or
13651407
// If the `finally` block completes normally, it inherits any non-normal
13661408
// completion that was current before the `finally` block was entered
13671409
c =
13681410
any(NestedCompletion nc |
1369-
this.lastFinally(last, nc.getAnInnerCompatibleCompletion(), nc.getOuterCompletion())
1411+
this.lastFinally(last, nc.getAnInnerCompatibleCompletion(), nc.getOuterCompletion(),
1412+
nc.getNestLevel())
13701413
)
13711414
}
13721415

@@ -1481,6 +1524,7 @@ module Statements {
14811524
this.isLast() and
14821525
c =
14831526
any(NestedCompletion nc |
1527+
nc.getNestLevel() = 0 and
14841528
this.throwMayBeUncaught(nc.getOuterCompletion().(ThrowCompletion)) and
14851529
(
14861530
// Incompatible exception type: clause itself

0 commit comments

Comments
 (0)