Skip to content

Commit 9634511

Browse files
authored
Merge pull request github#15489 from hvitved/csharp/lambda-field-flow
C#: Additional tracking of lambdas through fields and properties
2 parents 4d65e4e + bfe4a4b commit 9634511

File tree

5 files changed

+133
-29
lines changed

5 files changed

+133
-29
lines changed

csharp/ql/consistency-queries/DataFlowConsistency.ql

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ private import codeql.dataflow.internal.DataFlowImplConsistency
77
private module Input implements InputSig<CsharpDataFlow> {
88
private import CsharpDataFlow
99

10-
predicate uniqueEnclosingCallableExclude(Node n) {
11-
// TODO: Remove once static initializers are folded into the
12-
// static constructors
13-
exists(ControlFlow::Node cfn |
14-
cfn.getAstNode() = any(FieldOrProperty f | f.isStatic()).getAChild+() and
15-
cfn = n.getControlFlowNode()
16-
)
17-
}
18-
1910
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) {
2011
// TODO: Remove once static initializers are folded into the
2112
// static constructors
@@ -30,6 +21,8 @@ private module Input implements InputSig<CsharpDataFlow> {
3021
n instanceof ParameterNode
3122
or
3223
missingLocationExclude(n)
24+
or
25+
n instanceof FlowInsensitiveFieldNode
3326
}
3427

3528
predicate missingLocationExclude(Node n) {

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ private module Cached {
9898
cached
9999
newtype TDataFlowCallable =
100100
TDotNetCallable(DotNet::Callable c) { c.isUnboundDeclaration() } or
101-
TSummarizedCallable(DataFlowSummarizedCallable sc)
101+
TSummarizedCallable(DataFlowSummarizedCallable sc) or
102+
TFieldOrProperty(FieldOrProperty f)
102103

103104
cached
104105
newtype TDataFlowCall =
@@ -247,22 +248,33 @@ class ImplicitCapturedReturnKind extends ReturnKind, TImplicitCapturedReturnKind
247248

248249
/** A callable used for data flow. */
249250
class DataFlowCallable extends TDataFlowCallable {
250-
/** Get the underlying source code callable, if any. */
251+
/** Gets the underlying source code callable, if any. */
251252
DotNet::Callable asCallable() { this = TDotNetCallable(result) }
252253

253-
/** Get the underlying summarized callable, if any. */
254+
/** Gets the underlying summarized callable, if any. */
254255
FlowSummary::SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) }
255256

256-
/** Get the underlying callable. */
257+
/** Gets the underlying field or property, if any. */
258+
FieldOrProperty asFieldOrProperty() { this = TFieldOrProperty(result) }
259+
260+
/** Gets the underlying callable. */
257261
DotNet::Callable getUnderlyingCallable() {
258262
result = this.asCallable() or result = this.asSummarizedCallable()
259263
}
260264

261265
/** Gets a textual representation of this dataflow callable. */
262-
string toString() { result = this.getUnderlyingCallable().toString() }
266+
string toString() {
267+
result = this.getUnderlyingCallable().toString()
268+
or
269+
result = this.asFieldOrProperty().toString()
270+
}
263271

264272
/** Get the location of this dataflow callable. */
265-
Location getLocation() { result = this.getUnderlyingCallable().getLocation() }
273+
Location getLocation() {
274+
result = this.getUnderlyingCallable().getLocation()
275+
or
276+
result = this.asFieldOrProperty().getLocation()
277+
}
266278
}
267279

268280
/** A call relevant for data flow. */

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,26 @@ abstract class NodeImpl extends Node {
6969
abstract string toStringImpl();
7070
}
7171

72+
// TODO: Remove once static initializers are folded into the
73+
// static constructors
74+
private DataFlowCallable getEnclosingStaticFieldOrProperty(Expr e) {
75+
result.asFieldOrProperty() =
76+
any(FieldOrProperty f |
77+
f.isStatic() and
78+
e = f.getAChild+() and
79+
not exists(e.getEnclosingCallable())
80+
)
81+
}
82+
7283
private class ExprNodeImpl extends ExprNode, NodeImpl {
7384
override DataFlowCallable getEnclosingCallableImpl() {
7485
result.asCallable() =
7586
[
7687
this.getExpr().(CIL::Expr).getEnclosingCallable().(DotNet::Callable),
7788
this.getControlFlowNodeImpl().getEnclosingCallable()
7889
]
90+
or
91+
result = getEnclosingStaticFieldOrProperty(this.asExpr())
7992
}
8093

8194
override DotNet::Type getTypeImpl() {
@@ -911,7 +924,8 @@ private module Cached {
911924
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
912925
TParamsArgumentNode(ControlFlow::Node callCfn) {
913926
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
914-
}
927+
} or
928+
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() }
915929

916930
/**
917931
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
@@ -1021,6 +1035,8 @@ predicate nodeIsHidden(Node n) {
10211035
n instanceof ParamsArgumentNode
10221036
or
10231037
n.asExpr() = any(WithExpr we).getInitializer()
1038+
or
1039+
n instanceof FlowInsensitiveFieldNode
10241040
}
10251041

10261042
/** A CIL SSA definition, viewed as a node in a data flow graph. */
@@ -1346,6 +1362,8 @@ private module ArgumentNodes {
13461362

13471363
override DataFlowCallable getEnclosingCallableImpl() {
13481364
result.asCallable() = cfn.getEnclosingCallable()
1365+
or
1366+
result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
13491367
}
13501368

13511369
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
@@ -1385,6 +1403,8 @@ private module ArgumentNodes {
13851403

13861404
override DataFlowCallable getEnclosingCallableImpl() {
13871405
result.asCallable() = callCfn.getEnclosingCallable()
1406+
or
1407+
result = getEnclosingStaticFieldOrProperty(callCfn.getAstNode())
13881408
}
13891409

13901410
override Type getTypeImpl() { result = this.getParameter().getType() }
@@ -1784,6 +1804,30 @@ private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead
17841804
}
17851805
}
17861806

1807+
/**
1808+
* A data flow node used for control-flow insensitive flow through fields
1809+
* and properties.
1810+
*
1811+
* In global data flow this is used to model flow through static fields and
1812+
* properties, while for lambda flow we additionally use it to track assignments
1813+
* in constructors to uses within the same class.
1814+
*/
1815+
class FlowInsensitiveFieldNode extends NodeImpl, TFlowInsensitiveFieldNode {
1816+
private FieldOrProperty f;
1817+
1818+
FlowInsensitiveFieldNode() { this = TFlowInsensitiveFieldNode(f) }
1819+
1820+
override DataFlowCallable getEnclosingCallableImpl() { result.asFieldOrProperty() = f }
1821+
1822+
override Type getTypeImpl() { result = f.getType() }
1823+
1824+
override ControlFlow::Node getControlFlowNodeImpl() { none() }
1825+
1826+
override Location getLocationImpl() { result = f.getLocation() }
1827+
1828+
override string toStringImpl() { result = "[flow-insensitive] " + f }
1829+
}
1830+
17871831
/**
17881832
* Holds if `pred` can flow to `succ`, by jumping from one callable to
17891833
* another. Additional steps specified by the configuration are *not*
@@ -1792,13 +1836,16 @@ private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead
17921836
predicate jumpStep(Node pred, Node succ) {
17931837
pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ
17941838
or
1795-
exists(FieldOrProperty fl, FieldOrPropertyRead flr |
1796-
fl.isStatic() and
1797-
fl.isFieldLike() and
1798-
fl.getAnAssignedValue() = pred.asExpr() and
1799-
fl.getAnAccess() = flr and
1800-
flr = succ.asExpr() and
1801-
flr.hasNonlocalValue()
1839+
exists(FieldOrProperty f | f.isStatic() |
1840+
f.getAnAssignedValue() = pred.asExpr() and
1841+
succ = TFlowInsensitiveFieldNode(f)
1842+
or
1843+
exists(FieldOrPropertyRead fr |
1844+
pred = TFlowInsensitiveFieldNode(f) and
1845+
f.getAnAccess() = fr and
1846+
fr = succ.asExpr() and
1847+
fr.hasNonlocalValue()
1848+
)
18021849
)
18031850
or
18041851
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
@@ -2258,6 +2305,8 @@ module PostUpdateNodes {
22582305

22592306
override DataFlowCallable getEnclosingCallableImpl() {
22602307
result.asCallable() = cfn.getEnclosingCallable()
2308+
or
2309+
result = getEnclosingStaticFieldOrProperty(oc)
22612310
}
22622311

22632312
override DotNet::Type getTypeImpl() { result = oc.getType() }
@@ -2289,6 +2338,8 @@ module PostUpdateNodes {
22892338

22902339
override DataFlowCallable getEnclosingCallableImpl() {
22912340
result.asCallable() = cfn.getEnclosingCallable()
2341+
or
2342+
result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
22922343
}
22932344

22942345
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
@@ -2437,6 +2488,24 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
24372488
nodeTo.asExpr().(EventRead).getTarget() = aee.getTarget() and
24382489
preservesValue = false
24392490
)
2491+
or
2492+
preservesValue = true and
2493+
exists(FieldOrProperty f, FieldOrPropertyAccess fa |
2494+
fa = f.getAnAccess() and
2495+
fa.targetIsLocalInstance()
2496+
|
2497+
exists(AssignableDefinition def |
2498+
def.getTargetAccess() = fa and
2499+
nodeFrom.asExpr() = def.getSource() and
2500+
nodeTo = TFlowInsensitiveFieldNode(f) and
2501+
nodeFrom.getEnclosingCallable() instanceof Constructor
2502+
)
2503+
or
2504+
nodeFrom = TFlowInsensitiveFieldNode(f) and
2505+
f.getAnAccess() = fa and
2506+
fa = nodeTo.asExpr() and
2507+
fa.(FieldOrPropertyRead).hasNonlocalValue()
2508+
)
24402509
}
24412510

24422511
/**

csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,31 @@ public unsafe void M18()
128128
void M19(Action a, bool b)
129129
{
130130
if (b)
131-
a = () => {};
131+
a = () => { };
132132
a();
133133
}
134134

135-
void M20(bool b) => M19(() => {}, b);
135+
void M20(bool b) => M19(() => { }, b);
136+
137+
Action<int> Field;
138+
Action<int> Prop2 { get; set; }
139+
140+
DelegateFlow(Action<int> a, Action<int> b)
141+
{
142+
Field = a;
143+
Prop2 = b;
144+
}
145+
146+
void M20()
147+
{
148+
new DelegateFlow(
149+
_ => { },
150+
_ => { }
151+
);
152+
153+
this.Field(0);
154+
this.Prop2(0);
155+
Field(0);
156+
Prop2(0);
157+
}
136158
}

csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ delegateCall
2222
| DelegateFlow.cs:89:35:89:37 | delegate call | DelegateFlow.cs:93:13:93:21 | (...) => ... |
2323
| DelegateFlow.cs:114:9:114:16 | function pointer call | DelegateFlow.cs:7:17:7:18 | M2 |
2424
| DelegateFlow.cs:125:9:125:25 | function pointer call | DelegateFlow.cs:7:17:7:18 | M2 |
25-
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:131:17:131:24 | (...) => ... |
26-
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:29:135:36 | (...) => ... |
25+
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:131:17:131:25 | (...) => ... |
26+
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:29:135:37 | (...) => ... |
27+
| DelegateFlow.cs:153:9:153:21 | delegate call | DelegateFlow.cs:149:13:149:20 | (...) => ... |
28+
| DelegateFlow.cs:154:9:154:21 | delegate call | DelegateFlow.cs:150:13:150:20 | (...) => ... |
29+
| DelegateFlow.cs:155:9:155:16 | delegate call | DelegateFlow.cs:149:13:149:20 | (...) => ... |
30+
| DelegateFlow.cs:156:9:156:16 | delegate call | DelegateFlow.cs:150:13:150:20 | (...) => ... |
2731
viableLambda
2832
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:16:9:16:20 | call to method M2 | DelegateFlow.cs:16:12:16:19 | (...) => ... |
2933
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:17:9:17:14 | call to method M2 | DelegateFlow.cs:5:10:5:11 | M1 |
@@ -49,7 +53,11 @@ viableLambda
4953
| DelegateFlow.cs:89:35:89:37 | delegate call | DelegateFlow.cs:93:9:93:22 | call to local function M14 | DelegateFlow.cs:93:13:93:21 | (...) => ... |
5054
| DelegateFlow.cs:114:9:114:16 | function pointer call | DelegateFlow.cs:119:9:119:28 | call to method M16 | DelegateFlow.cs:7:17:7:18 | M2 |
5155
| DelegateFlow.cs:125:9:125:25 | function pointer call | file://:0:0:0:0 | (none) | DelegateFlow.cs:7:17:7:18 | M2 |
52-
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:25:135:40 | call to method M19 | DelegateFlow.cs:135:29:135:36 | (...) => ... |
53-
| DelegateFlow.cs:132:9:132:11 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:131:17:131:24 | (...) => ... |
56+
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:25:135:41 | call to method M19 | DelegateFlow.cs:135:29:135:37 | (...) => ... |
57+
| DelegateFlow.cs:132:9:132:11 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:131:17:131:25 | (...) => ... |
58+
| DelegateFlow.cs:153:9:153:21 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:149:13:149:20 | (...) => ... |
59+
| DelegateFlow.cs:154:9:154:21 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:150:13:150:20 | (...) => ... |
60+
| DelegateFlow.cs:155:9:155:16 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:149:13:149:20 | (...) => ... |
61+
| DelegateFlow.cs:156:9:156:16 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:150:13:150:20 | (...) => ... |
5462
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:105:9:105:24 | object creation of type Lazy<Int32> | DelegateFlow.cs:104:23:104:30 | (...) => ... |
5563
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:107:9:107:24 | object creation of type Lazy<Int32> | DelegateFlow.cs:106:13:106:20 | (...) => ... |

0 commit comments

Comments
 (0)