Skip to content

Commit 52bdd8b

Browse files
committed
C#: Add support for custom assert methods ([DoesNotReturnIf(true/false)])
1 parent 1b8d140 commit 52bdd8b

File tree

8 files changed

+127
-27
lines changed

8 files changed

+127
-27
lines changed

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

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ private import ControlFlow::BasicBlocks
99

1010
/** An assertion method. */
1111
abstract class AssertMethod extends Method {
12-
/** Gets the index of the parameter being asserted. */
13-
abstract int getAssertionIndex();
12+
/** Gets the index of a parameter being asserted. */
13+
abstract int getAnAssertionIndex();
1414

15-
/** Gets the parameter being asserted. */
16-
final Parameter getAssertedParameter() { result = this.getParameter(this.getAssertionIndex()) }
15+
/** Gets a parameter being asserted. */
16+
final Parameter getAnAssertedParameter() {
17+
result = this.getParameter(this.getAnAssertionIndex())
18+
}
1719

1820
/** Gets the exception being thrown if the assertion fails, if any. */
1921
abstract Class getExceptionClass();
@@ -40,8 +42,8 @@ class Assertion extends MethodCall {
4042
/** Gets the assertion method targeted by this assertion. */
4143
AssertMethod getAssertMethod() { result = target }
4244

43-
/** Gets the expression that this assertion pertains to. */
44-
Expr getExpr() { result = this.getArgumentForParameter(target.getAssertedParameter()) }
45+
/** Gets an expression that this assertion pertains to. */
46+
Expr getAnExpr() { result = this.getArgumentForParameter(target.getAnAssertedParameter()) }
4547

4648
/**
4749
* Holds if basic block `succ` is immediately dominated by this assertion.
@@ -144,7 +146,7 @@ class FailingAssertion extends Assertion {
144146
FailingAssertion() {
145147
exists(AssertMethod am, Expr e |
146148
am = this.getAssertMethod() and
147-
e = this.getExpr()
149+
e = this.getAnExpr()
148150
|
149151
am instanceof AssertTrueMethod and
150152
e.getValue() = "false"
@@ -163,7 +165,7 @@ class SystemDiagnosticsDebugAssertTrueMethod extends AssertTrueMethod {
163165
this = any(SystemDiagnosticsDebugClass c).getAssertMethod()
164166
}
165167

166-
override int getAssertionIndex() { result = 0 }
168+
override int getAnAssertionIndex() { result = 0 }
167169

168170
override Class getExceptionClass() {
169171
// A failing assertion generates a message box, see
@@ -186,7 +188,7 @@ class SystemDiagnosticsContractAssertTrueMethod extends AssertTrueMethod {
186188
)
187189
}
188190

189-
override int getAssertionIndex() { result = 0 }
191+
override int getAnAssertionIndex() { result = 0 }
190192

191193
override Class getExceptionClass() {
192194
// A failing assertion generates a message box, see
@@ -195,11 +197,60 @@ class SystemDiagnosticsContractAssertTrueMethod extends AssertTrueMethod {
195197
}
196198
}
197199

200+
private predicate isDoesNotReturnIfAttributeParameter(Parameter p, boolean value) {
201+
exists(Attribute a | a = p.getAnAttribute() |
202+
a.getType() = any(SystemDiagnosticsCodeAnalysisDoesNotReturnIfAttributeClass c) and
203+
a.getConstructorArgument(0).(BoolLiteral).getBoolValue() = value
204+
)
205+
}
206+
207+
/**
208+
* A method with a parameter that is annotated with
209+
* `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)`.
210+
*/
211+
class SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertTrueMethod extends AssertTrueMethod {
212+
SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertTrueMethod() {
213+
this = any(Method m | isDoesNotReturnIfAttributeParameter(m.getAParameter(), false))
214+
}
215+
216+
override int getAnAssertionIndex() {
217+
exists(Parameter p |
218+
this.getParameter(result) = p and isDoesNotReturnIfAttributeParameter(p, false)
219+
)
220+
}
221+
222+
override Class getExceptionClass() {
223+
// The user defines the thrown exception.
224+
any()
225+
}
226+
}
227+
228+
/**
229+
* A method with a parameter that is annotated with
230+
* `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(true)`.
231+
*/
232+
class SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertFalseMethod extends AssertFalseMethod {
233+
SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertFalseMethod() {
234+
this = any(Method m | isDoesNotReturnIfAttributeParameter(m.getAParameter(), true))
235+
}
236+
237+
override int getAnAssertionIndex() {
238+
exists(Parameter p |
239+
this.getParameter(result) = p and isDoesNotReturnIfAttributeParameter(p, true)
240+
)
241+
}
242+
243+
override Class getExceptionClass() {
244+
// The user defines the thrown exception.
245+
any()
246+
}
247+
}
248+
198249
/** A Visual Studio assertion method. */
199250
class VSTestAssertTrueMethod extends AssertTrueMethod {
200251
VSTestAssertTrueMethod() { this = any(VSTestAssertClass c).getIsTrueMethod() }
201252

202-
override int getAssertionIndex() { result = 0 }
253+
override int getAnAssertionIndex() { result = 0 }
203254

204255
override AssertFailedExceptionClass getExceptionClass() { any() }
205256
}
@@ -208,7 +259,7 @@ class VSTestAssertTrueMethod extends AssertTrueMethod {
208259
class VSTestAssertFalseMethod extends AssertFalseMethod {
209260
VSTestAssertFalseMethod() { this = any(VSTestAssertClass c).getIsFalseMethod() }
210261

211-
override int getAssertionIndex() { result = 0 }
262+
override int getAnAssertionIndex() { result = 0 }
212263

213264
override AssertFailedExceptionClass getExceptionClass() { any() }
214265
}
@@ -217,7 +268,7 @@ class VSTestAssertFalseMethod extends AssertFalseMethod {
217268
class VSTestAssertNullMethod extends AssertNullMethod {
218269
VSTestAssertNullMethod() { this = any(VSTestAssertClass c).getIsNullMethod() }
219270

220-
override int getAssertionIndex() { result = 0 }
271+
override int getAnAssertionIndex() { result = 0 }
221272

222273
override AssertFailedExceptionClass getExceptionClass() { any() }
223274
}
@@ -226,14 +277,14 @@ class VSTestAssertNullMethod extends AssertNullMethod {
226277
class VSTestAssertNonNullMethod extends AssertNonNullMethod {
227278
VSTestAssertNonNullMethod() { this = any(VSTestAssertClass c).getIsNotNullMethod() }
228279

229-
override int getAssertionIndex() { result = 0 }
280+
override int getAnAssertionIndex() { result = 0 }
230281

231282
override AssertFailedExceptionClass getExceptionClass() { any() }
232283
}
233284

234285
/** An NUnit assertion method. */
235286
abstract class NUnitAssertMethod extends AssertMethod {
236-
override int getAssertionIndex() { result = 0 }
287+
override int getAnAssertionIndex() { result = 0 }
237288

238289
override AssertionExceptionClass getExceptionClass() { any() }
239290
}
@@ -292,11 +343,11 @@ class ForwarderAssertMethod extends AssertMethod {
292343
strictcount(AssignableDefinition def | def.getTarget() = p) = 1 and
293344
forex(ControlFlowElement body | body = this.getBody() |
294345
bodyAsserts(this, body, a) and
295-
a.getExpr() = p.getAnAccess()
346+
a.getAnExpr() = p.getAnAccess()
296347
)
297348
}
298349

299-
override int getAssertionIndex() { result = p.getPosition() }
350+
override int getAnAssertionIndex() { result = p.getPosition() }
300351

301352
override Class getExceptionClass() {
302353
result = this.getUnderlyingAssertMethod().getExceptionClass()
@@ -345,4 +396,4 @@ class ForwarderAssertNonNullMethod extends ForwarderAssertMethod, AssertNonNullM
345396
}
346397

347398
/** Holds if expression `e` appears in an assertion. */
348-
predicate isExprInAssertion(Expr e) { e = any(Assertion a).getExpr().getAChildExpr*() }
399+
predicate isExprInAssertion(Expr e) { e = any(Assertion a).getAnExpr().getAChildExpr*() }

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ private predicate invalidCastCandidate(CastExpr ce) {
391391
}
392392

393393
private predicate assertion(Assertion a, AssertMethod am, Expr e) {
394-
e = a.getExpr() and
394+
e = a.getAnExpr() and
395395
am = a.getAssertMethod()
396396
}
397397

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ module AssertionSplitting {
445445

446446
override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
447447
exists(AssertMethod m |
448-
pred = last(a.getExpr(), c) and
448+
pred = last(a.getAnExpr(), c) and
449449
succ = succ(pred, c) and
450450
this.getAssertion() = a and
451451
m = a.getAssertMethod()

csharp/ql/src/semmle/code/csharp/frameworks/system/Diagnostics.qll

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,26 @@ class SystemDiagnosticsNamespace extends Namespace {
1111
}
1212
}
1313

14+
/** The `System.Diagnostics.CodeAnalysis` namespace. */
15+
class SystemDiagnosticsCodeAnalysisNamespace extends Namespace {
16+
SystemDiagnosticsCodeAnalysisNamespace() {
17+
this.getParentNamespace() instanceof SystemDiagnosticsNamespace and
18+
this.hasName("CodeAnalysis")
19+
}
20+
}
21+
1422
/** A class in the `System.Diagnostics` namespace. */
1523
class SystemDiagnosticsClass extends Class {
1624
SystemDiagnosticsClass() { this.getNamespace() instanceof SystemDiagnosticsNamespace }
1725
}
1826

27+
/** A class in the `System.Diagnostics.CodeAnalysis` namespace. */
28+
class SystemDiagnosticsCodeAnalysisClass extends Class {
29+
SystemDiagnosticsCodeAnalysisClass() {
30+
this.getNamespace() instanceof SystemDiagnosticsCodeAnalysisNamespace
31+
}
32+
}
33+
1934
/** The `System.Diagnostics.Debug` class. */
2035
class SystemDiagnosticsDebugClass extends SystemDiagnosticsClass {
2136
SystemDiagnosticsDebugClass() {
@@ -57,3 +72,10 @@ class SystemDiagnosticsProcessClass extends SystemDiagnosticsClass {
5772
result.getReturnType() instanceof SystemDiagnosticsProcessClass
5873
}
5974
}
75+
76+
/** The `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute` class. */
77+
class SystemDiagnosticsCodeAnalysisDoesNotReturnIfAttributeClass extends SystemDiagnosticsCodeAnalysisClass {
78+
SystemDiagnosticsCodeAnalysisDoesNotReturnIfAttributeClass() {
79+
this.hasName("DoesNotReturnIfAttribute")
80+
}
81+
}

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

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

44
query predicate assertTrue(Assertion a, Expr e) {
5-
a.getExpr() = e and
5+
a.getAnExpr() = e and
66
a.getTarget() instanceof AssertTrueMethod
77
}
88

99
query predicate assertFalse(Assertion a, Expr e) {
10-
a.getExpr() = e and
10+
a.getAnExpr() = e and
1111
a.getTarget() instanceof AssertFalseMethod
1212
}
1313

1414
query predicate assertNull(Assertion a, Expr e) {
15-
a.getExpr() = e and
15+
a.getAnExpr() = e and
1616
a.getTarget() instanceof AssertNullMethod
1717
}
1818

1919
query predicate assertNonNull(Assertion a, Expr e) {
20-
a.getExpr() = e and
20+
a.getAnExpr() = e and
2121
a.getTarget() instanceof AssertNonNullMethod
2222
}

csharp/ql/test/library-tests/frameworks/test/Assertions.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
assertTrue
22
| ../../../resources/stubs/Microsoft.VisualStudio.TestTools.UnitTesting.cs:9:28:9:33 | IsTrue | ../../../resources/stubs/Microsoft.VisualStudio.TestTools.UnitTesting.cs:9:40:9:40 | b |
3+
| DoesNotReturnIf.cs:8:22:8:31 | AssertTrue | DoesNotReturnIf.cs:8:95:8:103 | condition |
4+
| DoesNotReturnIf.cs:16:22:16:32 | AssertTrue2 | DoesNotReturnIf.cs:17:75:17:84 | condition1 |
5+
| DoesNotReturnIf.cs:16:22:16:32 | AssertTrue2 | DoesNotReturnIf.cs:18:75:18:84 | condition2 |
36
| nunit.cs:28:21:28:24 | True | nunit.cs:28:31:28:39 | condition |
47
| nunit.cs:29:21:29:24 | True | nunit.cs:29:31:29:39 | condition |
58
| nunit.cs:31:21:31:26 | IsTrue | nunit.cs:31:33:31:41 | condition |
@@ -9,6 +12,7 @@ assertTrue
912
| nunit.cs:54:21:54:24 | That | nunit.cs:54:31:54:39 | condition |
1013
assertFalse
1114
| ../../../resources/stubs/Microsoft.VisualStudio.TestTools.UnitTesting.cs:10:28:10:34 | IsFalse | ../../../resources/stubs/Microsoft.VisualStudio.TestTools.UnitTesting.cs:10:41:10:41 | b |
15+
| DoesNotReturnIf.cs:12:22:12:32 | AssertFalse | DoesNotReturnIf.cs:12:95:12:103 | condition |
1216
| nunit.cs:34:21:34:25 | False | nunit.cs:34:32:34:40 | condition |
1317
| nunit.cs:35:21:35:25 | False | nunit.cs:35:32:35:40 | condition |
1418
| nunit.cs:37:21:37:27 | IsFalse | nunit.cs:37:34:37:42 | condition |

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

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

44
query predicate assertTrue(AssertTrueMethod m, Parameter p) {
5-
m.fromSource() and m.fromSource() and p = m.getAssertedParameter()
5+
m.fromSource() and m.fromSource() and p = m.getAnAssertedParameter()
66
}
77

88
query predicate assertFalse(AssertFalseMethod m, Parameter p) {
9-
m.fromSource() and m.fromSource() and p = m.getAssertedParameter()
9+
m.fromSource() and m.fromSource() and p = m.getAnAssertedParameter()
1010
}
1111

1212
query predicate assertNull(AssertNullMethod m, Parameter p) {
13-
m.fromSource() and m.fromSource() and p = m.getAssertedParameter()
13+
m.fromSource() and m.fromSource() and p = m.getAnAssertedParameter()
1414
}
1515

1616
query predicate assertNonNull(AssertNonNullMethod m, Parameter p) {
17-
m.fromSource() and m.fromSource() and p = m.getAssertedParameter()
17+
m.fromSource() and m.fromSource() and p = m.getAnAssertedParameter()
1818
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace DoesNotReturnIfTests
5+
{
6+
class MyTestSuite
7+
{
8+
private void AssertTrue([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool condition)
9+
{
10+
}
11+
12+
private void AssertFalse([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(true)] bool condition)
13+
{
14+
}
15+
16+
private void AssertTrue2(
17+
[System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool condition1,
18+
[System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool condition2,
19+
bool nonCondition)
20+
{
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)