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

Skip to content

Commit 51f71d4

Browse files
committed
C#: Fix CFG for assertions with multiple assertion arguments
1 parent 5cd707f commit 51f71d4

12 files changed

Lines changed: 337 additions & 171 deletions

File tree

csharp/ql/src/semmle/code/csharp/commons/Assertions.qll

Lines changed: 217 additions & 72 deletions
Large diffs are not rendered by default.

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,7 @@ class Completion extends TCompletion {
9999
cfe instanceof ThrowElement and
100100
this = TThrowCompletion(cfe.(ThrowElement).getThrownExceptionType())
101101
or
102-
exists(AssertMethod m | assertion(cfe, m, _) |
103-
this = TThrowCompletion(m.getExceptionClass())
104-
or
105-
not exists(m.getExceptionClass()) and
106-
this = TExitCompletion()
107-
)
102+
this = assertionCompletion(cfe, _)
108103
or
109104
completionIsValidForStmt(cfe, this)
110105
or
@@ -390,11 +385,22 @@ private predicate invalidCastCandidate(CastExpr ce) {
390385
ce.getType() = ce.getExpr().getType().(ValueOrRefType).getASubType+()
391386
}
392387

393-
private predicate assertion(Assertion a, AssertMethod am, Expr e) {
394-
e = a.getAnExpr() and
388+
private predicate assertion(Assertion a, int i, AssertMethod am, Expr e) {
389+
e = a.getExpr(i) and
395390
am = a.getAssertMethod()
396391
}
397392

393+
/** Gets a valid completion when argument `i` fails in assertion `a`. */
394+
Completion assertionCompletion(Assertion a, int i) {
395+
exists(AssertMethod am | am = a.getAssertMethod() |
396+
result = TThrowCompletion(am.getExceptionClass(i))
397+
or
398+
i = am.getAnAssertionIndex() and
399+
not exists(am.getExceptionClass(i)) and
400+
result = TExitCompletion()
401+
)
402+
}
403+
398404
/**
399405
* Holds if a normal completion of `e` must be a Boolean completion.
400406
*/
@@ -422,8 +428,11 @@ private predicate inBooleanContext(Expr e, boolean isBooleanCompletionForParent)
422428
or
423429
exists(SpecificCatchClause scc | scc.getFilterClause() = e | isBooleanCompletionForParent = false)
424430
or
425-
assertion(_, [any(AssertTrueMethod m).(AssertMethod), any(AssertFalseMethod m)], e) and
426-
isBooleanCompletionForParent = false
431+
exists(BooleanAssertMethod m, int i |
432+
assertion(_, i, m, e) and
433+
i = m.getAnAssertionIndex(_) and
434+
isBooleanCompletionForParent = false
435+
)
427436
or
428437
exists(LogicalNotExpr lne | lne.getAnOperand() = e |
429438
inBooleanContext(lne, _) and
@@ -495,8 +504,11 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen
495504
isNullnessCompletionForParent = false
496505
)
497506
or
498-
assertion(_, [any(AssertNullMethod m).(AssertMethod), any(AssertNonNullMethod m)], e) and
499-
isNullnessCompletionForParent = false
507+
exists(NullnessAssertMethod m, int i |
508+
assertion(_, i, m, e) and
509+
i = m.getAnAssertionIndex(_) and
510+
isNullnessCompletionForParent = false
511+
)
500512
or
501513
exists(ConditionalExpr ce | inNullnessContext(ce, _) |
502514
(e = ce.getThen() or e = ce.getElse()) and

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ private class ExitingCall extends NonReturningCall {
2323
ExitingCall() {
2424
this.getTarget() instanceof ExitingCallable
2525
or
26-
exists(AssertMethod m | m = this.(FailingAssertion).getAssertMethod() |
27-
not exists(m.getExceptionClass())
28-
)
26+
this = any(FailingAssertion fa | not exists(fa.getExceptionClass()))
2927
}
3028

3129
override ExitCompletion getACompletion() { not result instanceof NestedCompletion }
@@ -39,9 +37,7 @@ private class ThrowingCall extends NonReturningCall {
3937
(
4038
c = this.getTarget().(ThrowingCallable).getACallCompletion()
4139
or
42-
exists(AssertMethod m | m = this.(FailingAssertion).getAssertMethod() |
43-
c.getExceptionClass() = m.getExceptionClass()
44-
)
40+
c.getExceptionClass() = this.(FailingAssertion).getExceptionClass()
4541
or
4642
exists(CIL::Method m, CIL::Type ex |
4743
this.getTarget().matchesHandle(m) and

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

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ private module Cached {
3636
cached
3737
newtype TSplit =
3838
TInitializerSplit(Constructor c) { InitializerSplitting::constructorInitializes(c, _) } or
39-
TAssertionSplit(AssertionSplitting::Assertion a, boolean success) { success = [true, false] } or
39+
TAssertionSplit(AssertionSplitting::Assertion a, int i, boolean success) {
40+
exists(a.getExpr(i)) and
41+
success in [false, true]
42+
} or
4043
TFinallySplit(FinallySplitting::FinallySplitType type, int nestLevel) {
4144
nestLevel = FinallySplitting::nestLevel(_)
4245
} or
4346
TExceptionHandlerSplit(ExceptionClass ec) or
4447
TBooleanSplit(BooleanSplitting::BooleanSplitSubKind kind, boolean branch) {
4548
kind.startsSplit(_) and
46-
(branch = true or branch = false)
49+
branch in [false, true]
4750
} or
4851
TLoopSplit(LoopSplitting::AnalyzableLoopStmt loop)
4952

@@ -414,8 +417,9 @@ module AssertionSplitting {
414417
class AssertionSplitImpl extends SplitImpl, TAssertionSplit {
415418
Assertion a;
416419
boolean success;
420+
int i;
417421

418-
AssertionSplitImpl() { this = TAssertionSplit(a, success) }
422+
AssertionSplitImpl() { this = TAssertionSplit(a, i, success) }
419423

420424
/** Gets the assertion. */
421425
Assertion getAssertion() { result = a }
@@ -445,37 +449,29 @@ module AssertionSplitting {
445449

446450
override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
447451
exists(AssertMethod m |
448-
pred = last(a.getAnExpr(), c) and
452+
pred = last(a.getExpr(i), c) and
449453
succ = succ(pred, c) and
450-
this.getAssertion() = a and
451-
m = a.getAssertMethod()
454+
m = a.getAssertMethod() and
455+
// The assertion only succeeds when all asserted arguments succeeded, so
456+
// we only enter a "success" state after the last argument has succeeded.
457+
//
458+
// The split is only entered if we are not already in a "failing" state
459+
// for one of the previous arguments, which ensures that the "success"
460+
// state is only entered when all arguments succeed. This also means
461+
// that if multiple arguments fail, then the first failing argument
462+
// will determine the exception being thrown by the assertion.
463+
if success = true then i = max(int j | exists(a.getExpr(j))) else any()
452464
|
453-
m instanceof AssertTrueMethod and
454-
(
455-
c instanceof TrueCompletion and success = true
456-
or
457-
c instanceof FalseCompletion and success = false
458-
)
459-
or
460-
m instanceof AssertFalseMethod and
461-
(
462-
c instanceof TrueCompletion and success = false
465+
exists(boolean b | i = m.(BooleanAssertMethod).getAnAssertionIndex(b) |
466+
c instanceof TrueCompletion and success = b
463467
or
464-
c instanceof FalseCompletion and success = true
468+
c instanceof FalseCompletion and success = b.booleanNot()
465469
)
466470
or
467-
m instanceof AssertNullMethod and
468-
(
469-
c.(NullnessCompletion).isNull() and success = true
470-
or
471-
c.(NullnessCompletion).isNonNull() and success = false
472-
)
473-
or
474-
m instanceof AssertNonNullMethod and
475-
(
476-
c.(NullnessCompletion).isNull() and success = false
471+
exists(boolean b | i = m.(NullnessAssertMethod).getAnAssertionIndex(b) |
472+
c.(NullnessCompletion).isNull() and success = b
477473
or
478-
c.(NullnessCompletion).isNonNull() and success = true
474+
c.(NullnessCompletion).isNonNull() and success = b.booleanNot()
479475
)
480476
)
481477
}
@@ -491,7 +487,7 @@ module AssertionSplitting {
491487
c instanceof NormalCompletion
492488
or
493489
success = false and
494-
not c instanceof NormalCompletion
490+
c = assertionCompletion(a, i)
495491
)
496492
}
497493

@@ -504,7 +500,7 @@ module AssertionSplitting {
504500
c instanceof NormalCompletion
505501
or
506502
success = false and
507-
not c instanceof NormalCompletion
503+
c = assertionCompletion(a, i)
508504
)
509505
}
510506

@@ -1604,7 +1600,7 @@ private module SuccSplits {
16041600

16051601
/**
16061602
* Holds if `succSplits` should not inherit a split of kind `sk` from
1607-
* `predSplits, except possibly because of a split in `except`.
1603+
* `predSplits`, except possibly because of a split in `except`.
16081604
*
16091605
* The predicate is written using explicit recursion, as opposed to a `forall`,
16101606
* to avoid negative recursion.

csharp/ql/test/library-tests/commons/Assertions/Assertions.ql

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@ import csharp
22
import semmle.code.csharp.commons.Assertions
33

44
query predicate assertTrue(Assertion a, Expr e) {
5-
a.getAnExpr() = e and
6-
a.getTarget() instanceof AssertTrueMethod
5+
exists(int i |
6+
a.getExpr(i) = e and
7+
i = a.getTarget().(BooleanAssertMethod).getAnAssertionIndex(true)
8+
)
79
}
810

911
query predicate assertFalse(Assertion a, Expr e) {
10-
a.getAnExpr() = e and
11-
a.getTarget() instanceof AssertFalseMethod
12+
exists(int i |
13+
a.getExpr(i) = e and
14+
i = a.getTarget().(BooleanAssertMethod).getAnAssertionIndex(false)
15+
)
1216
}
1317

1418
query predicate assertNull(Assertion a, Expr e) {
15-
a.getAnExpr() = e and
16-
a.getTarget() instanceof AssertNullMethod
19+
exists(int i |
20+
a.getExpr(i) = e and
21+
i = a.getTarget().(NullnessAssertMethod).getAnAssertionIndex(true)
22+
)
1723
}
1824

1925
query predicate assertNonNull(Assertion a, Expr e) {
20-
a.getAnExpr() = e and
21-
a.getTarget() instanceof AssertNonNullMethod
26+
exists(int i |
27+
a.getExpr(i) = e and
28+
i = a.getTarget().(NullnessAssertMethod).getAnAssertionIndex(false)
29+
)
2230
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@
138138
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:25:140:26 | access to parameter b1 | 5 |
139139
| Assert.cs:138:10:138:12 | exit M13 | Assert.cs:138:10:138:12 | exit M13 | 1 |
140140
| Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | Assert.cs:140:9:140:35 | [assertion failure] call to method AssertTrueFalse | 3 |
141-
| Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | Assert.cs:141:9:141:15 | return ...; | 4 |
141+
| Assert.cs:140:29:140:30 | access to parameter b2 | Assert.cs:140:29:140:30 | access to parameter b2 | 1 |
142+
| Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | Assert.cs:140:9:140:35 | [assertion failure] call to method AssertTrueFalse | 2 |
143+
| Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | Assert.cs:141:9:141:15 | return ...; | 3 |
142144
| Assignments.cs:3:10:3:10 | enter M | Assignments.cs:3:10:3:10 | exit M | 33 |
143145
| Assignments.cs:14:18:14:35 | enter (...) => ... | Assignments.cs:14:18:14:35 | exit (...) => ... | 3 |
144146
| Assignments.cs:17:40:17:40 | enter + | Assignments.cs:17:40:17:40 | exit + | 5 |

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,11 @@ conditionBlock
295295
| Assert.cs:123:36:123:36 | [b (line 84): true] access to parameter b | Assert.cs:127:9:127:39 | [assertion failure] call to method IsFalse | true |
296296
| Assert.cs:123:36:123:36 | [b (line 84): true] access to parameter b | Assert.cs:127:37:127:38 | [b (line 84): true] !... | false |
297297
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | false |
298-
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | true |
299-
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | false |
300-
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | true |
298+
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:29:140:30 | access to parameter b2 | true |
299+
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | true |
300+
| Assert.cs:138:10:138:12 | enter M13 | Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | true |
301+
| Assert.cs:140:29:140:30 | access to parameter b2 | Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | true |
302+
| Assert.cs:140:29:140:30 | access to parameter b2 | Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | false |
301303
| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:7:26:7:28 | String arg | false |
302304
| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:10:21:10:26 | break; | false |
303305
| BreakInTry.cs:7:26:7:28 | String arg | BreakInTry.cs:10:21:10:26 | break; | true |
@@ -1212,13 +1214,11 @@ conditionFlow
12121214
| Assert.cs:127:24:127:32 | [b (line 84): true] ... != ... | Assert.cs:127:37:127:38 | [b (line 84): true] !... | false |
12131215
| Assert.cs:127:38:127:38 | [b (line 84): true] access to parameter b | Assert.cs:127:9:127:39 | [assertion success] call to method IsFalse | true |
12141216
| Assert.cs:140:25:140:26 | access to parameter b1 | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | false |
1215-
| Assert.cs:140:25:140:26 | access to parameter b1 | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | true |
1216-
| Assert.cs:140:25:140:26 | access to parameter b1 | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | false |
1217-
| Assert.cs:140:25:140:26 | access to parameter b1 | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | true |
1217+
| Assert.cs:140:25:140:26 | access to parameter b1 | Assert.cs:140:29:140:30 | access to parameter b2 | true |
12181218
| Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | false |
12191219
| Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 | Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | true |
1220-
| Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | false |
1221-
| Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 | Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | true |
1220+
| Assert.cs:140:29:140:30 | access to parameter b2 | Assert.cs:140:33:140:34 | [assertion failure] access to parameter b3 | true |
1221+
| Assert.cs:140:29:140:30 | access to parameter b2 | Assert.cs:140:33:140:34 | [assertion success] access to parameter b3 | false |
12221222
| BreakInTry.cs:9:21:9:31 | ... == ... | BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | false |
12231223
| BreakInTry.cs:9:21:9:31 | ... == ... | BreakInTry.cs:10:21:10:26 | break; | true |
12241224
| BreakInTry.cs:15:17:15:28 | ... == ... | BreakInTry.cs:3:10:3:11 | exit M1 | false |

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,10 @@ nonUniqueSetRepresentation
33
breakInvariant2
44
breakInvariant3
55
breakInvariant4
6-
| Assert.cs:140:25:140:26 | access to parameter b1 | | Assert.cs:140:29:140:30 | access to parameter b2 | assertion failure | assertion success | false |
7-
| Assert.cs:140:25:140:26 | access to parameter b1 | | Assert.cs:140:29:140:30 | access to parameter b2 | assertion failure | assertion success | true |
8-
| Assert.cs:140:25:140:26 | access to parameter b1 | | Assert.cs:140:29:140:30 | access to parameter b2 | assertion success | assertion failure | false |
9-
| Assert.cs:140:25:140:26 | access to parameter b1 | | Assert.cs:140:29:140:30 | access to parameter b2 | assertion success | assertion failure | true |
6+
| Assert.cs:140:29:140:30 | access to parameter b2 | assertion failure | Assert.cs:140:33:140:34 | access to parameter b3 | assertion failure | assertion failure | true |
107
| Assert.cs:140:29:140:30 | access to parameter b2 | assertion failure | Assert.cs:140:33:140:34 | access to parameter b3 | assertion failure | assertion success | false |
11-
| Assert.cs:140:29:140:30 | access to parameter b2 | assertion failure | Assert.cs:140:33:140:34 | access to parameter b3 | assertion failure | assertion success | true |
12-
| Assert.cs:140:29:140:30 | access to parameter b2 | assertion success | Assert.cs:140:33:140:34 | access to parameter b3 | assertion success | assertion failure | false |
13-
| Assert.cs:140:29:140:30 | access to parameter b2 | assertion success | Assert.cs:140:33:140:34 | access to parameter b3 | assertion success | assertion failure | true |
148
breakInvariant5
159
multipleSuccessors
16-
| Assert.cs:140:25:140:26 | access to parameter b1 | false | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 |
17-
| Assert.cs:140:25:140:26 | access to parameter b1 | false | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 |
18-
| Assert.cs:140:25:140:26 | access to parameter b1 | true | Assert.cs:140:29:140:30 | [assertion failure] access to parameter b2 |
19-
| Assert.cs:140:25:140:26 | access to parameter b1 | true | Assert.cs:140:29:140:30 | [assertion success] access to parameter b2 |
2010
| ConditionalAccess.cs:30:28:30:32 | ... = ... | successor | ConditionalAccess.cs:1:7:1:23 | exit ConditionalAccess |
2111
| ConditionalAccess.cs:30:28:30:32 | ... = ... | successor | ConditionalAccess.cs:30:10:30:12 | exit Out |
2212
| MultiImplementationA.cs:6:22:6:31 | enter get_P1 | successor | MultiImplementationA.cs:6:28:6:31 | null |

0 commit comments

Comments
 (0)