Skip to content

Commit 6dd6d90

Browse files
committed
C#: Improve ConstantCondition.ql
1 parent c0ea069 commit 6dd6d90

File tree

2 files changed

+153
-3
lines changed

2 files changed

+153
-3
lines changed

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

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,76 @@
1616
import csharp
1717
import semmle.code.csharp.commons.Assertions
1818
import semmle.code.csharp.commons.Constants
19+
import semmle.code.csharp.controlflow.BasicBlocks
20+
import semmle.code.csharp.controlflow.Guards as Guards
21+
import codeql.controlflow.queries.ConstantCondition as ConstCond
22+
23+
module ConstCondInput implements ConstCond::InputSig<ControlFlow::BasicBlock> {
24+
class SsaDefinition = Ssa::Definition;
25+
26+
class GuardValue = Guards::GuardValue;
27+
28+
class Guard = Guards::Guards::Guard;
29+
30+
predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v) {
31+
Guards::Guards::ssaControlsBranchEdge(def, bb1, bb2, v)
32+
}
33+
34+
import Guards::Guards::InternalUtil
35+
}
36+
37+
module ConstCondImpl = ConstCond::Make<Location, Cfg, ConstCondInput>;
38+
39+
predicate constantGuard(
40+
Guards::Guards::Guard g, string msg, Guards::Guards::Guard reason, string reasonMsg
41+
) {
42+
ConstCondImpl::problems(g, msg, reason, reasonMsg) and
43+
// conditional qualified expressions sit at an akward place in the CFG, which
44+
// leads to FPs
45+
not g.(QualifiableExpr).getQualifier() = reason and
46+
// if a logical connective is constant, one of its operands is constant, so
47+
// we report that instead
48+
not g instanceof LogicalNotExpr and
49+
not g instanceof LogicalAndExpr and
50+
not g instanceof LogicalOrExpr and
51+
// if a logical connective is a reason for another condition to be constant,
52+
// then one of its operands is a more precise reason
53+
not reason instanceof LogicalNotExpr and
54+
not reason instanceof LogicalAndExpr and
55+
not reason instanceof LogicalOrExpr and
56+
// don't report double-checked locking
57+
not exists(LockStmt ls, BasicBlock bb |
58+
bb = ls.getBasicBlock() and
59+
reason.getBasicBlock().strictlyDominates(bb) and
60+
bb.dominates(g.getBasicBlock())
61+
)
62+
}
1963

2064
/** A constant condition. */
21-
abstract class ConstantCondition extends Expr {
65+
abstract class ConstantCondition extends Guards::Guards::Guard {
2266
/** Gets the alert message for this constant condition. */
2367
abstract string getMessage();
2468

69+
predicate hasReason(Guards::Guards::Guard reason, string reasonMsg) {
70+
// dummy value, overridden when message has a placeholder
71+
reason = this and reasonMsg = "dummy"
72+
}
73+
2574
/** Holds if this constant condition is white-listed. */
2675
predicate isWhiteListed() { none() }
2776
}
2877

78+
/** A constant guard. */
79+
class ConstantGuard extends ConstantCondition {
80+
ConstantGuard() { constantGuard(this, _, _, _) }
81+
82+
override string getMessage() { constantGuard(this, result, _, _) }
83+
84+
override predicate hasReason(Guards::Guards::Guard reason, string reasonMsg) {
85+
constantGuard(this, _, reason, reasonMsg)
86+
}
87+
}
88+
2989
/** A constant Boolean condition. */
3090
class ConstantBooleanCondition extends ConstantCondition {
3191
boolean b;
@@ -138,9 +198,10 @@ class ConstantMatchingCondition extends ConstantCondition {
138198
}
139199
}
140200

141-
from ConstantCondition c, string msg
201+
from ConstantCondition c, string msg, Guards::Guards::Guard reason, string reasonMsg
142202
where
143203
msg = c.getMessage() and
204+
c.hasReason(reason, reasonMsg) and
144205
not c.isWhiteListed() and
145206
not isExprInAssertion(c)
146-
select c, msg
207+
select c, msg, reason, reasonMsg
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Provides a query for detecting constant conditions.
3+
*/
4+
5+
private import codeql.controlflow.BasicBlock as BB
6+
private import codeql.util.Location
7+
8+
private signature class TypSig;
9+
10+
signature module InputSig<TypSig BasicBlock> {
11+
class SsaDefinition;
12+
13+
/** An abstract value that a `Guard` may evaluate to. */
14+
class GuardValue {
15+
/** Gets a textual representation of this value. */
16+
string toString();
17+
18+
/**
19+
* Gets the dual value. Examples of dual values include:
20+
* - null vs. not null
21+
* - true vs. false
22+
* - evaluating to a specific value vs. evaluating to any other value
23+
* - throwing an exception vs. not throwing an exception
24+
*/
25+
GuardValue getDualValue();
26+
27+
/** Holds if this value represents throwing an exception. */
28+
predicate isThrowsException();
29+
}
30+
31+
/**
32+
* A guard. This may be any expression whose value determines subsequent
33+
* control flow. It may also be a switch case, which as a guard is considered
34+
* to evaluate to either true or false depending on whether the case matches.
35+
*/
36+
class Guard {
37+
/** Gets a textual representation of this guard. */
38+
string toString();
39+
40+
/**
41+
* Holds if this guard evaluating to `v` corresponds to taking the edge
42+
* from `bb1` to `bb2`. For ordinary conditional branching this guard is
43+
* the last node in `bb1`, but for switch case matching it is the switch
44+
* expression, which may either be in `bb1` or an earlier basic block.
45+
*/
46+
predicate hasValueBranchEdge(BasicBlock bb1, BasicBlock bb2, GuardValue v);
47+
}
48+
49+
/**
50+
* Holds if `def` evaluating to `v` controls the control-flow branch
51+
* edge from `bb1` to `bb2`. That is, following the edge from `bb1` to
52+
* `bb2` implies that `def` evaluated to `v`.
53+
*/
54+
predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v);
55+
56+
bindingset[gv1, gv2]
57+
predicate disjointValues(GuardValue gv1, GuardValue gv2);
58+
}
59+
60+
module Make<LocationSig Location, BB::CfgSig<Location> Cfg, InputSig<Cfg::BasicBlock> Input> {
61+
private import Cfg
62+
private import Input
63+
64+
private predicate ssaControlsGuardEdge(
65+
SsaDefinition def, GuardValue v, Guard g, BasicBlock bb1, BasicBlock bb2, GuardValue gv
66+
) {
67+
g.hasValueBranchEdge(bb1, bb2, gv) and
68+
ssaControlsBranchEdge(def, bb1, bb2, v)
69+
}
70+
71+
query predicate problems(Guard g, string msg, Guard reason, string reasonMsg) {
72+
exists(
73+
BasicBlock bb, GuardValue gv, SsaDefinition def, GuardValue v1, GuardValue v2, BasicBlock bb1,
74+
BasicBlock bb2
75+
|
76+
// If `g` in `bb` evaluates to `gv` then `def` has value `v1`,
77+
ssaControlsGuardEdge(def, v1, g, bb, _, gv) and
78+
not gv.isThrowsException() and
79+
// but `def` controls `bb` with value `v2` via the guard `reason` taking the branch `bb1` to `bb2`,
80+
ssaControlsGuardEdge(def, v2, reason, bb1, bb2, _) and
81+
dominatingEdge(bb1, bb2) and
82+
bb2.dominates(bb) and
83+
// and `v1` and `v2` are disjoint so `g` cannot be `gv`.
84+
disjointValues(v1, v2) and
85+
msg = "Guard " + g + " is always " + gv.getDualValue() + " because of $@." and
86+
reasonMsg = reason.toString()
87+
)
88+
}
89+
}

0 commit comments

Comments
 (0)