Skip to content

Commit ec2bf91

Browse files
authored
Merge pull request #339 from hvitved/csharp/cfg/assertions
C#: Detect constantly failing assertions in the CFG
2 parents fa55e31 + a3d74b0 commit ec2bf91

40 files changed

+1305
-820
lines changed

change-notes/1.19/analysis-csharp.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
## General improvements
44

5-
* The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead.
6-
5+
* Control flow graph improvements:
6+
* The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead.
7+
* Code that is only reachable from a constant failing assertion, such as `Debug.Assert(false)`, is considered to be unreachable.
8+
79
## New queries
810

911
| **Query** | **Tags** | **Purpose** |

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

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
/** Provides classes for assertions. */
22
private import semmle.code.csharp.frameworks.system.Diagnostics
33
private import semmle.code.csharp.frameworks.test.VisualStudio
4+
private import semmle.code.csharp.frameworks.System
45

56
/** An assertion method. */
67
abstract class AssertMethod extends Method {
78
/** Gets the index of the parameter being asserted. */
89
abstract int getAssertionIndex();
10+
11+
/** Gets the parameter being asserted. */
12+
final Parameter getAssertedParameter() {
13+
result = this.getParameter(this.getAssertionIndex())
14+
}
15+
16+
/** Gets the exception being thrown if the assertion fails, if any. */
17+
abstract ExceptionClass getExceptionClass();
918
}
1019

1120
/** A positive assertion method. */
@@ -24,6 +33,36 @@ abstract class AssertNullMethod extends AssertMethod {
2433
abstract class AssertNonNullMethod extends AssertMethod {
2534
}
2635

36+
/** An assertion, that is, a call to an assertion method. */
37+
class Assertion extends MethodCall {
38+
private AssertMethod target;
39+
40+
Assertion() { this.getTarget() = target }
41+
42+
/** Gets the assertion method targeted by this assertion. */
43+
AssertMethod getAssertMethod() { result = target }
44+
45+
/** Gets the expression that this assertion pertains to. */
46+
Expr getExpr() {
47+
result = this.getArgumentForParameter(target.getAssertedParameter())
48+
}
49+
}
50+
51+
/** A trivially failing assertion, for example `Debug.Assert(false)`. */
52+
class FailingAssertion extends Assertion {
53+
FailingAssertion() {
54+
exists(AssertMethod am, Expr e |
55+
am = this.getAssertMethod() and
56+
e = this.getExpr() |
57+
am instanceof AssertTrueMethod and
58+
e.getValue() = "false"
59+
or
60+
am instanceof AssertFalseMethod and
61+
e.getValue() = "true"
62+
)
63+
}
64+
}
65+
2766
/**
2867
* A `System.Diagnostics.Debug` assertion method.
2968
*/
@@ -33,6 +72,12 @@ class SystemDiagnosticsDebugAssertTrueMethod extends AssertTrueMethod {
3372
}
3473

3574
override int getAssertionIndex() { result = 0 }
75+
76+
override ExceptionClass getExceptionClass() {
77+
// A failing assertion generates a message box, see
78+
// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert
79+
none()
80+
}
3681
}
3782

3883
/** A Visual Studio assertion method. */
@@ -42,6 +87,8 @@ class VSTestAssertTrueMethod extends AssertTrueMethod {
4287
}
4388

4489
override int getAssertionIndex() { result = 0 }
90+
91+
override AssertFailedExceptionClass getExceptionClass() { any() }
4592
}
4693

4794
/** A Visual Studio negated assertion method. */
@@ -51,6 +98,8 @@ class VSTestAssertFalseMethod extends AssertFalseMethod {
5198
}
5299

53100
override int getAssertionIndex() { result = 0 }
101+
102+
override AssertFailedExceptionClass getExceptionClass() { any() }
54103
}
55104

56105
/** A Visual Studio `null` assertion method. */
@@ -60,6 +109,8 @@ class VSTestAssertNullMethod extends AssertNullMethod {
60109
}
61110

62111
override int getAssertionIndex() { result = 0 }
112+
113+
override AssertFailedExceptionClass getExceptionClass() { any() }
63114
}
64115

65116
/** A Visual Studio non-`null` assertion method. */
@@ -69,28 +120,50 @@ class VSTestAssertNonNullMethod extends AssertNonNullMethod {
69120
}
70121

71122
override int getAssertionIndex() { result = 0 }
123+
124+
override AssertFailedExceptionClass getExceptionClass() { any() }
72125
}
73126

74127
/** A method that forwards to another assertion method. */
75128
class ForwarderAssertMethod extends AssertMethod {
76129
AssertMethod forwardee;
77-
int assertionIndex;
130+
Parameter p;
78131

79132
ForwarderAssertMethod() {
80-
exists(AssignableDefinitions::ImplicitParameterDefinition def, ParameterRead pr |
81-
def.getParameter() = this.getParameter(assertionIndex) and
82-
pr = def.getAReachableRead() and
83-
pr.getAControlFlowNode().postDominates(this.getEntryPoint()) and
84-
forwardee.getParameter(forwardee.getAssertionIndex()).getAnAssignedArgument() = pr
133+
p = this.getAParameter() and
134+
strictcount(AssignableDefinition def | def.getTarget() = p) = 1 and
135+
forex(ControlFlowElement body |
136+
body = this.getABody() |
137+
exists(Assertion a |
138+
body = getAnAssertingElement(a) and
139+
a.getExpr() = p.getAnAccess() and
140+
forwardee = a.getAssertMethod()
141+
)
85142
)
86143
}
87144

88-
override int getAssertionIndex() { result = assertionIndex }
145+
override int getAssertionIndex() { result = p.getPosition() }
89146

147+
override ExceptionClass getExceptionClass() {
148+
result = forwardee.getExceptionClass()
149+
}
150+
90151
/** Gets the underlying assertion method that is being forwarded to. */
91152
AssertMethod getUnderlyingAssertMethod() { result = forwardee }
92153
}
93154

155+
private ControlFlowElement getAnAssertingElement(Assertion a) {
156+
result = a
157+
or
158+
result = getAnAssertingStmt(a)
159+
}
160+
161+
private Stmt getAnAssertingStmt(Assertion a) {
162+
result.(ExprStmt).getExpr() = getAnAssertingElement(a)
163+
or
164+
result.(BlockStmt).getFirstStmt() = getAnAssertingElement(a)
165+
}
166+
94167
/** A method that forwards to a positive assertion method. */
95168
class ForwarderAssertTrueMethod extends ForwarderAssertMethod, AssertTrueMethod {
96169
ForwarderAssertTrueMethod() {
@@ -121,8 +194,5 @@ class ForwarderAssertNonNullMethod extends ForwarderAssertMethod, AssertNonNullM
121194

122195
/** Holds if expression `e` appears in an assertion. */
123196
predicate isExprInAssertion(Expr e) {
124-
exists(MethodCall call, AssertMethod assertMethod |
125-
call.getTarget() = assertMethod |
126-
e = call.getArgument(assertMethod.getAssertionIndex()).getAChildExpr*()
127-
)
197+
e = any(Assertion a).getExpr().getAChildExpr*()
128198
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ private newtype TCompletion =
5959
TGotoDefaultCompletion()
6060
or
6161
TThrowCompletion(ExceptionClass ec)
62+
or
63+
TExitCompletion()
6264

6365
/**
6466
* A completion of a statement or an expression.
@@ -642,3 +644,16 @@ class ThrowCompletion extends Completion, TThrowCompletion {
642644

643645
override string toString() { result = "throw(" + getExceptionClass() + ")" }
644646
}
647+
648+
/**
649+
* A completion that represents evaluation of a statement or an
650+
* expression resulting in a program exit, for example
651+
* `System.Environment.Exit(0)`.
652+
*
653+
* An exit completion is different from a `return` completion; the former
654+
* exits the whole application, and exists inside `try` statements skip
655+
* `finally` blocks.
656+
*/
657+
class ExitCompletion extends Completion, TExitCompletion {
658+
override string toString() { result = "exit" }
659+
}

0 commit comments

Comments
 (0)