Skip to content

Commit 91f08be

Browse files
authored
[c#] support setter assignments whose lhs is an identifier (#5296)
1 parent ec17221 commit 91f08be

File tree

2 files changed

+280
-60
lines changed

2 files changed

+280
-60
lines changed

joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForExpressionsCreator.scala

Lines changed: 145 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
5454
case SimpleMemberAccessExpression =>
5555
val base = createDotNetNodeInfo(expr.json(ParserKeys.Expression))
5656
Some(nodeTypeFullName(base))
57+
case IdentifierName =>
58+
scope.surroundingTypeDeclFullName
5759
case _ =>
5860
None
5961
}
@@ -78,89 +80,167 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
7880
case _ => None
7981
}
8082

81-
private def astForSetterAssignmentExpression(
82-
assignExpr: DotNetNodeInfo,
83+
/** Mainly to abstract the lowering of +=, *=, etc. assignments when the LHS is a property. Takes care of building the
84+
* RHS appropriately, e.g. by expanding `P += RHS` into `set_P(get_P() + RHS)`, etc.
85+
* @param expr
86+
* the full assignment expression, for `code`, `line`, etc.
87+
* @param assignOp
88+
* the assignment operator, cf. [[Operators]]
89+
* @param setterInfo
90+
* the setter meta-data, cf. [[tryResolveSetterInvocation]]
91+
*/
92+
private def lowerSetterAssignmentRhs(
93+
expr: DotNetNodeInfo,
94+
assignOp: String,
8395
setterInfo: (CSharpMethod, String),
84-
lhs: DotNetNodeInfo,
85-
opName: String,
86-
rhsNode: DotNetNodeInfo
96+
receiver: Option[Ast],
97+
rhs: DotNetNodeInfo
8798
): Seq[Ast] = {
8899
val (setterMethod, setterBaseType) = setterInfo
100+
val propertyName = setterMethod.name.stripPrefix("set_")
101+
val originalRhs = astForOperand(rhs)
89102

90-
lhs.node match {
91-
case SimpleMemberAccessExpression =>
92-
val baseNode = astForNode(createDotNetNodeInfo(lhs.json(ParserKeys.Expression)))
93-
val receiver = if setterMethod.isStatic then None else baseNode.headOption
94-
val propertyName = setterMethod.name.stripPrefix("set_")
95-
val originalRhs = astForOperand(rhsNode)
96-
97-
val rhsAst = opName match {
98-
case Operators.assignment => originalRhs
99-
case _ =>
100-
scope.tryResolveGetterInvocation(propertyName, Some(setterBaseType)) match {
101-
// Shouldn't happen, provided it is valid code. At any rate, log and emit the RHS verbatim.
103+
assignOp match {
104+
case Operators.assignment => originalRhs
105+
case _ =>
106+
scope.tryResolveGetterInvocation(propertyName, Some(setterBaseType)) match {
107+
// Shouldn't happen, provided it is valid code. At any rate, log and emit the RHS verbatim.
108+
case None =>
109+
logger.warn(s"Couldn't find matching getter for $propertyName in ${code(expr)}")
110+
originalRhs
111+
case Some(getterMethod) =>
112+
stripAssignmentFromOperator(assignOp) match {
102113
case None =>
103-
logger.warn(s"Couldn't find matching getter for $propertyName in ${code(assignExpr)}")
114+
logger.warn(s"Unrecognized assignment in ${code(expr)}")
104115
originalRhs
105-
case Some(getterMethod) =>
106-
stripAssignmentFromOperator(opName) match {
107-
case None =>
108-
logger.warn(s"Unrecognized assignment in ${code(assignExpr)}")
109-
originalRhs
110-
case Some(opName) =>
111-
val getterInvocation = createInvocationAst(
112-
assignExpr,
113-
getterMethod.name,
114-
Nil,
115-
receiver,
116-
Some(getterMethod),
117-
Some(setterBaseType)
118-
)
119-
val operatorCall = newOperatorCallNode(
120-
opName,
121-
code(assignExpr),
122-
Some(setterMethod.returnType),
123-
line(assignExpr),
124-
column(assignExpr)
125-
)
126-
callAst(operatorCall, getterInvocation +: originalRhs, None, None) :: Nil
127-
}
116+
case Some(opName) =>
117+
val getterInvocation =
118+
createInvocationAst(expr, getterMethod.name, Nil, receiver, Some(getterMethod), Some(setterBaseType))
119+
val operatorCall =
120+
newOperatorCallNode(opName, code(expr), Some(setterMethod.returnType), line(expr), column(expr))
121+
callAst(operatorCall, getterInvocation +: originalRhs, None, None) :: Nil
128122
}
129123
}
124+
}
125+
}
126+
127+
/** Lowers assignments such as `x.P = RHS` and `x.P += RHS` with `P` denoting a setter property into calls
128+
* `x.set_P(RHS)` and `x.set_P(x.get_P() + RHS)`.
129+
* @param assignExpr
130+
* the full assignment expression, for `code`, `line` properties
131+
* @param assignOp
132+
* the final assignment operator name, cf. [[Operators]]
133+
* @param setterInfo
134+
* the setter meta-data, cf. [[tryResolveSetterInvocation]]
135+
*/
136+
private def astForMemberAccessSetterAssignment(
137+
assignExpr: DotNetNodeInfo,
138+
lhs: DotNetNodeInfo,
139+
assignOp: String,
140+
rhs: DotNetNodeInfo,
141+
setterInfo: (CSharpMethod, String)
142+
): Seq[Ast] = {
143+
val (setterMethod, setterBaseType) = setterInfo
144+
val receiver = if (setterMethod.isStatic) {
145+
None
146+
} else {
147+
val baseNode = createDotNetNodeInfo(lhs.json(ParserKeys.Expression))
148+
astForNode(baseNode).headOption
149+
}
150+
val rhsAst = lowerSetterAssignmentRhs(assignExpr, assignOp, setterInfo, receiver, rhs)
151+
152+
createInvocationAst(
153+
assignExpr,
154+
setterMethod.name,
155+
rhsAst,
156+
receiver,
157+
Some(setterMethod),
158+
Some(setterBaseType)
159+
) :: Nil
160+
}
161+
162+
/** Lowers assignments such as `P = RHS` and `P += RHS` with `P` an identifier denoting a setter property into calls
163+
* `set_P(RHS)` and `set_P(get_P() + RHS)`, respectively.
164+
* @param assignExpr
165+
* the full assignment expression, for `code`, `line` properties.
166+
* @param assignOp
167+
* the final assignment operator name, cf. [[Operators]]
168+
* @param setterInfo
169+
* the setter meta-data, cf. [[tryResolveSetterInvocation]]
170+
*/
171+
private def astForIdentifierSetterAssignment(
172+
assignExpr: DotNetNodeInfo,
173+
lhs: DotNetNodeInfo,
174+
assignOp: String,
175+
rhs: DotNetNodeInfo,
176+
setterInfo: (CSharpMethod, String)
177+
): Seq[Ast] = {
178+
val (setterMethod, setterBaseType) = setterInfo
179+
val receiver = Option.when(!setterMethod.isStatic)(astForThisReceiver(lhs, scope.surroundingTypeDeclFullName))
180+
val rhsAst = lowerSetterAssignmentRhs(assignExpr, assignOp, setterInfo, receiver, rhs)
181+
182+
createInvocationAst(
183+
assignExpr,
184+
setterMethod.name,
185+
rhsAst,
186+
receiver,
187+
Some(setterMethod),
188+
Some(setterBaseType)
189+
) :: Nil
190+
}
130191

131-
createInvocationAst(
132-
assignExpr,
133-
setterMethod.name,
134-
rhsAst,
135-
receiver,
136-
Some(setterMethod),
137-
Some(setterBaseType)
138-
) :: Nil
192+
/** Lowers assignments such as `x.P = RHS` and `P += RHS` where `P` denotes a setter property into a call
193+
* `x.set_P(RHS)` and `set_P(get_P() + RHS)`, respectively.
194+
*
195+
* @param assignExpr
196+
* the full assignment expr, for `code`, `line` properties
197+
* @param setterInfo
198+
* the setter meta-data, cf. [[tryResolveSetterInvocation]]
199+
* @param assignOp
200+
* the final assignment operator name, cf. [[Operators]]
201+
*/
202+
private def astForSetterAssignmentExpression(
203+
assignExpr: DotNetNodeInfo,
204+
setterInfo: (CSharpMethod, String),
205+
lhs: DotNetNodeInfo,
206+
assignOp: String,
207+
rhs: DotNetNodeInfo
208+
): Seq[Ast] = {
209+
lhs.node match {
210+
case SimpleMemberAccessExpression =>
211+
astForMemberAccessSetterAssignment(assignExpr, lhs, assignOp, rhs, setterInfo)
212+
case IdentifierName => astForIdentifierSetterAssignment(assignExpr, lhs, assignOp, rhs, setterInfo)
139213
case _ =>
140214
logger.warn(s"Unsupported setter assignment: ${code(assignExpr)}")
141215
Nil
142216
}
143217
}
144218

219+
/** Lowers arbitrary assignment `LHS = RHS`, `LHS += RHS`, etc. expressions.
220+
* @param assignExpr
221+
* the full assignment, for `code`, `line` properties
222+
* @param assignOp
223+
* the final assignment operator name, cf. [[Operators]]
224+
*/
145225
private def astForAssignmentExpression(
146226
assignExpr: DotNetNodeInfo,
147-
lhsNode: DotNetNodeInfo,
148-
opName: String,
149-
rhsNode: DotNetNodeInfo
227+
lhs: DotNetNodeInfo,
228+
assignOp: String,
229+
rhs: DotNetNodeInfo
150230
): Seq[Ast] = {
151-
tryResolveSetterInvocation(lhsNode) match {
152-
case Some(setterInfo) => astForSetterAssignmentExpression(assignExpr, setterInfo, lhsNode, opName, rhsNode)
153-
case None => astForRegularAssignmentExpression(assignExpr, lhsNode, opName, rhsNode)
231+
tryResolveSetterInvocation(lhs) match {
232+
case Some(setterInfo) => astForSetterAssignmentExpression(assignExpr, setterInfo, lhs, assignOp, rhs)
233+
case None => astForRegularAssignmentExpression(assignExpr, lhs, assignOp, rhs)
154234
}
155235
}
156236

157237
private def astForRegularAssignmentExpression(
158238
assignExpr: DotNetNodeInfo,
159239
lhs: DotNetNodeInfo,
160-
opName: String,
240+
assignOp: String,
161241
rhs: DotNetNodeInfo
162242
): Seq[Ast] = {
163-
astForRegularBinaryExpression(assignExpr, lhs, opName, rhs)
243+
astForRegularBinaryExpression(assignExpr, lhs, assignOp, rhs)
164244
}
165245

166246
private def astForParenthesizedExpression(parenExpr: DotNetNodeInfo): Seq[Ast] = {
@@ -252,13 +332,18 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
252332
}
253333
}
254334

335+
/** @param binaryExpr
336+
* the full binary expression, for `code`, `line`, etc.
337+
* @param operatorName
338+
* the final operator name, cf. [[Operators]]
339+
*/
255340
private def astForRegularBinaryExpression(
256341
binaryExpr: DotNetNodeInfo,
257-
lhsNode: DotNetNodeInfo,
342+
lhs: DotNetNodeInfo,
258343
operatorName: String,
259-
rhsNode: DotNetNodeInfo
344+
rhs: DotNetNodeInfo
260345
): Seq[Ast] = {
261-
val args = astForOperand(lhsNode) ++: astForOperand(rhsNode)
346+
val args = astForOperand(lhs) ++: astForOperand(rhs)
262347
val typeFullName = fixedTypeOperators.get(operatorName).orElse(Some(getTypeFullNameFromAstNode(args)))
263348
val callNode = operatorCallNode(binaryExpr, operatorName, typeFullName)
264349
callAst(callNode, args) :: Nil

0 commit comments

Comments
 (0)