Skip to content

Commit 87a8f34

Browse files
static function detection and self param for synthetic constructor methods
- simple call to static function detection - self param for synthetic constructor methods
1 parent 2f8b965 commit 87a8f34

File tree

7 files changed

+161
-34
lines changed

7 files changed

+161
-34
lines changed

joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForDeclSyntaxCreator.scala

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
8484
}
8585

8686
private def isInitializedMember(node: DeclSyntax): Boolean = node match {
87-
case v: VariableDeclSyntax => v.bindings.children.exists(c => c.initializer.isDefined || c.accessorBlock.isDefined)
87+
case v: VariableDeclSyntax =>
88+
v.bindings.children.exists(c =>
89+
c.initializer.isDefined || c.accessorBlock.exists(_.accessors.isInstanceOf[CodeBlockItemListSyntax])
90+
)
8891
case e: EnumCaseDeclSyntax => e.elements.children.exists(c => c.rawValue.isDefined)
8992
case _ => false
9093
}
@@ -149,26 +152,26 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
149152
methodNode(node, constructorName, constructorName, methodFullName, Some(signature), parserResult.filename)
150153
val modifiers = Seq(NewModifier().modifierType(ModifierTypes.CONSTRUCTOR))
151154

152-
methodAstParentStack.push(methodNode_)
153155
val methodReturnNode_ = methodReturnNode(node, typeDeclNode.fullName)
154156

157+
val blockNode = NewBlock()
158+
methodAstParentStack.push(methodNode_)
159+
scope.pushNewMethodScope(methodFullName, constructorName, blockNode, typeRefIdStack.headOption)
160+
localAstParentStack.push(blockNode)
161+
val methodBlockContentAsts = methodBlockContent.map(m => astForDeclMember(m, typeDeclNode))
162+
val parameterNode =
163+
parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, typeDeclNode.fullName)
164+
scope.addVariable("self", parameterNode, typeDeclNode.fullName, VariableScopeManager.ScopeType.MethodScope)
165+
localAstParentStack.pop()
155166
methodAstParentStack.pop()
156-
157-
val mAst = if (methodBlockContent.isEmpty) {
158-
methodStubAst(methodNode_, Seq.empty, methodReturnNode_, modifiers)
159-
} else {
160-
val blockNode = NewBlock()
161-
localAstParentStack.push(blockNode)
162-
val methodBlockContentAsts = methodBlockContent.map(m => astForDeclMember(m, typeDeclNode))
163-
localAstParentStack.pop()
164-
methodAstWithAnnotations(
165-
methodNode_,
166-
Seq.empty,
167-
blockAst(blockNode, methodBlockContentAsts),
168-
methodReturnNode_,
169-
modifiers
170-
)
171-
}
167+
scope.popScope()
168+
val mAst = methodAstWithAnnotations(
169+
methodNode_,
170+
Seq(Ast(parameterNode)),
171+
blockAst(blockNode, methodBlockContentAsts),
172+
methodReturnNode_,
173+
modifiers
174+
)
172175

173176
val typeDeclAst = createFunctionTypeAndTypeDecl(methodNode_)
174177
Ast.storeInDiffGraph(mAst.merge(typeDeclAst), diffGraph)

joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.*
44
import io.joern.swiftsrc2cpg.passes.GlobalBuiltins
55
import io.joern.x2cpg
66
import io.joern.x2cpg.datastructures.Stack.*
7+
import io.joern.x2cpg.datastructures.VariableScopeManager
78
import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines
89
import io.joern.x2cpg.{Ast, ValidationMode}
910
import io.shiftleft.codepropertygraph.generated.*
@@ -230,24 +231,75 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
230231
callAst(callNode_, args, Option(baseAst))
231232
}
232233

234+
private def constructorInvocationBlockAst(expr: FunctionCallExprSyntax): Ast = {
235+
// get call is safe as this function is guarded by isRefToConstructor
236+
val tpe = fullnameProvider.typeFullname(expr).get
237+
registerType(tpe)
238+
239+
val callExprCode = code(expr)
240+
val blockNode_ = blockNode(expr, callExprCode, tpe)
241+
scope.pushNewBlockScope(blockNode_)
242+
243+
val tmpNodeName = scopeLocalUniqueName("tmp")
244+
val tmpNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe)
245+
val localTmpNode = localNode(expr, tmpNodeName, tmpNodeName, tpe)
246+
scope.addVariable(tmpNodeName, localTmpNode, tpe, VariableScopeManager.ScopeType.BlockScope)
247+
248+
val allocOp = Operators.alloc
249+
val allocCallNode = callNode(expr, allocOp, allocOp, allocOp, DispatchTypes.STATIC_DISPATCH)
250+
val assignmentCallOp = Operators.assignment
251+
val assignmentCallNode =
252+
callNode(expr, s"$tmpNodeName = $allocOp", assignmentCallOp, assignmentCallOp, DispatchTypes.STATIC_DISPATCH)
253+
val assignmentAst = callAst(assignmentCallNode, List(Ast(tmpNode), Ast(allocCallNode)))
254+
255+
val baseNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe)
256+
scope.addVariableReference(tmpNodeName, baseNode, tpe, EvaluationStrategies.BY_SHARING)
257+
258+
val constructorCallNode = callNode(
259+
expr,
260+
callExprCode,
261+
"init",
262+
x2cpg.Defines.UnresolvedNamespace,
263+
DispatchTypes.STATIC_DISPATCH,
264+
Some(x2cpg.Defines.UnresolvedSignature),
265+
Some(Defines.Void)
266+
)
267+
setFullNameInfoForCall(expr, constructorCallNode)
268+
269+
val trailingClosureAsts = expr.trailingClosure.toList.map(astForNode)
270+
val additionalTrailingClosuresAsts = expr.additionalTrailingClosures.children.map(c => astForNode(c.closure))
271+
val args = expr.arguments.children.map(astForNode) ++ trailingClosureAsts ++ additionalTrailingClosuresAsts
272+
273+
val constructorCallAst = callAst(constructorCallNode, args, base = Some(Ast(baseNode)))
274+
275+
val retNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe)
276+
scope.addVariableReference(tmpNodeName, retNode, tpe, EvaluationStrategies.BY_SHARING)
277+
val retAst = Ast(retNode)
278+
279+
scope.popScope()
280+
Ast(blockNode_).withChildren(Seq(assignmentAst, constructorCallAst, retAst))
281+
}
282+
233283
private def astForFunctionCallExprSyntax(node: FunctionCallExprSyntax): Ast = {
234284
val callee = node.calledExpression
235285
val calleeCode = code(callee)
236286
if (GlobalBuiltins.builtins.contains(calleeCode)) {
237287
createBuiltinStaticCall(node, callee, calleeCode)
238288
} else {
239-
// TODO: extend the GsonTypeInfoReader to query for information whether
240-
// the call is a call to a static function and generate a proper static call here
241289
callee match {
242290
case m: MemberAccessExprSyntax if m.base.isEmpty || code(m.base.get) == "self" =>
243291
// referencing implicit self
244292
val selfTpe = typeForSelfExpression()
245293
val selfNode = identifierNode(node, "self", "self", selfTpe)
246294
scope.addVariableReference("self", selfNode, selfTpe, EvaluationStrategies.BY_REFERENCE)
247295
handleCallNodeArgs(node, Ast(selfNode), code(m.declName.baseName))
296+
case m: MemberAccessExprSyntax if isRefToStaticFunction(calleeCode) =>
297+
createBuiltinStaticCall(node, callee, calleeCode)
248298
case m: MemberAccessExprSyntax =>
249299
val memberCode = code(m.declName)
250300
handleCallNodeArgs(node, astForNode(m.base.get), memberCode)
301+
case other if isRefToConstructor(node, other) =>
302+
constructorInvocationBlockAst(node)
251303
case other if isRefToClosure(node, other) =>
252304
astForClosureCall(node)
253305
case declReferenceExprSyntax: DeclReferenceExprSyntax if code(declReferenceExprSyntax) != "self" =>
@@ -271,7 +323,6 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
271323

272324
val trailingClosureAsts = expr.trailingClosure.toList.map(astForNode)
273325
val additionalTrailingClosuresAsts = expr.additionalTrailingClosures.children.map(c => astForNode(c.closure))
274-
275326
val args = expr.arguments.children.map(astForNode) ++ trailingClosureAsts ++ additionalTrailingClosuresAsts
276327

277328
val callExprCode = code(expr)
@@ -290,19 +341,42 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
290341
private def isRefToClosure(func: FunctionCallExprSyntax, node: ExprSyntax): Boolean = {
291342
if (!config.swiftBuild) {
292343
// Early exit; without types from the compiler we will be unable to identify closure calls anyway.
293-
// This saves us the typeFullname lookup below.
344+
// This saves us the fullnameProvider lookups below.
294345
return false
295346
}
296347
node match {
297348
case refExpr: DeclReferenceExprSyntax
298-
if refExpr.baseName.isInstanceOf[identifier] && refExpr.argumentNames.isEmpty &&
349+
if refExpr.baseName.isInstanceOf[identifier] &&
299350
fullnameProvider.declFullname(func).isEmpty &&
300351
fullnameProvider.typeFullname(refExpr).exists(_.startsWith(s"${Defines.Function}<")) =>
301352
true
302353
case _ => false
303354
}
304355
}
305356

357+
private def isRefToStaticFunction(calleeCode: String): Boolean = {
358+
// TODO: extend the GsonTypeInfoReader to query for information whether the call is a call to a static function
359+
calleeCode.headOption.exists(_.isUpper) && !calleeCode.contains("(") && !calleeCode.contains(")")
360+
}
361+
362+
private def isRefToConstructor(func: FunctionCallExprSyntax, node: ExprSyntax): Boolean = {
363+
if (!config.swiftBuild) {
364+
// Early exit; without types from the compiler we will be unable to identify constructor calls anyway.
365+
// This saves us the fullnameProvider lookups below.
366+
return false
367+
}
368+
node match {
369+
case refExpr: DeclReferenceExprSyntax
370+
if refExpr.baseName.isInstanceOf[identifier] &&
371+
fullnameProvider.typeFullname(func).nonEmpty &&
372+
fullnameProvider.declFullname(func).exists { fullName =>
373+
fullName.contains(".init(") && fullName.contains(")->")
374+
} =>
375+
true
376+
case _ => false
377+
}
378+
}
379+
306380
private def astForGenericSpecializationExprSyntax(node: GenericSpecializationExprSyntax): Ast = {
307381
astForNode(node.expression)
308382
}

joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/GlobalBuiltins.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object GlobalBuiltins {
2121
"Set", // Creates a set from a sequence
2222

2323
// Types and type casting
24-
"type", // Returns the dynamic type of a value
24+
"type", // Returns the dynamic type of some value
2525
"numericCast", // Returns the given integer as the equivalent value in a different integer type
2626
"unsafeDowncast", // Returns the given instance cast unconditionally to the specified type
2727
"unsafeBitCast", // Returns the bits of the given instance, interpreted as having the specified type
@@ -89,7 +89,7 @@ object GlobalBuiltins {
8989

9090
// C Interoperability
9191
"withVaList", // Invokes the given closure with a C va_list argument derived from the given array of arguments
92-
"getVaList", // Returns a CVaListPointer that is backed by autoreleased storage, built from the given array of arguments
92+
"getVaList", // Returns a CVaListPointer that is backed by auto-released storage, built from the given array of arguments
9393

9494
// Miscellaneous Functions
9595
"swap", // Exchanges the values of two variables

joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ActorTests.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@ class ActorTests extends SwiftCompilerSrc2CpgSuite {
2222
myActor.boundMethod.fullName.l shouldBe List(
2323
"Sources/main.swift:<global>.MyActor1.init:()->Sources/main.swift:<global>.MyActor1"
2424
)
25+
val List(constructorParam) = myActor.boundMethod.parameter.l
26+
constructorParam.name shouldBe "self"
27+
constructorParam.typeFullName shouldBe "Sources/main.swift:<global>.MyActor1"
2528

2629
val List(myActorSwiftc) = compilerCpg.typeDecl.nameExact("MyActor1").l
2730
myActorSwiftc.fullName shouldBe "SwiftTest.MyActor1"
2831
myActorSwiftc.member shouldBe empty
2932
myActorSwiftc.boundMethod.fullName.l shouldBe List("SwiftTest.MyActor1.init:()->SwiftTest.MyActor1")
33+
val List(constructorParamSwiftc) = myActorSwiftc.boundMethod.parameter.l
34+
constructorParamSwiftc.name shouldBe "self"
35+
constructorParamSwiftc.typeFullName shouldBe "SwiftTest.MyActor1"
3036
}
3137

3238
"testActor2" in {

joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/CallTests.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.joern.swiftsrc2cpg.passes.ast
22

33
import io.joern.swiftsrc2cpg.testfixtures.SwiftCompilerSrc2CpgSuite
44
import io.joern.x2cpg
5+
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}
56
import io.shiftleft.codepropertygraph.generated.nodes.*
67
import io.shiftleft.semanticcpg.language.*
78

@@ -75,6 +76,49 @@ class CallTests extends SwiftCompilerSrc2CpgSuite {
7576
barCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo"
7677
}
7778

79+
"be correct for simple call to constructor with compiler support" in {
80+
val testCode =
81+
"""
82+
|class Foo {}
83+
|
84+
|func main() {
85+
| Foo()
86+
|}
87+
|""".stripMargin
88+
val cpg = codeWithSwiftSetup(testCode)
89+
90+
val List(constructorCallBlock) = cpg.block.codeExact("Foo()").l
91+
val List(tmpAssignment) = constructorCallBlock.astChildren.isCall.isAssignment.l
92+
tmpAssignment.code shouldBe s"<tmp>0 = ${Operators.alloc}"
93+
tmpAssignment.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo")
94+
tmpAssignment.argument.isCall.name.l shouldBe List(Operators.alloc)
95+
val List(constructorCall) = constructorCallBlock.astChildren.isCall.nameExact("init").l
96+
constructorCall.methodFullName shouldBe "SwiftTest.Foo.init:()->SwiftTest.Foo"
97+
constructorCall.typeFullName shouldBe "SwiftTest.Foo"
98+
constructorCall.signature shouldBe "()->SwiftTest.Foo"
99+
constructorCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
100+
constructorCall.argument.isIdentifier.name.l shouldBe List("<tmp>0")
101+
constructorCall.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo")
102+
val List(returnId) = constructorCallBlock.astChildren.isIdentifier.nameExact("<tmp>0").l
103+
returnId.typeFullName shouldBe "SwiftTest.Foo"
104+
}
105+
106+
"be correct for simple call to static function" in {
107+
// TODO: extend the GsonTypeInfoReader to query for information whether the call is a call to a static function
108+
val testCode =
109+
"""
110+
|func main() {
111+
| Foo.staticFunc()
112+
|}
113+
|""".stripMargin
114+
val cpg = code(testCode)
115+
116+
val List(staticFuncCall) = cpg.call.nameExact("staticFunc").l
117+
staticFuncCall.methodFullName shouldBe "Foo.staticFunc"
118+
staticFuncCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
119+
staticFuncCall.argument shouldBe empty
120+
}
121+
78122
}
79123

80124
}

joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ClosureWithCompilerTests.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,9 @@ class ClosureWithCompilerTests extends SwiftCompilerSrc2CpgSuite {
185185
|Foo().main()
186186
|""".stripMargin
187187

188-
val cpg = codeWithSwiftSetup(testCode)
189-
val compareClosureFullName = "Sources/main.swift:<global>.Foo.<lambda>0:(Swift.String,Swift.String)->Swift.Bool"
188+
val cpg = codeWithSwiftSetup(testCode)
189+
val compareClosureFullName =
190+
"Sources/main.swift:<global>.Foo.init.<lambda>0:(Swift.String,Swift.String)->Swift.Bool"
190191

191192
cpg.local.nameExact("compare") shouldBe empty
192193

@@ -235,12 +236,11 @@ class ClosureWithCompilerTests extends SwiftCompilerSrc2CpgSuite {
235236
mainCall.methodFullName shouldBe "SwiftTest.Foo.main:()->()"
236237
mainCall.signature shouldBe "()->()"
237238
mainCall.typeFullName shouldBe "()"
238-
val List(mainCallReceiver) = mainCall.receiver.isCall.l
239-
mainCallReceiver.name shouldBe "Foo"
240-
mainCallReceiver.methodFullName shouldBe "SwiftTest.Foo.init:()->SwiftTest.Foo"
241-
mainCallReceiver.signature shouldBe "()->SwiftTest.Foo"
242-
mainCallReceiver.typeFullName shouldBe "SwiftTest.Foo"
243-
mainCallReceiver._methodViaCallOut.l shouldBe List(fooConstructor)
239+
val List(fooConstructorCall) = mainCall.receiver.isBlock.astChildren.isCall.nameExact("init").l
240+
fooConstructorCall.methodFullName shouldBe "SwiftTest.Foo.init:()->SwiftTest.Foo"
241+
fooConstructorCall.signature shouldBe "()->SwiftTest.Foo"
242+
fooConstructorCall.typeFullName shouldBe "SwiftTest.Foo"
243+
fooConstructorCall._methodViaCallOut.l shouldBe List(fooConstructor)
244244
}
245245

246246
"create type decls and bindings correctly (closure as function parameter)" in {

joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerFullnameTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SwiftCompilerFullnameTests extends SwiftCompilerSrc2CpgSuite {
3333
mainTypeDecl.fullName shouldBe "SwiftTest.Main"
3434
val List(mainConstructor) = mainTypeDecl.ast.isMethod.isConstructor.l
3535
mainConstructor.fullName shouldBe "SwiftTest.Main.init:()->SwiftTest.Main"
36-
val List(mainConstructorCall) = cpg.call.nameExact("Main").l
36+
val List(mainConstructorCall) = cpg.call.nameExact("init").l
3737
mainConstructorCall.methodFullName shouldBe "SwiftTest.Main.init:()->SwiftTest.Main"
3838

3939
mainConstructor.fullName shouldBe mainConstructorCall.methodFullName

0 commit comments

Comments
 (0)