Skip to content

Commit 8a3f62b

Browse files
authored
Merge pull request #20558 from aschackmull/csharp/guards3
C#: Instantiate shared Guards and shared ControlFlowReachability and replace nullness
2 parents c7ef8a5 + 7d0e4f5 commit 8a3f62b

File tree

70 files changed

+7006
-14283
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+7006
-14283
lines changed

cpp/ql/test/library-tests/controlflow/guards/GuardsControl.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@
115115
| test.c:127:9:127:9 | 1 | not 0 | test.c:131:10:132:16 | { ... } |
116116
| test.c:131:7:131:7 | b | not 0 | test.c:131:10:132:16 | { ... } |
117117
| test.c:131:7:131:7 | b | true | test.c:131:10:132:16 | { ... } |
118-
| test.c:137:7:137:7 | 0 | 0 | test.c:142:3:136:10 | return ... |
119118
| test.c:137:7:137:7 | 0 | false | test.c:142:3:136:10 | return ... |
120119
| test.c:145:16:145:16 | x | 0 | test.c:146:11:147:9 | { ... } |
121120
| test.c:146:7:146:8 | ! ... | true | test.c:146:11:147:9 | { ... } |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* The representation of the C# control-flow graph has been significantly changed. This has minor effects on a wide range of queries including both minor improvements and minor regressions, for example, improved precision has been observed for `cs/inefficient-containskey` and `cs/stringbuilder-creation-in-loop`. Two queries stand out as being significantly affected with great improvements: `cs/dereferenced-value-may-be-null` has been completely rewritten which removes a very significant number of false positives. Furthermore, `cs/constant-condition` has been updated to report many new results - these new results are primarily expected to be true positives, but a few new false positives are expected as well. As part of these changes, `cs/dereferenced-value-may-be-null` has been changed from a `path-problem` query to a `problem` query, so paths are no longer reported for this query.

csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
4040
*/
4141
Nodes::ElementNode getAControlFlowNode() { result.getAstNode() = this }
4242

43+
/** Gets the control flow node for this element. */
44+
ControlFlow::Node getControlFlowNode() { result.getAstNode() = this }
45+
46+
/** Gets the basic block in which this element occurs. */
47+
BasicBlock getBasicBlock() { result = this.getAControlFlowNode().getBasicBlock() }
48+
4349
/**
4450
* Gets a first control flow node executed within this element.
4551
*/

csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ module ControlFlow {
251251
}
252252
}
253253

254+
/** A control flow node indicating normal termination of a callable. */
255+
class NormalExitNode extends AnnotatedExitNode instanceof Impl::NormalExitNode { }
256+
254257
/** A node for a callable exit point. */
255258
class ExitNode extends Node instanceof Impl::ExitNode {
256259
/** Gets the callable that this exit applies to. */
@@ -292,13 +295,7 @@ module ControlFlow {
292295

293296
class Split = Splitting::Split;
294297

295-
class FinallySplit = Splitting::FinallySplitting::FinallySplit;
296-
297298
class ExceptionHandlerSplit = Splitting::ExceptionHandlerSplitting::ExceptionHandlerSplit;
298-
299-
class BooleanSplit = Splitting::BooleanSplitting::BooleanSplit;
300-
301-
class LoopSplit = Splitting::LoopSplitting::LoopSplit;
302299
}
303300

304301
class BasicBlock = BBs::BasicBlock;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Provides an implementation of local (intraprocedural) control flow reachability.
3+
*/
4+
5+
import csharp
6+
private import codeql.controlflow.ControlFlow
7+
private import semmle.code.csharp.controlflow.BasicBlocks
8+
private import semmle.code.csharp.controlflow.Guards as Guards
9+
private import semmle.code.csharp.ExprOrStmtParent
10+
11+
private module ControlFlowInput implements
12+
InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock>
13+
{
14+
private import csharp as CS
15+
16+
AstNode getEnclosingAstNode(ControlFlow::Node node) {
17+
node.getAstNode() = result
18+
or
19+
not exists(node.getAstNode()) and result = node.getEnclosingCallable()
20+
}
21+
22+
class AstNode = ExprOrStmtParent;
23+
24+
AstNode getParent(AstNode node) { result = node.getParent() }
25+
26+
class FinallyBlock extends AstNode {
27+
FinallyBlock() { any(TryStmt try).getFinally() = this }
28+
}
29+
30+
class Expr = CS::Expr;
31+
32+
class SourceVariable = Ssa::SourceVariable;
33+
34+
class SsaDefinition = Ssa::Definition;
35+
36+
class SsaWriteDefinition extends SsaDefinition instanceof Ssa::ExplicitDefinition {
37+
Expr getDefinition() { result = super.getADefinition().getSource() }
38+
}
39+
40+
class SsaPhiNode = Ssa::PhiNode;
41+
42+
class SsaUncertainDefinition = Ssa::UncertainDefinition;
43+
44+
class GuardValue = Guards::GuardValue;
45+
46+
predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v) {
47+
Guards::Guards::ssaControlsBranchEdge(def, bb1, bb2, v)
48+
}
49+
50+
predicate ssaControls(SsaDefinition def, BasicBlock bb, GuardValue v) {
51+
Guards::Guards::ssaControls(def, bb, v)
52+
}
53+
54+
import Guards::Guards::InternalUtil
55+
}
56+
57+
module ControlFlowReachability = Make<Location, Cfg, ControlFlowInput>;

csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,284 @@ private import semmle.code.csharp.frameworks.System
1313
private import semmle.code.csharp.frameworks.system.Linq
1414
private import semmle.code.csharp.frameworks.system.Collections
1515
private import semmle.code.csharp.frameworks.system.collections.Generic
16+
private import codeql.controlflow.Guards as SharedGuards
17+
18+
private module GuardsInput implements
19+
SharedGuards::InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock>
20+
{
21+
private import csharp as CS
22+
23+
class NormalExitNode = ControlFlow::Nodes::NormalExitNode;
24+
25+
class AstNode = ControlFlowElement;
26+
27+
class Expr = CS::Expr;
28+
29+
private newtype TConstantValue =
30+
TStringValue(string value) { any(StringLiteral s).getValue() = value }
31+
32+
class ConstantValue extends TConstantValue {
33+
string toString() { this = TStringValue(result) }
34+
}
35+
36+
abstract class ConstantExpr extends Expr {
37+
predicate isNull() { none() }
38+
39+
boolean asBooleanValue() { none() }
40+
41+
int asIntegerValue() { none() }
42+
43+
ConstantValue asConstantValue() { none() }
44+
}
45+
46+
private class NullConstant extends ConstantExpr {
47+
NullConstant() { nullValueImplied(this) }
48+
49+
override predicate isNull() { any() }
50+
}
51+
52+
private class BooleanConstant extends ConstantExpr instanceof BoolLiteral {
53+
override boolean asBooleanValue() { result = super.getBoolValue() }
54+
}
55+
56+
private predicate intConst(Expr e, int i) {
57+
e.getValue().toInt() = i and
58+
(
59+
e.getType() instanceof Enum
60+
or
61+
e.getType() instanceof IntegralType
62+
)
63+
}
64+
65+
private class IntegerConstant extends ConstantExpr {
66+
IntegerConstant() { intConst(this, _) }
67+
68+
override int asIntegerValue() { intConst(this, result) }
69+
}
70+
71+
private class EnumConst extends ConstantExpr {
72+
EnumConst() { this.getType() instanceof Enum and this.hasValue() }
73+
74+
override int asIntegerValue() { result = this.getValue().toInt() }
75+
}
76+
77+
private class StringConstant extends ConstantExpr instanceof StringLiteral {
78+
override ConstantValue asConstantValue() { result = TStringValue(this.getValue()) }
79+
}
80+
81+
class NonNullExpr extends Expr {
82+
NonNullExpr() { nonNullValueImplied(this) }
83+
}
84+
85+
class Case extends AstNode instanceof CS::Case {
86+
Expr getSwitchExpr() { super.getExpr() = result }
87+
88+
predicate isDefaultCase() { this instanceof DefaultCase }
89+
90+
ConstantExpr asConstantCase() { super.getPattern() = result }
91+
92+
private predicate hasEdge(BasicBlock bb1, BasicBlock bb2, MatchingCompletion c) {
93+
exists(PatternExpr pe |
94+
super.getPattern() = pe and
95+
c.isValidFor(pe) and
96+
bb1.getLastNode() = pe.getAControlFlowNode() and
97+
bb1.getASuccessor(c.getAMatchingSuccessorType()) = bb2
98+
)
99+
}
100+
101+
predicate matchEdge(BasicBlock bb1, BasicBlock bb2) {
102+
exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isMatch())
103+
}
104+
105+
predicate nonMatchEdge(BasicBlock bb1, BasicBlock bb2) {
106+
exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isNonMatch())
107+
}
108+
}
109+
110+
abstract private class BinExpr extends BinaryOperation { }
111+
112+
class AndExpr extends BinExpr {
113+
AndExpr() {
114+
this instanceof LogicalAndExpr or
115+
this instanceof BitwiseAndExpr
116+
}
117+
}
118+
119+
class OrExpr extends BinExpr {
120+
OrExpr() {
121+
this instanceof LogicalOrExpr or
122+
this instanceof BitwiseOrExpr
123+
}
124+
}
125+
126+
class NotExpr = LogicalNotExpr;
127+
128+
class IdExpr extends Expr {
129+
IdExpr() { this instanceof AssignExpr or this instanceof CastExpr }
130+
131+
Expr getEqualChildExpr() {
132+
result = this.(AssignExpr).getRValue()
133+
or
134+
result = this.(CastExpr).getExpr()
135+
}
136+
}
137+
138+
predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) {
139+
exists(ComparisonTest ct |
140+
ct.getExpr() = eqtest and
141+
ct.getFirstArgument() = left and
142+
ct.getSecondArgument() = right
143+
|
144+
ct.getComparisonKind().isEquality() and polarity = true
145+
or
146+
ct.getComparisonKind().isInequality() and polarity = false
147+
)
148+
or
149+
exists(IsExpr ie, PatternExpr pat |
150+
ie = eqtest and
151+
ie.getExpr() = left and
152+
ie.getPattern() = pat
153+
|
154+
right = pat.(ConstantPatternExpr) and
155+
polarity = true
156+
or
157+
right = pat.(NotPatternExpr).getPattern().(ConstantPatternExpr) and
158+
polarity = false
159+
)
160+
}
161+
162+
class ConditionalExpr = CS::ConditionalExpr;
163+
164+
class Parameter = CS::Parameter;
165+
166+
private int parameterPosition() { result in [-1, any(Parameter p).getPosition()] }
167+
168+
class ParameterPosition extends int {
169+
ParameterPosition() { this = parameterPosition() }
170+
}
171+
172+
class ArgumentPosition extends int {
173+
ArgumentPosition() { this = parameterPosition() }
174+
}
175+
176+
pragma[inline]
177+
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
178+
179+
final private class FinalCallable = Callable;
180+
181+
class NonOverridableMethod extends FinalCallable {
182+
NonOverridableMethod() { not this.(Overridable).isOverridableOrImplementable() }
183+
184+
Parameter getParameter(ParameterPosition ppos) {
185+
super.getParameter(ppos) = result and
186+
not result.isParams()
187+
}
188+
189+
Expr getAReturnExpr() { this.canReturn(result) }
190+
}
191+
192+
class NonOverridableMethodCall extends Expr instanceof Call {
193+
NonOverridableMethod getMethod() { super.getTarget().getUnboundDeclaration() = result }
194+
195+
Expr getArgument(ArgumentPosition apos) {
196+
result = super.getArgumentForParameter(any(Parameter p | p.getPosition() = apos))
197+
}
198+
}
199+
}
200+
201+
private module GuardsImpl = SharedGuards::Make<Location, Cfg, GuardsInput>;
202+
203+
class GuardValue = GuardsImpl::GuardValue;
204+
205+
private module LogicInput implements GuardsImpl::LogicInputSig {
206+
class SsaDefinition extends Ssa::Definition {
207+
Expr getARead() { super.getARead() = result }
208+
}
209+
210+
class SsaWriteDefinition extends SsaDefinition instanceof Ssa::ExplicitDefinition {
211+
Expr getDefinition() { result = super.getADefinition().getSource() }
212+
}
213+
214+
class SsaPhiNode extends SsaDefinition instanceof Ssa::PhiNode {
215+
predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) {
216+
super.hasInputFromBlock(inp, bb)
217+
}
218+
}
219+
220+
predicate parameterDefinition(Parameter p, SsaDefinition def) {
221+
def.(Ssa::ImplicitParameterDefinition).getParameter() = p
222+
}
223+
224+
predicate additionalNullCheck(GuardsImpl::PreGuard guard, GuardValue val, Expr e, boolean isNull) {
225+
// Comparison with a non-`null` value, for example `x?.Length > 0`
226+
exists(ComparisonTest ct, ComparisonKind ck, Expr arg | ct.getExpr() = guard |
227+
e instanceof DereferenceableExpr and
228+
ct.getAnArgument() = e and
229+
ct.getAnArgument() = arg and
230+
arg = any(NullValue nv | nv.isNonNull()).getAnExpr() and
231+
ck = ct.getComparisonKind() and
232+
e != arg and
233+
isNull = false and
234+
not ck.isEquality() and
235+
not ck.isInequality() and
236+
val.asBooleanValue() = true
237+
)
238+
or
239+
// Call to `string.IsNullOrEmpty()` or `string.IsNullOrWhiteSpace()`
240+
exists(MethodCall mc, string name | guard = mc |
241+
mc.getTarget() = any(SystemStringClass c).getAMethod(name) and
242+
name.regexpMatch("IsNullOr(Empty|WhiteSpace)") and
243+
mc.getArgument(0) = e and
244+
val.asBooleanValue() = false and
245+
isNull = false
246+
)
247+
or
248+
guard =
249+
any(PatternMatch pm |
250+
e instanceof DereferenceableExpr and
251+
e = pm.getExpr() and
252+
(
253+
val.asBooleanValue().booleanNot() = patternMatchesNull(pm.getPattern()) and
254+
isNull = false
255+
or
256+
exists(TypePatternExpr tpe |
257+
// E.g. `x is string` where `x` has type `string`
258+
typePattern(guard, tpe, tpe.getCheckedType()) and
259+
val.asBooleanValue() = false and
260+
isNull = true
261+
)
262+
)
263+
)
264+
or
265+
e.(DereferenceableExpr).hasNullableType() and
266+
guard =
267+
any(PropertyAccess pa |
268+
pa.getQualifier() = e and
269+
pa.getTarget().hasName("HasValue") and
270+
val.asBooleanValue().booleanNot() = isNull
271+
)
272+
}
273+
274+
predicate additionalImpliesStep(
275+
GuardsImpl::PreGuard g1, GuardValue v1, GuardsImpl::PreGuard g2, GuardValue v2
276+
) {
277+
g1 instanceof DereferenceableExpr and
278+
g1 = getNullEquivParent(g2) and
279+
v1.isNullness(_) and
280+
v2 = v1
281+
or
282+
g1 instanceof DereferenceableExpr and
283+
g2 = getANullImplyingChild(g1) and
284+
v1.isNonNullValue() and
285+
v2 = v1
286+
or
287+
g2 = g1.(NullCoalescingExpr).getAnOperand() and
288+
v1.isNullValue() and
289+
v2 = v1
290+
}
291+
}
292+
293+
module Guards = GuardsImpl::Logic<LogicInput>;
16294

17295
/** An expression whose value may control the execution of another element. */
18296
class Guard extends Expr {

0 commit comments

Comments
 (0)