@@ -21,6 +21,7 @@ private import semmle.code.csharp.frameworks.system.threading.Tasks
2121private import semmle.code.cil.Ssa:: Ssa as CilSsa
2222private import semmle.code.cil.internal.SsaImpl as CilSsaImpl
2323private import codeql.util.Unit
24+ private import codeql.util.Boolean
2425
2526/** Gets the callable in which this node occurs. */
2627DataFlowCallable 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+
4054abstract 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. */
204231module 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. */
17491890class 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
23662563private 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`. */
25542764pragma [ nomagic]
25552765ContentApprox 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/**
0 commit comments