Skip to content

Commit bafea91

Browse files
authored
Merge pull request #15474 from michaelnebel/csharp/primaryconstructors
C# 12: Primary constructors.
2 parents fb2d36d + 7c59c7b commit bafea91

24 files changed

+659
-114
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* C# 12: The QL and data flow library now support primary constructors.

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,24 @@ class InstanceConstructor extends Constructor {
406406
override string getAPrimaryQlClass() { result = "InstanceConstructor" }
407407
}
408408

409+
/**
410+
* A primary constructor, for example `public class C(object o)` on line 1 in
411+
* ```csharp
412+
* public class C(object o) {
413+
* ...
414+
* }
415+
* ```
416+
*/
417+
class PrimaryConstructor extends Constructor {
418+
PrimaryConstructor() {
419+
not this.hasBody() and
420+
this.getDeclaringType().fromSource() and
421+
this.fromSource()
422+
}
423+
424+
override string getAPrimaryQlClass() { result = "PrimaryConstructor" }
425+
}
426+
409427
/**
410428
* A destructor, for example `~C() { }` on line 2 in
411429
*

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

Lines changed: 218 additions & 5 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,19 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos)
3738
arg.argumentOf(c, pos)
3839
}
3940

41+
/**
42+
* Gets a control flow node used for data flow purposes for the primary constructor
43+
* parameter access `pa`.
44+
*/
45+
private ControlFlow::Node getAPrimaryConstructorParameterCfn(ParameterAccess pa) {
46+
pa.getTarget().getCallable() instanceof PrimaryConstructor and
47+
(
48+
result = pa.(ParameterRead).getAControlFlowNode()
49+
or
50+
pa = any(AssignableDefinition def | result = def.getAControlFlowNode()).getTargetAccess()
51+
)
52+
}
53+
4054
abstract class NodeImpl extends Node {
4155
/** Do not call: use `getEnclosingCallable()` instead. */
4256
abstract DataFlowCallable getEnclosingCallableImpl();
@@ -124,9 +138,22 @@ private module ThisFlow {
124138
n.(InstanceParameterNode).getCallable() = cfn.(ControlFlow::Nodes::EntryNode).getCallable()
125139
or
126140
n.asExprAtNode(cfn) = any(Expr e | e instanceof ThisAccess or e instanceof BaseAccess)
141+
or
142+
n =
143+
any(InstanceParameterAccessNode pan |
144+
pan.getUnderlyingControlFlowNode() = cfn and pan.isPreUpdate()
145+
)
127146
}
128147

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

131158
private predicate thisRank(Node n, BasicBlock bb, int rankix) {
132159
exists(int i |
@@ -202,7 +229,7 @@ CIL::DataFlowNode asCilDataFlowNode(Node node) {
202229

203230
/** Provides predicates related to local data flow. */
204231
module LocalFlow {
205-
private class LocalExprStepConfiguration extends ControlFlowReachabilityConfiguration {
232+
class LocalExprStepConfiguration extends ControlFlowReachabilityConfiguration {
206233
LocalExprStepConfiguration() { this = "LocalExprStepConfiguration" }
207234

208235
override predicate candidate(
@@ -925,7 +952,17 @@ private module Cached {
925952
TParamsArgumentNode(ControlFlow::Node callCfn) {
926953
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
927954
} or
928-
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() }
955+
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() } or
956+
TInstanceParameterAccessNode(ControlFlow::Node cfn, boolean isPostUpdate) {
957+
exists(ParameterAccess pa | cfn = getAPrimaryConstructorParameterCfn(pa) |
958+
isPostUpdate = false
959+
or
960+
pa instanceof ParameterWrite and isPostUpdate = true
961+
)
962+
} or
963+
TPrimaryConstructorThisAccessNode(Parameter p, Boolean isPostUpdate) {
964+
p.getCallable() instanceof PrimaryConstructor
965+
}
929966

930967
/**
931968
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
@@ -961,14 +998,20 @@ private module Cached {
961998
TFieldContent(Field f) { f.isUnboundDeclaration() } or
962999
TPropertyContent(Property p) { p.isUnboundDeclaration() } or
9631000
TElementContent() or
964-
TSyntheticFieldContent(SyntheticField f)
1001+
TSyntheticFieldContent(SyntheticField f) or
1002+
TPrimaryConstructorParameterContent(Parameter p) {
1003+
p.getCallable() instanceof PrimaryConstructor
1004+
}
9651005

9661006
cached
9671007
newtype TContentApprox =
9681008
TFieldApproxContent(string firstChar) { firstChar = approximateFieldContent(_) } or
9691009
TPropertyApproxContent(string firstChar) { firstChar = approximatePropertyContent(_) } or
9701010
TElementApproxContent() or
971-
TSyntheticFieldApproxContent()
1011+
TSyntheticFieldApproxContent() or
1012+
TPrimaryConstructorParameterApproxContent(string firstChar) {
1013+
firstChar = approximatePrimaryConstructorParameterContent(_)
1014+
}
9721015

9731016
pragma[nomagic]
9741017
private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantGvnType t2) {
@@ -1037,6 +1080,10 @@ predicate nodeIsHidden(Node n) {
10371080
n.asExpr() = any(WithExpr we).getInitializer()
10381081
or
10391082
n instanceof FlowInsensitiveFieldNode
1083+
or
1084+
n instanceof InstanceParameterAccessNode
1085+
or
1086+
n instanceof PrimaryConstructorThisAccessNode
10401087
}
10411088

10421089
/** A CIL SSA definition, viewed as a node in a data flow graph. */
@@ -1745,6 +1792,100 @@ class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
17451792
override string toStringImpl() { result = this.getSummaryNode().toString() }
17461793
}
17471794

1795+
/**
1796+
* A data-flow node used to model reading and writing of primary constructor parameters.
1797+
* For example, in
1798+
* ```csharp
1799+
* public class C(object o)
1800+
* {
1801+
* public object GetParam() => o; // (1)
1802+
*
1803+
* public void SetParam(object value) => o = value; // (2)
1804+
* }
1805+
* ```
1806+
* the first access to `o` (1) is modeled as `this.o_backing_field` and
1807+
* the second access to `o` (2) is modeled as `this.o_backing_field = value`.
1808+
* Both models need a pre-update this node, and the latter need an additional post-update this access,
1809+
* all of which are represented by an `InstanceParameterAccessNode` node.
1810+
*/
1811+
class InstanceParameterAccessNode extends NodeImpl, TInstanceParameterAccessNode {
1812+
private ControlFlow::Node cfn;
1813+
private boolean isPostUpdate;
1814+
private Parameter p;
1815+
1816+
InstanceParameterAccessNode() {
1817+
this = TInstanceParameterAccessNode(cfn, isPostUpdate) and
1818+
exists(ParameterAccess pa | cfn = getAPrimaryConstructorParameterCfn(pa) and pa.getTarget() = p)
1819+
}
1820+
1821+
override DataFlowCallable getEnclosingCallableImpl() {
1822+
result.asCallable() = cfn.getEnclosingCallable()
1823+
}
1824+
1825+
override Type getTypeImpl() { result = cfn.getEnclosingCallable().getDeclaringType() }
1826+
1827+
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
1828+
1829+
override Location getLocationImpl() { result = cfn.getLocation() }
1830+
1831+
override string toStringImpl() { result = "this" }
1832+
1833+
/**
1834+
* Gets the underlying control flow node.
1835+
*/
1836+
ControlFlow::Node getUnderlyingControlFlowNode() { result = cfn }
1837+
1838+
/**
1839+
* Gets the primary constructor parameter that this is a this access to.
1840+
*/
1841+
Parameter getParameter() { result = p }
1842+
1843+
/**
1844+
* Holds if the this parameter access node corresponds to a pre-update node.
1845+
*/
1846+
predicate isPreUpdate() { isPostUpdate = false }
1847+
}
1848+
1849+
/**
1850+
* A data-flow node used to synthesize the body of a primary constructor.
1851+
*
1852+
* For example, in
1853+
* ```csharp
1854+
* public class C(object o) { }
1855+
* ```
1856+
* we synthesize the primary constructor for `C` as
1857+
* ```csharp
1858+
* public C(object o) => this.o_backing_field = o;
1859+
* ```
1860+
* The synthesized (pre/post-update) this access is represented an `PrimaryConstructorThisAccessNode` node.
1861+
*/
1862+
class PrimaryConstructorThisAccessNode extends NodeImpl, TPrimaryConstructorThisAccessNode {
1863+
private Parameter p;
1864+
private boolean isPostUpdate;
1865+
1866+
PrimaryConstructorThisAccessNode() { this = TPrimaryConstructorThisAccessNode(p, isPostUpdate) }
1867+
1868+
override DataFlowCallable getEnclosingCallableImpl() { result.asCallable() = p.getCallable() }
1869+
1870+
override Type getTypeImpl() { result = p.getCallable().getDeclaringType() }
1871+
1872+
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
1873+
1874+
override Location getLocationImpl() { result = p.getLocation() }
1875+
1876+
override string toStringImpl() { result = "this" }
1877+
1878+
/**
1879+
* Gets the primary constructor parameter that this is a this access to.
1880+
*/
1881+
Parameter getParameter() { result = p }
1882+
1883+
/**
1884+
* Holds if this is a this access for a primary constructor parameter write.
1885+
*/
1886+
predicate isPostUpdate() { isPostUpdate = true }
1887+
}
1888+
17481889
/** A field or a property. */
17491890
class FieldOrProperty extends Assignable, Modifiable {
17501891
FieldOrProperty() {
@@ -1881,6 +2022,18 @@ private PropertyContent getResultContent() {
18812022
result.getProperty() = any(SystemThreadingTasksTaskTClass c_).getResultProperty()
18822023
}
18832024

2025+
private predicate primaryConstructorParameterStore(
2026+
SsaDefinitionExtNode node1, PrimaryConstructorParameterContent c, Node node2
2027+
) {
2028+
exists(Ssa::ExplicitDefinition def, ControlFlow::Node cfn, Parameter p |
2029+
def = node1.getDefinitionExt() and
2030+
p = def.getSourceVariable().getAssignable() and
2031+
cfn = def.getControlFlowNode() and
2032+
node2 = TInstanceParameterAccessNode(cfn, true) and
2033+
c.getParameter() = p
2034+
)
2035+
}
2036+
18842037
/**
18852038
* Holds if data can flow from `node1` to `node2` via an assignment to
18862039
* content `c`.
@@ -1918,6 +2071,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
19182071
c = getResultContent()
19192072
)
19202073
or
2074+
primaryConstructorParameterStore(node1, c, node2)
2075+
or
2076+
exists(Parameter p |
2077+
node1 = TExplicitParameterNode(p) and
2078+
node2 = TPrimaryConstructorThisAccessNode(p, true) and
2079+
c.(PrimaryConstructorParameterContent).getParameter() = p
2080+
)
2081+
or
19212082
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
19222083
node2.(FlowSummaryNode).getSummaryNode())
19232084
}
@@ -2010,6 +2171,14 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
20102171
node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and
20112172
c = getResultContent()
20122173
or
2174+
node1 =
2175+
any(InstanceParameterAccessNode n |
2176+
n.getUnderlyingControlFlowNode() = node2.(ExprNode).getControlFlowNode() and
2177+
n.getParameter() = c.(PrimaryConstructorParameterContent).getParameter() and
2178+
n.isPreUpdate()
2179+
) and
2180+
node2.asExpr() instanceof ParameterRead
2181+
or
20132182
// node1 = (..., node2, ...)
20142183
// node1.ItemX flows to node2
20152184
exists(TupleExpr te, int i, Expr item |
@@ -2072,6 +2241,8 @@ predicate clearsContent(Node n, ContentSet c) {
20722241
c.(FieldContent).getField() = f.getUnboundDeclaration() and
20732242
not f.isRef()
20742243
)
2244+
or
2245+
n = any(PostUpdateNode n1 | primaryConstructorParameterStore(_, c, n1)).getPreUpdateNode()
20752246
}
20762247

20772248
/**
@@ -2361,6 +2532,32 @@ module PostUpdateNodes {
23612532

23622533
override Node getPreUpdateNode() { result.(FlowSummaryNode).getSummaryNode() = preUpdateNode }
23632534
}
2535+
2536+
private class InstanceParameterAccessPostUpdateNode extends PostUpdateNode,
2537+
InstanceParameterAccessNode
2538+
{
2539+
private ControlFlow::Node cfn;
2540+
2541+
InstanceParameterAccessPostUpdateNode() { this = TInstanceParameterAccessNode(cfn, true) }
2542+
2543+
override Node getPreUpdateNode() { result = TInstanceParameterAccessNode(cfn, false) }
2544+
2545+
override string toStringImpl() { result = "[post] this" }
2546+
}
2547+
2548+
private class PrimaryConstructorThisAccessPostUpdateNode extends PostUpdateNode,
2549+
PrimaryConstructorThisAccessNode
2550+
{
2551+
private Parameter p;
2552+
2553+
PrimaryConstructorThisAccessPostUpdateNode() {
2554+
this = TPrimaryConstructorThisAccessNode(p, true)
2555+
}
2556+
2557+
override Node getPreUpdateNode() { result = TPrimaryConstructorThisAccessNode(p, false) }
2558+
2559+
override string toStringImpl() { result = "[post] this" }
2560+
}
23642561
}
23652562

23662563
private import PostUpdateNodes
@@ -2537,6 +2734,11 @@ class ContentApprox extends TContentApprox {
25372734
this = TElementApproxContent() and result = "element"
25382735
or
25392736
this = TSyntheticFieldApproxContent() and result = "approximated synthetic field"
2737+
or
2738+
exists(string firstChar |
2739+
this = TPrimaryConstructorParameterApproxContent(firstChar) and
2740+
result = "approximated parameter field " + firstChar
2741+
)
25402742
}
25412743
}
25422744

@@ -2550,6 +2752,14 @@ private string approximatePropertyContent(PropertyContent pc) {
25502752
result = pc.getProperty().getName().prefix(1)
25512753
}
25522754

2755+
/**
2756+
* Gets a string for approximating the name of a synthetic field corresponding
2757+
* to a primary constructor parameter.
2758+
*/
2759+
private string approximatePrimaryConstructorParameterContent(PrimaryConstructorParameterContent pc) {
2760+
result = pc.getParameter().getName().prefix(1)
2761+
}
2762+
25532763
/** Gets an approximated value for content `c`. */
25542764
pragma[nomagic]
25552765
ContentApprox getContentApprox(Content c) {
@@ -2560,6 +2770,9 @@ ContentApprox getContentApprox(Content c) {
25602770
c instanceof ElementContent and result = TElementApproxContent()
25612771
or
25622772
c instanceof SyntheticFieldContent and result = TSyntheticFieldApproxContent()
2773+
or
2774+
result =
2775+
TPrimaryConstructorParameterApproxContent(approximatePrimaryConstructorParameterContent(c))
25632776
}
25642777

25652778
/**

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 " + 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)