Skip to content

Commit 64dcfbd

Browse files
authored
Merge pull request #4484 from tamasvajk/feature/custom-assert-methods
C#: Add support for custom assert methods (DoesNotReturnIfAttribute)
2 parents ae84d13 + 410af42 commit 64dcfbd

File tree

9 files changed

+142
-27
lines changed

9 files changed

+142
-27
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lgtm,codescanning
2+
* Assertion methods are now taking into account methods with parameters that are annotated
3+
with `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute`. This change is expected to
4+
lead to higher precision in any query that relies on control flow graphs.

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

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,27 @@ 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+
/**
13+
* DEPRECATED: renamed to `getAnAssertionIndex`.
14+
*
15+
* Gets the index of a parameter being asserted.
16+
*/
17+
deprecated int getAssertionIndex() { result = getAnAssertionIndex() }
18+
19+
/** Gets the index of a parameter being asserted. */
20+
abstract int getAnAssertionIndex();
21+
22+
/**
23+
* DEPRECATED: renamed to `getAnAssertedParameter`.
24+
*
25+
* Gets a parameter being asserted.
26+
*/
27+
deprecated Parameter getAssertedParameter() { result = getAnAssertedParameter() }
1428

15-
/** Gets the parameter being asserted. */
16-
final Parameter getAssertedParameter() { result = this.getParameter(this.getAssertionIndex()) }
29+
/** Gets a parameter being asserted. */
30+
final Parameter getAnAssertedParameter() {
31+
result = this.getParameter(this.getAnAssertionIndex())
32+
}
1733

1834
/** Gets the exception being thrown if the assertion fails, if any. */
1935
abstract Class getExceptionClass();
@@ -40,8 +56,15 @@ class Assertion extends MethodCall {
4056
/** Gets the assertion method targeted by this assertion. */
4157
AssertMethod getAssertMethod() { result = target }
4258

43-
/** Gets the expression that this assertion pertains to. */
44-
Expr getExpr() { result = this.getArgumentForParameter(target.getAssertedParameter()) }
59+
/**
60+
* DEPRECATED: renamed to `getAnExpr`.
61+
*
62+
* Gets an expression that this assertion pertains to.
63+
*/
64+
deprecated Expr getExpr() { result = this.getAnExpr() }
65+
66+
/** Gets an expression that this assertion pertains to. */
67+
Expr getAnExpr() { result = this.getArgumentForParameter(target.getAnAssertedParameter()) }
4568

4669
/**
4770
* Holds if basic block `succ` is immediately dominated by this assertion.
@@ -144,7 +167,7 @@ class FailingAssertion extends Assertion {
144167
FailingAssertion() {
145168
exists(AssertMethod am, Expr e |
146169
am = this.getAssertMethod() and
147-
e = this.getExpr()
170+
e = this.getAnExpr()
148171
|
149172
am instanceof AssertTrueMethod and
150173
e.getValue() = "false"
@@ -163,7 +186,7 @@ class SystemDiagnosticsDebugAssertTrueMethod extends AssertTrueMethod {
163186
this = any(SystemDiagnosticsDebugClass c).getAssertMethod()
164187
}
165188

166-
override int getAssertionIndex() { result = 0 }
189+
override int getAnAssertionIndex() { result = 0 }
167190

168191
override Class getExceptionClass() {
169192
// A failing assertion generates a message box, see
@@ -186,7 +209,7 @@ class SystemDiagnosticsContractAssertTrueMethod extends AssertTrueMethod {
186209
)
187210
}
188211

189-
override int getAssertionIndex() { result = 0 }
212+
override int getAnAssertionIndex() { result = 0 }
190213

191214
override Class getExceptionClass() {
192215
// A failing assertion generates a message box, see
@@ -195,11 +218,50 @@ class SystemDiagnosticsContractAssertTrueMethod extends AssertTrueMethod {
195218
}
196219
}
197220

221+
private predicate isDoesNotReturnIfAttributeParameter(Parameter p, boolean value) {
222+
exists(Attribute a | a = p.getAnAttribute() |
223+
a.getType() instanceof SystemDiagnosticsCodeAnalysisDoesNotReturnIfAttributeClass and
224+
a.getConstructorArgument(0).(BoolLiteral).getBoolValue() = value
225+
)
226+
}
227+
228+
/**
229+
* A method with a parameter that is annotated with
230+
* `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(false)`.
231+
*/
232+
class SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertTrueMethod extends AssertTrueMethod {
233+
private int i;
234+
235+
SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertTrueMethod() {
236+
isDoesNotReturnIfAttributeParameter(this.getParameter(i), false)
237+
}
238+
239+
override int getAnAssertionIndex() { result = i }
240+
241+
override SystemExceptionClass getExceptionClass() { any() }
242+
}
243+
244+
/**
245+
* A method with a parameter that is annotated with
246+
* `System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute(true)`.
247+
*/
248+
class SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertFalseMethod extends AssertFalseMethod {
249+
private int i;
250+
251+
SystemDiagnosticsCodeAnalysisDoesNotReturnIfAnnotatedAssertFalseMethod() {
252+
isDoesNotReturnIfAttributeParameter(this.getParameter(i), true)
253+
}
254+
255+
override int getAnAssertionIndex() { result = i }
256+
257+
override SystemExceptionClass getExceptionClass() { any() }
258+
}
259+
198260
/** A Visual Studio assertion method. */
199261
class VSTestAssertTrueMethod extends AssertTrueMethod {
200262
VSTestAssertTrueMethod() { this = any(VSTestAssertClass c).getIsTrueMethod() }
201263

202-
override int getAssertionIndex() { result = 0 }
264+
override int getAnAssertionIndex() { result = 0 }
203265

204266
override AssertFailedExceptionClass getExceptionClass() { any() }
205267
}
@@ -208,7 +270,7 @@ class VSTestAssertTrueMethod extends AssertTrueMethod {
208270
class VSTestAssertFalseMethod extends AssertFalseMethod {
209271
VSTestAssertFalseMethod() { this = any(VSTestAssertClass c).getIsFalseMethod() }
210272

211-
override int getAssertionIndex() { result = 0 }
273+
override int getAnAssertionIndex() { result = 0 }
212274

213275
override AssertFailedExceptionClass getExceptionClass() { any() }
214276
}
@@ -217,7 +279,7 @@ class VSTestAssertFalseMethod extends AssertFalseMethod {
217279
class VSTestAssertNullMethod extends AssertNullMethod {
218280
VSTestAssertNullMethod() { this = any(VSTestAssertClass c).getIsNullMethod() }
219281

220-
override int getAssertionIndex() { result = 0 }
282+
override int getAnAssertionIndex() { result = 0 }
221283

222284
override AssertFailedExceptionClass getExceptionClass() { any() }
223285
}
@@ -226,14 +288,14 @@ class VSTestAssertNullMethod extends AssertNullMethod {
226288
class VSTestAssertNonNullMethod extends AssertNonNullMethod {
227289
VSTestAssertNonNullMethod() { this = any(VSTestAssertClass c).getIsNotNullMethod() }
228290

229-
override int getAssertionIndex() { result = 0 }
291+
override int getAnAssertionIndex() { result = 0 }
230292

231293
override AssertFailedExceptionClass getExceptionClass() { any() }
232294
}
233295

234296
/** An NUnit assertion method. */
235297
abstract class NUnitAssertMethod extends AssertMethod {
236-
override int getAssertionIndex() { result = 0 }
298+
override int getAnAssertionIndex() { result = 0 }
237299

238300
override AssertionExceptionClass getExceptionClass() { any() }
239301
}
@@ -292,11 +354,11 @@ class ForwarderAssertMethod extends AssertMethod {
292354
strictcount(AssignableDefinition def | def.getTarget() = p) = 1 and
293355
forex(ControlFlowElement body | body = this.getBody() |
294356
bodyAsserts(this, body, a) and
295-
a.getExpr() = p.getAnAccess()
357+
a.getAnExpr() = p.getAnAccess()
296358
)
297359
}
298360

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

301363
override Class getExceptionClass() {
302364
result = this.getUnderlyingAssertMethod().getExceptionClass()
@@ -345,4 +407,4 @@ class ForwarderAssertNonNullMethod extends ForwarderAssertMethod, AssertNonNullM
345407
}
346408

347409
/** Holds if expression `e` appears in an assertion. */
348-
predicate isExprInAssertion(Expr e) { e = any(Assertion a).getExpr().getAChildExpr*() }
410+
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)