Skip to content

Commit 6fc36f6

Browse files
authored
Merge pull request #6 from hvitved/csharp/query/constant-condition
Approved by calumgrant
2 parents 7e23382 + 579d64c commit 6fc36f6

26 files changed

+1476
-324
lines changed

change-notes/1.18/analysis-csharp.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

1616
| **Query** | **Tags** | **Purpose** |
1717
|-----------------------------|-----------|--------------------------------------------------------------------|
18-
| [Exposing internal representation (cs/expose-implementation)] | Different results | The query has been rewritten, based on the equivalent Java query. |
18+
| Constant condition (cs/constant-condition) | More results | The query has been generalized to cover both `Null-coalescing left operand is constant (cs/constant-null-coalescing)` and `Switch selector is constant (cs/constant-switch-selector)`. |
19+
| Exposing internal representation (cs/expose-implementation) | Different results | The query has been rewritten, based on the equivalent Java query. |
1920
| Local scope variable shadows member (cs/local-shadows-member) | maintainability, readability | Replaces the existing queries [Local variable shadows class member (cs/local-shadows-class-member)](https://help.semmle.com/wiki/display/CSHARP/Local+variable+shadows+class+member), [Local variable shadows struct member (cs/local-shadows-struct-member)](https://help.semmle.com/wiki/display/CSHARP/Local+variable+shadows+struct+member), [Parameter shadows class member (cs/parameter-shadows-class-member)](https://help.semmle.com/wiki/display/CSHARP/Parameter+shadows+class+member), and [Parameter shadows struct member (cs/parameter-shadows-struct-member)](https://help.semmle.com/wiki/display/CSHARP/Parameter+shadows+struct+member). |
21+
| Null-coalescing left operand is constant (cs/constant-null-coalescing) | No results | The query has been removed, as it is now covered by `Constant condition (cs/constant-condition)`. |
22+
| Switch selector is constant (cs/constant-switch-selector) | No results | The query has been removed, as it is now covered by `Constant condition (cs/constant-condition)`. |
2023

2124
## Changes to existing queries
2225

csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,111 @@
1414
import csharp
1515
import semmle.code.csharp.commons.Assertions
1616
import semmle.code.csharp.commons.Constants
17+
import ControlFlowGraph
1718

18-
/** A condition of an `if` statement or a conditional expression. */
19-
private class IfCondition extends Expr {
20-
IfCondition() {
21-
this = any(IfStmt is).getCondition() or
22-
this = any(ConditionalExpr ce).getCondition()
19+
/** A constant condition. */
20+
abstract class ConstantCondition extends Expr {
21+
/** Gets the alert message for this constant condition. */
22+
abstract string getMessage();
23+
24+
/** Holds if this constant condition is white-listed. */
25+
predicate isWhiteListed() { none() }
26+
}
27+
28+
/** A constant Boolean condition. */
29+
class ConstantBooleanCondition extends ConstantCondition {
30+
boolean b;
31+
32+
ConstantBooleanCondition() {
33+
isConstantCondition(this, b)
34+
}
35+
36+
override string getMessage() {
37+
result = "Condition always evaluates to '" + b + "'."
38+
}
39+
40+
override predicate isWhiteListed() {
41+
// E.g. `x ?? false`
42+
this.(BoolLiteral) = any(NullCoalescingExpr nce).getRightOperand()
43+
}
44+
}
45+
46+
/** A constant condition in an `if` statement or a conditional expression. */
47+
class ConstantIfCondition extends ConstantBooleanCondition {
48+
ConstantIfCondition() {
49+
this = any(IfStmt is).getCondition().getAChildExpr*() or
50+
this = any(ConditionalExpr ce).getCondition().getAChildExpr*()
51+
}
52+
53+
override predicate isWhiteListed() {
54+
ConstantBooleanCondition.super.isWhiteListed()
55+
or
56+
// It is a common pattern to use a local constant/constant field to control
57+
// whether code parts must be executed or not
58+
this instanceof AssignableRead
2359
}
2460
}
2561

26-
/** A loop condition */
27-
private class LoopCondition extends Expr {
28-
LoopCondition() {
62+
/** A constant loop condition. */
63+
class ConstantLoopCondition extends ConstantBooleanCondition {
64+
ConstantLoopCondition() {
2965
this = any(LoopStmt ls).getCondition()
3066
}
67+
68+
override predicate isWhiteListed() {
69+
// Clearly intentional infinite loops are allowed
70+
this.(BoolLiteral).getBoolValue() = true
71+
}
72+
}
73+
74+
/** A constant nullness condition. */
75+
class ConstantNullnessCondition extends ConstantCondition {
76+
boolean b;
77+
78+
ConstantNullnessCondition() {
79+
forex(ControlFlowNode cfn |
80+
cfn = this.getAControlFlowNode() |
81+
exists(ControlFlowEdgeNullness t |
82+
exists(cfn.getASuccessorByType(t)) |
83+
if t.isNull() then b = true else b = false
84+
) and
85+
strictcount(ControlFlowEdgeType t | exists(cfn.getASuccessorByType(t))) = 1
86+
)
87+
}
88+
89+
override string getMessage() {
90+
if b = true then
91+
result = "Expression is always 'null'."
92+
else
93+
result = "Expression is never 'null'."
94+
}
3195
}
3296

33-
/** Holds if `e` is a conditional expression that is allowed to be constant. */
34-
predicate isWhiteListed(Expr e) {
35-
// It is a common pattern to use a local constant/constant field to control
36-
// whether code parts must be executed or not
37-
e = any(IfCondition ic).getAChildExpr*() and
38-
e instanceof AssignableRead
39-
or
40-
// Clearly intentional infinite loops are allowed
41-
e instanceof LoopCondition and
42-
e.(BoolLiteral).getBoolValue() = true
43-
or
44-
// E.g. `x ?? false`
45-
e.(BoolLiteral) = any(NullCoalescingExpr nce).getRightOperand()
97+
/** A constant matching condition. */
98+
class ConstantMatchingCondition extends ConstantCondition {
99+
boolean b;
100+
101+
ConstantMatchingCondition() {
102+
forex(ControlFlowNode cfn |
103+
cfn = this.getAControlFlowNode() |
104+
exists(ControlFlowEdgeMatching t |
105+
exists(cfn.getASuccessorByType(t)) |
106+
if t.isMatch() then b = true else b = false
107+
) and
108+
strictcount(ControlFlowEdgeType t | exists(cfn.getASuccessorByType(t))) = 1
109+
)
110+
}
111+
112+
override string getMessage() {
113+
if b = true then
114+
result = "Pattern always matches."
115+
else
116+
result = "Pattern never matches."
117+
}
46118
}
47119

48-
from Expr e, boolean b
49-
where isConstantCondition(e, b)
50-
and not isWhiteListed(e)
51-
and not isExprInAssertion(e)
52-
select e, "Condition always evaluates to '" + b + "'."
120+
from ConstantCondition c, string msg
121+
where msg = c.getMessage()
122+
and not c.isWhiteListed()
123+
and not isExprInAssertion(c)
124+
select c, msg

csharp/ql/src/Bad Practices/Control-Flow/ConstantNullCoalescingLeftHandOperand.qhelp

Lines changed: 0 additions & 15 deletions
This file was deleted.

csharp/ql/src/Bad Practices/Control-Flow/ConstantNullCoalescingLeftHandOperand.ql

Lines changed: 0 additions & 16 deletions
This file was deleted.

csharp/ql/src/Bad Practices/Control-Flow/ConstantSwitchSelector.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

csharp/ql/src/Bad Practices/Control-Flow/ConstantSwitchSelector.qhelp

Lines changed: 0 additions & 14 deletions
This file was deleted.

csharp/ql/src/Bad Practices/Control-Flow/ConstantSwitchSelector.ql

Lines changed: 0 additions & 16 deletions
This file was deleted.

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

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ class Completion extends TCompletion {
8181
or
8282
if mustHaveBooleanCompletion(cfe) then
8383
exists(boolean value |
84-
isConstant(cfe, value) |
84+
isBooleanConstant(cfe, value) |
8585
this = TBooleanCompletion(value, value)
8686
)
8787
or
88-
not isConstant(cfe, _) and
88+
not isBooleanConstant(cfe, _) and
8989
exists(boolean b | this = TBooleanCompletion(b, b))
9090
or
9191
// Corner case: In `if (x ?? y) { ... }`, `x` must have both a `true`
@@ -94,8 +94,20 @@ class Completion extends TCompletion {
9494
mustHaveNullnessCompletion(cfe) and
9595
this = TNullnessCompletion(true)
9696
else if mustHaveNullnessCompletion(cfe) then
97+
exists(boolean value |
98+
isNullnessConstant(cfe, value) |
99+
this = TNullnessCompletion(value)
100+
)
101+
or
102+
not isNullnessConstant(cfe, _) and
97103
this = TNullnessCompletion(_)
98-
else if mustHaveMatchingCompletion(cfe) then
104+
else if mustHaveMatchingCompletion(_, cfe) then
105+
exists(boolean value |
106+
isMatchingConstant(cfe, value) |
107+
this = TMatchingCompletion(value)
108+
)
109+
or
110+
not isMatchingConstant(cfe, _) and
99111
this = TMatchingCompletion(_)
100112
else if mustHaveEmptinessCompletion(cfe) then
101113
this = TEmptinessCompletion(_)
@@ -118,14 +130,82 @@ class Completion extends TCompletion {
118130
}
119131
}
120132

121-
private predicate isConstant(Expr e, boolean value) {
122-
e.getValue() = "true" and
123-
value = true
124-
or
125-
e.getValue() = "false" and
126-
value = false
127-
or
128-
isConstantComparison(e, value)
133+
/** Holds if expression `e` has the Boolean constant value `value`. */
134+
private predicate isBooleanConstant(Expr e, boolean value) {
135+
mustHaveBooleanCompletion(e) and
136+
(
137+
e.getValue() = "true" and
138+
value = true
139+
or
140+
e.getValue() = "false" and
141+
value = false
142+
or
143+
isConstantComparison(e, value)
144+
)
145+
}
146+
147+
/**
148+
* Holds if expression `e` is constantly `null` (`value = true`) or constantly
149+
* non-`null` (`value = false`).
150+
*/
151+
private predicate isNullnessConstant(Expr e, boolean value) {
152+
mustHaveNullnessCompletion(e) and
153+
exists(Expr stripped |
154+
stripped = e.stripCasts() |
155+
stripped.getType() = any(ValueType t |
156+
not t instanceof NullableType and
157+
// Extractor bug: the type of `x?.Length` is reported as `int`, but it should
158+
// be `int?`
159+
not getQualifier*(stripped).(QualifiableExpr).isConditional()
160+
) and
161+
value = false
162+
or
163+
stripped instanceof NullLiteral and
164+
value = true
165+
or
166+
stripped.hasValue() and
167+
not stripped instanceof NullLiteral and
168+
value = false
169+
)
170+
}
171+
172+
private Expr getQualifier(QualifiableExpr e) {
173+
// `e.getQualifier()` does not work for calls to extension methods
174+
result = e.getChildExpr(-1)
175+
}
176+
177+
/**
178+
* Holds if expression `e` constantly matches (`value = true`) or constantly
179+
* non-matches (`value = false`).
180+
*/
181+
private predicate isMatchingConstant(Expr e, boolean value) {
182+
exists(SwitchStmt ss |
183+
mustHaveMatchingCompletion(ss, e) |
184+
exists(Expr stripped |
185+
stripped = ss.getCondition().stripCasts() |
186+
exists(ConstCase cc, string strippedValue |
187+
cc = ss.getAConstCase() and
188+
e = cc.getExpr() and
189+
strippedValue = stripped.getValue() |
190+
if strippedValue = e.getValue() then
191+
value = true
192+
else
193+
value = false
194+
)
195+
or
196+
exists(TypeCase tc, Type t, Type strippedType |
197+
tc = ss.getATypeCase() |
198+
e = tc.getTypeAccess() and
199+
t = e.getType() and
200+
strippedType = stripped.getType() and
201+
not t.isImplicitlyConvertibleTo(strippedType) and
202+
not t instanceof Interface and
203+
not t.containsTypeParameters() and
204+
not strippedType.containsTypeParameters() and
205+
value = false
206+
)
207+
)
208+
)
129209
}
130210

131211
/** A control flow element that is inside a `try` block. */
@@ -325,12 +405,10 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen
325405
* Holds if a normal completion of `e` must be a matching completion. Thats is,
326406
* whether `e` determines a match in a `switch` statement.
327407
*/
328-
private predicate mustHaveMatchingCompletion(Expr e) {
329-
exists(SwitchStmt ss |
330-
e = ss.getAConstCase().getExpr()
331-
or
332-
e = ss.getATypeCase().getTypeAccess() // use type access to represent the type test
333-
)
408+
private predicate mustHaveMatchingCompletion(SwitchStmt ss, Expr e) {
409+
e = ss.getAConstCase().getExpr()
410+
or
411+
e = ss.getATypeCase().getTypeAccess() // use type access to represent the type test
334412
}
335413

336414
/**

0 commit comments

Comments
 (0)