diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreator.scala index df4f39d0fb3c..7e9e7c8a8e47 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreator.scala @@ -36,14 +36,12 @@ class AstCreator( protected val logger: Logger = LoggerFactory.getLogger(classOf[AstCreator]) - protected val scope = new SwiftVariableScopeManager() - protected val fullnameProvider = new FullnameProvider(typeMap) - - protected val methodAstParentStack = new Stack[NewNode]() - protected val typeRefIdStack = new Stack[NewTypeRef] - protected val dynamicInstanceTypeStack = new Stack[String] - protected val localAstParentStack = new Stack[NewBlock]() - protected val scopeLocalUniqueNames = mutable.HashMap.empty[String, Int] + protected val scope = new SwiftVariableScopeManager() + protected val fullnameProvider = new FullnameProvider(typeMap) + protected val methodAstParentStack = new Stack[NewNode]() + protected val typeRefIdStack = new Stack[NewTypeRef] + protected val localAstParentStack = new Stack[NewBlock]() + protected val scopeLocalUniqueNames = mutable.HashMap.empty[String, Int] protected lazy val definedSymbols: Map[String, String] = { config.defines.map { @@ -75,6 +73,7 @@ class AstCreator( val fakeGlobalMethod = methodNode(ast, name, name, fullName, None, path, Option(NodeTypes.TYPE_DECL), Option(fullName)) methodAstParentStack.push(fakeGlobalMethod) + scope.pushNewTypeDeclScope(fullName) scope.pushNewMethodScope(fullName, name, fakeGlobalMethod, None) val sourceFileAst = astForNode(ast) val methodReturn = methodReturnNode(ast, Defines.Any) @@ -86,7 +85,7 @@ class AstCreator( } protected def astForNode(node: SwiftNode): Ast = node match { - case func: FunctionDeclLike => astForFunctionLike(func) + case func: FunctionDeclLike => astForFunctionLike(func, List.empty, None) case swiftToken: SwiftToken => astForSwiftToken(swiftToken) case syntax: Syntax => astForSyntax(syntax) case exprSyntax: ExprSyntax => astForExprSyntax(exprSyntax) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreatorHelper.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreatorHelper.scala index 61bccc0ec434..997fd3b1aee1 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreatorHelper.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstCreatorHelper.scala @@ -164,16 +164,38 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As protected def astForIdentifier(node: SwiftNode): Ast = { val identifierName = code(node) - val identNode = identifierNode(node, identifierName) - val variableOption = scope.lookupVariable(identifierName) - val tpe = variableOption match { - case Some((_, variableTypeName)) if variableTypeName != Defines.Any => variableTypeName - case None if identNode.typeFullName != Defines.Any => identNode.typeFullName - case _ => Defines.Any + if (scope.variableIsInTypeDeclScope(identifierName)) { + // we found it as member of the surrounding type decl + // (Swift does not allow to access any member / function of the outer class instance) + val tpe = scope.typeDeclFullNameForMember(identifierName).getOrElse(typeForSelfExpression()) + val selfNode = identifierNode(node, "self", "self", tpe) + scope.addVariableReference("self", selfNode, selfNode.typeFullName, EvaluationStrategies.BY_REFERENCE) + + val variableOption = scope.lookupVariable(identifierName) + val callTpe = variableOption.map(_._2).getOrElse(Defines.Any) + fieldAccessAst(node, node, Ast(selfNode), s"self.$identifierName", identifierName, callTpe) + } else { + if (config.swiftBuild && scope.lookupVariable(identifierName).isEmpty) { + val tpe = typeForSelfExpression() + val selfNode = identifierNode(node, "self", "self", tpe) + scope.addVariableReference("self", selfNode, selfNode.typeFullName, EvaluationStrategies.BY_REFERENCE) + + val callTpe = fullnameProvider.typeFullname(node).getOrElse(Defines.Any) + fieldAccessAst(node, node, Ast(selfNode), s"self.$identifierName", identifierName, callTpe) + } else { + // otherwise it must come from a variable (potentially captured from an outer scope) + val identNode = identifierNode(node, identifierName) + val variableOption = scope.lookupVariable(identifierName) + val tpe = variableOption match { + case Some((_, variableTypeName)) if variableTypeName != Defines.Any => variableTypeName + case None if identNode.typeFullName != Defines.Any => identNode.typeFullName + case _ => Defines.Any + } + identNode.typeFullName = tpe + scope.addVariableReference(identifierName, identNode, tpe, EvaluationStrategies.BY_REFERENCE) + Ast(identNode) + } } - identNode.typeFullName = tpe - scope.addVariableReference(identifierName, identNode, tpe, EvaluationStrategies.BY_REFERENCE) - Ast(identNode) } protected def registerType(typeFullName: String): Unit = { @@ -339,8 +361,12 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As private def typeNameInfoForNode(node: SwiftNode, name: String): TypeInfo = { fullnameProvider.declFullname(node) match { case Some(declFullname) => - registerType(declFullname) - TypeInfo(name, declFullname) + val cleanedFullName = if (declFullname.contains("(")) { + val fullName = declFullname.substring(0, declFullname.indexOf("(")) + fullName.substring(0, fullName.lastIndexOf(".")) + } else declFullname + registerType(cleanedFullName) + TypeInfo(name, cleanedFullName) case None => val (_, declFullname) = calcNameAndFullName(name) registerType(declFullname) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForDeclSyntaxCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForDeclSyntaxCreator.scala index d3ad1e4b4f94..267b4f621d2c 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForDeclSyntaxCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForDeclSyntaxCreator.scala @@ -8,6 +8,7 @@ import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines import io.joern.x2cpg.{Ast, ValidationMode} import io.shiftleft.codepropertygraph.generated.* import io.shiftleft.codepropertygraph.generated.nodes.* +import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal import scala.annotation.unused @@ -20,10 +21,6 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { protected type FunctionDeclLike = FunctionDeclSyntax | AccessorDeclSyntax | InitializerDeclSyntax | DeinitializerDeclSyntax | ClosureExprSyntax | SubscriptDeclSyntax - private def astForAccessorDeclSyntax(node: AccessorDeclSyntax): Ast = { - astForNode(node) - } - private def astForActorDeclSyntax(node: ActorDeclSyntax): Ast = { astForTypeDeclSyntax(node) } @@ -87,7 +84,10 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } private def isInitializedMember(node: DeclSyntax): Boolean = node match { - case v: VariableDeclSyntax => v.bindings.children.exists(c => c.initializer.isDefined || c.accessorBlock.isDefined) + case v: VariableDeclSyntax => + v.bindings.children.exists(c => + c.initializer.isDefined || c.accessorBlock.exists(_.accessors.isInstanceOf[CodeBlockItemListSyntax]) + ) case e: EnumCaseDeclSyntax => e.elements.children.exists(c => c.rawValue.isDefined) case _ => false } @@ -142,7 +142,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { private def createFakeConstructor( node: TypeDeclLike, typeDeclNode: NewTypeDecl, - methodBlockContent: List[Ast] = List.empty + methodBlockContent: List[DeclSyntax] ): Unit = { val constructorName = Defines.ConstructorMethodName val signature = s"()->${typeDeclNode.fullName}" @@ -152,17 +152,26 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { methodNode(node, constructorName, constructorName, methodFullName, Some(signature), parserResult.filename) val modifiers = Seq(NewModifier().modifierType(ModifierTypes.CONSTRUCTOR)) - methodAstParentStack.push(methodNode_) val methodReturnNode_ = methodReturnNode(node, typeDeclNode.fullName) + val blockNode = NewBlock() + methodAstParentStack.push(methodNode_) + scope.pushNewMethodScope(methodFullName, constructorName, blockNode, typeRefIdStack.headOption) + localAstParentStack.push(blockNode) + val methodBlockContentAsts = methodBlockContent.map(m => astForDeclMember(m, typeDeclNode)) + val parameterNode = + parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, typeDeclNode.fullName) + scope.addVariable("self", parameterNode, typeDeclNode.fullName, VariableScopeManager.ScopeType.MethodScope) + localAstParentStack.pop() methodAstParentStack.pop() - - val mAst = if (methodBlockContent.isEmpty) { - methodStubAst(methodNode_, Seq.empty, methodReturnNode_, modifiers) - } else { - val bodyAst = blockAst(NewBlock(), methodBlockContent) - methodAstWithAnnotations(methodNode_, Seq.empty, bodyAst, methodReturnNode_, modifiers) - } + scope.popScope() + val mAst = methodAstWithAnnotations( + methodNode_, + Seq(Ast(parameterNode)), + blockAst(blockNode, methodBlockContentAsts), + methodReturnNode_, + modifiers + ) val typeDeclAst = createFunctionTypeAndTypeDecl(methodNode_) Ast.storeInDiffGraph(mAst.merge(typeDeclAst), diffGraph) @@ -202,22 +211,10 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } } - private def astForDeclMember(node: DeclSyntax, typeDeclNode: NewTypeDecl): Ast = { + protected def astForDeclMember(node: DeclSyntax, typeDeclNode: NewTypeDecl): Ast = { node match { case d: FunctionDeclLike => - val ast = astForFunctionLike(d) - ast.root.collect { - case function: NewMethod => - val tpeFromTypeMap = fullnameProvider.typeFullname(d) - val typeFullName = tpeFromTypeMap.getOrElse(typeNameForDeclSyntax(d)) - val memberNode_ = memberNode(d, function.name, code(d), typeFullName, Seq(function.fullName)) - diffGraph.addEdge(typeDeclNode, memberNode_, EdgeTypes.AST) - case methodRef: NewMethodRef => - val tpeFromTypeMap = fullnameProvider.typeFullname(d) - val typeFullName = tpeFromTypeMap.getOrElse(typeNameForDeclSyntax(d)) - val memberNode_ = memberNode(d, methodRef.code, code(d), typeFullName, Seq(methodRef.methodFullName)) - diffGraph.addEdge(typeDeclNode, memberNode_, EdgeTypes.AST) - } + val ast = astForFunctionLike(d, List.empty, None) Ast.storeInDiffGraph(ast, diffGraph) ast.root.foreach(r => diffGraph.addEdge(typeDeclNode, r, EdgeTypes.AST)) Ast() @@ -239,16 +236,20 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val tpeFromTypeMap = fullnameProvider.typeFullname(c) val typeFullName = tpeFromTypeMap.getOrElse(typeNameForDeclSyntax(d)) val memberNode_ = memberNode(c, cCode, cCode, typeFullName) + scope.addVariable(cCode, memberNode_, typeFullName, VariableScopeManager.ScopeType.TypeDeclScope) diffGraph.addEdge(typeDeclNode, memberNode_, EdgeTypes.AST) } ast case d: VariableDeclSyntax => - val ast = astForNode(d) + val ast = astForVariableDeclSyntax(d, true) d.bindings.children.foreach { c => val cCode = code(c.pattern) val tpeFromTypeMap = fullnameProvider.typeFullname(c) - val typeFullName = tpeFromTypeMap.getOrElse(typeNameForDeclSyntax(d)) - val memberNode_ = memberNode(c, cCode, cCode, typeFullName) + val typeFullName = tpeFromTypeMap.getOrElse( + c.typeAnnotation.map(t => AstCreatorHelper.cleanType(code(t.`type`))).getOrElse(Defines.Any) + ) + val memberNode_ = memberNode(c, cCode, cCode, typeFullName) + scope.addVariable(cCode, memberNode_, typeFullName, VariableScopeManager.ScopeType.TypeDeclScope) diffGraph.addEdge(typeDeclNode, memberNode_, EdgeTypes.AST) } ast @@ -262,21 +263,13 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { private def createDeclConstructor( node: TypeDeclLike, typeDeclNode: NewTypeDecl, - constructorContent: List[Ast], - constructorBlock: Ast = Ast() + constructorContent: List[DeclSyntax] ): Unit = findDeclConstructor(node) match { case Some(constructor: InitializerDeclSyntax) => - val ast = astForFunctionLike(constructor, methodBlockContent = constructorContent) + val ast = astForFunctionLike(constructor, methodBlockContent = constructorContent, Some(typeDeclNode)) Ast.storeInDiffGraph(ast, diffGraph) ast.root.foreach(r => diffGraph.addEdge(typeDeclNode, r, EdgeTypes.AST)) - case _ if constructorBlock.root.isDefined => - constructorBlock.root.foreach { r => - constructorContent.foreach { c => - Ast.storeInDiffGraph(c, diffGraph) - c.root.foreach(diffGraph.addEdge(r, _, EdgeTypes.AST)) - } - } case _ => createFakeConstructor(node, typeDeclNode, methodBlockContent = constructorContent) } @@ -343,18 +336,15 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } methodAstParentStack.push(typeDeclNode_) - dynamicInstanceTypeStack.push(typeFullName) typeRefIdStack.push(typeRefNode_) + scope.pushNewTypeDeclScope(typeFullName) scope.pushNewMethodScope(typeFullName, typeName, typeDeclNode_, None) val allClassMembers = declMembers(node, withConstructor = false).toList // adding all other members and retrieving their initialization calls - val memberInitCalls = allClassMembers - .filter(m => !isStaticMember(m) && isInitializedMember(m)) - .map(m => astForDeclMember(m, typeDeclNode_)) - - createDeclConstructor(node, typeDeclNode_, memberInitCalls) + val memberInits = allClassMembers.filter(m => !isStaticMember(m) && isInitializedMember(m)) + createDeclConstructor(node, typeDeclNode_, memberInits) // adding all class methods / functions and uninitialized, non-static members allClassMembers @@ -362,26 +352,26 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { .foreach(m => astForDeclMember(m, typeDeclNode_)) // adding all static members and retrieving their initialization calls - val staticMemberInitCalls = - allClassMembers.filter(isStaticMember).map(m => astForDeclMember(m, typeDeclNode_)) + val staticMemberInits = allClassMembers.filter(isStaticMember) - methodAstParentStack.pop() - dynamicInstanceTypeStack.pop() - typeRefIdStack.pop() - scope.popScope() - - if (staticMemberInitCalls.nonEmpty) { + if (staticMemberInits.nonEmpty) { val init = staticInitMethodAstAndBlock( node, - staticMemberInitCalls, + staticMemberInits, s"$typeFullName.${io.joern.x2cpg.Defines.StaticInitMethodName}", None, - Defines.Any + Defines.Any, + typeDeclNode_ ) Ast.storeInDiffGraph(init.ast, diffGraph) diffGraph.addEdge(typeDeclNode_, init.method, EdgeTypes.AST) } + methodAstParentStack.pop() + typeRefIdStack.pop() + scope.popScope() + scope.popScope() + Ast(typeDeclNode_) } @@ -396,9 +386,6 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } private def astForEnumCaseDeclSyntax(node: EnumCaseDeclSyntax): Ast = { - val attributeAsts = node.attributes.children.map(astForNode) - val modifiers = modifiersForDecl(node) - val bindingAsts = node.elements.children.map { binding => val name = code(binding.name) val nLocalNode = localNode(binding, name, name, Defines.Any).order(0) @@ -409,11 +396,15 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { if (initAsts.isEmpty) { Ast() } else { + val attributeAsts = node.attributes.children.map(astForNode) + val modifiers = modifiersForDecl(node) + val patternAst = astForNode(binding.name) modifiers.foreach { mod => patternAst.root.foreach { r => diffGraph.addEdge(r, mod, EdgeTypes.AST) } } attributeAsts.foreach { attrAst => + Ast.storeInDiffGraph(attrAst, diffGraph) patternAst.root.foreach { r => attrAst.root.foreach { attr => diffGraph.addEdge(r, attr, EdgeTypes.AST) } } } createAssignmentCallAst(binding, patternAst, initAsts.head, code(binding).stripSuffix(",")) @@ -486,18 +477,18 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } methodAstParentStack.push(typeDeclNode_) - dynamicInstanceTypeStack.push(typeFullName) typeRefIdStack.push(typeRefNode_) + scope.pushNewTypeDeclScope(typeFullName) scope.pushNewMethodScope(typeFullName, typeName, typeDeclNode_, None) + scope.restoreMembersForExtension(typeFullName) + val allClassMembers = declMembers(node, withConstructor = false).toList // adding all other members and retrieving their initialization calls - val memberInitCalls = allClassMembers - .filter(m => !isStaticMember(m) && isInitializedMember(m)) - .map(m => astForDeclMember(m, typeDeclNode_)) + val memberInits = allClassMembers.filter(m => !isStaticMember(m) && isInitializedMember(m)) - createDeclConstructor(node, typeDeclNode_, memberInitCalls) + createDeclConstructor(node, typeDeclNode_, memberInits) // adding all class methods / functions and uninitialized, non-static members allClassMembers @@ -505,26 +496,26 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { .foreach(m => astForDeclMember(m, typeDeclNode_)) // adding all static members and retrieving their initialization calls - val staticMemberInitCalls = - allClassMembers.filter(isStaticMember).map(m => astForDeclMember(m, typeDeclNode_)) - - methodAstParentStack.pop() - dynamicInstanceTypeStack.pop() - typeRefIdStack.pop() - scope.popScope() + val staticMemberInits = allClassMembers.filter(isStaticMember) - if (staticMemberInitCalls.nonEmpty) { + if (staticMemberInits.nonEmpty) { val init = staticInitMethodAstAndBlock( node, - staticMemberInitCalls, + staticMemberInits, s"$typeFullName.${io.joern.x2cpg.Defines.StaticInitMethodName}", None, - Defines.Any + Defines.Any, + typeDeclNode_ ) Ast.storeInDiffGraph(init.ast, diffGraph) diffGraph.addEdge(typeDeclNode_, init.method, EdgeTypes.AST) } + methodAstParentStack.pop() + typeRefIdStack.pop() + scope.popScope() + scope.popScope() + Ast(typeDeclNode_) } @@ -603,7 +594,11 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } } - protected def astForFunctionLike(node: FunctionDeclLike, methodBlockContent: List[Ast] = List.empty): Ast = { + protected def astForFunctionLike( + node: FunctionDeclLike, + methodBlockContent: List[DeclSyntax], + typeDecl: Option[NewTypeDecl] + ): Ast = { // TODO: handle genericParameterClause // TODO: handle genericWhereClause @@ -625,8 +620,10 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val MethodInfo(methodName, methodFullName, signature, returnType) = methodInfo val methodFullNameAndSignature = methodInfo.fullNameAndSignature - val shouldCreateFunctionReference = - typeRefIdStack.isEmpty || node.isInstanceOf[ClosureExprSyntax] || node.isInstanceOf[AccessorDeclSyntax] + val shouldCreateFunctionReference = typeRefIdStack.isEmpty || + methodAstParentStack.headOption.exists(_.isInstanceOf[NewMethod]) || + node.isInstanceOf[ClosureExprSyntax] || + node.isInstanceOf[AccessorDeclSyntax] val methodRefNode_ = if (!shouldCreateFunctionReference) { None } else { Option(methodRefNode(node, methodName, methodFullNameAndSignature, methodFullNameAndSignature)) } val capturingRefNode = methodRefNode_.orElse(typeRefIdStack.headOption) @@ -642,9 +639,19 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val parameterAsts = node match { case f: FunctionDeclSyntax => - f.signature.parameterClause.parameters.children.map(astForNode) + val selfAst = + if (!isStaticMember(f) && !typeForSelfExpression().endsWith(NamespaceTraversal.globalNamespaceName)) { + val parameterNode = + parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, parentFullName) + scope.addVariable("self", parameterNode, parentFullName, VariableScopeManager.ScopeType.MethodScope) + Seq(Ast(parameterNode)) + } else Seq.empty + selfAst ++ f.signature.parameterClause.parameters.children.map(astForNode) case a: AccessorDeclSyntax => - a.parameters.toSeq.map(astForNode) + val parameterNode = + parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, parentFullName) + scope.addVariable("self", parameterNode, parentFullName, VariableScopeManager.ScopeType.MethodScope) + Ast(parameterNode) +: a.parameters.toSeq.map(astForNode) case i: InitializerDeclSyntax => val parameterNode = parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, parentFullName) @@ -656,12 +663,22 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { scope.addVariable("self", parameterNode, parentFullName, VariableScopeManager.ScopeType.MethodScope) Seq(Ast(parameterNode)) case s: SubscriptDeclSyntax => - s.parameterClause.parameters.children.map(astForNode) + val parameterNode = + parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, parentFullName) + scope.addVariable("self", parameterNode, parentFullName, VariableScopeManager.ScopeType.MethodScope) + Ast(parameterNode) +: s.parameterClause.parameters.children.map(astForNode) case c: ClosureExprSyntax => - c.signature.flatMap(_.parameterClause) match + val selfAst = if (!typeForSelfExpression().endsWith(NamespaceTraversal.globalNamespaceName)) { + val parameterNode = + parameterInNode(node, "self", "self", 0, false, EvaluationStrategies.BY_SHARING, parentFullName) + scope.addVariable("self", parameterNode, parentFullName, VariableScopeManager.ScopeType.MethodScope) + Seq(Ast(parameterNode)) + } else Seq.empty + selfAst ++ (c.signature.flatMap(_.parameterClause) match { case Some(p: ClosureShorthandParameterListSyntax) => p.children.map(astForNode) case Some(p: ClosureParameterClauseSyntax) => p.parameters.children.map(astForNode) case None => Seq.empty + }) } val body: Option[CodeBlockSyntax | AccessorDeclListSyntax | CodeBlockItemListSyntax] = node match { @@ -704,7 +721,8 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val methodReturnNode_ = methodReturnNode(node, returnType) - val blockAst_ = blockAst(block, methodBlockContent ++ bodyStmtAsts) + val methodBlockContentAsts = methodBlockContent.map(m => astForDeclMember(m, typeDecl.get)) + val blockAst_ = blockAst(block, methodBlockContentAsts ++ bodyStmtAsts) val astForMethod = methodAstWithAnnotations( methodNode_, @@ -732,7 +750,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } private def astForFunctionDeclSyntax(node: FunctionDeclSyntax): Ast = { - astForFunctionLike(node) + astForFunctionLike(node, List.empty, None) } protected def ifConfigDeclConditionIsSatisfied(node: IfConfigClauseSyntax): Boolean = { @@ -818,7 +836,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { private def astForPrecedenceGroupDeclSyntax(@unused node: PrecedenceGroupDeclSyntax): Ast = Ast() private def astForSubscriptDeclSyntax(node: SubscriptDeclSyntax): Ast = { - astForFunctionLike(node) + astForFunctionLike(node, List.empty, None) } private def nameFromTypeSyntaxAst(node: TypeSyntax): String = { @@ -890,7 +908,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val parameterAsts = if (parameters.isEmpty && accessorSpecifier == "set") { val name = "newValue" // Swift default parameter name for set accessors val parameterNode = parameterInNode(node, name, name, 1, false, EvaluationStrategies.BY_VALUE, Some(tpe)) - scope.addVariable(name, parameterNode, Defines.Any, VariableScopeManager.ScopeType.MethodScope) + scope.addVariable(name, parameterNode, parameterNode.typeFullName, VariableScopeManager.ScopeType.MethodScope) Seq(Ast(parameterNode)) } else { parameters.map(astForNode) @@ -912,15 +930,17 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val syntheticBodyStmtAst = if (bodyStmtAsts.isEmpty) { accessorSpecifier match { case "set" => - val thisNode = identifierNode(node, "this") - val fieldAccess = fieldAccessAst(node, node, Ast(thisNode), s"this.$variableName", variableName, tpe) + val selfNode = identifierNode(node, "self") + scope.addVariableReference("self", selfNode, selfNode.typeFullName, EvaluationStrategies.BY_REFERENCE) + val fieldAccess = fieldAccessAst(node, node, Ast(selfNode), s"self.$variableName", variableName, tpe) val sourceName = parameterAsts.head.root.collect { case p: NewMethodParameterIn => p.name }.get val barIdentifier = identifierNode(node, sourceName).typeFullName(tpe) scope.addVariableReference(sourceName, barIdentifier, tpe, EvaluationStrategies.BY_REFERENCE) - List(createAssignmentCallAst(node, fieldAccess, Ast(barIdentifier), s"this.$variableName = $sourceName")) + List(createAssignmentCallAst(node, fieldAccess, Ast(barIdentifier), s"self.$variableName = $sourceName")) case "get" => - val thisNode = identifierNode(node, "this") - val fieldAccess = fieldAccessAst(node, node, Ast(thisNode), s"this.$variableName", variableName, tpe) + val selfNode = identifierNode(node, "self") + scope.addVariableReference("self", selfNode, selfNode.typeFullName, EvaluationStrategies.BY_REFERENCE) + val fieldAccess = fieldAccessAst(node, node, Ast(selfNode), s"self.$variableName", variableName, tpe) List(returnAst(returnNode(node, variableName), List(fieldAccess))) case _ => List.empty[Ast] } @@ -950,14 +970,12 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { diffGraph.addEdge(methodAstParentStack.head, methodNode_, EdgeTypes.AST) } - private def astForVariableDeclSyntax(node: VariableDeclSyntax): Ast = { - val attributeAsts = node.attributes.children.map(astForNode) - val modifiers = node.modifiers.children.flatMap(c => astForNode(c).root.map(_.asInstanceOf[NewModifier])) - val kind = code(node.bindingSpecifier) + private def astForVariableDeclSyntax(variableDecl: VariableDeclSyntax, isTypeDeclMember: Boolean = false): Ast = { + val kind = code(variableDecl.bindingSpecifier) val scopeType = if (kind == "let") { VariableScopeManager.ScopeType.BlockScope } else { VariableScopeManager.ScopeType.MethodScope } - val bindingAsts = node.bindings.children.flatMap { binding => + val bindingAsts = variableDecl.bindings.children.flatMap { binding => val namesWithNode = binding.pattern match { case expr: ExpressionPatternSyntax => notHandledYet(expr) @@ -983,9 +1001,12 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val tpeFromAst = binding.typeAnnotation.map(t => AstCreatorHelper.cleanType(code(t.`type`))) val typeFullName = tpeFromTypeMap.orElse(tpeFromAst).getOrElse(Defines.Any) registerType(typeFullName) - val nLocalNode = localNode(binding, cleanedName, cleanedName, typeFullName).order(0) - scope.addVariable(cleanedName, nLocalNode, typeFullName, scopeType) - diffGraph.addEdge(localAstParentStack.head, nLocalNode, EdgeTypes.AST) + + if (!isTypeDeclMember) { + val nLocalNode = localNode(binding, cleanedName, cleanedName, typeFullName).order(0) + scope.addVariable(cleanedName, nLocalNode, typeFullName, scopeType) + diffGraph.addEdge(localAstParentStack.head, nLocalNode, EdgeTypes.AST) + } val accessorBlocks = binding.accessorBlock.map(_.accessors).collect { case accessorList: AccessorDeclListSyntax => @@ -999,15 +1020,29 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { if (initAsts.isEmpty) { Ast() } else { - val patternIdentifier = identifierNode(binding.pattern, cleanedName).typeFullName(typeFullName) - scope.addVariableReference(cleanedName, patternIdentifier, typeFullName, EvaluationStrategies.BY_REFERENCE) - val patternAst = Ast(patternIdentifier) - modifiers.foreach { mod => - diffGraph.addEdge(patternIdentifier, mod, EdgeTypes.AST) - } - attributeAsts.foreach { attrAst => - attrAst.root.foreach { attr => diffGraph.addEdge(patternIdentifier, attr, EdgeTypes.AST) } + val patternAst = if (!isTypeDeclMember) { + val attributeAsts = variableDecl.attributes.children.map(astForNode) + val modifiers = + variableDecl.modifiers.children.flatMap(c => astForNode(c).root.map(_.asInstanceOf[NewModifier])) + + val patternIdentifier = identifierNode(binding.pattern, cleanedName).typeFullName(typeFullName) + scope.addVariableReference(cleanedName, patternIdentifier, typeFullName, EvaluationStrategies.BY_REFERENCE) + modifiers.foreach { mod => + diffGraph.addEdge(patternIdentifier, mod, EdgeTypes.AST) + } + attributeAsts.foreach { attrAst => + Ast.storeInDiffGraph(attrAst, diffGraph) + attrAst.root.foreach { attr => diffGraph.addEdge(patternIdentifier, attr, EdgeTypes.AST) } + } + Ast(patternIdentifier) + } else { + // TODO: handle static members + val selfTpe = typeForSelfExpression() + val baseNode = identifierNode(node, "self", "self", selfTpe) + scope.addVariableReference("self", baseNode, selfTpe, EvaluationStrategies.BY_REFERENCE) + fieldAccessAst(node, node, Ast(baseNode), s"self.$name", name, typeFullName) } + val initCode = binding.initializer.fold("")(i => s" ${code(i).strip()}") val accessorBlockCode = binding.accessorBlock.fold("")(a => s" ${code(a).strip()}") val typeCode = binding.typeAnnotation.fold("")(t => code(t).strip()) @@ -1035,7 +1070,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { case Nil => Ast() case head :: Nil => head case others => - val block = blockNode(node, code(node), Defines.Any) + val block = blockNode(variableDecl, code(variableDecl), Defines.Any) blockAst(block, others.toList) } } @@ -1043,7 +1078,6 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { private def astForMissingDeclSyntax(@unused node: MissingDeclSyntax): Ast = Ast() protected def astForDeclSyntax(declSyntax: DeclSyntax): Ast = declSyntax match { - case node: AccessorDeclSyntax => astForAccessorDeclSyntax(node) case node: ActorDeclSyntax => astForActorDeclSyntax(node) case node: AssociatedTypeDeclSyntax => astForAssociatedTypeDeclSyntax(node) case node: ClassDeclSyntax => astForTypeDeclSyntax(node) @@ -1067,6 +1101,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) { case node: SubscriptDeclSyntax => astForSubscriptDeclSyntax(node) case node: TypeAliasDeclSyntax => astForTypeAliasDeclSyntax(node) case node: VariableDeclSyntax => astForVariableDeclSyntax(node) + case other => notHandledYet(other) } } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala index fe8d7e27131c..41fdb0464580 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala @@ -4,10 +4,11 @@ import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.* import io.joern.swiftsrc2cpg.passes.GlobalBuiltins import io.joern.x2cpg import io.joern.x2cpg.datastructures.Stack.* +import io.joern.x2cpg.datastructures.VariableScopeManager import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines import io.joern.x2cpg.{Ast, ValidationMode} import io.shiftleft.codepropertygraph.generated.* -import io.shiftleft.codepropertygraph.generated.nodes.{NewCall, NewNode} +import io.shiftleft.codepropertygraph.generated.nodes.NewCall import scala.annotation.unused @@ -63,8 +64,10 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val lhsAst = astForNode(dictElement.key) val rhsAst = astForNode(dictElement.value) - val lhsTmpNode = Ast(identifierNode(dictElement, tmpName)) - val lhsIndexAccessCallAst = createIndexAccessCallAst(dictElement, lhsTmpNode, lhsAst) + val lhsTmpNode = identifierNode(dictElement, tmpName) + scope.addVariableReference(tmpName, lhsTmpNode, Defines.Any, EvaluationStrategies.BY_REFERENCE) + + val lhsIndexAccessCallAst = createIndexAccessCallAst(dictElement, Ast(lhsTmpNode), lhsAst) createAssignmentCallAst( dictElement, @@ -76,6 +79,7 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } val tmpNode = identifierNode(node, tmpName) + scope.addVariableReference(tmpName, tmpNode, Defines.Any, EvaluationStrategies.BY_REFERENCE) scope.popScope() localAstParentStack.pop() @@ -204,30 +208,18 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { callAst(callNode, argAsts) } - private def handleCallNodeArgs( - callExpr: FunctionCallExprSyntax, - receiverAst: Ast, - baseNode: NewNode, - callName: String - ): Ast = { + private def handleCallNodeArgs(callExpr: FunctionCallExprSyntax, baseAst: Ast, callName: String): Ast = { val trailingClosureAsts = callExpr.trailingClosure.toList.map(astForNode) val additionalTrailingClosuresAsts = callExpr.additionalTrailingClosures.children.map(c => astForNode(c.closure)) val args = callExpr.arguments.children.map(astForNode) ++ trailingClosureAsts ++ additionalTrailingClosuresAsts val callExprCode = code(callExpr) - val callCode = callExprCode match { - case c if c.startsWith(".") && codeOf(baseNode) != "this" => s"${codeOf(baseNode)}$callExprCode" - case c if c.contains("#if ") => - val recCode = codeOf(receiverAst.nodes.head) - if (recCode.endsWith(callName)) { - s"${codeOf(receiverAst.nodes.head)}(${code(callExpr.arguments)})" - } else { - s"${codeOf(receiverAst.nodes.head)}$callName(${code(callExpr.arguments)})" - } - case _ => callExprCode - } - + val callCode = if (callExprCode.startsWith(".")) { + s"${codeOf(baseAst.root.get)}$callExprCode" + } else if (callExprCode.contains("#if ")) { + s"${codeOf(baseAst.root.get)}.$callName(${code(callExpr.arguments)})" + } else callExprCode val callNode_ = callNode( callExpr, callCode, @@ -239,7 +231,58 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { ) setFullNameInfoForCall(callExpr, callNode_) - callAst(callNode_, args, receiver = Option(receiverAst), base = Option(Ast(baseNode))) + callAst(callNode_, args, Option(baseAst)) + } + + private def astForConstructorInvocation(expr: FunctionCallExprSyntax): Ast = { + // get call is safe as this function is guarded by isRefToConstructor + val tpe = fullnameProvider.typeFullname(expr).get + registerType(tpe) + + val callExprCode = code(expr) + val blockNode_ = blockNode(expr, callExprCode, tpe) + scope.pushNewBlockScope(blockNode_) + + val tmpNodeName = scopeLocalUniqueName("tmp") + val localTmpNode = localNode(expr, tmpNodeName, tmpNodeName, tpe).order(0) + diffGraph.addEdge(blockNode_, localTmpNode, EdgeTypes.AST) + scope.addVariable(tmpNodeName, localTmpNode, tpe, VariableScopeManager.ScopeType.BlockScope) + + val tmpNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe) + scope.addVariableReference(tmpNodeName, tmpNode, tpe, EvaluationStrategies.BY_SHARING) + + val allocOp = Operators.alloc + val allocCallNode = callNode(expr, allocOp, allocOp, allocOp, DispatchTypes.STATIC_DISPATCH) + val assignmentCallOp = Operators.assignment + val assignmentCallNode = + callNode(expr, s"$tmpNodeName = $allocOp", assignmentCallOp, assignmentCallOp, DispatchTypes.STATIC_DISPATCH) + val assignmentAst = callAst(assignmentCallNode, List(Ast(tmpNode), Ast(allocCallNode))) + + val baseNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe) + scope.addVariableReference(tmpNodeName, baseNode, tpe, EvaluationStrategies.BY_SHARING) + + val constructorCallNode = callNode( + expr, + callExprCode, + "init", + x2cpg.Defines.UnresolvedNamespace, + DispatchTypes.STATIC_DISPATCH, + Some(x2cpg.Defines.UnresolvedSignature), + Some(Defines.Void) + ) + setFullNameInfoForCall(expr, constructorCallNode) + + val trailingClosureAsts = expr.trailingClosure.toList.map(astForNode) + val additionalTrailingClosuresAsts = expr.additionalTrailingClosures.children.map(c => astForNode(c.closure)) + val args = expr.arguments.children.map(astForNode) ++ trailingClosureAsts ++ additionalTrailingClosuresAsts + + val constructorCallAst = callAst(constructorCallNode, args, base = Some(Ast(baseNode))) + + val retNode = identifierNode(expr, tmpNodeName, tmpNodeName, tpe) + scope.addVariableReference(tmpNodeName, retNode, tpe, EvaluationStrategies.BY_SHARING) + + scope.popScope() + Ast(blockNode_).withChildren(Seq(assignmentAst, constructorCallAst, Ast(retNode))) } private def astForFunctionCallExprSyntax(node: FunctionCallExprSyntax): Ast = { @@ -248,55 +291,30 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { if (GlobalBuiltins.builtins.contains(calleeCode)) { createBuiltinStaticCall(node, callee, calleeCode) } else { - val (receiverAst, baseNode, callName) = callee match { + callee match { + case m: MemberAccessExprSyntax if m.base.isEmpty || code(m.base.get) == "self" => + // referencing implicit self + val selfTpe = typeForSelfExpression() + val selfNode = identifierNode(node, "self", "self", selfTpe) + scope.addVariableReference("self", selfNode, selfTpe, EvaluationStrategies.BY_REFERENCE) + handleCallNodeArgs(node, Ast(selfNode), code(m.declName.baseName)) + case m: MemberAccessExprSyntax if isRefToStaticFunction(calleeCode) => + createBuiltinStaticCall(node, callee, calleeCode) case m: MemberAccessExprSyntax => - val base = m.base - val member = m.declName - val memberCode = code(member) - base match { - case None => - // referencing implicit this - val receiverAst = astForNode(callee) - val baseNodeId = identifierNode(m, "this") - scope.addVariableReference("this", baseNodeId, baseNodeId.typeFullName, EvaluationStrategies.BY_REFERENCE) - (receiverAst, baseNodeId, memberCode) - case Some(d: DeclReferenceExprSyntax) => - val receiverAst = astForNode(callee) - val baseNodeId = identifierNode(d, code(d)) - scope.addVariableReference( - code(d), - baseNodeId, - baseNodeId.typeFullName, - EvaluationStrategies.BY_REFERENCE - ) - (receiverAst, baseNodeId, memberCode) - case Some(otherBase) => - val tmpVarName = scopeLocalUniqueName("tmp") - val baseTmpNode = identifierNode(otherBase, tmpVarName) - scope.addVariableReference( - tmpVarName, - baseTmpNode, - baseTmpNode.typeFullName, - EvaluationStrategies.BY_REFERENCE - ) - val baseAst = astForNode(otherBase) - val codeField = s"(${codeOf(baseTmpNode)} = ${codeOf(baseAst.nodes.head)})" - val tmpAssignmentAst = - createAssignmentCallAst(otherBase, Ast(baseTmpNode), baseAst, codeField) - val memberNode = fieldIdentifierNode(member, memberCode, memberCode) - val fieldAccessAst = createFieldAccessCallAst(callee, tmpAssignmentAst, memberNode) - val thisTmpNode = identifierNode(callee, tmpVarName) - (fieldAccessAst, thisTmpNode, memberCode) - } + val memberCode = code(m.declName) + handleCallNodeArgs(node, astForNode(m.base.get), memberCode) + case other if isRefToConstructor(node, other) => + astForConstructorInvocation(node) case other if isRefToClosure(node, other) => - return astForClosureCall(node) - case _ => - val receiverAst = astForNode(callee) - val thisNode = identifierNode(callee, "this") - scope.addVariableReference(thisNode.name, thisNode, thisNode.typeFullName, EvaluationStrategies.BY_REFERENCE) - (receiverAst, thisNode, calleeCode) + astForClosureCall(node) + case declReferenceExprSyntax: DeclReferenceExprSyntax if code(declReferenceExprSyntax) != "self" => + val selfTpe = typeForSelfExpression() + val selfNode = identifierNode(declReferenceExprSyntax, "self", "self", selfTpe) + scope.addVariableReference(selfNode.name, selfNode, selfTpe, EvaluationStrategies.BY_REFERENCE) + handleCallNodeArgs(node, Ast(selfNode), calleeCode) + case other => + handleCallNodeArgs(node, astForNode(other), calleeCode) } - handleCallNodeArgs(node, receiverAst, baseNode, callName) } } @@ -306,11 +324,10 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val signature = fullnameProvider.typeFullnameRaw(expr.calledExpression).getOrElse(x2cpg.Defines.UnresolvedSignature) val callName = Defines.ClosureApplyMethodName val callMethodFullname = s"${Defines.Function}<$signature>.$callName:$signature" - val baseAst = astForNode(expr.calledExpression) + val baseAst = astForIdentifier(expr.calledExpression) val trailingClosureAsts = expr.trailingClosure.toList.map(astForNode) val additionalTrailingClosuresAsts = expr.additionalTrailingClosures.children.map(c => astForNode(c.closure)) - val args = expr.arguments.children.map(astForNode) ++ trailingClosureAsts ++ additionalTrailingClosuresAsts val callExprCode = code(expr) @@ -329,12 +346,12 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { private def isRefToClosure(func: FunctionCallExprSyntax, node: ExprSyntax): Boolean = { if (!config.swiftBuild) { // Early exit; without types from the compiler we will be unable to identify closure calls anyway. - // This saves us the typeFullname lookup below. + // This saves us the fullnameProvider lookups below. return false } node match { case refExpr: DeclReferenceExprSyntax - if refExpr.baseName.isInstanceOf[identifier] && refExpr.argumentNames.isEmpty && + if refExpr.baseName.isInstanceOf[identifier] && fullnameProvider.declFullname(func).isEmpty && fullnameProvider.typeFullname(refExpr).exists(_.startsWith(s"${Defines.Function}<")) => true @@ -342,6 +359,29 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { } } + private def isRefToStaticFunction(calleeCode: String): Boolean = { + // TODO: extend the GsonTypeInfoReader to query for information whether the call is a call to a static function + calleeCode.headOption.exists(_.isUpper) && !calleeCode.contains("(") && !calleeCode.contains(")") + } + + private def isRefToConstructor(func: FunctionCallExprSyntax, node: ExprSyntax): Boolean = { + if (!config.swiftBuild) { + // Early exit; without types from the compiler we will be unable to identify constructor calls anyway. + // This saves us the fullnameProvider lookups below. + return false + } + node match { + case refExpr: DeclReferenceExprSyntax + if refExpr.baseName.isInstanceOf[identifier] && + fullnameProvider.typeFullname(func).nonEmpty && + fullnameProvider.declFullname(func).exists { fullName => + fullName.contains(".init(") && fullName.contains(")->") + } => + true + case _ => false + } + } + private def astForGenericSpecializationExprSyntax(node: GenericSpecializationExprSyntax): Ast = { astForNode(node.expression) } @@ -422,13 +462,10 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val member = node.declName val baseAst = base match { case None => - // referencing implicit this - val baseNode = identifierNode(node, "this") - scope.addVariableReference("this", baseNode, Defines.Any, EvaluationStrategies.BY_REFERENCE) - Ast(baseNode) - case Some(d: DeclReferenceExprSyntax) if code(d) == "this" || code(d) == "self" => - val baseNode = identifierNode(d, code(d)) - scope.addVariableReference(code(d), baseNode, Defines.Any, EvaluationStrategies.BY_REFERENCE) + // referencing implicit self + val selfTpe = typeForSelfExpression() + val baseNode = identifierNode(node, "self", "self", selfTpe) + scope.addVariableReference("self", baseNode, selfTpe, EvaluationStrategies.BY_REFERENCE) Ast(baseNode) case Some(otherBase) => astForNode(otherBase) @@ -442,7 +479,6 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { val memberNode = fieldIdentifierNode(other, code(other), code(other)) createFieldAccessCallAst(node, baseAst, memberNode) } - } private def astForMissingExprSyntax(@unused node: MissingExprSyntax): Ast = Ast() diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala index 94489bf417e0..0e4138285641 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala @@ -50,7 +50,13 @@ trait AstForSyntaxCollectionCreator(implicit withSchemaValidation: ValidationMod ) private def astForCodeBlockItemListSyntax(node: CodeBlockItemListSyntax): Ast = { - astForListSyntaxChildren(node, node.children) + astForListSyntaxChildren( + node, + node.children.sortBy { + case item if item.item.isInstanceOf[ExtensionDeclSyntax] => 2 + case _ => 1 + } + ) } private def astForCompositionTypeElementListSyntax(node: CompositionTypeElementListSyntax): Ast = notHandledYet(node) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala index 631495e4896c..1fa5bbbd7a4b 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala @@ -45,7 +45,7 @@ trait AstForSyntaxCreator(implicit withSchemaValidation: ValidationMode) { this: val assign = NewAnnotationParameterAssign().code(code(argument)) val assignChildren = List(Ast(parameter), argumentAst) setArgumentIndices(assignChildren) - List(Ast(assign).withChild(Ast(parameter)).withChild(argumentAst)) + List(Ast(assign).withChildren(assignChildren)) case None => Nil } val attributeCode = code(node) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala index 66464b9b6a57..0837b041c523 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala @@ -3,6 +3,7 @@ package io.joern.swiftsrc2cpg.astcreation import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.* import io.joern.x2cpg import io.joern.x2cpg.{Ast, ValidationMode} +import io.joern.x2cpg.datastructures.Stack.* import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines import io.shiftleft.codepropertygraph.generated.nodes.* import io.shiftleft.codepropertygraph.generated.{DispatchTypes, ModifierTypes, Operators} @@ -86,6 +87,7 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC partAst: Ast, additionalArgsAst: Seq[Ast] = Seq.empty ): Ast = { + val tpe = fullnameProvider.typeFullname(node).orElse(Some(Defines.Any)) val callNode_ = callNode( node, s"${codeOf(baseAst.nodes.head)}[${codeOf(partAst.nodes.head)}]", @@ -93,13 +95,14 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC Operators.indexAccess, DispatchTypes.STATIC_DISPATCH, None, - Some(Defines.Any) + tpe ) val arguments = List(baseAst, partAst) ++ additionalArgsAst callAst(callNode_, arguments) } protected def createFieldAccessCallAst(node: SwiftNode, baseAst: Ast, partNode: NewNode): Ast = { + val tpe = fullnameProvider.typeFullname(node).orElse(Some(Defines.Any)) val callNode_ = callNode( node, s"${codeOf(baseAst.nodes.head)}.${codeOf(partNode)}", @@ -107,7 +110,7 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC Operators.fieldAccess, DispatchTypes.STATIC_DISPATCH, None, - Some(Defines.Any) + tpe ) val arguments = List(baseAst, Ast(partNode)) callAst(callNode_, arguments) @@ -135,11 +138,8 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC callAst(callNode_, arguments) } - private def typeHintForThisExpression(): Seq[String] = { - dynamicInstanceTypeStack.headOption match { - case Some(tpe) => Seq(tpe) - case None => methodAstParentStack.collectFirst { case t: NewTypeDecl => t.fullName }.toSeq - } + protected def typeForSelfExpression(): String = { + scope.getEnclosingTypeDeclFullName.getOrElse(Defines.Any) } protected def identifierNode(node: SwiftNode, name: String): NewIdentifier = { @@ -150,18 +150,19 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC t } .getOrElse(name match { - case "this" | "self" | "Self" => typeHintForThisExpression().headOption.getOrElse(Defines.Any) - case _ => Defines.Any + case "self" | "Self" => typeForSelfExpression() + case _ => Defines.Any }) identifierNode(node, name, name, tpe) } def staticInitMethodAstAndBlock( node: SwiftNode, - initAsts: List[Ast], + inits: List[DeclSyntax], fullName: String, signature: Option[String], returnType: String, + typeDecl: NewTypeDecl, fileName: Option[String] = None ): AstAndMethod = { val methodNode = NewMethod() @@ -175,8 +176,14 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC if (fileName.isDefined) { methodNode.filename(fileName.get) } + + val blockNode = NewBlock() + localAstParentStack.push(blockNode) + val initAsts = inits.map(m => astForDeclMember(m, typeDecl)) + localAstParentStack.pop() + val staticModifier = NewModifier().modifierType(ModifierTypes.STATIC) - val body = blockAst(NewBlock(), initAsts) + val body = blockAst(blockNode, initAsts) val methodReturn = methodReturnNode(node, returnType) AstAndMethod(methodAst(methodNode, Nil, body, methodReturn, List(staticModifier)), methodNode, body) } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/SwiftVariableScopeManager.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/SwiftVariableScopeManager.scala index 8a98175a5cf7..2ac5ea0b8560 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/SwiftVariableScopeManager.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/SwiftVariableScopeManager.scala @@ -1,9 +1,50 @@ package io.joern.swiftsrc2cpg.astcreation import io.joern.x2cpg.datastructures.VariableScopeManager +import io.joern.x2cpg.datastructures.VariableScopeManager.ScopeType +import io.shiftleft.codepropertygraph.generated.nodes.NewNode + +import scala.collection.mutable class SwiftVariableScopeManager extends VariableScopeManager { override val ScopePathSeparator: String = "." + private case class MemberElement(variableName: String, variableNode: NewNode, tpe: String) + + // mapping from TypDecl fullname to members to access members from extensions + private val typeDeclToMemberMap = mutable.HashMap.empty[String, mutable.ArrayBuffer[MemberElement]] + + def typeDeclFullNameForMember(variableName: String): Option[String] = { + typeDeclToMemberMap.collectFirst { + case (typeDeclFullName, members) if members.exists(_.variableName == variableName) => typeDeclFullName + } + } + + def restoreMembersForExtension(typeDeclFullName: String): Unit = { + val members = typeDeclToMemberMap.get(typeDeclFullName.stripSuffix("")) + members.foreach(memberList => + memberList.foreach(memberElement => + super.addVariable( + memberElement.variableName, + memberElement.variableNode, + memberElement.tpe, + ScopeType.TypeDeclScope + ) + ) + ) + } + + override def addVariable(variableName: String, variableNode: NewNode, tpe: String, scopeType: ScopeType): Unit = { + super.addVariable(variableName, variableNode, tpe, scopeType) + if (scopeType == ScopeType.TypeDeclScope) { + val typeDeclFullName = this.getEnclosingTypeDeclFullName + if (typeDeclFullName.isDefined) { + val members = typeDeclToMemberMap.getOrElse(typeDeclFullName.get, mutable.ArrayBuffer.empty) + members.addOne(MemberElement(variableName, variableNode, tpe)) + typeDeclToMemberMap(typeDeclFullName.get) = members + } + } + } + } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/GlobalBuiltins.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/GlobalBuiltins.scala index 53328c484c59..d2f801a292ec 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/GlobalBuiltins.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/GlobalBuiltins.scala @@ -21,7 +21,7 @@ object GlobalBuiltins { "Set", // Creates a set from a sequence // Types and type casting - "type", // Returns the dynamic type of a value + "type", // Returns the dynamic type of some value "numericCast", // Returns the given integer as the equivalent value in a different integer type "unsafeDowncast", // Returns the given instance cast unconditionally to the specified type "unsafeBitCast", // Returns the bits of the given instance, interpreted as having the specified type @@ -89,7 +89,7 @@ object GlobalBuiltins { // C Interoperability "withVaList", // Invokes the given closure with a C va_list argument derived from the given array of arguments - "getVaList", // Returns a CVaListPointer that is backed by autoreleased storage, built from the given array of arguments + "getVaList", // Returns a CVaListPointer that is backed by auto-released storage, built from the given array of arguments // Miscellaneous Functions "swap", // Exchanges the values of two variables diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/utils/GsonTypeInfoReader.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/utils/GsonTypeInfoReader.scala index e9f5aa50bf0f..3a35a64dba3d 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/utils/GsonTypeInfoReader.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/utils/GsonTypeInfoReader.scala @@ -30,6 +30,7 @@ object GsonTypeInfoReader { val DeclRef = "decl_ref" val CallExpr = "call_expr" val ReturnStmt = "return_stmt" + val DotCallExpr = "dot_syntax_call_expr" } /** Safely retrieves a string property from a JsonObject. @@ -69,7 +70,11 @@ object GsonTypeInfoReader { val rangeObj = obj.get("range").getAsJsonObject val start = rangeObj.get("start").getAsInt val end = rangeObj.get("end").getAsInt - if (start == end) (start, end) else (start, end + 1) + val offset = astNodeKind(obj) match { + case NodeKinds.DotCallExpr => 3 // offsets are off by 2 from SwiftParser for simple dot calls + case _ => 1 + } + if (start == end) (start, end) else (start, end + offset) } /** Safely extracts the source range from a JSON object. diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ActorTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ActorTests.scala index 10a9066c62a1..c06ed2cc753e 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ActorTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ActorTests.scala @@ -22,11 +22,17 @@ class ActorTests extends SwiftCompilerSrc2CpgSuite { myActor.boundMethod.fullName.l shouldBe List( "Sources/main.swift:.MyActor1.init:()->Sources/main.swift:.MyActor1" ) + val List(constructorParam) = myActor.boundMethod.parameter.l + constructorParam.name shouldBe "self" + constructorParam.typeFullName shouldBe "Sources/main.swift:.MyActor1" val List(myActorSwiftc) = compilerCpg.typeDecl.nameExact("MyActor1").l myActorSwiftc.fullName shouldBe "SwiftTest.MyActor1" myActorSwiftc.member shouldBe empty myActorSwiftc.boundMethod.fullName.l shouldBe List("SwiftTest.MyActor1.init:()->SwiftTest.MyActor1") + val List(constructorParamSwiftc) = myActorSwiftc.boundMethod.parameter.l + constructorParamSwiftc.name shouldBe "self" + constructorParamSwiftc.typeFullName shouldBe "SwiftTest.MyActor1" } "testActor2" in { @@ -41,7 +47,6 @@ class ActorTests extends SwiftCompilerSrc2CpgSuite { val List(myActor) = cpg.typeDecl.nameExact("MyActor2").l myActor.fullName shouldBe "Sources/main.swift:.MyActor2" - myActor.member.name.l shouldBe List("hello", "foo") val List(constructor) = myActor.method.isConstructor.l constructor.name shouldBe "init" constructor.fullName shouldBe "Sources/main.swift:.MyActor2.init:()->Sources/main.swift:.MyActor2" @@ -54,7 +59,6 @@ class ActorTests extends SwiftCompilerSrc2CpgSuite { val List(myActorSwiftc) = compilerCpg.typeDecl.nameExact("MyActor2").l myActorSwiftc.fullName shouldBe "SwiftTest.MyActor2" - myActorSwiftc.member.name.l shouldBe List("hello", "foo") val List(constructorSwiftc) = myActorSwiftc.method.isConstructor.l constructorSwiftc.name shouldBe "init" constructorSwiftc.fullName shouldBe "SwiftTest.MyActor2.init:()->SwiftTest.MyActor2" diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/AsyncTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/AsyncTests.scala index adc124b0cc31..982ff29f6d99 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/AsyncTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/AsyncTests.scala @@ -112,7 +112,6 @@ class AsyncTests extends AstSwiftSrc2CpgSuite { "let 0 = await asyncGlobal1()", "let 1 = myFuture.await()", "let myFuture = MyFuture()", - "myFuture.await", "myFuture.await()" ) } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/CallTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/CallTests.scala new file mode 100644 index 000000000000..a188b2e3d02c --- /dev/null +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/CallTests.scala @@ -0,0 +1,342 @@ +package io.joern.swiftsrc2cpg.passes.ast + +import io.joern.swiftsrc2cpg.testfixtures.SwiftCompilerSrc2CpgSuite +import io.joern.x2cpg +import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators} +import io.shiftleft.codepropertygraph.generated.nodes.* +import io.shiftleft.semanticcpg.language.* + +class CallTests extends SwiftCompilerSrc2CpgSuite { + + "CallTests" should { + + "be correct for simple calls" in { + val testCode = + """ + |class Foo { + | func foo() {} + | func bar() {} + | func main() { + | foo() + | self.bar() + | other.method() + | } + |} + |""".stripMargin + val cpg = code(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + fooCall.signature shouldBe "" + fooCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "Sources/main.swift:.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + barCall.signature shouldBe "" + barCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "Sources/main.swift:.Foo" + + val List(methodCall) = cpg.call.nameExact("method").l + methodCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + methodCall.signature shouldBe "" + methodCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(methodCallReceiverCall) = methodCall.receiver.isIdentifier.l + methodCallReceiverCall.name shouldBe "other" + methodCallReceiverCall.typeFullName shouldBe "ANY" + } + + "be correct for simple calls with compiler support" in { + val testCode = + """ + |class Foo { + | func foo() {} + | func bar() -> String { return "" } + | func main() { + | foo() + | self.bar() + | } + |} + |""".stripMargin + val cpg = codeWithSwiftSetup(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe "SwiftTest.Foo.foo:()->()" + fooCall.signature shouldBe "()->()" + fooCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "SwiftTest.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe "SwiftTest.Foo.bar:()->Swift.String" + barCall.signature shouldBe "()->Swift.String" + barCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo" + } + + "be correct for simple call to constructor with compiler support" in { + val testCode = + """ + |class Foo {} + | + |func main() { + | Foo() + |} + |""".stripMargin + val cpg = codeWithSwiftSetup(testCode) + + val List(constructorCallBlock) = cpg.block.codeExact("Foo()").l + val List(tmpAssignment) = constructorCallBlock.astChildren.isCall.isAssignment.l + tmpAssignment.code shouldBe s"0 = ${Operators.alloc}" + tmpAssignment.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo") + tmpAssignment.argument.isCall.name.l shouldBe List(Operators.alloc) + val List(constructorCall) = constructorCallBlock.astChildren.isCall.nameExact("init").l + constructorCall.methodFullName shouldBe "SwiftTest.Foo.init:()->SwiftTest.Foo" + constructorCall.typeFullName shouldBe "SwiftTest.Foo" + constructorCall.signature shouldBe "()->SwiftTest.Foo" + constructorCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH + constructorCall.argument.isIdentifier.name.l shouldBe List("0") + constructorCall.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo") + val List(returnId) = constructorCallBlock.astChildren.isIdentifier.nameExact("0").l + returnId.typeFullName shouldBe "SwiftTest.Foo" + } + + "be correct for simple calls to functions from extensions" in { + val testCode = + """ + |extension Foo { + | func foo() {} + | func bar() {} + |} + |class Foo { + | func main() { + | foo() + | self.bar() + | } + |} + |""".stripMargin + pendingUntilFixed { + val cpg = code(testCode) + + // These extension calls should be static calls. + // Currently, there is no way to detect this as the swift-parser AST does not carry this information. + // Hence, they are treated as dynamic calls like regular method calls. + // We will leave this test in to document to current state and to remind us to fix this in the future. + // TODO: Fix this once we can detect extension method calls properly. (see similar test with compiler support below) + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + fooCall.signature shouldBe "" + fooCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH // Should be STATIC_DISPATCH for extension methods, but it is not + + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "Sources/main.swift:.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + barCall.signature shouldBe "" + barCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH // Should be STATIC_DISPATCH for extension methods, but it is not + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "Sources/main.swift:.Foo" + } + } + + "be correct for simple calls to functions from extensions with compiler support" in { + val testCode = + """ + |extension Foo { + | func foo() {} + | func bar() {} + |} + |class Foo { + | func main() { + | foo() + | self.bar() + | } + |} + |""".stripMargin + + pendingUntilFixed { + // These extension calls should be static calls. + // Currently, there is no way to detect this as the swift-parser AST does not carry this information. + // Hence, they are treated as dynamic calls like regular method calls. + // We will leave this test in to document to current state and to remind us to fix this in the future. + // TODO: Fix this once we can detect extension method calls properly. + // TODO: Extend the compiler info queries to be able to detect static calls reliably. + val cpg = codeWithSwiftSetup(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe "SwiftTest.Foo.foo:()->()" + fooCall.signature shouldBe "()->()" + fooCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH // Should be STATIC_DISPATCH for extension methods, but it is not + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "SwiftTest.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe "SwiftTest.Foo.bar:()->()" + barCall.signature shouldBe "()->()" + barCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH // Should be STATIC_DISPATCH for extension methods, but it is not + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo" + } + } + + "be correct for simple calls to functions from protocols" in { + val testCode = + """ + |protocol FooProtocol { + | func foo() + | func bar() + |} + |class Foo: FooProtocol { + | func foo() {} + | func bar() {} + | func main() { + | foo() + | self.bar() + | } + |} + |""".stripMargin + val cpg = code(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + fooCall.signature shouldBe "" + fooCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "Sources/main.swift:.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe x2cpg.Defines.DynamicCallUnknownFullName + barCall.signature shouldBe "" + barCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "Sources/main.swift:.Foo" + } + + "be correct for simple calls to functions from protocols with compiler support" in { + val testCode = + """ + |protocol FooProtocol { + | func foo() + | func bar() + |} + |class Foo: FooProtocol { + | func foo() {} + | func bar() {} + | func main() { + | foo() + | self.bar() + | } + |} + |""".stripMargin + val cpg = codeWithSwiftSetup(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe "SwiftTest.Foo.foo:()->()" + fooCall.signature shouldBe "()->()" + fooCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "SwiftTest.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe "SwiftTest.Foo.bar:()->()" + barCall.signature shouldBe "()->()" + barCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo" + } + + "be correct for simple calls to functions from multiple protocols with compiler support" in { + val testCode = + """ + |protocol FooProtocol { + | func foo() + | func bar() + |} + |protocol FooBarProtocol { + | func foobar() + |} + |class Foo: FooProtocol, FooBarProtocol { + | func foo() {} + | func bar() {} + | func foobar() {} + | func main() { + | foo() + | self.bar() + | foobar() + | } + |} + |""".stripMargin + val cpg = codeWithSwiftSetup(testCode) + + val List(fooCall) = cpg.call.nameExact("foo").l + fooCall.methodFullName shouldBe "SwiftTest.Foo.foo:()->()" + fooCall.signature shouldBe "()->()" + fooCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + val List(fooCallReceiver) = fooCall.receiver.isIdentifier.l + fooCallReceiver.name shouldBe "self" + fooCallReceiver.typeFullName shouldBe "SwiftTest.Foo" + + val List(barCall) = cpg.call.nameExact("bar").l + barCall.methodFullName shouldBe "SwiftTest.Foo.bar:()->()" + barCall.signature shouldBe "()->()" + barCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(barCallReceiverCall) = barCall.receiver.isIdentifier.l + barCallReceiverCall.name shouldBe "self" + barCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo" + + val List(foobarCall) = cpg.call.nameExact("foobar").l + foobarCall.methodFullName shouldBe "SwiftTest.Foo.foobar:()->()" + foobarCall.signature shouldBe "()->()" + foobarCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + + val List(foobarCallReceiverCall) = foobarCall.receiver.isIdentifier.l + foobarCallReceiverCall.name shouldBe "self" + foobarCallReceiverCall.typeFullName shouldBe "SwiftTest.Foo" + } + + "be correct for simple call to static function" in { + // TODO: extend the GsonTypeInfoReader to query for information whether the call is a call to a static function + val testCode = + """ + |func main() { + | Foo.staticFunc() + |} + |""".stripMargin + val cpg = code(testCode) + + val List(staticFuncCall) = cpg.call.nameExact("staticFunc").l + staticFuncCall.methodFullName shouldBe "Foo.staticFunc" + staticFuncCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH + staticFuncCall.argument shouldBe empty + } + + } + +} diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ClosureWithCompilerTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ClosureWithCompilerTests.scala index 41ea8411e9c3..8f5332438762 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ClosureWithCompilerTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ClosureWithCompilerTests.scala @@ -181,19 +181,33 @@ class ClosureWithCompilerTests extends SwiftCompilerSrc2CpgSuite { | func main() { | let compareResult = compare("1", "2") | } - |}""".stripMargin + |} + |Foo().main() + |""".stripMargin val cpg = codeWithSwiftSetup(testCode) + val compareClosureFullName = + "Sources/main.swift:.Foo.init.0:(Swift.String,Swift.String)->Swift.Bool" + + cpg.local.nameExact("compare") shouldBe empty - val List(compareClassLocal, compareFunctionLocal) = cpg.local.nameExact("compare").l - compareClassLocal.typeFullName shouldBe "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" - compareFunctionLocal.typeFullName shouldBe "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" + val List(fooConstructor) = cpg.method.isConstructor.fullNameExact("SwiftTest.Foo.init:()->SwiftTest.Foo").l + val List(compareAssignment) = fooConstructor.ast.isCall.isAssignment.l + val List(compareTarget) = compareAssignment.target.fieldAccess.l + compareTarget.code shouldBe "self.compare" + compareTarget.typeFullName shouldBe "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" + inside(compareTarget.argument.l) { case List(selfId: Identifier, fieldId: FieldIdentifier) => + selfId.typeFullName shouldBe "SwiftTest.Foo" + fieldId.code shouldBe "compare" + } + + val compareSource = compareAssignment.source.asInstanceOf[MethodRef] + compareSource.methodFullName shouldBe compareClosureFullName val List(compareResultLocal) = cpg.local.nameExact("compareResult").l compareResultLocal.typeFullName shouldBe "Swift.Bool" - val compareClosureFullName = "Sources/main.swift:.Foo.0:(Swift.String,Swift.String)->Swift.Bool" - val List(compareClosure) = cpg.method.fullNameExact(compareClosureFullName).l + val List(compareClosure) = cpg.method.fullNameExact(compareClosureFullName).l val List(compareClosureTypeDecl) = cpg.typeDecl.fullNameExact(compareClosureFullName).l compareClosureTypeDecl.inheritsFromTypeFullName.l shouldBe List( "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" @@ -207,12 +221,26 @@ class ClosureWithCompilerTests extends SwiftCompilerSrc2CpgSuite { compareClosureCall.name shouldBe "single_apply" compareClosure.signature shouldBe "(Swift.String,Swift.String)->Swift.Bool" compareClosureCall.methodFullName shouldBe "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>.single_apply:(Swift.String,Swift.String)->Swift.Bool" - compareClosureCall.receiver.isIdentifier.name.l shouldBe List("compare") - compareClosureCall.receiver.isIdentifier.typeFullName.l shouldBe List( - "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" - ) + + val List(compareClosureCallReceiver) = compareClosureCall.receiver.fieldAccess.l + compareClosureCallReceiver.code shouldBe "self.compare" + compareClosureCallReceiver.typeFullName shouldBe "Swift.Function<(Swift.String,Swift.String)->Swift.Bool>" + inside(compareClosureCallReceiver.argument.l) { case List(selfId: Identifier, fieldId: FieldIdentifier) => + selfId.typeFullName shouldBe "SwiftTest.Foo" + fieldId.code shouldBe "compare" + } compareClosureCall.argument(1).code shouldBe """"1"""" compareClosureCall.argument(2).code shouldBe """"2"""" + + val List(mainCall) = cpg.call.nameExact("main").l + mainCall.methodFullName shouldBe "SwiftTest.Foo.main:()->()" + mainCall.signature shouldBe "()->()" + mainCall.typeFullName shouldBe "()" + val List(fooConstructorCall) = mainCall.receiver.isBlock.astChildren.isCall.nameExact("init").l + fooConstructorCall.methodFullName shouldBe "SwiftTest.Foo.init:()->SwiftTest.Foo" + fooConstructorCall.signature shouldBe "()->SwiftTest.Foo" + fooConstructorCall.typeFullName shouldBe "SwiftTest.Foo" + fooConstructorCall._methodViaCallOut.l shouldBe List(fooConstructor) } "create type decls and bindings correctly (closure as function parameter)" in { diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DirectiveTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DirectiveTests.scala index d819a20cd61e..125891d4a58f 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DirectiveTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DirectiveTests.scala @@ -61,7 +61,7 @@ class DirectiveTests extends AstSwiftSrc2CpgSuite { |.baz() |#endif |""".stripMargin).withConfig(Config(Set("CONFIG1"))) - cpg.call.code.l shouldBe List("foo.bar()", "foo.bar") + cpg.call.code.l shouldBe List("foo.bar()") } "testConfigExpression7 (call behind define with trailing call)" in { @@ -74,13 +74,7 @@ class DirectiveTests extends AstSwiftSrc2CpgSuite { |#endif |.oneMore(x: 1) |""".stripMargin).withConfig(Config(Set("CONFIG1"))) - cpg.call.code.l shouldBe List( - "(0 = foo.bar()).oneMore(x: 1)", - "(0 = foo.bar()).oneMore", - "(0 = foo.bar())", - "foo.bar()", - "foo.bar" - ) + cpg.call.code.l shouldBe List("foo.bar().oneMore(x: 1)", "foo.bar()") } "testSourceLocation1" ignore { diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExpressionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExpressionTests.scala index b37cd43033a0..d7d4c52dce4a 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExpressionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExpressionTests.scala @@ -264,7 +264,7 @@ class ExpressionTests extends AstSwiftSrc2CpgSuite { controlStruct.code should startWith("if .random() {") controlStruct.controlStructureType shouldBe ControlStructureTypes.IF inside(controlStruct.condition.l) { case List(cndNode) => - cndNode.code shouldBe ".random()" + cndNode.code shouldBe "self.random()" } controlStruct.whenTrue.code.l shouldBe List("0") controlStruct.whenFalse.code.l shouldBe List("1") diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExtensionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExtensionTests.scala index 33c74519fa0c..da7d6dc6d53b 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExtensionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/ExtensionTests.scala @@ -38,7 +38,7 @@ class ExtensionTests extends AstSwiftSrc2CpgSuite { "create stable ASTs from multiple files" in { val List(fooTypeDecl) = cpg.typeDecl.fullNameExact("Foo.swift:.Foo").l fooTypeDecl.name shouldBe "Foo" - fooTypeDecl.member.name.l.sorted shouldBe List("a", "b", "c", "someFunc") + fooTypeDecl.member.name.l.sorted shouldBe List("a", "b", "c") val List(fooConstructor) = fooTypeDecl.method.isConstructor.l fooConstructor.fullName shouldBe s"Foo.swift:.Foo.init:()->Foo.swift:.Foo" fooConstructor.block.astChildren.assignment.code.l.sorted shouldBe List("var a = 1") @@ -48,7 +48,7 @@ class ExtensionTests extends AstSwiftSrc2CpgSuite { val List(fooExt1TypeDecl) = cpg.typeDecl.fullNameExact("Ext1.swift:.Foo").l fooExt1TypeDecl.name shouldBe "Foo" - fooExt1TypeDecl.member.name.l.sorted shouldBe List("d", "e", "f", "someOtherFunc") + fooExt1TypeDecl.member.name.l.sorted shouldBe List("d", "e", "f") val List(fooExt1TypeDeclConstructor) = fooExt1TypeDecl.method.isConstructor.l fooExt1TypeDeclConstructor.fullName shouldBe s"Ext1.swift:.Foo.init:()->Ext1.swift:.Foo" fooExt1TypeDeclConstructor.block.astChildren.assignment.code.l.sorted shouldBe List("var d = 0.0") diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/SimpleAstCreationPassTest.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/SimpleAstCreationPassTest.scala index 96f8f36d3df4..148bb91ac405 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/SimpleAstCreationPassTest.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/SimpleAstCreationPassTest.scala @@ -201,6 +201,269 @@ class SimpleAstCreationPassTest extends AstSwiftSrc2CpgSuite { n2.astIn.l shouldBe List(n1) } + "have correct structure for implicit self access" in { + // Swift does not allow to access any member / function of the outer class instance. + // Something like `Foo.self.f` in Foo.Bar.bar is not possible. + // Hence, we do not need to implement or test this here. + val cpg = code(""" + |class Foo { + | let f: String = "f" + | class Bar { + | let b = "b" + | func bar() { + | let f: Int = 1 + | handleB(b) + | handleF(f) + | } + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + cpg.typeDecl.nameExact("Bar").member.name.l shouldBe List("b") + val List(bAccess) = cpg.call.nameExact("handleB").argument.fieldAccess.l + bAccess.code shouldBe "self.b" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo.Bar" + fieldName.canonicalName shouldBe "b" + } + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.Int + val fLocal = id._localViaRefOut.get + fLocal.name shouldBe "f" + fLocal.typeFullName shouldBe Defines.Int + } + } + + "have correct structure for implicit self access with shadowing a member in class" in { + val cpg = code(""" + |class Foo { + | let f: String = "f" + | func bar() { + | let f: Int = 1 + | handleF(f) + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.Int + val fLocal = id._localViaRefOut.get + fLocal.name shouldBe "f" + fLocal.typeFullName shouldBe Defines.Int + } + } + + "have correct structure for implicit self access with shadowing a variable from outer scope" in { + val cpg = code(""" + |let f: String = "a" + |class Foo { + | let f: String = "b" + | func bar() { + | handleF(f) + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + val List(bAccess) = cpg.call.nameExact("handleF").argument.fieldAccess.l + bAccess.code shouldBe "self.f" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo" + fieldName.canonicalName shouldBe "f" + } + } + + "have correct structure for implicit self access from within nested functions" in { + val cpg = code(""" + |let f: String = "a" + |class Foo { + | let f: String = "b" + | func foo() -> String { + | func bar() -> String { + | return handleF(f) + | } + | return bar() + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + val List(bAccess) = cpg.call.nameExact("handleF").argument.fieldAccess.l + bAccess.code shouldBe "self.f" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo" + fieldName.canonicalName shouldBe "f" + } + + val List(fooMethod) = cpg.method.nameExact("foo").l + val List(fooBlock) = fooMethod.astChildren.isBlock.l + val List(barRef) = fooBlock.astChildren.isMethodRef.l + barRef.captureOut shouldBe empty + + val List(barMethod) = cpg.method.nameExact("bar").l + val List(barMethodBlock) = barMethod.astChildren.isBlock.l + barMethodBlock.astChildren.isLocal.name("f") shouldBe empty + } + + "have correct structure for implicit self access from within nested functions with shadowing" in { + val cpg = code(""" + |let f: String = "a" + |class Foo { + | let f: String = "b" + | func foo() -> String { + | let f: String = "c" + | func bar() -> String { + | return handleF(f) + | } + | return bar() + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.String + val fLocal = id._localViaRefOut.get + fLocal.name shouldBe "f" + fLocal.typeFullName shouldBe Defines.String + } + val List(fooMethod) = cpg.method.nameExact("foo").l + val List(fooBlock) = fooMethod.astChildren.isBlock.l + val List(fooLocalF) = fooBlock.astChildren.isLocal.nameExact("f").l + val List(barRef) = fooBlock.astChildren.isMethodRef.l + val List(closureBindingF) = barRef.captureOut.l + closureBindingF.closureBindingId shouldBe Option("Test0.swift:.Foo.foo.bar:f") + closureBindingF.refOut.head shouldBe fooLocalF + closureBindingF.evaluationStrategy shouldBe EvaluationStrategies.BY_REFERENCE + + val List(barMethod) = cpg.method.nameExact("bar").l + val List(barMethodBlock) = barMethod.astChildren.isBlock.l + val List(barLocal) = barMethodBlock.astChildren.isLocal.name("f").l + barLocal.closureBindingId shouldBe Option("Test0.swift:.Foo.foo.bar:f") + + val List(identifierF) = barMethodBlock.ast.isIdentifier.nameExact("f").l + identifierF.refOut.head shouldBe barLocal + } + + "have correct structure for implicit self access except for variable in closure" in { + val cpg = code(""" + |class Foo { + | let f: String = "f" + | class Bar { + | let b = "b" + | func foo() { + | let compare = { (s1: String, s2: String) -> Bool in + | let f: Int = 1 + | handleB(b) + | handleF(f) + | return s1 > s2 + | } + | } + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + cpg.typeDecl.nameExact("Bar").member.name.l shouldBe List("b") + val List(bAccess) = cpg.call.nameExact("handleB").argument.fieldAccess.l + bAccess.code shouldBe "self.b" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo.Bar" + fieldName.canonicalName shouldBe "b" + } + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.Int + val fLocal = id._localViaRefOut.get + fLocal.name shouldBe "f" + fLocal.typeFullName shouldBe Defines.Int + } + + val List(fooMethod) = cpg.method.nameExact("foo").l + val List(fooBlock) = fooMethod.astChildren.isBlock.l + val List(compareRef) = fooBlock.ast.isMethodRef.l + compareRef.captureOut shouldBe empty + + val List(compareClosure) = cpg.method.nameExact("0").l + val List(compareClosureBlock) = compareClosure.astChildren.isBlock.l + val List(compareClosureLocal) = compareClosureBlock.astChildren.isLocal.name("f").l + val List(identifierFFromLocalDecl, identifierFInCall) = compareClosureBlock.ast.isIdentifier.nameExact("f").l + identifierFFromLocalDecl.refOut.head shouldBe compareClosureLocal + identifierFFromLocalDecl.lineNumber shouldBe Some(8) + identifierFInCall.refOut.head shouldBe compareClosureLocal + identifierFInCall.lineNumber shouldBe Some(10) + } + + "have correct structure for implicit self access except for parameters" in { + val cpg = code(""" + |class Foo { + | let f: String = "f" + | class Bar { + | let b = "b" + | func bar(f: Int) { + | handleB(b) + | handleF(f) + | } + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + cpg.typeDecl.nameExact("Bar").member.name.l shouldBe List("b") + val List(bAccess) = cpg.call.nameExact("handleB").argument.fieldAccess.l + bAccess.code shouldBe "self.b" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo.Bar" + fieldName.canonicalName shouldBe "b" + } + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.Int + inside(id.refsTo.l) { case List(p: MethodParameterIn) => + p.name shouldBe "f" + p.typeFullName shouldBe Defines.Int + } + } + } + + "have correct structure for implicit self access except for parameters in closure" in { + val cpg = code(""" + |class Foo { + | let f: String = "f" + | class Bar { + | let b = "b" + | func bar() { + | let compare = { (f: String, g: String) -> Bool in + | handleB(b) + | handleF(f) + | return f > g + | } + | } + | } + |} + |""".stripMargin) + cpg.typeDecl.nameExact("Foo").member.name.l shouldBe List("f") + cpg.typeDecl.nameExact("Bar").member.name.l shouldBe List("b") + val List(bAccess) = cpg.call.nameExact("handleB").argument.fieldAccess.l + bAccess.code shouldBe "self.b" + inside(bAccess.argument.l) { case List(id: Identifier, fieldName: FieldIdentifier) => + id.name shouldBe "self" + id.typeFullName shouldBe "Test0.swift:.Foo.Bar" + fieldName.canonicalName shouldBe "b" + } + inside(cpg.call.nameExact("handleF").argument(1).l) { case List(id: Identifier) => + id.name shouldBe "f" + id.typeFullName shouldBe Defines.String + inside(id.refsTo.l) { case List(p: MethodParameterIn) => + p.name shouldBe "f" + p.typeFullName shouldBe Defines.String + } + } + } + "have correct structure for function in accessor block" in { val cpg = code(""" |public protocol Foo { @@ -222,7 +485,7 @@ class SimpleAstCreationPassTest extends AstSwiftSrc2CpgSuite { thisBar.name shouldBe Operators.fieldAccess thisBar.typeFullName shouldBe "Swift.Int" inside(thisBar.argument.l) { case List(base: Identifier, field: FieldIdentifier) => - base.name shouldBe "this" + base.name shouldBe "self" base.typeFullName shouldBe "Test0.swift:.Foo" field.canonicalName shouldBe "bar" } @@ -231,7 +494,7 @@ class SimpleAstCreationPassTest extends AstSwiftSrc2CpgSuite { val List(setParam) = set.parameter.l setParam.name shouldBe "newValue" setParam.typeFullName shouldBe "Swift.Int" - set.body.astChildren.isCall.code.l shouldBe List("this.bar = newValue") + set.body.astChildren.isCall.code.l shouldBe List("self.bar = newValue") willSet.fullName shouldBe "Test0.swift:.Foo.bar.willSet:Swift.Int" didSet.fullName shouldBe "Test0.swift:.Foo.bar.didSet:Swift.Int" diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionTests.scala index a7653b5a54a5..5be7db3294fd 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionTests.scala @@ -79,18 +79,7 @@ class ClassExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFoo) = cpg.typeDecl.nameExact("Foo").l typeDeclFoo.fullName shouldBe "Test0.swift:.Foo" - typeDeclFoo.member.name.l.sorted shouldBe List( - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "someFunc", - "someMethod", - "square" - ) + typeDeclFoo.member.name.l.sorted shouldBe List("a", "b", "c", "d", "e", "f", "g") typeDeclFoo.inheritsFromTypeFullName.sorted.l shouldBe List("Bar", "Test0.swift:.Foo") typeDeclFoo.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) @@ -109,7 +98,7 @@ class ClassExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFooExtension) = cpg.typeDecl.nameExact("Foo").l typeDeclFooExtension.fullName shouldBe "Test0.swift:.Foo" - typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j", "someOtherFunc") + typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j") typeDeclFooExtension.inheritsFromTypeFullName.l shouldBe List("AnotherProtocol", "SomeProtocol") typeDeclFooExtension.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) @@ -145,6 +134,70 @@ class ClassExtensionTests extends SwiftSrc2CpgSuite { testClassExtension(cpg) } + "resolve self correctly" in { + val cpg = code(s""" + |class Foo { + | var a: String + | var b: Int = 1 + |} + |extension Foo { + | var c: String + | + | func foo() { + | print(a) + | var b: String = "b" + | print(b) + | print(c) + | } + |} + |""".stripMargin) + cpg.call.codeExact("print(a)").argument.isCall.code.l shouldBe List("self.a") + cpg.call.codeExact("print(a)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(a)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List( + "Test0.swift:.Foo" + ) + + cpg.call.codeExact("print(b)").argument.isIdentifier.typeFullName.l shouldBe List("Swift.String") + + cpg.call.codeExact("print(c)").argument.isCall.code.l shouldBe List("self.c") + cpg.call.codeExact("print(c)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(c)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List( + "Test0.swift:.Foo" + ) + } + + "resolve self correctly (extension first)" in { + val cpg = code(s""" + |extension Foo { + | var c: String + | + | func foo() { + | print(a) + | var b: String = "b" + | print(b) + | print(c) + | } + |} + |class Foo { + | var a: String + | var b: Int = 1 + |} + |""".stripMargin) + cpg.call.codeExact("print(a)").argument.isCall.code.l shouldBe List("self.a") + cpg.call.codeExact("print(a)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(a)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List( + "Test0.swift:.Foo" + ) + + cpg.call.codeExact("print(b)").argument.isIdentifier.typeFullName.l shouldBe List("Swift.String") + + cpg.call.codeExact("print(c)").argument.isCall.code.l shouldBe List("self.c") + cpg.call.codeExact("print(c)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(c)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List( + "Test0.swift:.Foo" + ) + } + } } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionWithCompilerTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionWithCompilerTests.scala new file mode 100644 index 000000000000..903cfd037d8f --- /dev/null +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ClassExtensionWithCompilerTests.scala @@ -0,0 +1,46 @@ +package io.joern.swiftsrc2cpg.passes.inheritance + +import io.joern.swiftsrc2cpg.testfixtures.SwiftCompilerSrc2CpgSuite +import io.shiftleft.semanticcpg.language.* + +class ClassExtensionWithCompilerTests extends SwiftCompilerSrc2CpgSuite { + + "ClassExtensionWithCompilerTests" should { + + "resolve self correctly (separate files)" in { + val cpg = codeWithSwiftSetup( + s""" + |class Foo { + | var a = "a" + | var b = 1 + | var c = "c" + |}""".stripMargin, + "main.swift" + ).moreCode( + s""" + |extension Foo { + | // var c = "c" --> compiler says: extensions must not contain stored properties + | + | func foo() { + | print(a) + | var b = "b" + | print(b) + | print(c) + | } + |}""".stripMargin, + "SwiftTest/Sources/FooExt.swift" + ) + cpg.call.codeExact("print(a)").argument.isCall.code.l shouldBe List("self.a") + cpg.call.codeExact("print(a)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(a)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo") + + cpg.call.codeExact("print(b)").argument.isIdentifier.typeFullName.l shouldBe List("Swift.String") + + cpg.call.codeExact("print(c)").argument.isCall.code.l shouldBe List("self.c") + cpg.call.codeExact("print(c)").argument.isCall.typeFullName.l shouldBe List("Swift.String") + cpg.call.codeExact("print(c)").argument.isCall.argument.isIdentifier.typeFullName.l shouldBe List("SwiftTest.Foo") + } + + } + +} diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/EnumerationExtensionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/EnumerationExtensionTests.scala index 4f480911aa4b..07f081a490bb 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/EnumerationExtensionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/EnumerationExtensionTests.scala @@ -81,23 +81,7 @@ class EnumerationExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFoo) = cpg.typeDecl.nameExact("Foo").l typeDeclFoo.fullName shouldBe "Test0.swift:.Foo" - typeDeclFoo.member.name.l.sorted shouldBe List( - "a", - "b", - "c", - "c1", - "c2", - "c3", - "c4", - "d", - "e", - "f", - "g", - "someFunc", - "someMethod", - "square", - "tuple" - ) + typeDeclFoo.member.name.l.sorted shouldBe List("a", "b", "c", "c1", "c2", "c3", "c4", "d", "e", "f", "g", "tuple") typeDeclFoo.inheritsFromTypeFullName.l shouldBe List("Bar", "Test0.swift:.Foo") typeDeclFoo.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) @@ -117,7 +101,7 @@ class EnumerationExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFooExtension) = cpg.typeDecl.nameExact("Foo").l typeDeclFooExtension.fullName shouldBe "Test0.swift:.Foo" - typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j", "someOtherFunc") + typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j") typeDeclFooExtension.inheritsFromTypeFullName.l shouldBe List("AnotherProtocol", "SomeProtocol") typeDeclFooExtension.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ProtocolExtensionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ProtocolExtensionTests.scala index a6fd74d7ce85..20b4ba247223 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ProtocolExtensionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/ProtocolExtensionTests.scala @@ -79,18 +79,7 @@ class ProtocolExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFoo) = cpg.typeDecl.nameExact("Foo").l typeDeclFoo.fullName shouldBe "Test0.swift:.Foo" - typeDeclFoo.member.name.l.sorted shouldBe List( - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "someFunc", - "someMethod", - "square" - ) + typeDeclFoo.member.name.l.sorted shouldBe List("a", "b", "c", "d", "e", "f", "g") typeDeclFoo.inheritsFromTypeFullName.l shouldBe List("Bar", "Test0.swift:.Foo") typeDeclFoo.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) @@ -109,7 +98,7 @@ class ProtocolExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFooExtension) = cpg.typeDecl.nameExact("Foo").l typeDeclFooExtension.fullName shouldBe "Test0.swift:.Foo" - typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j", "someOtherFunc") + typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j") typeDeclFooExtension.inheritsFromTypeFullName.l shouldBe List("AnotherProtocol", "SomeProtocol") typeDeclFooExtension.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/StructureExtensionTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/StructureExtensionTests.scala index 3e7d5d5875f0..a81c4971376f 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/StructureExtensionTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/inheritance/StructureExtensionTests.scala @@ -77,18 +77,7 @@ class StructureExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFoo) = cpg.typeDecl.nameExact("Foo").l typeDeclFoo.fullName shouldBe "Test0.swift:.Foo" - typeDeclFoo.member.name.l.sorted shouldBe List( - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "someFunc", - "someMethod", - "square" - ) + typeDeclFoo.member.name.l.sorted shouldBe List("a", "b", "c", "d", "e", "f", "g") typeDeclFoo.inheritsFromTypeFullName.l shouldBe List("Bar", "Test0.swift:.Foo") typeDeclFoo.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) @@ -107,7 +96,7 @@ class StructureExtensionTests extends SwiftSrc2CpgSuite { val List(typeDeclFooExtension) = cpg.typeDecl.nameExact("Foo").l typeDeclFooExtension.fullName shouldBe "Test0.swift:.Foo" - typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j", "someOtherFunc") + typeDeclFooExtension.member.name.l.sorted shouldBe List("h", "i", "j") typeDeclFooExtension.inheritsFromTypeFullName.l shouldBe List("AnotherProtocol", "SomeProtocol") typeDeclFooExtension.modifier.modifierType.l shouldBe List(ModifierTypes.PRIVATE) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/testfixtures/SwiftCompilerSrc2CpgSuite.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/testfixtures/SwiftCompilerSrc2CpgSuite.scala index 900c3a171a58..5544ccaa2387 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/testfixtures/SwiftCompilerSrc2CpgSuite.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/testfixtures/SwiftCompilerSrc2CpgSuite.scala @@ -22,10 +22,19 @@ class SwiftCompilerSrc2CpgSuite(fileSuffix: String = ".swift") override def code(code: String): SwiftCompilerTestCpg = super.code(code, TestFilename) + override def code(code: String, fileName: String): SwiftCompilerTestCpg = + super.code(code, s"SwiftTest/Sources/$fileName") + protected def codeWithSwiftSetup(codeString: String): SwiftCompilerTestCpg = { code(codeString) .moreCode(PackageSwiftContent, PackageSwiftFilename) .withConfig(Config(swiftBuild = true)) } + protected def codeWithSwiftSetup(codeString: String, fileName: String): SwiftCompilerTestCpg = { + code(codeString, fileName) + .moreCode(PackageSwiftContent, PackageSwiftFilename) + .withConfig(Config(swiftBuild = true)) + } + } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerFullnameTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerFullnameTests.scala index 4d121e5570be..706eb6021519 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerFullnameTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerFullnameTests.scala @@ -33,7 +33,7 @@ class SwiftCompilerFullnameTests extends SwiftCompilerSrc2CpgSuite { mainTypeDecl.fullName shouldBe "SwiftTest.Main" val List(mainConstructor) = mainTypeDecl.ast.isMethod.isConstructor.l mainConstructor.fullName shouldBe "SwiftTest.Main.init:()->SwiftTest.Main" - val List(mainConstructorCall) = cpg.call.nameExact("Main").l + val List(mainConstructorCall) = cpg.call.nameExact("init").l mainConstructorCall.methodFullName shouldBe "SwiftTest.Main.init:()->SwiftTest.Main" mainConstructor.fullName shouldBe mainConstructorCall.methodFullName diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerTests.scala index 06c075d46c67..881c0761c472 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/utils/SwiftCompilerTests.scala @@ -420,7 +420,7 @@ class SwiftCompilerTests extends AnyWordSpec with Matchers { ( "Main.swift", "dot_syntax_call_expr", - (127, 136), + (127, 138), Some("(from:Swift.String)->()"), Some("SwiftHelloWorldLib.HelloWorld.greet(from:Swift.String)->()") ), @@ -597,8 +597,10 @@ class SwiftCompilerTests extends AnyWordSpec with Matchers { Some("SwiftHelloWorldLib.HelloWorld.init()->SwiftHelloWorldLib.HelloWorld") ) ) - mainMappings should contain( - ("func_decl", (67, 155), Some("Swift.Void"), Some("SwiftHelloWorld.Main.main()->()")) + mainMappings should contain oneElementOf List( + ("func_decl", (67, 155), Some("Swift.Void"), Some("SwiftHelloWorld.Main.main()->()")), + // TODO: in preparation for Swift 6.2.x migration, Void changed to () just like in the function signature + ("func_decl", (67, 155), Some("()"), Some("SwiftHelloWorld.Main.main()->()")) ) mainMappings should contain(("var_decl", (102, 102), Some("SwiftHelloWorldLib.HelloWorld"), None)) diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/datastructures/VariableScopeManager.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/datastructures/VariableScopeManager.scala index 8e1aa519cb7e..a4aa514f5684 100644 --- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/datastructures/VariableScopeManager.scala +++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/datastructures/VariableScopeManager.scala @@ -36,7 +36,7 @@ object VariableScopeManager { /** Enumeration representing the types of scopes. */ enum ScopeType { - case MethodScope, BlockScope + case MethodScope, BlockScope, TypeDeclScope } /** Case class representing a method scope element. @@ -109,6 +109,15 @@ object VariableScopeManager { */ private case class BlockScopeElement(scopeNode: NewNode, surroundingScope: Option[ScopeElement]) extends ScopeElement + /** Case class representing a type declaration scope element. + * + * @param fullname + * The full name (qualified name) of the type declaration this scope represents. + * @param surroundingScope + * The surrounding scope, if any. + */ + private case class TypeDeclScopeElement(fullname: String, surroundingScope: Option[ScopeElement]) extends ScopeElement + } /** Class for managing variable scopes during CPG conversion. Provides methods for managing scope stacks, adding @@ -146,6 +155,17 @@ class VariableScopeManager { variableFromStack(stack, identifier).map { case (variableNodeId, tpe, _) => (variableNodeId, tpe) } } + /** Looks up a variable by its identifier in the current type decl scope stack. + * + * @param identifier + * The name of the variable. + * @return + * An optional String representing the type decl fullname + */ + private def lookupVariableInSurroundingTypeDel(identifier: String): Option[String] = { + variableFromTypeDeclStack(stack, identifier) + } + /** Checks if a variable is in the current method scope. * * @param identifier @@ -157,6 +177,27 @@ class VariableScopeManager { getEnclosingMethodScopeElement(stack).exists(_.nameToVariableNode.contains(identifier)) } + /** Checks whether a variable with the given identifier is declared in the type decl scope. + * + * This method inspects the current scope stack: + * - If the top-most scope is a `TypeDeclScopeElement` that contains `identifier`, returns `true`. + * - If a nearer (non-type) scope contains the identifier, returns `false` (the variable is shadowed by that + * scope). + * - Otherwise, searches surrounding type-declaration scopes to determine presence. + * + * @param identifier + * the variable name to check + * @return + * `true` if the variable is declared in a type-declaration scope reachable from the current stack + */ + def variableIsInTypeDeclScope(identifier: String): Boolean = { + stack.exists { + case typeDeclScope: TypeDeclScopeElement if typeDeclScope.nameToVariableNode.contains(identifier) => true + case other if other.nameToVariableNode.contains(identifier) => false + case other => lookupVariableInSurroundingTypeDel(identifier).isDefined + } + } + /** Pushes a new method scope onto the scope stack. * * @param methodFullName @@ -186,6 +227,15 @@ class VariableScopeManager { stack = Option(BlockScopeElement(scopeNode, stack)) } + /** Pushes a new type decl scope onto the scope stack. + * + * @param fullName + * The full name (fully qualified name) of the type decl + */ + def pushNewTypeDeclScope(fullName: String): Unit = { + stack = Option(TypeDeclScopeElement(fullName, stack)) + } + /** Pops the current scope from the scope stack. */ def popScope(): Unit = { stack = stack.flatMap(_.surroundingScope) @@ -313,7 +363,36 @@ class VariableScopeManager { protected def getEnclosingMethodScopeElement(scopeHead: Option[ScopeElement]): Option[MethodScopeElement] = { scopeHead.flatMap { case methodScope: MethodScopeElement => Some(methodScope) - case blockScope: BlockScopeElement => getEnclosingMethodScopeElement(blockScope.surroundingScope) + case other => getEnclosingMethodScopeElement(other.surroundingScope) + } + } + + /** Retrieves the enclosing type decl scope element from the given scope head. + * + * @param scopeHead + * The starting scope element. + * @return + * The enclosing type decl scope element, if any. + */ + private def getEnclosingTypeDeclScopeElement(scopeHead: Option[ScopeElement]): Option[TypeDeclScopeElement] = { + scopeHead.flatMap { + case typeDeclScope: TypeDeclScopeElement => Some(typeDeclScope) + case other => getEnclosingTypeDeclScopeElement(other.surroundingScope) + } + } + + /** Returns the full name of the enclosing type declaration, if any. + * + * Searches the current `stack` for a `TypeDeclScopeElement`. If the top of the stack is a `TypeDeclScopeElement`, + * its `fullname` is returned. Otherwise, the surrounding scopes are searched recursively. + * + * @return + * `Some(fullname)` of the enclosing type declaration, or `None` if none found. + */ + def getEnclosingTypeDeclFullName: Option[String] = { + stack.flatMap { + case typeDeclScope: TypeDeclScopeElement => Some(typeDeclScope.fullname) + case other => getEnclosingTypeDeclScopeElement(other.surroundingScope).map(_.fullname) } } @@ -360,8 +439,8 @@ class VariableScopeManager { .collect { case methodScope: MethodScopeElement => methodScope +: getAllEnclosingMethodScopeElements(methodScope.surroundingScope) - case blockScope: BlockScopeElement => - getAllEnclosingMethodScopeElements(blockScope.surroundingScope) + case other => + getAllEnclosingMethodScopeElements(other.surroundingScope) } .getOrElse(Seq.empty) } @@ -438,6 +517,32 @@ class VariableScopeManager { } } + /** Search the given scope `stack` for a surrounding type decl scope that declares `variableName`. + * + * Traversal rules: + * - If a nearer (non-type) scope (block or method) contains `variableName`, the search stops and `None` is + * returned because the name is shadowed by that nearer scope. + * - Otherwise the surrounding scopes are searched until a `TypeDeclScopeElement` that contains the name is found. + * + * @param stack + * The starting scope head to search (can be `None`). + * @param variableName + * The variable name to look up. + * @return + * `Some(fullname)` of the declaring type decl scope if found, otherwise `None`. + */ + private def variableFromTypeDeclStack(stack: Option[ScopeElement], variableName: String): Option[String] = { + stack.flatMap { + case blockScope: BlockScopeElement if blockScope.nameToVariableNode.contains(variableName) => None + case blockScope: BlockScopeElement => variableFromTypeDeclStack(blockScope.surroundingScope, variableName) + case methodScope: MethodScopeElement if methodScope.nameToVariableNode.contains(variableName) => None + case methodScope: MethodScopeElement => variableFromTypeDeclStack(methodScope.surroundingScope, variableName) + case typeDeclScope: TypeDeclScopeElement if typeDeclScope.nameToVariableNode.contains(variableName) => + Some(typeDeclScope.fullname) + case _ => None + } + } + /** Adds a variable to the appropriate scope in the stack. * * @param stack @@ -462,8 +567,9 @@ class VariableScopeManager { scopeType: ScopeType ): Unit = { val scopeToAddTo = scopeType match { - case ScopeType.MethodScope => getEnclosingMethodScopeElement(stack) - case _ => stack + case ScopeType.MethodScope => getEnclosingMethodScopeElement(stack) + case ScopeType.TypeDeclScope => getEnclosingTypeDeclScopeElement(stack) + case _ => stack } scopeToAddTo.foreach(_.addVariable(variableName, variableNode, tpe, evaluationStrategy)) } diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftInheritanceNamePass.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftInheritanceNamePass.scala index 265e33b0d4fa..aa5d689eda37 100644 --- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftInheritanceNamePass.scala +++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftInheritanceNamePass.scala @@ -9,7 +9,6 @@ import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal */ class SwiftInheritanceNamePass(cpg: Cpg) extends XInheritanceFullNamePass(cpg) { - override val pathSep: Char = ':' override val moduleName: String = NamespaceTraversal.globalNamespaceName override val fileExt: String = ".swift" diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeHintCallLinker.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeHintCallLinker.scala index e2f724368eec..3bef79cd36b9 100644 --- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeHintCallLinker.scala +++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeHintCallLinker.scala @@ -2,15 +2,5 @@ package io.joern.x2cpg.frontendspecific.swiftsrc2cpg import io.joern.x2cpg.passes.frontend.XTypeHintCallLinker import io.shiftleft.codepropertygraph.generated.Cpg -import io.shiftleft.codepropertygraph.generated.nodes.Call -import io.shiftleft.semanticcpg.language.* -class SwiftTypeHintCallLinker(cpg: Cpg) extends XTypeHintCallLinker(cpg) { - - override protected val pathSep = ":" - - override protected def calls: Iterator[Call] = cpg.call - .or(_.nameNot(".*", ".*"), _.name(".new")) - .filter(c => calleeNames(c).nonEmpty && c.callee.isEmpty) - -} +class SwiftTypeHintCallLinker(cpg: Cpg) extends XTypeHintCallLinker(cpg) diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeRecovery.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeRecovery.scala index 83964e72d0ba..d3bcb0a38139 100644 --- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeRecovery.scala +++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/frontendspecific/swiftsrc2cpg/SwiftTypeRecovery.scala @@ -35,12 +35,10 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, import io.joern.x2cpg.passes.frontend.XTypeRecovery.AllNodeTypesFromNodeExt - override protected val pathSep = ":" - /** A heuristic method to determine if a call is a constructor or not. */ override protected def isConstructor(c: Call): Boolean = { - c.name.endsWith("factory") && c.inCall.astParent.headOption.exists(_.isInstanceOf[Block]) + isConstructor(c.name) && c.inCall.astParent.headOption.exists(_.isInstanceOf[Block]) } override protected def isConstructor(name: String): Boolean = name == "init" @@ -65,8 +63,8 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, override protected def prepopulateSymbolTable(): Unit = { super.prepopulateSymbolTable() - cu.ast.isMethod.foreach(f => symbolTable.put(CallAlias(f.name, Option("this")), Set(f.fullName))) - (cu.ast.isParameter.whereNot(_.nameExact("this")) ++ cu.ast.isMethod.methodReturn).filter(hasTypes).foreach { p => + cu.ast.isMethod.foreach(f => symbolTable.put(CallAlias(f.name, Option("self")), Set(f.fullName))) + (cu.ast.isParameter.whereNot(_.nameExact("self")) ++ cu.ast.isMethod.methodReturn).filter(hasTypes).foreach { p => val resolvedHints = p.getKnownTypes .map { t => t.split("\\.").headOption match { @@ -77,7 +75,7 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, } .flatMap { case (t, ts) if Set(t) == ts => Set(t) - case (_, ts) => ts.map(_.replaceAll("\\.(?!swift:)", pathSep.toString)) + case (_, ts) => ts.map(_.replaceAll("\\.(?!swift:)", pathSep)) } p match { case _: MethodParameterIn => symbolTable.put(p, resolvedHints) @@ -113,7 +111,7 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, } lazy val possibleConstructorPointer = newChildren.astChildren.isFieldIdentifier - .map(f => CallAlias(f.canonicalName, Option("this"))) + .map(f => CallAlias(f.canonicalName, Option("self"))) .headOption match { case Some(fi) => symbolTable.get(fi) case None => Set.empty[String] @@ -153,8 +151,8 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, if (symbolTable.contains(LocalVar(fieldName))) { val fieldTypes = symbolTable.get(LocalVar(fieldName)) symbolTable.append(i, fieldTypes) - } else if (symbolTable.contains(CallAlias(fieldName, Option("this")))) { - symbolTable.get(CallAlias(fieldName, Option("this"))) + } else if (symbolTable.contains(CallAlias(fieldName, Option("self")))) { + symbolTable.get(CallAlias(fieldName, Option("self"))) } else { super.associateInterproceduralTypes( i: Identifier, @@ -171,20 +169,20 @@ private class RecoverForSwiftFile(cpg: Cpg, cu: File, builder: DiffGraphBuilder, m: MethodRef, rec: Option[String] = None ): Set[String] = - super.visitIdentifierAssignedToMethodRef(i, m, Option("this")) + super.visitIdentifierAssignedToMethodRef(i, m, Option("self")) override protected def visitIdentifierAssignedToTypeRef( i: Identifier, t: TypeRef, rec: Option[String] = None ): Set[String] = - super.visitIdentifierAssignedToTypeRef(i, t, Option("this")) + super.visitIdentifierAssignedToTypeRef(i, t, Option("self")) override protected def postSetTypeInformation(): Unit = { - // often there are "this" identifiers with type hints but this can be set to a type hint if they meet the criteria + // often there are "self" identifiers with type hints but this can be set to a type hint if they meet the criteria cu.method .flatMap(_._identifierViaContainsOut) - .nameExact("this") + .nameExact("self") .where(_.typeFullNameExact(Defines.Any)) .filterNot(_.dynamicTypeHintFullName.isEmpty) .foreach(setTypeFromTypeHints)