Skip to content

Commit 42f4656

Browse files
committed
C#: Data flow for primary constructors.
1 parent f5d4c49 commit 42f4656

File tree

3 files changed

+233
-4
lines changed

3 files changed

+233
-4
lines changed

csharp/ql/lib/semmle/code/csharp/Callable.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,15 @@ class Constructor extends DotNet::Constructor, Callable, Member, Attributable, @
370370
predicate isParameterless() { this.getNumberOfParameters() = 0 }
371371

372372
override string getUndecoratedName() { result = ".ctor" }
373+
374+
/**
375+
* Holds if this a primary constructor in source code.
376+
*/
377+
predicate isPrimary() {
378+
not this.hasBody() and
379+
this.getDeclaringType().fromSource() and
380+
this.fromSource()
381+
}
373382
}
374383

375384
/**
@@ -406,6 +415,20 @@ class InstanceConstructor extends Constructor {
406415
override string getAPrimaryQlClass() { result = "InstanceConstructor" }
407416
}
408417

418+
/**
419+
* A primary constructor, for example `public class C(object o)` on line 1 in
420+
* ```csharp
421+
* public class C(object o) {
422+
* ...
423+
* }
424+
* ```
425+
*/
426+
class PrimaryConstructor extends Constructor {
427+
PrimaryConstructor() { this.isPrimary() }
428+
429+
override string getAPrimaryQlClass() { result = "PrimaryConstructor" }
430+
}
431+
409432
/**
410433
* A destructor, for example `~C() { }` on line 2 in
411434
*

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

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ private import semmle.code.csharp.frameworks.system.threading.Tasks
2121
private import semmle.code.cil.Ssa::Ssa as CilSsa
2222
private import semmle.code.cil.internal.SsaImpl as CilSsaImpl
2323
private import codeql.util.Unit
24+
private import codeql.util.Boolean
2425

2526
/** Gets the callable in which this node occurs. */
2627
DataFlowCallable nodeGetEnclosingCallable(Node n) {
@@ -37,6 +38,21 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos)
3738
arg.argumentOf(c, pos)
3839
}
3940

41+
/**
42+
* Gets the control flow node used for data flow purposes for the primary constructor
43+
* parameter access `pa`.
44+
*/
45+
private ControlFlow::Node getPrimaryConstructorParameterCfn(ParameterAccess pa) {
46+
pa.getTarget().getCallable() instanceof PrimaryConstructor and
47+
(
48+
pa instanceof ParameterRead and
49+
result = pa.getAControlFlowNode()
50+
or
51+
pa instanceof ParameterWrite and
52+
exists(AssignExpr ae | pa = ae.getLValue() and result = ae.getAControlFlowNode())
53+
)
54+
}
55+
4056
abstract class NodeImpl extends Node {
4157
/** Do not call: use `getEnclosingCallable()` instead. */
4258
abstract DataFlowCallable getEnclosingCallableImpl();
@@ -124,9 +140,21 @@ private module ThisFlow {
124140
n.(InstanceParameterNode).getCallable() = cfn.(ControlFlow::Nodes::EntryNode).getCallable()
125141
or
126142
n.asExprAtNode(cfn) = any(Expr e | e instanceof ThisAccess or e instanceof BaseAccess)
143+
or
144+
exists(InstanceParameterAccessNode pan | pan = n |
145+
pan.getUnderlyingControlFlowNode() = cfn and pan.isPreUpdate()
146+
)
127147
}
128148

129-
private predicate thisAccess(Node n, BasicBlock bb, int i) { thisAccess(n, bb.getNode(i)) }
149+
private predicate thisAccess(Node n, BasicBlock bb, int i) {
150+
thisAccess(n, bb.getNode(i))
151+
or
152+
exists(Parameter p | n.(PrimaryConstructorThisAccessNode).getParameter() = p |
153+
bb.getCallable() = p.getCallable() and
154+
i = p.getPosition() + 1 and
155+
not n instanceof PostUpdateNode
156+
)
157+
}
130158

131159
private predicate thisRank(Node n, BasicBlock bb, int rankix) {
132160
exists(int i |
@@ -925,7 +953,17 @@ private module Cached {
925953
TParamsArgumentNode(ControlFlow::Node callCfn) {
926954
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
927955
} or
928-
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() }
956+
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() } or
957+
TInstanceParameterAccessNode(ControlFlow::Node cfn, boolean isPostUpdate) {
958+
exists(ParameterAccess pa | cfn = getPrimaryConstructorParameterCfn(pa) |
959+
isPostUpdate = false
960+
or
961+
pa instanceof ParameterWrite and isPostUpdate = true
962+
)
963+
} or
964+
TPrimaryConstructorThisAccessNode(Parameter p, Boolean isPostUpdate) {
965+
p.getCallable() instanceof PrimaryConstructor
966+
}
929967

930968
/**
931969
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
@@ -961,14 +999,20 @@ private module Cached {
961999
TFieldContent(Field f) { f.isUnboundDeclaration() } or
9621000
TPropertyContent(Property p) { p.isUnboundDeclaration() } or
9631001
TElementContent() or
964-
TSyntheticFieldContent(SyntheticField f)
1002+
TSyntheticFieldContent(SyntheticField f) or
1003+
TPrimaryConstructorParameterContent(Parameter p) {
1004+
p.getCallable() instanceof PrimaryConstructor
1005+
}
9651006

9661007
cached
9671008
newtype TContentApprox =
9681009
TFieldApproxContent(string firstChar) { firstChar = approximateFieldContent(_) } or
9691010
TPropertyApproxContent(string firstChar) { firstChar = approximatePropertyContent(_) } or
9701011
TElementApproxContent() or
971-
TSyntheticFieldApproxContent()
1012+
TSyntheticFieldApproxContent() or
1013+
TPrimaryConstructorParameterApproxContent(string firstChar) {
1014+
firstChar = approximatePrimaryConstructorParameterContent(_)
1015+
}
9721016

9731017
pragma[nomagic]
9741018
private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantGvnType t2) {
@@ -1037,6 +1081,10 @@ predicate nodeIsHidden(Node n) {
10371081
n.asExpr() = any(WithExpr we).getInitializer()
10381082
or
10391083
n instanceof FlowInsensitiveFieldNode
1084+
or
1085+
n instanceof InstanceParameterAccessNode
1086+
or
1087+
n instanceof PrimaryConstructorThisAccessNode
10401088
}
10411089

10421090
/** A CIL SSA definition, viewed as a node in a data flow graph. */
@@ -1745,6 +1793,77 @@ class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
17451793
override string toStringImpl() { result = this.getSummaryNode().toString() }
17461794
}
17471795

1796+
/**
1797+
* A data-flow node used to model reading and writing of primary constructor parameters.
1798+
*/
1799+
class InstanceParameterAccessNode extends NodeImpl, TInstanceParameterAccessNode {
1800+
private ControlFlow::Node cfn;
1801+
private boolean isPostUpdate;
1802+
private Parameter p;
1803+
1804+
InstanceParameterAccessNode() {
1805+
this = TInstanceParameterAccessNode(cfn, isPostUpdate) and
1806+
exists(ParameterAccess pa | cfn = getPrimaryConstructorParameterCfn(pa) and pa.getTarget() = p)
1807+
}
1808+
1809+
override DataFlowCallable getEnclosingCallableImpl() {
1810+
result.asCallable() = cfn.getEnclosingCallable()
1811+
}
1812+
1813+
override Type getTypeImpl() { result = cfn.getEnclosingCallable().getDeclaringType() }
1814+
1815+
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
1816+
1817+
override Location getLocationImpl() { result = cfn.getLocation() }
1818+
1819+
override string toStringImpl() { result = "this" }
1820+
1821+
/**
1822+
* Gets the underlying control flow node.
1823+
*/
1824+
ControlFlow::Node getUnderlyingControlFlowNode() { result = cfn }
1825+
1826+
/**
1827+
* Gets the primary constructor parameter that this is a this access to.
1828+
*/
1829+
Parameter getParameter() { result = p }
1830+
1831+
/**
1832+
* Holds if the this parameter access node corresponds to a pre-update node.
1833+
*/
1834+
predicate isPreUpdate() { isPostUpdate = false }
1835+
}
1836+
1837+
/**
1838+
* A data-flow node used to synthesize the body of a primary constructor.
1839+
*/
1840+
class PrimaryConstructorThisAccessNode extends NodeImpl, TPrimaryConstructorThisAccessNode {
1841+
private Parameter p;
1842+
private boolean isPostUpdate;
1843+
1844+
PrimaryConstructorThisAccessNode() { this = TPrimaryConstructorThisAccessNode(p, isPostUpdate) }
1845+
1846+
override DataFlowCallable getEnclosingCallableImpl() { result.asCallable() = p.getCallable() }
1847+
1848+
override Type getTypeImpl() { result = p.getCallable().getDeclaringType() }
1849+
1850+
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
1851+
1852+
override Location getLocationImpl() { result = p.getLocation() }
1853+
1854+
override string toStringImpl() { result = "this" }
1855+
1856+
/**
1857+
* Gets the primary constructor parameter that this is a this access to.
1858+
*/
1859+
Parameter getParameter() { result = p }
1860+
1861+
/**
1862+
* Holds if this is a this access for a primary constructor parameter write.
1863+
*/
1864+
predicate isPostUpdate() { isPostUpdate = true }
1865+
}
1866+
17481867
/** A field or a property. */
17491868
class FieldOrProperty extends Assignable, Modifiable {
17501869
FieldOrProperty() {
@@ -1881,6 +2000,16 @@ private PropertyContent getResultContent() {
18812000
result.getProperty() = any(SystemThreadingTasksTaskTClass c_).getResultProperty()
18822001
}
18832002

2003+
private predicate primaryConstructorParameterStore(Node node1, ContentSet c, Node node2) {
2004+
exists(AssignExpr ae, ParameterWrite pa, PrimaryConstructor constructor |
2005+
ae.getLValue() = pa and
2006+
pa.getTarget() = constructor.getAParameter() and
2007+
node1.asExpr() = ae.getRValue() and
2008+
node2 = TInstanceParameterAccessNode(ae.getAControlFlowNode(), true) and
2009+
c.(PrimaryConstructorParameterContent).getParameter() = pa.getTarget()
2010+
)
2011+
}
2012+
18842013
/**
18852014
* Holds if data can flow from `node1` to `node2` via an assignment to
18862015
* content `c`.
@@ -1918,6 +2047,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
19182047
c = getResultContent()
19192048
)
19202049
or
2050+
primaryConstructorParameterStore(node1, c, node2)
2051+
or
2052+
exists(Parameter p |
2053+
node1 = TExplicitParameterNode(p) and
2054+
node2 = TPrimaryConstructorThisAccessNode(p, true) and
2055+
c.(PrimaryConstructorParameterContent).getParameter() = p
2056+
)
2057+
or
19212058
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
19222059
node2.(FlowSummaryNode).getSummaryNode())
19232060
}
@@ -2010,6 +2147,12 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
20102147
node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and
20112148
c = getResultContent()
20122149
or
2150+
exists(InstanceParameterAccessNode n | n = node1 |
2151+
n.getUnderlyingControlFlowNode() = node2.(ExprNode).getControlFlowNode() and
2152+
n.getParameter() = c.(PrimaryConstructorParameterContent).getParameter()
2153+
) and
2154+
node2.asExpr() instanceof ParameterRead
2155+
or
20132156
// node1 = (..., node2, ...)
20142157
// node1.ItemX flows to node2
20152158
exists(TupleExpr te, int i, Expr item |
@@ -2072,6 +2215,10 @@ predicate clearsContent(Node n, ContentSet c) {
20722215
c.(FieldContent).getField() = f.getUnboundDeclaration() and
20732216
not f.isRef()
20742217
)
2218+
or
2219+
exists(Node n1 |
2220+
primaryConstructorParameterStore(_, c, n1) and n = n1.(PostUpdateNode).getPreUpdateNode()
2221+
)
20752222
}
20762223

20772224
/**
@@ -2361,6 +2508,32 @@ module PostUpdateNodes {
23612508

23622509
override Node getPreUpdateNode() { result.(FlowSummaryNode).getSummaryNode() = preUpdateNode }
23632510
}
2511+
2512+
private class InstanceParameterAccessPostUpdateNode extends PostUpdateNode,
2513+
InstanceParameterAccessNode
2514+
{
2515+
private ControlFlow::Node cfg;
2516+
2517+
InstanceParameterAccessPostUpdateNode() { this = TInstanceParameterAccessNode(cfg, true) }
2518+
2519+
override Node getPreUpdateNode() { result = TInstanceParameterAccessNode(cfg, false) }
2520+
2521+
override string toStringImpl() { result = "[post] this" }
2522+
}
2523+
2524+
private class PrimaryConstructorThisAccessPostUpdateNode extends PostUpdateNode,
2525+
PrimaryConstructorThisAccessNode
2526+
{
2527+
private Parameter p;
2528+
2529+
PrimaryConstructorThisAccessPostUpdateNode() {
2530+
this = TPrimaryConstructorThisAccessNode(p, true)
2531+
}
2532+
2533+
override Node getPreUpdateNode() { result = TPrimaryConstructorThisAccessNode(p, false) }
2534+
2535+
override string toStringImpl() { result = "[post] this" }
2536+
}
23642537
}
23652538

23662539
private import PostUpdateNodes
@@ -2537,6 +2710,11 @@ class ContentApprox extends TContentApprox {
25372710
this = TElementApproxContent() and result = "element"
25382711
or
25392712
this = TSyntheticFieldApproxContent() and result = "approximated synthetic field"
2713+
or
2714+
exists(string firstChar |
2715+
this = TPrimaryConstructorParameterApproxContent(firstChar) and
2716+
result = "approximated parameter field " + firstChar
2717+
)
25402718
}
25412719
}
25422720

@@ -2550,6 +2728,14 @@ private string approximatePropertyContent(PropertyContent pc) {
25502728
result = pc.getProperty().getName().prefix(1)
25512729
}
25522730

2731+
/**
2732+
* Gets a string for approximating the name of a synthetic field corresponding
2733+
* to a primary constructor parameter.
2734+
*/
2735+
private string approximatePrimaryConstructorParameterContent(PrimaryConstructorParameterContent pc) {
2736+
result = pc.getParameter().getName().prefix(1)
2737+
}
2738+
25532739
/** Gets an approximated value for content `c`. */
25542740
pragma[nomagic]
25552741
ContentApprox getContentApprox(Content c) {
@@ -2560,6 +2746,9 @@ ContentApprox getContentApprox(Content c) {
25602746
c instanceof ElementContent and result = TElementApproxContent()
25612747
or
25622748
c instanceof SyntheticFieldContent and result = TSyntheticFieldApproxContent()
2749+
or
2750+
result =
2751+
TPrimaryConstructorParameterApproxContent(approximatePrimaryConstructorParameterContent(c))
25632752
}
25642753

25652754
/**

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,23 @@ class PropertyContent extends Content, TPropertyContent {
239239
override Location getLocation() { result = p.getLocation() }
240240
}
241241

242+
/**
243+
* A reference to a synthetic field corresponding to a
244+
* primary constructor parameter.
245+
*/
246+
class PrimaryConstructorParameterContent extends Content, TPrimaryConstructorParameterContent {
247+
private Parameter p;
248+
249+
PrimaryConstructorParameterContent() { this = TPrimaryConstructorParameterContent(p) }
250+
251+
/** Gets the underlying parameter. */
252+
Parameter getParameter() { result = p }
253+
254+
override string toString() { result = "parameter field " + p.getName() }
255+
256+
override Location getLocation() { result = p.getLocation() }
257+
}
258+
242259
/** A reference to an element in a collection. */
243260
class ElementContent extends Content, TElementContent {
244261
override string toString() { result = "element" }

0 commit comments

Comments
 (0)