Repository: joernio/joern
Pull request #5371 (2025-03-13 12:26:43)

[c2cpg] Simplify for-loop lowering
More in line with javasrc2cpg and kotlin2cpg now. Was too jssrc-ish.


Patch:
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreator.scala
index 92bfda5e6..76bb61b0b 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreator.scala
@@ -23,7 +23,7 @@ class AstCreator(
   val cdtAst: IASTTranslationUnit,
   val headerFileFinder: HeaderFileFinder,
   val file2OffsetTable: ConcurrentHashMap[String, Array[Int]]
-) extends AstCreatorBase(filename)(config.schemaValidation)
+) extends AstCreatorBase[IASTNode, AstCreator](filename)(config.schemaValidation)
     with AstForTypesCreator
     with AstForFunctionsCreator
     with AstForPrimitivesCreator
@@ -33,8 +33,7 @@ class AstCreator(
     with AstNodeBuilder
     with AstCreatorHelper
     with FullNameProvider
-    with MacroHandler
-    with X2CpgAstNodeBuilder[IASTNode, AstCreator] {
+    with MacroHandler {
 
   protected implicit val schemaValidation: ValidationMode = config.schemaValidation
 
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
index 66ea853d2..8d6c4ebe0 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
@@ -1,7 +1,6 @@
 package io.joern.c2cpg.astcreation
 
-import io.joern.x2cpg.Ast
-import io.joern.x2cpg.SourceFiles
+import io.joern.x2cpg.{Ast, AstNodeBuilder, SourceFiles}
 import io.joern.x2cpg.utils.IntervalKeyPool
 import io.joern.x2cpg.utils.NodeBuilders
 import io.joern.x2cpg.utils.NodeBuilders.newDependencyNode
@@ -434,7 +433,7 @@ trait AstCreatorHelper { this: AstCreator =>
     variableName: String,
     tpe: String
   ): NewLocal = {
-    val local = localNodeWithExplicitPositionInfo(variableName, variableName, tpe).order(0)
+    val local = AstNodeBuilder.localNodeWithExplicitPositionInfo(variableName, variableName, tpe).order(0)
     diffGraph.addEdge(methodScopeNodeId, local, EdgeTypes.AST)
     local
   }
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForStatementsCreator.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForStatementsCreator.scala
index 7b255c60a..7ba348d1d 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForStatementsCreator.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForStatementsCreator.scala
@@ -363,8 +363,7 @@ trait AstForStatementsCreator { this: AstCreator =>
     *
     * to:
     *
-    * { var _iterator = <operator>.iterator(arr); var _result; var i; while (!(_result = _iterator.next()).done) { i =
-    * _result.value; body } }
+    * { var _iterator = arr.iterator(); var i; while (_iterator.hasNext()) { i = _iterator.next(); body } }
     */
   private def astForForEachStatementWithIdentifier(forStmt: ICPPASTRangeBasedForStatement): Ast = {
     // surrounding block:
@@ -373,48 +372,42 @@ trait AstForStatementsCreator { this: AstCreator =>
 
     val collection     = forStmt.getInitializerClause
     val collectionName = code(collection)
-    val collectionType = registerType(typeFor(forStmt.getInitializerClause))
     val idType         = registerType(typeFor(forStmt.getDeclaration))
+    val collectionType = scope.lookupVariable(collectionName).map(_._2).getOrElse(registerType(typeFor(collection)))
 
     // iterator assignment:
     val iteratorName      = uniqueName("", "", "iterator")._1
     val iteratorLocalNode = localNode(forStmt, iteratorName, iteratorName, registerType(Defines.Iterator)).order(0)
-    val iteratorNode      = this.identifierNode(forStmt, iteratorName, iteratorName, Defines.Iterator)
+    val iteratorNode      = identifierNode(forStmt, iteratorName, iteratorName, Defines.Iterator)
     diffGraph.addEdge(blockNode, iteratorLocalNode, EdgeTypes.AST)
     scope.addVariableReference(iteratorName, iteratorNode, Defines.Iterator, EvaluationStrategies.BY_REFERENCE)
 
     val iteratorCall =
-      // TODO: add operator to schema
       callNode(
         forStmt,
-        s"<operator>.iterator($collectionName)",
-        "<operator>.iterator",
-        "<operator>.iterator",
-        DispatchTypes.STATIC_DISPATCH
+        s"$collectionName.iterator()",
+        "iterator",
+        s"${Defines.UnresolvedNamespace}.iterator:${Defines.Iterator}()",
+        DispatchTypes.DYNAMIC_DISPATCH,
+        Some(s"${Defines.Iterator}()"),
+        Some(Defines.Iterator)
       )
 
-    val objectKeysCallArgs = List(astForNode(collection))
-    val objectKeysCallAst  = callAst(iteratorCall, objectKeysCallArgs)
+    val iteratorCallBase = astForNode(collection)
+    val iteratorCallAst  = callAst(iteratorCall, base = Some(iteratorCallBase))
 
     val iteratorAssignmentNode =
       callNode(
         forStmt,
-        s"$iteratorName = <operator>.iterator($collectionName)",
+        s"$iteratorName = ${iteratorCall.code}",
         Operators.assignment,
         Operators.assignment,
         DispatchTypes.STATIC_DISPATCH
       )
 
-    val iteratorAssignmentArgs = List(Ast(iteratorNode), objectKeysCallAst)
+    val iteratorAssignmentArgs = List(Ast(iteratorNode), iteratorCallAst)
     val iteratorAssignmentAst  = callAst(iteratorAssignmentNode, iteratorAssignmentArgs)
 
-    // result:
-    val resultName      = uniqueName("", "", "result")._1
-    val resultLocalNode = localNode(forStmt, resultName, resultName, idType).order(0)
-    val resultNode      = identifierNode(forStmt, resultName, resultName, idType)
-    diffGraph.addEdge(blockNode, resultLocalNode, EdgeTypes.AST)
-    scope.addVariableReference(resultName, resultNode, idType, EvaluationStrategies.BY_REFERENCE)
-
     // loop variable:
     val loopVariableName = forStmt.getDeclaration match {
       case decl: IASTSimpleDeclaration =>
@@ -438,81 +431,49 @@ trait AstForStatementsCreator { this: AstCreator =>
     val testCallNode =
       callNode(
         forStmt,
-        s"!($resultName = $iteratorName.next()).done",
-        Operators.not,
-        Operators.not,
-        DispatchTypes.STATIC_DISPATCH
-      )
-
-    val doneBaseNode =
-      callNode(
-        forStmt,
-        s"($resultName = $iteratorName.next())",
-        Operators.assignment,
-        Operators.assignment,
-        DispatchTypes.STATIC_DISPATCH
+        s"$iteratorName.hasNext()",
+        "hasNext",
+        s"${Defines.Iterator}.hasNext:bool()",
+        DispatchTypes.DYNAMIC_DISPATCH,
+        Some("bool()"),
+        Some(registerType("bool"))
       )
 
-    val lhsNode = identifierNode(forStmt, resultName, resultName, idType)
-
-    val rhsNode =
-      callNode(forStmt, s"$iteratorName.next()", "next", s"${Defines.Iterator}.next", DispatchTypes.DYNAMIC_DISPATCH)
-
-    val nextBaseNode = identifierNode(forStmt, iteratorName, iteratorName, collectionType)
+    val hasNextReceiverNode = identifierNode(forStmt, iteratorName, iteratorName, Defines.Iterator)
+    scope.addVariableReference(iteratorName, hasNextReceiverNode, Defines.Iterator, EvaluationStrategies.BY_REFERENCE)
 
-    val nextMemberNode = fieldIdentifierNode(forStmt, "next", "next")
+    val testAst = callAst(testCallNode, base = Option(Ast(hasNextReceiverNode)))
 
-    val op     = Operators.fieldAccess
-    val nextMa = callNode(forStmt, s"$iteratorName.next", op, op, DispatchTypes.STATIC_DISPATCH, None, Some(idType))
-    val nextReceiverNode = callAst(nextMa, List(Ast(nextBaseNode), Ast(nextMemberNode)))
-
-    val thisNextNode = identifierNode(forStmt, iteratorName, iteratorName, collectionType)
-
-    val rhsArgs = List(Ast(thisNextNode))
-    val rhsAst  = callAst(rhsNode, rhsArgs, receiver = Option(nextReceiverNode))
-
-    val doneBaseArgs = List(Ast(lhsNode), rhsAst)
-    val doneBaseAst  = callAst(doneBaseNode, doneBaseArgs)
-    Ast.storeInDiffGraph(doneBaseAst, diffGraph)
-
-    val doneMemberNode = fieldIdentifierNode(forStmt, "done", "done")
-
-    val doneMa = callNode(
-      forStmt,
-      s"${doneBaseNode.code}.done",
-      op,
-      op,
-      DispatchTypes.STATIC_DISPATCH,
-      None,
-      Some(registerType("bool"))
-    )
-    val testNode = callAst(doneMa, List(Ast(doneBaseNode), Ast(doneMemberNode)))
-
-    val testCallArgs = List(testNode)
-    val testCallAst  = callAst(testCallNode, testCallArgs)
-
-    val whileLoopAst = Ast(whileLoopNode).withChild(testCallAst).withConditionEdge(whileLoopNode, testCallNode)
+    val whileLoopAst = Ast(whileLoopNode).withChild(testAst).withConditionEdge(whileLoopNode, testCallNode)
 
     // while loop variable assignment:
     val whileLoopVariableNode = identifierNode(forStmt, loopVariableName, loopVariableName, idType)
 
-    val baseNode = identifierNode(forStmt, resultName, resultName, idType)
+    val nextCallNode =
+      callNode(
+        forStmt,
+        s"$iteratorName.next()",
+        "next",
+        s"${Defines.Iterator}.next:${Defines.Any}()",
+        DispatchTypes.DYNAMIC_DISPATCH,
+        Some(s"${Defines.Any}()"),
+        Some(Defines.Any)
+      )
 
-    val memberNode = fieldIdentifierNode(forStmt, "value", "value")
+    val nextReceiverNode = identifierNode(forStmt, iteratorName, iteratorName, Defines.Iterator)
+    scope.addVariableReference(iteratorName, nextReceiverNode, Defines.Iterator, EvaluationStrategies.BY_REFERENCE)
 
-    val valueMa =
-      callNode(forStmt, s"$iteratorName.value", op, op, DispatchTypes.STATIC_DISPATCH, None, Some(idType))
-    val accessAst = callAst(valueMa, List(Ast(baseNode), Ast(memberNode)))
+    val nextCallAst = callAst(nextCallNode, receiver = Option(Ast(nextReceiverNode)))
 
     val loopVariableAssignmentNode = callNode(
       forStmt,
-      s"$loopVariableName = $resultName.value",
+      s"$loopVariableName = ${nextCallNode.code}",
       Operators.assignment,
       Operators.assignment,
       DispatchTypes.STATIC_DISPATCH
     )
 
-    val loopVariableAssignmentArgs = List(Ast(whileLoopVariableNode), accessAst)
+    val loopVariableAssignmentArgs = List(Ast(whileLoopVariableNode), nextCallAst)
     val loopVariableAssignmentAst  = callAst(loopVariableAssignmentNode, loopVariableAssignmentArgs)
 
     // while loop block:
@@ -531,7 +492,7 @@ trait AstForStatementsCreator { this: AstCreator =>
     scope.popScope()
 
     val blockChildren =
-      List(iteratorAssignmentAst, Ast(resultNode), Ast(loopVariableNode), whileLoopAst.withChild(whileLoopBlockAst))
+      List(iteratorAssignmentAst, Ast(loopVariableNode), whileLoopAst.withChild(whileLoopBlockAst))
     setArgumentIndices(blockChildren)
     blockAst(blockNode, blockChildren)
   }
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForTypesCreator.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForTypesCreator.scala
index 5903b8e7d..96ce38c79 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForTypesCreator.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForTypesCreator.scala
@@ -306,8 +306,6 @@ trait AstForTypesCreator { this: AstCreator =>
       astForDeclarator(typeSpecifier.getParent.asInstanceOf[IASTSimpleDeclaration], d, i)
     }
 
-    val lineNumber                       = line(typeSpecifier)
-    val columnNumber                     = column(typeSpecifier)
     val TypeFullNameInfo(name, fullName) = typeFullNameInfo(typeSpecifier)
     val codeString                       = code(typeSpecifier)
     val nameAlias                        = decls.headOption.map(d => registerType(shortName(d))).filter(_.nonEmpty)
@@ -339,13 +337,12 @@ trait AstForTypesCreator { this: AstCreator =>
       Ast(typeDecl).withChildren(member) +: declAsts
     } else {
       val init = staticInitMethodAst(
+        typeSpecifier,
         calls,
         s"$fullName.${io.joern.x2cpg.Defines.StaticInitMethodName}",
         None,
         Defines.Any,
-        Some(filename),
-        lineNumber,
-        columnNumber
+        Some(filename)
       )
       Ast(typeDecl).withChildren(member).withChild(init) +: declAsts
     }
@@ -412,8 +409,6 @@ trait AstForTypesCreator { this: AstCreator =>
       astForDeclarator(typeSpecifier.getParent.asInstanceOf[IASTSimpleDeclaration], d, i)
     }
 
-    val lineNumber                       = line(typeSpecifier)
-    val columnNumber                     = column(typeSpecifier)
     val TypeFullNameInfo(name, fullName) = typeFullNameInfo(typeSpecifier)
     val nameAlias                        = decls.headOption.map(d => registerType(shortName(d))).filter(_.nonEmpty)
     val alias                            = filterNameAlias(nameAlias, None, fullName)
@@ -449,13 +444,12 @@ trait AstForTypesCreator { this: AstCreator =>
       Ast(typeDecl).withChildren(member) +: declAsts
     } else {
       val init = staticInitMethodAst(
+        typeSpecifier,
         calls,
         s"$deAliasedFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}",
         None,
         Defines.Any,
-        Some(filename),
-        lineNumber,
-        columnNumber
+        Some(filename)
       )
       Ast(typeDecl).withChildren(member).withChild(init) +: declAsts
     }
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/Defines.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/Defines.scala
index f5abbde72..073406cfe 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/Defines.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/Defines.scala
@@ -2,6 +2,7 @@ package io.joern.c2cpg.astcreation
 
 object Defines {
   val Any: String                            = io.joern.x2cpg.Defines.Any
+  val UnresolvedNamespace: String            = io.joern.x2cpg.Defines.UnresolvedNamespace
   val Void: String                           = "void"
   val Function: String                       = "std.function"
   val Array: String                          = "std.array"
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/passes/TypeDeclNodePass.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/passes/TypeDeclNodePass.scala
index 3817591b9..586d83d57 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/passes/TypeDeclNodePass.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/passes/TypeDeclNodePass.scala
@@ -1,5 +1,6 @@
 package io.joern.c2cpg.passes
 
+import io.joern.x2cpg.AstNodeBuilder
 import io.joern.c2cpg.astcreation.Defines
 import io.joern.c2cpg.Config
 import io.shiftleft.codepropertygraph.generated.Cpg
@@ -10,7 +11,6 @@ import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
 import io.shiftleft.semanticcpg.language.*
 import io.joern.x2cpg.passes.frontend.MetaDataPass
 import io.joern.x2cpg.{Ast, ValidationMode}
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 
 class TypeDeclNodePass(cpg: Cpg, config: Config) extends CpgPass(cpg) {
 
@@ -53,7 +53,7 @@ class TypeDeclNodePass(cpg: Cpg, config: Config) extends CpgPass(cpg) {
         .astParentType(NodeTypes.NAMESPACE_BLOCK)
         .astParentFullName(fullName)
     val blockNode    = NewBlock().typeFullName(Defines.Any)
-    val methodReturn = newMethodReturnNode(Defines.Any, line = None, column = None)
+    val methodReturn = AstNodeBuilder.methodReturnNodeWithExplicitPositionInfo(Defines.Any, lineNumber = Option(1))
     Ast(includesFile).withChild(
       Ast(namespaceBlock)
         .withChild(Ast(fakeGlobalIncludesMethod).withChild(Ast(blockNode)).withChild(Ast(methodReturn)))
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
index 90b5c6ddd..51479ad2d 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
@@ -5,7 +5,7 @@ import io.joern.csharpsrc2cpg.datastructures.{CSharpProgramSummary, CSharpScope,
 import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.*
 import io.joern.csharpsrc2cpg.parser.{DotNetNodeInfo, ParserKeys}
 import io.joern.csharpsrc2cpg.utils.Utils.*
-import io.joern.x2cpg.astgen.{AstGenNodeBuilder, ParserResult}
+import io.joern.x2cpg.astgen.{AstGenNodeBuilder, BaseNodeInfo, ParserResult}
 import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
 import io.joern.x2cpg.{Ast, AstCreatorBase, ValidationMode}
 import io.shiftleft.codepropertygraph.generated.{DiffGraphBuilder, ModifierTypes, NodeTypes}
@@ -29,7 +29,7 @@ class AstCreator(
   val parserResult: ParserResult,
   val programSummary: CSharpProgramSummary = CSharpProgramSummary()
 )(implicit withSchemaValidation: ValidationMode)
-    extends AstCreatorBase(relativeFileName)
+    extends AstCreatorBase[BaseNodeInfo[?], AstCreator](relativeFileName)
     with AstCreatorHelper
     with AstForDeclarationsCreator
     with AstForPrimitivesCreator
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala
index c9129e03c..ad00ef972 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala
@@ -13,7 +13,7 @@ import io.joern.csharpsrc2cpg.utils.Utils.{
   composeSetterName,
   withoutSignature
 }
-import io.joern.x2cpg.utils.NodeBuilders.{newMethodReturnNode, newModifierNode}
+import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
 import io.joern.x2cpg.{Ast, Defines, ValidationMode}
 import io.shiftleft.codepropertygraph.generated.*
 import io.shiftleft.codepropertygraph.generated.nodes.*
@@ -78,8 +78,8 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
     scope.pushNewScope(TypeScope(fullName))
     val modifiers = astForModifiers(classDecl)
     val members = astForMembers(classDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq)
-      ++ addConstructorWithFieldInitializationsIfNeeded(fullName)
-      ++ addStaticConstructorWithFieldInitializationsIfNeeded(fullName)
+      ++ addConstructorWithFieldInitializationsIfNeeded(classDecl, fullName)
+      ++ addStaticConstructorWithFieldInitializationsIfNeeded(classDecl, fullName)
 
     scope.popScope()
     val typeDeclAst = Ast(typeDecl)
@@ -89,7 +89,10 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
     Seq(typeDeclAst)
   }
 
-  private def addConstructorWithFieldInitializationsIfNeeded(typeDeclFullName: String): Seq[Ast] = {
+  private def addConstructorWithFieldInitializationsIfNeeded(
+    node: DotNetNodeInfo,
+    typeDeclFullName: String
+  ): Seq[Ast] = {
     val dynamicFields = scope.getFieldsInScope.filter(f => !f.isStatic && f.isInitialized)
     val hasExplicitCtor =
       scope.tryResolveTypeReference(typeDeclFullName).exists(_.methods.exists(_.name == Defines.ConstructorMethodName))
@@ -98,7 +101,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
     val shouldBuildCtor = dynamicFields.nonEmpty && !hasExplicitCtor && parseLevel == FULL_AST
 
     if (shouldBuildCtor) {
-      val methodReturn = newMethodReturnNode(DotNetTypeMap(BuiltinTypes.Void), None, None, None)
+      val methodReturn = methodReturnNode(node, DotNetTypeMap(BuiltinTypes.Void))
       val signature    = composeMethodLikeSignature(methodReturn.typeFullName)
       val modifiers    = Seq(newModifierNode(ModifierTypes.CONSTRUCTOR), newModifierNode(ModifierTypes.INTERNAL))
       val name         = Defines.ConstructorMethodName
@@ -133,14 +136,17 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
     }
   }
 
-  private def addStaticConstructorWithFieldInitializationsIfNeeded(typeDeclFullname: String): Seq[Ast] = {
+  private def addStaticConstructorWithFieldInitializationsIfNeeded(
+    node: DotNetNodeInfo,
+    typeDeclFullname: String
+  ): Seq[Ast] = {
     val staticFields = scope.getFieldsInScope.filter(f => f.isStatic && f.isInitialized)
     val hasExplicitCtor =
       scope.tryResolveTypeReference(typeDeclFullname).exists(_.methods.exists(_.name == Defines.StaticInitMethodName))
     val shouldBuildCtor = staticFields.nonEmpty && !hasExplicitCtor && parseLevel == FULL_AST
 
     if (shouldBuildCtor) {
-      val methodReturn = newMethodReturnNode(DotNetTypeMap(BuiltinTypes.Void), None, None, None)
+      val methodReturn = methodReturnNode(node, DotNetTypeMap(BuiltinTypes.Void))
       val signature    = composeMethodLikeSignature(methodReturn.typeFullName)
       val modifiers = Seq(
         newModifierNode(ModifierTypes.CONSTRUCTOR),
@@ -361,7 +367,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
       .toSeq
     // TODO: Decide on proper return type for constructors. No `ReturnType` key in C# JSON for constructors so just
     //  defaulted to void (same as java) for now
-    val methodReturn     = newMethodReturnNode(DotNetTypeMap(BuiltinTypes.Void), None, None, None)
+    val methodReturn     = methodReturnNode(constructorDecl, DotNetTypeMap(BuiltinTypes.Void))
     val signature        = composeMethodLikeSignature(DotNetTypeMap(BuiltinTypes.Void), params)
     val typeDeclFullName = scope.surroundingTypeDeclFullName.getOrElse(Defines.UnresolvedNamespace);
 
diff --git a/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstCreator.scala
index 1d0a90cbd..6ff377b0e 100644
--- a/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstCreator.scala
@@ -4,7 +4,7 @@ import io.joern.gosrc2cpg.datastructures.GoGlobal
 import io.joern.gosrc2cpg.model.GoModHelper
 import io.joern.gosrc2cpg.parser.ParserAst.*
 import io.joern.gosrc2cpg.parser.{GoAstJsonParser, ParserKeys, ParserNodeInfo}
-import io.joern.x2cpg.astgen.AstGenNodeBuilder
+import io.joern.x2cpg.astgen.{AstGenNodeBuilder, BaseNodeInfo}
 import io.joern.x2cpg.datastructures.Scope
 import io.joern.x2cpg.datastructures.Stack.*
 import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
@@ -24,7 +24,7 @@ class AstCreator(
   val goMod: GoModHelper,
   val goGlobal: GoGlobal
 )(implicit withSchemaValidation: ValidationMode)
-    extends AstCreatorBase(relPathFileName)
+    extends AstCreatorBase[BaseNodeInfo[?], AstCreator](relPathFileName)
     with AstCreatorHelper
     with AstForGenDeclarationCreator
     with AstForExpressionCreator
diff --git a/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstForPackageConstructorCreator.scala b/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstForPackageConstructorCreator.scala
index 31b8d03a1..0a4caabf8 100644
--- a/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstForPackageConstructorCreator.scala
+++ b/joern-cli/frontends/gosrc2cpg/src/main/scala/io/joern/gosrc2cpg/astcreation/AstForPackageConstructorCreator.scala
@@ -3,7 +3,7 @@ package io.joern.gosrc2cpg.astcreation
 import io.joern.gosrc2cpg.datastructures.PackageMemberAst
 import io.joern.gosrc2cpg.parser.ParserAst.Unknown
 import io.joern.gosrc2cpg.parser.ParserNodeInfo
-import io.joern.x2cpg.astgen.AstGenNodeBuilder
+import io.joern.x2cpg.astgen.{AstGenNodeBuilder, BaseNodeInfo}
 import io.joern.x2cpg.{Ast, AstCreatorBase, ValidationMode, Defines as XDefines}
 import io.shiftleft.codepropertygraph.generated.NodeTypes
 import org.apache.commons.lang3.StringUtils
@@ -14,7 +14,7 @@ import scala.collection.immutable.Set
 
 class AstForPackageConstructorCreator(val pacakgePath: String, statements: Set[PackageMemberAst])(implicit
   withSchemaValidation: ValidationMode
-) extends AstCreatorBase(pacakgePath)
+) extends AstCreatorBase[BaseNodeInfo[?], AstForPackageConstructorCreator](pacakgePath)
     with AstGenNodeBuilder[AstForPackageConstructorCreator] {
 
   override def createAst(): DiffGraphBuilder = {
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/AstCreator.scala
index 4230465d4..34299d3ce 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/AstCreator.scala
@@ -94,8 +94,7 @@ class AstCreator(
   protected val keepTypeArguments: Boolean,
   val loggedExceptionCounts: scala.collection.concurrent.Map[Class[?], Int]
 )(implicit val withSchemaValidation: ValidationMode, val disableTypeFallback: Boolean)
-    extends AstCreatorBase(filename)
-    with AstNodeBuilder[Node, AstCreator]
+    extends AstCreatorBase[Node, AstCreator](filename)
     with AstForDeclarationsCreator
     with AstForExpressionsCreator
     with AstForStatementsCreator {
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForMethodsCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForMethodsCreator.scala
index 974b1d828..ca91de020 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForMethodsCreator.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForMethodsCreator.scala
@@ -115,11 +115,9 @@ private[declarations] trait AstForMethodsCreator { this: AstCreator =>
       case Failure(_)   => (line(methodDeclaration), column(methodDeclaration))
     }
     val methodReturn =
-      newMethodReturnNode(
-        returnTypeFullName.getOrElse(defaultTypeFallback(methodDeclaration.getType)),
-        None,
-        lineNr,
-        columnNr
+      methodReturnNode(
+        methodDeclaration.getType,
+        returnTypeFullName.getOrElse(defaultTypeFallback(methodDeclaration.getType))
       )
 
     val annotationAsts = methodDeclaration.getAnnotations.asScala.map(astForAnnotationExpr).toSeq
@@ -146,7 +144,7 @@ private[declarations] trait AstForMethodsCreator { this: AstCreator =>
     val methodFullName = composeMethodFullName(recordTypeFullName, parameterName, signature)
 
     val methodReturn =
-      newMethodReturnNode(parameterTypeFullName, line = line(parameter), column = column(parameter))
+      methodReturnNode(parameter, parameterTypeFullName)
 
     val genericSignature = binarySignatureCalculator.recordParameterAccessorBinarySignature(parameter)
     val methodRoot = methodNode(
@@ -246,12 +244,12 @@ private[declarations] trait AstForMethodsCreator { this: AstCreator =>
     }.toList
   }
 
-  def clinitAstFromStaticInits(staticInits: Seq[Ast]): Option[Ast] = {
+  def clinitAstFromStaticInits(node: Node, staticInits: Seq[Ast]): Option[Ast] = {
     Option.when(staticInits.nonEmpty) {
       val signature         = composeMethodLikeSignature(TypeConstants.Void, Nil)
       val enclosingDeclName = scope.enclosingTypeDecl.fullName.getOrElse(Defines.UnresolvedNamespace)
       val fullName          = composeMethodFullName(enclosingDeclName, Defines.StaticInitMethodName, signature)
-      staticInitMethodAst(staticInits.toList, fullName, Some(signature), TypeConstants.Void)
+      staticInitMethodAst(node, staticInits.toList, fullName, Some(signature), TypeConstants.Void)
     }
   }
 
@@ -310,7 +308,7 @@ private[declarations] trait AstForMethodsCreator { this: AstCreator =>
       astsForFieldInitializers(instanceFieldDeclarations) ++ recordParameterAssignments
     val patternLocalAsts = scope.enclosingMethod.map(_.getAndClearUnaddedPatternLocals()).getOrElse(Nil).map(Ast(_))
 
-    val returnNode = newMethodReturnNode(TypeConstants.Void, line = None, column = None)
+    val returnNode = methodReturnNode(originNode, TypeConstants.Void)
 
     val modifiers = List(newModifierNode(ModifierTypes.CONSTRUCTOR), newModifierNode(ModifierTypes.PUBLIC))
     val partialConstructor =
@@ -658,9 +656,7 @@ private[declarations] trait AstForMethodsCreator { this: AstCreator =>
   private def constructorReturnNode(
     constructorDeclaration: ConstructorDeclaration | CompactConstructorDeclaration
   ): NewMethodReturn = {
-    val line   = constructorDeclaration.getEnd.map(_.line).toScala
-    val column = constructorDeclaration.getEnd.map(_.column).toScala
-    newMethodReturnNode(TypeConstants.Void, None, line, column)
+    methodReturnNode(constructorDeclaration, TypeConstants.Void)
   }
 
   private def astForConstructorBody(originNode: Node, bodyStmts: List[Ast], captureInitializers: List[Ast]): Ast = {
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForTypeDeclsCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForTypeDeclsCreator.scala
index 84a72f70d..ecbc5c9d6 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForTypeDeclsCreator.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/declarations/AstForTypeDeclsCreator.scala
@@ -384,7 +384,7 @@ private[declarations] trait AstForTypeDeclsCreator { this: AstCreator =>
     val (staticFields, instanceFields) = fields.partition(_.isStatic)
     val staticFieldInitializers        = getStaticFieldInitializers(staticFields)
 
-    val clinitAst = clinitAstFromStaticInits(staticFieldInitializers)
+    val clinitAst = clinitAstFromStaticInits(originNode, staticFieldInitializers)
 
     val membersAstPairs: List[(Node, List[Ast])] = members.map { member =>
       val memberAsts = member match {
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/expressions/AstForLambdasCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/expressions/AstForLambdasCreator.scala
index eff09f72f..9606248de 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/expressions/AstForLambdasCreator.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/expressions/AstForLambdasCreator.scala
@@ -93,7 +93,7 @@ private[expressions] trait AstForLambdasCreator { this: AstCreator =>
       .collect { case identifier: NewIdentifier => identifier }
       .filter { identifier => lambdaParameterNamesToNodes.contains(identifier.name) }
 
-    val returnNode = newMethodReturnNode(returnType.getOrElse(defaultTypeFallback()), None, line(expr), column(expr))
+    val returnNode      = methodReturnNode(expr, returnType.getOrElse(defaultTypeFallback()))
     val virtualModifier = Some(newModifierNode(ModifierTypes.VIRTUAL))
     val staticModifier  = Option.when(thisParam.isEmpty)(newModifierNode(ModifierTypes.STATIC))
     val privateModifier = Some(newModifierNode(ModifierTypes.PRIVATE))
@@ -369,7 +369,7 @@ private[expressions] trait AstForLambdasCreator { this: AstCreator =>
     }
 
     if (paramTypesList.sizeIs != lambdaParameters.size) {
-      logger.error(s"Found different number lambda params and param types for $expr. Some parameters will be missing.")
+      logger.debug(s"Found different number lambda params and param types for $expr. Some parameters will be missing.")
     }
 
     val parameterNodes = lambdaParameters
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/statements/AstForForLoopsCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/statements/AstForForLoopsCreator.scala
index fd65a4626..ffba4402a 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/statements/AstForForLoopsCreator.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/astcreation/statements/AstForForLoopsCreator.scala
@@ -233,7 +233,7 @@ trait AstForForLoopsCreator { this: AstCreator =>
 
     val iterableAst = astsForExpression(iterableExpression, expectedType = expectedType) match {
       case Nil =>
-        logger.error(s"Could not create AST for iterable expr $iterableExpression: $filename:l$lineNo")
+        logger.warn(s"Could not create AST for iterable expr $iterableExpression: $filename:l$lineNo")
         Ast()
       case iterableAstHead :: Nil => iterableAstHead
       case iterableAsts =>
@@ -340,7 +340,7 @@ trait AstForForLoopsCreator { this: AstCreator =>
     // Create item local
     val maybeVariable = stmt.getVariable.getVariables.asScala.toList match {
       case Nil =>
-        logger.error(s"ForEach statement has empty variable list: $filename$lineNo")
+        logger.warn(s"ForEach statement has empty variable list: $filename$lineNo")
         None
       case variable :: Nil => Some(variable)
       case variable :: _ =>
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/DescriptorParser.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/DescriptorParser.scala
index a9579335a..96de8a207 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/DescriptorParser.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/DescriptorParser.scala
@@ -27,11 +27,11 @@ object DescriptorParser extends TypeParser {
       case Success(signature, _) => signature
 
       case Failure(err, _) =>
-        logger.error(s"parseClassSignature failed with $err")
+        logger.warn(s"parseClassSignature failed with $err")
         throw new IllegalArgumentException(s"FAILURE: Parsing invalid signature descriptor $descriptor")
 
       case Error(err, _) =>
-        logger.error(s"parseClassSignature raised error $err")
+        logger.warn(s"parseClassSignature raised error $err")
         throw new IllegalArgumentException(s"ERROR: Parsing invalid signature descriptor $descriptor")
     }
   }
diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/TypeParser.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/TypeParser.scala
index 6694c27ab..23fcda6e4 100644
--- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/TypeParser.scala
+++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/jartypereader/descriptorparser/TypeParser.scala
@@ -33,7 +33,7 @@ trait TypeParser extends TokenParser {
       case Some("+") ~ typeSignature => BoundWildcard(BoundAbove, typeSignature)
 
       case Some(symbol) ~ typeSignature =>
-        logger.error(s"Invalid wildcard indicator `$symbol`. Treating as unbound wildcard")
+        logger.debug(s"Invalid wildcard indicator `$symbol`. Treating as unbound wildcard")
         UnboundWildcard
 
       case None ~ typeSignature => SimpleTypeArgument(typeSignature)
diff --git a/joern-cli/frontends/jimple2cpg/src/main/scala/io/joern/jimple2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/jimple2cpg/src/main/scala/io/joern/jimple2cpg/astcreation/AstCreator.scala
index 6f248ffa4..3d72afd6c 100644
--- a/joern-cli/frontends/jimple2cpg/src/main/scala/io/joern/jimple2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/jimple2cpg/src/main/scala/io/joern/jimple2cpg/astcreation/AstCreator.scala
@@ -26,11 +26,10 @@ class AstCreator(
   global: Global,
   fileContent: Option[String] = None
 )(implicit withSchemaValidation: ValidationMode)
-    extends AstCreatorBase(filename)
+    extends AstCreatorBase[Host, AstCreator](filename)
     with AstForDeclarationsCreator
     with AstForStatementsCreator
-    with AstForExpressionsCreator
-    with AstNodeBuilder[Host, AstCreator] {
+    with AstForExpressionsCreator {
 
   private val logger = LoggerFactory.getLogger(getClass)
 
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
index a2c81ee3c..9a7e2b905 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
@@ -9,11 +9,9 @@ import io.joern.jssrc2cpg.parser.BabelNodeInfo
 import io.joern.x2cpg.Ast
 import io.joern.x2cpg.AstCreatorBase
 import io.joern.x2cpg.ValidationMode
-import io.joern.x2cpg.AstNodeBuilder as X2CpgAstNodeBuilder
 import io.joern.x2cpg.datastructures.Global
 import io.joern.x2cpg.datastructures.Stack.*
 import io.joern.x2cpg.frontendspecific.jssrc2cpg.Defines
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
 import io.shiftleft.codepropertygraph.generated.EvaluationStrategies
 import io.shiftleft.codepropertygraph.generated.ModifierTypes
@@ -32,7 +30,7 @@ import scala.collection.mutable
 
 class AstCreator(val config: Config, val global: Global, val parserResult: ParseResult)(implicit
   withSchemaValidation: ValidationMode
-) extends AstCreatorBase(parserResult.filename)
+) extends AstCreatorBase[BabelNodeInfo, AstCreator](parserResult.filename)
     with AstForExpressionsCreator
     with AstForPrimitivesCreator
     with AstForTypesCreator
@@ -42,8 +40,7 @@ class AstCreator(val config: Config, val global: Global, val parserResult: Parse
     with AstForTemplateDomCreator
     with AstNodeBuilder
     with TypeHelper
-    with AstCreatorHelper
-    with X2CpgAstNodeBuilder[BabelNodeInfo, AstCreator] {
+    with AstCreatorHelper {
 
   protected val logger: Logger = LoggerFactory.getLogger(classOf[AstCreator])
 
@@ -107,7 +104,7 @@ class AstCreator(val config: Config, val global: Global, val parserResult: Parse
     val methodChildren = astsForFile(astNodeInfo)
     setArgumentIndices(methodChildren)
 
-    val methodReturn = newMethodReturnNode(Defines.Any, line = None, column = None)
+    val methodReturn = methodReturnNode(astNodeInfo, Defines.Any)
 
     localAstParentStack.pop()
     scope.popScope()
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreatorHelper.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreatorHelper.scala
index 11a05832b..ccf12d9c6 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreatorHelper.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreatorHelper.scala
@@ -6,13 +6,13 @@ import io.joern.jssrc2cpg.parser.BabelNodeInfo
 import io.joern.x2cpg.frontendspecific.jssrc2cpg.Defines
 import io.joern.x2cpg.utils.IntervalKeyPool
 import io.joern.x2cpg.utils.NodeBuilders.newClosureBindingNode
-import io.joern.x2cpg.{Ast, ValidationMode}
+import io.joern.x2cpg.{Ast, AstNodeBuilder, ValidationMode}
 import io.shiftleft.codepropertygraph.generated.nodes.*
 import io.shiftleft.codepropertygraph.generated.{EdgeTypes, EvaluationStrategies}
 import io.shiftleft.codepropertygraph.generated.nodes.File.PropertyDefaults
 import ujson.Value
 
-import scala.collection.{mutable, SortedMap}
+import scala.collection.{SortedMap, mutable}
 import scala.util.{Success, Try}
 
 trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>
@@ -261,12 +261,14 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
                 capturedLocals.updateWith(closureBindingIdProperty) {
                   case None =>
                     val methodScopeNode = methodScope.scopeNode
-                    val localNode_ = localNodeWithExplicitPositionInfo(
-                      origin.variableName,
-                      origin.variableName,
-                      Defines.Any,
-                      Option(closureBindingIdProperty)
-                    ).order(0)
+                    val localNode_ = AstNodeBuilder
+                      .localNodeWithExplicitPositionInfo(
+                        origin.variableName,
+                        origin.variableName,
+                        Defines.Any,
+                        Option(closureBindingIdProperty)
+                      )
+                      .order(0)
                     diffGraph.addEdge(methodScopeNode, localNode_, EdgeTypes.AST)
                     val closureBindingNode = newClosureBindingNode(
                       closureBindingIdProperty,
@@ -302,7 +304,7 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
     methodScopeNodeId: NewNode,
     variableName: String
   ): (NewNode, ScopeType) = {
-    val local = localNodeWithExplicitPositionInfo(variableName, variableName, Defines.Any).order(0)
+    val local = AstNodeBuilder.localNodeWithExplicitPositionInfo(variableName, variableName, Defines.Any).order(0)
     diffGraph.addEdge(methodScopeNodeId, local, EdgeTypes.AST)
     (local, MethodScope)
   }
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala
index c20227c61..5171eda74 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala
@@ -266,7 +266,13 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode) { this:
       Ast.storeInDiffGraph(Ast(typeDeclNode_).withChildren(member), diffGraph)
     } else {
       val init =
-        staticInitMethodAst(calls, s"$typeFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}", None, Defines.Any)
+        staticInitMethodAst(
+          tsEnum,
+          calls,
+          s"$typeFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}",
+          None,
+          Defines.Any
+        )
       Ast.storeInDiffGraph(Ast(typeDeclNode_).withChildren(member).withChild(init), diffGraph)
     }
 
@@ -379,6 +385,7 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode) { this:
 
     if (staticMemberInitCalls.nonEmpty || staticInitBlockAsts.nonEmpty) {
       val init = staticInitMethodAst(
+        clazz,
         staticMemberInitCalls ++ staticInitBlockAsts,
         s"$typeFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}",
         None,
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
index 2c6ccdb5d..f048c9a1c 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
@@ -4,7 +4,6 @@ import io.joern.jssrc2cpg.parser.BabelNodeInfo
 import io.joern.x2cpg
 import io.joern.x2cpg.{Ast, ValidationMode}
 import io.joern.x2cpg.frontendspecific.jssrc2cpg.Defines
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.shiftleft.codepropertygraph.generated.nodes.*
 import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators, PropertyNames}
 
@@ -13,7 +12,7 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC
     val tpe           = typeFor(func)
     val possibleTypes = Seq(tpe)
     val typeFullName  = if (Defines.isBuiltinType(tpe)) tpe else Defines.Any
-    newMethodReturnNode(typeFullName, line = func.lineNumber, column = func.columnNumber).possibleTypes(possibleTypes)
+    methodReturnNode(func, typeFullName).possibleTypes(possibleTypes)
   }
 
   protected def setOrderExplicitly(ast: Ast, order: Int): Unit = {
diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
index 36160912a..23e3e3dfd 100644
--- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
@@ -8,14 +8,12 @@ import io.joern.kotlin2cpg.types.TypeConstants
 import io.joern.kotlin2cpg.types.TypeInfoProvider
 import io.joern.x2cpg.Ast
 import io.joern.x2cpg.AstCreatorBase
-import io.joern.x2cpg.AstNodeBuilder
 import io.joern.x2cpg.Defines
 import io.joern.x2cpg.ValidationMode
 import io.joern.x2cpg.datastructures.Global
 import io.joern.x2cpg.datastructures.Stack.*
 import io.joern.x2cpg.utils.IntervalKeyPool
 import io.joern.x2cpg.utils.NodeBuilders
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.shiftleft.codepropertygraph.generated.*
 import io.shiftleft.codepropertygraph.generated.nodes.*
 import io.shiftleft.codepropertygraph.generated.DiffGraphBuilder
@@ -46,13 +44,12 @@ object AstCreator {
 
 class AstCreator(fileWithMeta: KtFileWithMeta, bindingContext: BindingContext, global: Global)(implicit
   withSchemaValidation: ValidationMode
-) extends AstCreatorBase(fileWithMeta.filename)
+) extends AstCreatorBase[PsiElement, AstCreator](fileWithMeta.filename)
     with AstForDeclarationsCreator
     with AstForPrimitivesCreator
     with AstForFunctionsCreator
     with AstForStatementsCreator
-    with AstForExpressionsCreator
-    with AstNodeBuilder[PsiElement, AstCreator] {
+    with AstForExpressionsCreator {
 
   import AstCreator.BindingInfo
   import AstCreator.ClosureBindingDef
@@ -345,19 +342,20 @@ class AstCreator(fileWithMeta: KtFileWithMeta, bindingContext: BindingContext, g
       for {
         node <- importAsts.flatMap(_.root.collectAll[NewImport])
         name = getName(node)
-      } yield Ast(NodeBuilders.newNamespaceBlockNode(name, name, relativizedPath))
+      } yield Ast(namespaceBlockNode(fileWithMeta.f, name, name, relativizedPath))
 
     val packageName = ktFile.getPackageFqName.toString
     val node =
       if (packageName == Constants.Root) {
-        NodeBuilders.newNamespaceBlockNode(
+        namespaceBlockNode(
+          fileWithMeta.f,
           NamespaceTraversal.globalNamespaceName,
           NamespaceTraversal.globalNamespaceName,
           relativizedPath
         )
       } else {
         val name = packageName.split("\\.").lastOption.getOrElse("")
-        NodeBuilders.newNamespaceBlockNode(name, packageName, relativizedPath)
+        namespaceBlockNode(fileWithMeta.f, name, packageName, relativizedPath)
       }
     methodAstParentStack.push(node)
 
@@ -373,7 +371,7 @@ class AstCreator(fileWithMeta: KtFileWithMeta, bindingContext: BindingContext, g
     scope.pushNewScope(fakeGlobalMethod)
 
     val blockNode_   = blockNode(ktFile, "<empty>", registerType(TypeConstants.Any))
-    val methodReturn = newMethodReturnNode(TypeConstants.Any, None, None, None)
+    val methodReturn = methodReturnNode(ktFile, TypeConstants.Any)
 
     val declarationsAsts = ktFile.getDeclarations.asScala.flatMap(astsForDeclaration)
     val fileNode         = NewFile().name(fileWithMeta.relativizedPath)
diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForDeclarationsCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForDeclarationsCreator.scala
index 5c58e7675..960927e03 100644
--- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForDeclarationsCreator.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForDeclarationsCreator.scala
@@ -10,7 +10,6 @@ import io.joern.x2cpg.Defines
 import io.joern.x2cpg.ValidationMode
 import io.joern.x2cpg.utils.NodeBuilders
 import io.joern.x2cpg.utils.NodeBuilders.newBindingNode
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
 import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EdgeTypes, ModifierTypes, Operators}
 import io.shiftleft.codepropertygraph.generated.nodes.NewBlock
@@ -140,12 +139,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
     val anonymousInitExpressions = ktClass.getAnonymousInitializers.asScala
     val anonymousInitAsts        = anonymousInitExpressions.flatMap(astsForExpression(_, None))
 
-    val constructorMethodReturn = newMethodReturnNode(
-      TypeConstants.Void,
-      None,
-      line(ktClass.getPrimaryConstructor),
-      column(ktClass.getPrimaryConstructor)
-    )
+    val constructorMethodReturn = methodReturnNode(ktClass.getPrimaryConstructor, TypeConstants.Void)
     val constructorAst = methodAst(
       primaryCtorMethodNode,
       constructorParamsAsts,
@@ -461,7 +455,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
         methodNode(valueParam, componentName, fullName, signature, relativizedPath),
         Seq(Ast(thisParam)),
         methodBlockAst,
-        newMethodReturnNode(typeFullName, None, None, None)
+        methodReturnNode(valueParam, typeFullName)
       )
     }
   }
@@ -505,7 +499,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
       scope.popScope()
 
       val ctorMethodReturnNode =
-        newMethodReturnNode(TypeConstants.Void, None, line(ctor), column(ctor))
+        methodReturnNode(ctor, TypeConstants.Void)
 
       // TODO: see if necessary to take the other asts for the ctorMethodBlock
       methodAst(
diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForFunctionsCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForFunctionsCreator.scala
index 5e9ea0931..d1fb3c5ad 100644
--- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForFunctionsCreator.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForFunctionsCreator.scala
@@ -9,7 +9,6 @@ import io.joern.x2cpg.datastructures.Stack.StackWrapper
 import io.joern.x2cpg.utils.NodeBuilders
 import io.joern.x2cpg.utils.NodeBuilders.newBindingNode
 import io.joern.x2cpg.utils.NodeBuilders.newClosureBindingNode
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
 import io.shiftleft.codepropertygraph.generated.EvaluationStrategies
 import io.shiftleft.codepropertygraph.generated.ModifierTypes
@@ -152,7 +151,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) {
     val otherBodyAsts     = bodyAsts.drop(1)
     val explicitTypeName  = Option(ktFn.getTypeReference).map(_.getText).getOrElse(TypeConstants.Any)
     val typeFullName      = registerType(nameRenderer.typeFullName(funcDesc.getReturnType).getOrElse(explicitTypeName))
-    val _methodReturnNode = newMethodReturnNode(typeFullName, None, line(ktFn), column(ktFn))
+    val _methodReturnNode = methodReturnNode(ktFn, typeFullName)
 
     val visibilityModifierType =
       modifierTypeForVisibility(funcDesc.getVisibility)
@@ -343,7 +342,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) {
       lambdaMethodNode,
       parametersAsts,
       bodyAst,
-      newMethodReturnNode(returnTypeFullName, None, line(fn), column(fn)),
+      methodReturnNode(fn, returnTypeFullName),
       NodeBuilders.newModifierNode(ModifierTypes.VIRTUAL) :: NodeBuilders.newModifierNode(ModifierTypes.LAMBDA) :: Nil
     )
 
@@ -480,7 +479,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) {
       lambdaMethodNode,
       paramAsts.toSeq,
       bodyAst,
-      newMethodReturnNode(returnTypeFullName, None, line(expr), column(expr)),
+      methodReturnNode(expr, returnTypeFullName),
       newModifierNode(ModifierTypes.VIRTUAL) :: newModifierNode(ModifierTypes.LAMBDA) :: Nil
     )
 
diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForPrimitivesCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForPrimitivesCreator.scala
index 38072b6b1..523737e8e 100644
--- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForPrimitivesCreator.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForPrimitivesCreator.scala
@@ -16,6 +16,7 @@ import io.shiftleft.codepropertygraph.generated.nodes.NewMember
 import io.shiftleft.codepropertygraph.generated.nodes.NewMethodParameterIn
 import io.shiftleft.semanticcpg.language.*
 import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
+import org.jetbrains.kotlin.com.intellij.psi.PsiElement
 import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
 import org.jetbrains.kotlin.descriptors.ValueDescriptor
@@ -233,17 +234,18 @@ trait AstForPrimitivesCreator(implicit withSchemaValidation: ValidationMode) {
   }
 
   @unused
-  def astForPackageDeclaration(packageName: String): Ast = {
+  def astForPackageDeclaration(originNode: PsiElement, packageName: String): Ast = {
     val node =
       if (packageName == Constants.Root)
-        NodeBuilders.newNamespaceBlockNode(
+        namespaceBlockNode(
+          originNode,
           NamespaceTraversal.globalNamespaceName,
           NamespaceTraversal.globalNamespaceName,
           relativizedPath
         )
       else {
         val name = packageName.split("\\.").lastOption.getOrElse("")
-        NodeBuilders.newNamespaceBlockNode(name, packageName, relativizedPath)
+        namespaceBlockNode(originNode, name, packageName, relativizedPath)
       }
     Ast(node)
   }
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
index 4a971841d..2c9920e9f 100644
--- a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
@@ -23,8 +23,7 @@ import scala.collection.mutable
 
 class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, disableFileContent: Boolean)(implicit
   withSchemaValidation: ValidationMode
-) extends AstCreatorBase(relativeFileName)
-    with AstNodeBuilder[PhpNode, AstCreator] {
+) extends AstCreatorBase[PhpNode, AstCreator](relativeFileName) {
 
   private val logger          = LoggerFactory.getLogger(AstCreator.getClass)
   private val scope           = new Scope()(() => nextClosureName())
@@ -244,7 +243,7 @@ class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, di
     val returnType = decl.returnType.map(_.name).getOrElse(TypeConstants.Any)
 
     val methodBodyStmts = bodyPrefixAsts ++ decl.stmts.flatMap(astsForStmt)
-    val methodReturn    = newMethodReturnNode(returnType, line = line(decl), column = None)
+    val methodReturn    = methodReturnNode(decl, returnType)
 
     val attributeAsts = decl.attributeGroups.flatMap(astForAttributeGroup)
     val methodBody    = blockAst(blockNode(decl), methodBodyStmts)
@@ -796,7 +795,7 @@ class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, di
     Ast(typeDecl).withChildren(modifiers).withChildren(bodyStmts).withChildren(annotationAsts)
   }
 
-  private def astForStaticAndConstInits: Option[Ast] = {
+  private def astForStaticAndConstInits(node: PhpNode): Option[Ast] = {
     scope.getConstAndStaticInits match {
       case Nil => None
 
@@ -804,7 +803,14 @@ class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, di
         val signature = s"${TypeConstants.Void}()"
         val fullName  = composeMethodFullName(StaticInitMethodName, isStatic = true)
         val ast =
-          staticInitMethodAst(inits, fullName, Option(signature), TypeConstants.Void, fileName = Some(relativeFileName))
+          staticInitMethodAst(
+            node,
+            inits,
+            fullName,
+            Option(signature),
+            TypeConstants.Void,
+            fileName = Some(relativeFileName)
+          )
 
         for {
           method  <- ast.root.collect { case method: NewMethod => method }
@@ -845,7 +851,7 @@ class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, di
       case stmt => astsForStmt(stmt)
     }
 
-    val clinitAst           = astForStaticAndConstInits
+    val clinitAst           = astForStaticAndConstInits(classLike)
     val anonymousMethodAsts = scope.getAndClearAnonymousMethods
 
     List(classConsts, properties, clinitAst, constructorAst, anonymousMethodAsts, otherBodyStmts).flatten
@@ -881,7 +887,7 @@ class AstCreator(relativeFileName: String, fileName: String, phpAst: PhpFile, di
 
     val methodBody = blockAst(blockNode(originNode), scope.getFieldInits)
 
-    val methodReturn = newMethodReturnNode(TypeConstants.Any, line = None, column = None)
+    val methodReturn = methodReturnNode(originNode, TypeConstants.Any)
 
     methodAstWithAnnotations(method, thisParam :: Nil, methodBody, methodReturn, modifiers)
   }
diff --git a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/NodeBuilder.scala b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/NodeBuilder.scala
index 6a4e0d837..b403c61cd 100644
--- a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/NodeBuilder.scala
+++ b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/NodeBuilder.scala
@@ -183,13 +183,13 @@ class NodeBuilder(diffGraph: DiffGraphBuilder) {
     dynamicTypeHintFullName: Option[String],
     lineAndColumn: LineAndColumn
   ): nodes.NewMethodReturn = {
-    val methodReturnNode = NodeBuilders
-      .newMethodReturnNode(
-        staticTypeHint.getOrElse(Constants.ANY),
-        dynamicTypeHintFullName,
-        Some(lineAndColumn.line),
-        Some(lineAndColumn.column)
-      )
+    val methodReturnNode = nodes
+      .NewMethodReturn()
+      .typeFullName(staticTypeHint.getOrElse(Constants.ANY))
+      .dynamicTypeHintFullName(dynamicTypeHintFullName)
+      .lineNumber(lineAndColumn.line)
+      .columnNumber(lineAndColumn.column)
+      .code("RET")
       .evaluationStrategy(EvaluationStrategies.BY_SHARING)
 
     addNodeToDiff(methodReturnNode)
diff --git a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala
index 0aeb0afda..b704b7f4c 100644
--- a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala
+++ b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala
@@ -4,7 +4,7 @@ import PythonAstVisitor.{logger, metaClassSuffix, noLineAndColumn}
 import io.joern.pysrc2cpg.memop.*
 import io.joern.x2cpg.frontendspecific.pysrc2cpg.Constants.builtinPrefix
 import io.joern.pythonparser.ast
-import io.joern.pythonparser.ast.{Arguments, iexpr, istmt}
+import io.joern.pythonparser.ast.{Arguments, iast, iexpr, istmt}
 import io.joern.x2cpg.frontendspecific.pysrc2cpg.Constants
 import io.joern.x2cpg.{AstCreatorBase, ValidationMode}
 import io.shiftleft.codepropertygraph.generated.*
@@ -32,7 +32,7 @@ class PythonAstVisitor(
   version: PythonVersion,
   enableFileContent: Boolean
 )(implicit withSchemaValidation: ValidationMode)
-    extends AstCreatorBase(relFileName)
+    extends AstCreatorBase[ast.iast, PythonAstVisitor](relFileName)
     with PythonAstVisitorHelpers {
 
   private val redefintionSuffix = "$redefinition"
@@ -2159,6 +2159,12 @@ class PythonAstVisitor(
       relFileName + ":" + name
     }
   }
+
+  override protected def line(node: iast): Option[Int]         = None
+  override protected def column(node: iast): Option[Int]       = None
+  override protected def lineEnd(node: iast): Option[Int]      = None
+  override protected def columnEnd(element: iast): Option[Int] = None
+  override protected def code(node: iast): String              = node.toString
 }
 
 object PythonAstVisitor {
diff --git a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
index 98f3f3b65..e8ac640d7 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
@@ -21,15 +21,14 @@ class AstCreator(
   val fileContent: String = "",
   val rootNode: StatementList
 )(implicit withSchemaValidation: ValidationMode)
-    extends AstCreatorBase(fileName)
+    extends AstCreatorBase[RubyExpression, AstCreator](fileName)
     with AstCreatorHelper
     with AstForStatementsCreator
     with AstForExpressionsCreator
     with AstForControlStructuresCreator
     with AstForFunctionsCreator
     with AstForTypesCreator
-    with AstSummaryVisitor
-    with AstNodeBuilder[RubyExpression, AstCreator] {
+    with AstSummaryVisitor {
 
   val tmpGen: FreshNameGenerator[String]                      = FreshNameGenerator(i => s"<tmp-$i>")
   val procParamGen: FreshNameGenerator[Left[String, Nothing]] = FreshNameGenerator(i => Left(s"<proc-param-$i>"))
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 af843e9d2..7d56a2f56 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
@@ -5,7 +5,6 @@ import io.joern.swiftsrc2cpg.datastructures.Scope
 import io.joern.swiftsrc2cpg.parser.SwiftJsonParser.ParseResult
 import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.*
 import io.joern.x2cpg.datastructures.Stack.*
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.joern.x2cpg.{Ast, AstCreatorBase, ValidationMode, AstNodeBuilder as X2CpgAstNodeBuilder}
 import io.joern.x2cpg.datastructures.Global
 import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines
@@ -27,7 +26,7 @@ import scala.collection.mutable
 
 class AstCreator(val config: Config, val global: Global, val parserResult: ParseResult)(implicit
   withSchemaValidation: ValidationMode
-) extends AstCreatorBase(parserResult.filename)
+) extends AstCreatorBase[SwiftNode, AstCreator](parserResult.filename)
     with AstForSwiftTokenCreator
     with AstForSyntaxCreator
     with AstForExprSyntaxCreator
@@ -37,8 +36,7 @@ class AstCreator(val config: Config, val global: Global, val parserResult: Parse
     with AstForStmtSyntaxCreator
     with AstForSyntaxCollectionCreator
     with AstCreatorHelper
-    with AstNodeBuilder
-    with X2CpgAstNodeBuilder[SwiftNode, AstCreator] {
+    with AstNodeBuilder {
 
   protected val logger: Logger = LoggerFactory.getLogger(classOf[AstCreator])
 
@@ -84,7 +82,7 @@ class AstCreator(val config: Config, val global: Global, val parserResult: Parse
     methodAstParentStack.push(fakeGlobalMethod)
     scope.pushNewMethodScope(fullName, name, fakeGlobalMethod, None)
     val sourceFileAst = astForNode(ast)
-    val methodReturn  = newMethodReturnNode(Defines.Any, None, line(ast), column(ast))
+    val methodReturn  = methodReturnNode(ast, Defines.Any)
     val modifiers = Seq(newModifierNode(ModifierTypes.VIRTUAL).order(0), newModifierNode(ModifierTypes.MODULE).order(1))
     Ast(fakeGlobalTypeDecl).withChild(
       methodAst(fakeGlobalMethod, Seq.empty, sourceFileAst, methodReturn, modifiers = modifiers)
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 6e7fa30f0..218d58dd7 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
@@ -11,8 +11,8 @@ import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.InitializerDeclSyntax
 import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.SwiftNode
 import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines
 import io.joern.x2cpg.utils.IntervalKeyPool
-import io.joern.x2cpg.{Ast, ValidationMode}
-import io.joern.x2cpg.utils.NodeBuilders.{newClosureBindingNode}
+import io.joern.x2cpg.{Ast, AstNodeBuilder, ValidationMode}
+import io.joern.x2cpg.utils.NodeBuilders.newClosureBindingNode
 import io.shiftleft.codepropertygraph.generated.nodes.NewNode
 import io.shiftleft.codepropertygraph.generated.{EdgeTypes, EvaluationStrategies}
 import io.shiftleft.codepropertygraph.generated.nodes.NewNamespaceBlock
@@ -178,12 +178,14 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
                 capturedLocals.updateWith(closureBindingIdProperty) {
                   case None =>
                     val methodScopeNode = methodScope.scopeNode
-                    val localNode_ = localNodeWithExplicitPositionInfo(
-                      origin.variableName,
-                      origin.variableName,
-                      Defines.Any,
-                      Option(closureBindingIdProperty)
-                    ).order(0)
+                    val localNode_ = AstNodeBuilder
+                      .localNodeWithExplicitPositionInfo(
+                        origin.variableName,
+                        origin.variableName,
+                        Defines.Any,
+                        Option(closureBindingIdProperty)
+                      )
+                      .order(0)
                     diffGraph.addEdge(methodScopeNode, localNode_, EdgeTypes.AST)
                     val closureBindingNode = newClosureBindingNode(
                       closureBindingIdProperty,
@@ -220,7 +222,7 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
     methodScopeNodeId: NewNode,
     variableName: String
   ): (NewNode, ScopeType) = {
-    val local = localNodeWithExplicitPositionInfo(variableName, variableName, Defines.Any).order(0)
+    val local = AstNodeBuilder.localNodeWithExplicitPositionInfo(variableName, variableName, Defines.Any).order(0)
     diffGraph.addEdge(methodScopeNodeId, local, EdgeTypes.AST)
     (local, MethodScope)
   }
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 f131b8691..fc256d98b 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
@@ -164,13 +164,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
     val modifiers   = Seq(NewModifier().modifierType(ModifierTypes.CONSTRUCTOR))
 
     methodAstParentStack.push(methodNode_)
-    val methodReturnNode =
-      newMethodReturnNode(
-        typeDeclNode.fullName,
-        dynamicTypeHintFullName = None,
-        line = line(node),
-        column = column(node)
-      )
+    val methodReturnNode_ = methodReturnNode(node, typeDeclNode.fullName)
 
     methodAstParentStack.pop()
 
@@ -178,11 +172,11 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
       createFunctionTypeAndTypeDeclAst(node, methodNode_, constructorName, methodFullName)
 
     val (mAst, bAst) = if (methodBlockContent.isEmpty) {
-      (methodStubAst(methodNode_, Seq.empty, methodReturnNode, modifiers), Ast())
+      (methodStubAst(methodNode_, Seq.empty, methodReturnNode_, modifiers), Ast())
     } else {
       setArgumentIndices(methodBlockContent)
       val bodyAst = blockAst(NewBlock(), methodBlockContent)
-      (methodAstWithAnnotations(methodNode_, Seq.empty, bodyAst, methodReturnNode, modifiers), bodyAst)
+      (methodAstWithAnnotations(methodNode_, Seq.empty, bodyAst, methodReturnNode_, modifiers), bodyAst)
     }
 
     Ast.storeInDiffGraph(mAst, diffGraph)
@@ -382,6 +376,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
 
     if (staticMemberInitCalls.nonEmpty) {
       val init = staticInitMethodAstAndBlock(
+        node,
         staticMemberInitCalls,
         s"$typeFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}",
         None,
@@ -530,6 +525,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
 
     if (staticMemberInitCalls.nonEmpty) {
       val init = staticInitMethodAstAndBlock(
+        node,
         staticMemberInitCalls,
         s"$typeFullName:${io.joern.x2cpg.Defines.StaticInitMethodName}",
         None,
@@ -739,8 +735,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
         List.empty[Ast]
     }
 
-    val methodReturnNode =
-      newMethodReturnNode(returnType, dynamicTypeHintFullName = None, line = line(node), column = column(node))
+    val methodReturnNode_ = methodReturnNode(node, returnType)
 
     val bodyAsts = methodBlockContent ++ bodyStmtAsts
     setArgumentIndices(bodyAsts)
@@ -750,7 +745,7 @@ trait AstForDeclSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
         methodNode_,
         parameterAsts,
         blockAst_,
-        methodReturnNode,
+        methodReturnNode_,
         modifiers = modifiers,
         annotations = attributes
       )
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 6d1d6f337..f75b58b68 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
@@ -5,7 +5,6 @@ import io.joern.x2cpg
 import io.joern.x2cpg.Ast
 import io.joern.x2cpg.ValidationMode
 import io.joern.x2cpg.frontendspecific.swiftsrc2cpg.Defines
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
 import io.shiftleft.codepropertygraph.generated.nodes.*
 import io.shiftleft.codepropertygraph.generated.DispatchTypes
 import io.shiftleft.codepropertygraph.generated.ModifierTypes
@@ -242,19 +241,18 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC
   }
 
   def staticInitMethodAstAndBlock(
+    node: SwiftNode,
     initAsts: List[Ast],
     fullName: String,
     signature: Option[String],
     returnType: String,
-    fileName: Option[String] = None,
-    lineNumber: Option[Int] = None,
-    columnNumber: Option[Int] = None
+    fileName: Option[String] = None
   ): AstAndMethod = {
     val methodNode = NewMethod()
       .name(io.joern.x2cpg.Defines.StaticInitMethodName)
       .fullName(fullName)
-      .lineNumber(lineNumber)
-      .columnNumber(columnNumber)
+      .lineNumber(line(node))
+      .columnNumber(column(node))
     if (signature.isDefined) {
       methodNode.signature(signature.get)
     }
@@ -263,7 +261,7 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC
     }
     val staticModifier = NewModifier().modifierType(ModifierTypes.STATIC)
     val body           = blockAst(NewBlock(), initAsts)
-    val methodReturn   = newMethodReturnNode(returnType, None, None, None)
+    val methodReturn   = methodReturnNode(node, returnType)
     AstAndMethod(methodAst(methodNode, Nil, body, methodReturn, List(staticModifier)), methodNode, body)
   }
 
diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstCreatorBase.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstCreatorBase.scala
index 4c3c3c55b..f83d923d3 100644
--- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstCreatorBase.scala
+++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstCreatorBase.scala
@@ -2,7 +2,7 @@ package io.joern.x2cpg
 
 import io.joern.x2cpg.passes.frontend.MetaDataPass
 import io.joern.x2cpg.utils.IntervalKeyPool
-import io.joern.x2cpg.utils.NodeBuilders.{newFieldIdentifierNode, newMethodReturnNode, newOperatorCallNode}
+import io.joern.x2cpg.utils.NodeBuilders.{newFieldIdentifierNode, newOperatorCallNode}
 import io.shiftleft.semanticcpg.utils.FileUtil.*
 import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, Cpg, DiffGraphBuilder, ModifierTypes, Operators}
 import io.shiftleft.codepropertygraph.generated.nodes.*
@@ -10,7 +10,8 @@ import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
 
 import java.nio.file.Paths
 
-abstract class AstCreatorBase(filename: String)(implicit withSchemaValidation: ValidationMode) {
+abstract class AstCreatorBase[Node, NodeProcessor](filename: String)(implicit withSchemaValidation: ValidationMode)
+    extends AstNodeBuilder[Node, NodeProcessor] { this: NodeProcessor =>
   val diffGraph: DiffGraphBuilder = Cpg.newDiffGraphBuilder
 
   private val closureKeyPool = new IntervalKeyPool(first = 0, last = Long.MaxValue)
@@ -94,19 +95,19 @@ abstract class AstCreatorBase(filename: String)(implicit withSchemaValidation: V
       .withChild(Ast(methodReturn))
 
   def staticInitMethodAst(
+    node: Node,
     initAsts: List[Ast],
     fullName: String,
     signature: Option[String],
     returnType: String,
-    fileName: Option[String] = None,
-    lineNumber: Option[Int] = None,
-    columnNumber: Option[Int] = None
+    fileName: Option[String] = None
   ): Ast = {
+    // TODO Use methodNode builder method
     val methodNode = NewMethod()
       .name(Defines.StaticInitMethodName)
       .fullName(fullName)
-      .lineNumber(lineNumber)
-      .columnNumber(columnNumber)
+      .lineNumber(line(node))
+      .columnNumber(column(node))
     if (signature.isDefined) {
       methodNode.signature(signature.get)
     }
@@ -115,7 +116,7 @@ abstract class AstCreatorBase(filename: String)(implicit withSchemaValidation: V
     }
     val staticModifier = NewModifier().modifierType(ModifierTypes.STATIC)
     val body           = blockAst(NewBlock().typeFullName(Defines.Any), initAsts)
-    val methodReturn   = newMethodReturnNode(returnType, None, None, None)
+    val methodReturn   = methodReturnNode(node, returnType)
     methodAst(methodNode, Nil, body, methodReturn, List(staticModifier))
   }
 
diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstNodeBuilder.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstNodeBuilder.scala
index 8250090ef..2df63c52f 100644
--- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstNodeBuilder.scala
+++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/AstNodeBuilder.scala
@@ -1,6 +1,6 @@
 package io.joern.x2cpg
 
-import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
+import io.joern.x2cpg.AstNodeBuilder.methodReturnNodeWithExplicitPositionInfo
 import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EvaluationStrategies}
 import io.shiftleft.codepropertygraph.generated.nodes.Block.PropertyDefaults as BlockDefaults
 import io.shiftleft.codepropertygraph.generated.nodes.{
@@ -290,7 +290,7 @@ trait AstNodeBuilder[Node, NodeProcessor] { this: NodeProcessor =>
   ): NewLocal = {
     val (nodeOffset, nodeOffsetEnd) =
       offset(node).map((offset, offsetEnd) => (Option(offset), Option(offsetEnd))).getOrElse((None, None))
-    localNodeWithExplicitPositionInfo(
+    AstNodeBuilder.localNodeWithExplicitPositionInfo(
       name,
       code,
       typeFullName,
@@ -303,33 +303,6 @@ trait AstNodeBuilder[Node, NodeProcessor] { this: NodeProcessor =>
     )
   }
 
-  /** It is useful (perhaps necessary) to be able to create locals without an "origin node" in some cases, so allow that
-    * but make it clear that positional information (line/col number and offsets) must be specified explicitly.
-    */
-  protected def localNodeWithExplicitPositionInfo(
-    name: String,
-    code: String,
-    typeFullName: String,
-    closureBindingId: Option[String] = None,
-    genericSignature: Option[String] = None,
-    lineNumber: Option[Int] = None,
-    columnNumber: Option[Int] = None,
-    offset: Option[Int] = None,
-    offsetEnd: Option[Int] = None
-  ): NewLocal = {
-    val node_ = NewLocal()
-      .name(name)
-      .code(code)
-      .typeFullName(typeFullName)
-      .closureBindingId(closureBindingId)
-      .lineNumber(lineNumber)
-      .columnNumber(columnNumber)
-      .offset(offset)
-      .offsetEnd(offsetEnd)
-    genericSignature.foreach(node_.genericSignature(_))
-    node_
-  }
-
   protected def identifierNode(
     node: Node,
     name: String,
@@ -382,8 +355,13 @@ trait AstNodeBuilder[Node, NodeProcessor] { this: NodeProcessor =>
     node_
   }
 
-  protected def methodReturnNode(node: Node, typeFullName: String): NewMethodReturn = {
-    newMethodReturnNode(typeFullName, None, line(node), column(node))
+  protected def methodReturnNode(
+    node: Node,
+    typeFullName: String,
+    dynamicTypeHintFullName: Option[String] = None
+  ): NewMethodReturn = {
+    // TODO: Add offsets
+    methodReturnNodeWithExplicitPositionInfo(typeFullName, dynamicTypeHintFullName, line(node), column(node))
   }
 
   protected def jumpTargetNode(
@@ -399,4 +377,63 @@ trait AstNodeBuilder[Node, NodeProcessor] { this: NodeProcessor =>
       .lineNumber(line(node))
       .columnNumber(column(node))
   }
+
+  protected def namespaceBlockNode(node: Node, name: String, fullName: String, fileName: String): NewNamespaceBlock = {
+    NewNamespaceBlock()
+      .name(name)
+      .fullName(fullName)
+      .filename(fileName)
+      .lineNumber(line(node))
+      .columnNumber(column(node))
+  }
+}
+
+/** It is sometimes necessary to create nodes without an origin node to use as a reference for positional information
+  * (for example in passes after AST creation). These methods provide a way to do that, but the AstNodeBuilder trait
+  * methods are STRONGLY preferred and should be used instead of these static methods whenever possible.
+  */
+object AstNodeBuilder {
+
+  private[joern] def localNodeWithExplicitPositionInfo(
+    name: String,
+    code: String,
+    typeFullName: String,
+    closureBindingId: Option[String] = None,
+    genericSignature: Option[String] = None,
+    lineNumber: Option[Int] = None,
+    columnNumber: Option[Int] = None,
+    offset: Option[Int] = None,
+    offsetEnd: Option[Int] = None
+  ): NewLocal = {
+    val node_ = NewLocal()
+      .name(name)
+      .code(code)
+      .typeFullName(typeFullName)
+      .closureBindingId(closureBindingId)
+      .lineNumber(lineNumber)
+      .columnNumber(columnNumber)
+      .offset(offset)
+      .offsetEnd(offsetEnd)
+    genericSignature.foreach(node_.genericSignature(_))
+    node_
+  }
+
+  private[joern] def methodReturnNodeWithExplicitPositionInfo(
+    typeFullName: String,
+    dynamicTypeHintFullName: Option[String] = None,
+    lineNumber: Option[Int] = None,
+    columnNumber: Option[Int] = None,
+    offset: Option[Int] = None,
+    offsetEnd: Option[Int] = None
+  ): NewMethodReturn = {
+    NewMethodReturn()
+      .typeFullName(typeFullName)
+      .dynamicTypeHintFullName(dynamicTypeHintFullName)
+      .code("RET")
+      .evaluationStrategy(EvaluationStrategies.BY_VALUE)
+      .lineNumber(lineNumber)
+      .columnNumber(columnNumber)
+      .offset(offset)
+      .offsetEnd(offsetEnd)
+  }
 }
diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/utils/NodeBuilders.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/utils/NodeBuilders.scala
index 2233f0d39..2a73d3c1c 100644
--- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/utils/NodeBuilders.scala
+++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/utils/NodeBuilders.scala
@@ -187,8 +187,10 @@ object NodeBuilders {
       .order(0)
   }
 
-  /** Create a method return node
-    */
+  @deprecated(
+    "Deprecated in favour of the corresponding method io.joern.x2cpg.AstNodeBuilder and will be removed in a future version",
+    "4.0.286"
+  )
   def newMethodReturnNode(
     typeFullName: String,
     dynamicTypeHintFullName: Option[String] = None,
@@ -203,6 +205,10 @@ object NodeBuilders {
       .lineNumber(line)
       .columnNumber(column)
 
+  @deprecated(
+    "Deprecated in favour of the corresponding method io.joern.x2cpg.AstNodeBuilder and will be removed in a future version",
+    "4.0.288"
+  )
   def newNamespaceBlockNode(name: String, fullName: String, fileName: String): NewNamespaceBlock = {
     NewNamespaceBlock()
       .name(name)


Test patch:
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/ForEachLoopTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/ForEachLoopTests.scala
index c27d796ab..9ef1ba25c 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/ForEachLoopTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/ForEachLoopTests.scala
@@ -3,11 +3,8 @@ package io.joern.c2cpg.passes.ast
 import io.joern.c2cpg.astcreation.Defines
 import io.joern.c2cpg.parser.FileDefaults
 import io.joern.c2cpg.testfixtures.AstC2CpgSuite
-import io.shiftleft.codepropertygraph.generated.DispatchTypes
-import io.shiftleft.codepropertygraph.generated.Operators
+import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, DispatchTypes, Operators}
 import io.shiftleft.codepropertygraph.generated.nodes.Block
-import io.shiftleft.codepropertygraph.generated.nodes.Identifier
-import io.shiftleft.codepropertygraph.generated.ControlStructureTypes
 import io.shiftleft.semanticcpg.language.*
 
 class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
@@ -31,7 +28,7 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
       val List(methodBlock) = method.astChildren.isBlock.l
       val List(loopBlock)   = methodBlock.astChildren.isBlock.l
       cpg.controlStructure.isWhile.code.l shouldBe List("for (const char* item:items)")
-      checkForInOrOf(loopBlock, "char*")
+      checkForEachForLoop(loopBlock, "char*", "char*[]")
     }
   }
 
@@ -41,7 +38,7 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
      |  public:
      |    static void sink(const char* s) {}
      |
-     |    static void foo(const char* items[]) {
+     |    static void foo(const char* items) {
      |      for (const char* item : items) {
      |        sink(item);
      |      }
@@ -53,7 +50,7 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
       val List(methodBlock) = method.astChildren.isBlock.l
       val List(loopBlock)   = methodBlock.astChildren.isBlock.l
       cpg.controlStructure.isWhile.code.l shouldBe List("for (const char* item:items)")
-      checkForInOrOf(loopBlock, "char*")
+      checkForEachForLoop(loopBlock, "char*", "char*")
     }
   }
 
@@ -75,25 +72,21 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
       val List(methodBlock) = method.astChildren.isBlock.l
       val List(loopBlock)   = methodBlock.astChildren.isBlock.l
       cpg.controlStructure.isWhile.code.l shouldBe List("for (const std::string& item:items)")
-      checkForInOrOf(loopBlock, "std.string&")
+      checkForEachForLoop(loopBlock, "std.string&", "std.vector<std.string>&")
     }
   }
 
-  private def checkForInOrOf(node: Block, expectedItemType: String): Unit = {
+  private def checkForEachForLoop(node: Block, expectedItemType: String, expectedCollectionType: String): Unit = {
     val List(localIterator) = node.astChildren.isLocal.nameExact("<iterator>0").l
     localIterator.code shouldBe "<iterator>0"
     localIterator.typeFullName shouldBe Defines.Iterator
 
-    val List(localResult) = node.astChildren.isLocal.nameExact("<result>1").l
-    localResult.code shouldBe "<result>1"
-    localResult.typeFullName shouldBe expectedItemType
-
     val List(localI) = node.astChildren.isLocal.nameExact("item").l
     localI.code shouldBe "item"
     localI.typeFullName shouldBe expectedItemType
 
     val List(iteratorAssignment) =
-      node.astChildren.isCall.codeExact("<iterator>0 = <operator>.iterator(items)").l
+      node.astChildren.isCall.codeExact("<iterator>0 = items.iterator()").l
     iteratorAssignment.name shouldBe Operators.assignment
 
     val List(iteratorAssignmentLhs) = iteratorAssignment.astChildren.isIdentifier.l
@@ -103,19 +96,18 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
     iteratorAssignmentLhs.typeFullName shouldBe Defines.Iterator
 
     val List(iteratorAssignmentRhs) = iteratorAssignment.astChildren.isCall.l
-    iteratorAssignmentRhs.code shouldBe "<operator>.iterator(items)"
+    iteratorAssignmentRhs.code shouldBe "items.iterator()"
     iteratorAssignmentRhs.order shouldBe 2
     iteratorAssignmentRhs.argumentIndex shouldBe 2
-    iteratorAssignmentRhs.name shouldBe "<operator>.iterator"
-    iteratorAssignmentRhs.methodFullName shouldBe "<operator>.iterator"
-    iteratorAssignmentRhs.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
-
-    val objectKeysCallArg = iteratorAssignmentRhs.argument(1).asInstanceOf[Identifier]
-    objectKeysCallArg.name shouldBe "items"
-    objectKeysCallArg.order shouldBe 1
+    iteratorAssignmentRhs.name shouldBe "iterator"
+    iteratorAssignmentRhs.methodFullName shouldBe s"${Defines.UnresolvedNamespace}.iterator:std.iterator()"
+    iteratorAssignmentRhs.signature shouldBe "std.iterator()"
+    iteratorAssignmentRhs.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH
 
-    val List(varResult) = node.astChildren.isIdentifier.nameExact("<result>1").l
-    varResult.code shouldBe "<result>1"
+    val List(iteratorCallReceiver) = iteratorAssignmentRhs.receiver.isIdentifier.l
+    iteratorCallReceiver.name shouldBe "items"
+    iteratorCallReceiver.order shouldBe 1
+    iteratorCallReceiver.typeFullName shouldBe expectedCollectionType
 
     val List(varI) = node.astChildren.isIdentifier.nameExact("item").l
     varI.code shouldBe "item"
@@ -124,39 +116,25 @@ class ForEachLoopTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) {
     val List(loop) = node.astChildren.isControlStructure.l
     loop.controlStructureType shouldBe ControlStructureTypes.WHILE
 
-    val List(loopTestCall) = loop.astChildren.isCall.codeExact("!(<result>1 = <iterator>0.next()).done").l
-    loopTestCall.name shouldBe Operators.not
+    val List(loopTestCall) = loop.astChildren.isCall.codeExact("<iterator>0.hasNext()").l
+    loopTestCall.name shouldBe "hasNext"
+    loopTestCall.methodFullName shouldBe s"${Defines.Iterator}.hasNext:bool()"
     loopTestCall.order shouldBe 1
-
-    val List(doneMaCall) = loopTestCall.astChildren.isCall.codeExact("(<result>1 = <iterator>0.next()).done").l
-    doneMaCall.name shouldBe Operators.fieldAccess
-
-    val List(doneMaBase) = doneMaCall.astChildren.isCall.codeExact("(<result>1 = <iterator>0.next())").l
-    doneMaBase.name shouldBe Operators.assignment
-    doneMaBase.order shouldBe 1
-    doneMaBase.argumentIndex shouldBe 1
-
-    val List(doneMaBaseLhs) = doneMaBase.astChildren.isIdentifier.order(1).l
-    doneMaBaseLhs.name shouldBe "<result>1"
-    doneMaBaseLhs.argumentIndex shouldBe 1
-    doneMaBaseLhs.typeFullName shouldBe expectedItemType
-
-    val List(doneMaBaseRhs) = doneMaBase.astChildren.isCall.order(2).l
-    doneMaBaseRhs.code shouldBe "<iterator>0.next()"
-    doneMaBaseRhs.argumentIndex shouldBe 2
-    doneMaBaseRhs.methodFullName shouldBe s"${Defines.Iterator}.next"
-
-    val List(doneMember) = doneMaCall.astChildren.isFieldIdentifier.canonicalNameExact("done").l
-    doneMember.order shouldBe 2
-    doneMember.argumentIndex shouldBe 2
+    loopTestCall.receiver.isIdentifier.typeFullName.l shouldBe List(Defines.Iterator)
 
     val List(whileLoopBlock) = loop.astChildren.isBlock.l
     whileLoopBlock.order shouldBe 2
 
-    val List(loopVarAssignmentCall) = whileLoopBlock.astChildren.isCall.codeExact("item = <result>1.value").l
+    val List(loopVarAssignmentCall) = whileLoopBlock.astChildren.isCall.codeExact("item = <iterator>0.next()").l
     loopVarAssignmentCall.name shouldBe Operators.assignment
     loopVarAssignmentCall.order shouldBe 1
 
+    val List(loopNextCall) = loopVarAssignmentCall.argument.isCall.codeExact("<iterator>0.next()").l
+    loopNextCall.name shouldBe "next"
+    loopNextCall.methodFullName shouldBe s"${Defines.Iterator}.next:ANY()"
+    loopNextCall.typeFullName shouldBe "ANY"
+    loopNextCall.receiver.isIdentifier.typeFullName.l shouldBe List(Defines.Iterator)
+
     val List(sinkCall) = whileLoopBlock.astChildren.isBlock.astChildren.isCall.codeExact("sink(item)").l
     sinkCall.name shouldBe "sink"
   }
diff --git a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/dataflow/DestructuringTests.scala b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/dataflow/DestructuringTests.scala
index 94fd1551f..4ae6949c6 100644
--- a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/dataflow/DestructuringTests.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/dataflow/DestructuringTests.scala
@@ -36,7 +36,6 @@ class DestructuringTests extends KotlinCode2CpgFixture(withOssDataflow = true) {
             ("tmp_1.component1()", Some(6)),
             ("component1(this)", None),
             ("RET", Some(4)),
-            ("RET", None),
             ("tmp_1.component1()", Some(6)),
             ("name = tmp_1.component1()", Some(6)),
             ("println(name)", Some(7))
@@ -64,7 +63,6 @@ class DestructuringTests extends KotlinCode2CpgFixture(withOssDataflow = true) {
             ("tmp_1.component2()", Some(6)),
             ("component2(this)", None),
             ("RET", Some(4)),
-            ("RET", None),
             ("tmp_1.component2()", Some(6)),
             ("id = tmp_1.component2()", Some(6)),
             ("println(id)", Some(8))