Repository: joernio/joern (scala)

Issue:
[Bug] Ruby tests warning messages
Running `rubysrc2cpg/test` yields many warning messages like so:

```
...
[info] CPG for code with modules
[WARN ] The overlay base already exists - skipping creation
[WARN ] The overlay controlflow already exists - skipping creation
[WARN ] The overlay typerel already exists - skipping creation
[WARN ] The overlay callgraph already exists - skipping creation
...
```

Seems to have been inadvertently introduced during #3053 .

Pull request:
* It is invalid to have a body directly under a type declaration in terms of using the `Node.method` implementation, but also logically we should use the `<clinit>`-style method. So this has been implemented
* Also fixed the ruby test fixture to not attempt to run duplicate overlays and have combined the test fixtures into one, configurable test fixture.

CC: @khemrajrathore 

Resolves https://github.com/joernio/joern/issues/3064

Patch:
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 053cdc6b946e..5df100e69c4b 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
@@ -6,7 +6,7 @@ import io.joern.rubysrc2cpg.utils.PackageContext
 import io.joern.x2cpg.Ast.storeInDiffGraph
 import io.joern.x2cpg.Defines.DynamicCallUnknownFullName
 import io.joern.x2cpg.datastructures.{Global, Scope}
-import io.joern.x2cpg.{Ast, AstCreatorBase, AstNodeBuilder}
+import io.joern.x2cpg.{Ast, AstCreatorBase, AstNodeBuilder, Defines as XDefines}
 import io.shiftleft.codepropertygraph.generated.*
 import io.shiftleft.codepropertygraph.generated.nodes.*
 import org.antlr.v4.runtime.tree.TerminalNode
@@ -1092,7 +1092,27 @@ class AstCreator(
         val modifierAst = Ast(NewModifier().modifierType(modifierType))
         Ast(m).withChild(modifierAst)
       }
-    Seq(blockAst(blockNode(classCtx), blockStmts.toList)) ++ uniqueMemberReferences ++ methodStmts
+
+    // Create class initialization method to host all field initializers
+    val classInitMethodAst = if (blockStmts.nonEmpty) {
+      val classInitFullName = (classStack.reverse :+ XDefines.StaticInitMethodName).mkString(":")
+      val classInitMethod = methodNode(
+        classCtx,
+        XDefines.StaticInitMethodName,
+        XDefines.StaticInitMethodName,
+        classInitFullName,
+        None,
+        filename,
+        Option(NodeTypes.TYPE_DECL),
+        Option(classStack.reverse.mkString(":"))
+      )
+      val classInitBody = blockAst(blockNode(classCtx), blockStmts.toList)
+      Seq(methodAst(classInitMethod, Seq.empty, classInitBody, methodReturnNode(classCtx, Defines.Any)))
+    } else {
+      Seq.empty
+    }
+
+    classInitMethodAst ++ uniqueMemberReferences ++ methodStmts
   }
 
   private def convertLastStmtToReturn(compoundStatementAsts: Seq[Ast], ctxStmt: StatementsContext): Seq[Ast] = {


Test patch:
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/DataFlowTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/DataFlowTests.scala
index ffda2a7b3d9a..c71d8b689a92 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/DataFlowTests.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/DataFlowTests.scala
@@ -1,10 +1,10 @@
 package io.joern.rubysrc2cpg.dataflow
 
-import io.joern.dataflowengineoss.language._
-import io.joern.rubysrc2cpg.testfixtures.DataFlowCodeToCpgSuite
-import io.shiftleft.semanticcpg.language._
+import io.joern.dataflowengineoss.language.*
+import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
+import io.shiftleft.semanticcpg.language.*
 
-class DataFlowTests extends DataFlowCodeToCpgSuite {
+class DataFlowTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataFlow = true) {
 
   "Data flow through if-elseif-else" should {
     val cpg = code("""
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/passes/ast/TypeDeclAstCreationPassTest.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/passes/ast/TypeDeclAstCreationPassTest.scala
index a1d75641c574..8d2bc8b43d6d 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/passes/ast/TypeDeclAstCreationPassTest.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/passes/ast/TypeDeclAstCreationPassTest.scala
@@ -3,6 +3,8 @@ package io.joern.rubysrc2cpg.passes.ast
 import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
 import io.shiftleft.codepropertygraph.generated.ModifierTypes
 import io.shiftleft.semanticcpg.language.*
+import io.joern.x2cpg.Defines as XDefines
+import io.shiftleft.codepropertygraph.generated.Operators
 
 class TypeDeclAstCreationPassTest extends RubyCode2CpgFixture {
 
@@ -76,6 +78,11 @@ class TypeDeclAstCreationPassTest extends RubyCode2CpgFixture {
       song.name shouldBe "Song"
       song.fullName shouldBe "Test0.rb::program:Song"
 
+      val List(classInit) = song.method.name(XDefines.StaticInitMethodName).l
+      classInit.fullName shouldBe s"Test0.rb::program:Song:${XDefines.StaticInitMethodName}"
+      val List(playsDef) = classInit.call.nameExact(Operators.fieldAccess).fieldAccess.l
+      playsDef.fieldIdentifier.canonicalName.headOption shouldBe Option("plays")
+
       val List(artist, duration, name, plays) = song.member.l
 
       plays.name shouldBe "plays"
@@ -136,7 +143,7 @@ class TypeDeclAstCreationPassTest extends RubyCode2CpgFixture {
       myClass.name shouldBe "MyClass"
       myClass.fullName shouldBe "Test0.rb::program:MyClass"
 
-      val List(m1, m2, m3, m4) = myClass.method.l
+      val List(_, m1, m2, m3, m4) = myClass.method.l
       m1.name shouldBe "method1"
       m2.name shouldBe "method2"
       m3.name shouldBe "method3"
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallGraphTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallGraphTests.scala
index d2ed1c0b41ad..79bd96038f99 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallGraphTests.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallGraphTests.scala
@@ -4,7 +4,7 @@ import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
 import io.shiftleft.codepropertygraph.generated.nodes.Method
 import io.shiftleft.semanticcpg.language._
 
-class CallGraphTests extends RubyCode2CpgFixture {
+class CallGraphTests extends RubyCode2CpgFixture(withPostProcessing = true) {
 
   val cpg = code("""
       |def bar(content)
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/DataFlowCodeToCpgSuite.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/DataFlowCodeToCpgSuite.scala
deleted file mode 100644
index 43884596c9a3..000000000000
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/DataFlowCodeToCpgSuite.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-package io.joern.rubysrc2cpg.testfixtures
-
-import io.joern.dataflowengineoss.language._
-import io.joern.dataflowengineoss.layers.dataflows.{OssDataFlow, OssDataFlowOptions}
-import io.joern.dataflowengineoss.queryengine.EngineContext
-import io.joern.rubysrc2cpg.RubySrc2Cpg
-import io.joern.x2cpg.X2Cpg
-import io.joern.x2cpg.testfixtures.{Code2CpgFixture, TestCpg}
-import io.shiftleft.semanticcpg.layers.LayerCreatorContext
-
-class DataFlowTestCpg extends TestCpg with RubyFrontend {
-
-  override protected def applyPasses(): Unit = {
-    X2Cpg.applyDefaultOverlays(this)
-    val context = new LayerCreatorContext(this)
-    val options = new OssDataFlowOptions()
-    new OssDataFlow(options).run(context)
-  }
-}
-
-class DataFlowCodeToCpgSuite(withDependencyDownload: Boolean = false)
-    extends Code2CpgFixture(() => new DataFlowTestCpg()) {
-
-  protected implicit val context: EngineContext = EngineContext()
-
-  protected def flowToResultPairs(path: Path): List[(String, Integer)] =
-    path.resultPairs().collect { case (firstElement: String, secondElement: Option[Integer]) =>
-      (firstElement, secondElement.getOrElse(-1))
-    }
-
-}
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala
index 94a680f2ee31..360cc14a41c6 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala
@@ -1,11 +1,13 @@
 package io.joern.rubysrc2cpg.testfixtures
 
+import io.joern.dataflowengineoss.layers.dataflows.{OssDataFlow, OssDataFlowOptions}
 import io.joern.dataflowengineoss.queryengine.EngineContext
 import io.joern.rubysrc2cpg.{Config, RubySrc2Cpg}
 import io.joern.x2cpg.X2Cpg
-import io.joern.x2cpg.testfixtures.{Code2CpgFixture, DefaultTestCpg, LanguageFrontend}
+import io.joern.x2cpg.testfixtures.{Code2CpgFixture, DefaultTestCpg, LanguageFrontend, TestCpg}
 import io.shiftleft.codepropertygraph.Cpg
 import io.shiftleft.semanticcpg.language.{ICallResolver, NoResolve}
+import io.shiftleft.semanticcpg.layers.LayerCreatorContext
 
 import java.io.File
 
@@ -17,24 +19,34 @@ trait RubyFrontend extends LanguageFrontend {
       getConfig()
         .map(_.asInstanceOf[Config])
         .getOrElse(Config())
-    new RubySrc2Cpg()
-      .createCpg(sourceCodeFile.getAbsolutePath)
-      .map { cpg =>
-        X2Cpg.applyDefaultOverlays(cpg)
-        applyPostProcessingPasses(cpg)
-      }
-      .get
+    new RubySrc2Cpg().createCpg(sourceCodeFile.getAbsolutePath).get
   }
 
-  private def applyPostProcessingPasses(cpg: Cpg): Cpg = {
-    RubySrc2Cpg.postProcessingPasses(cpg).foreach(_.createAndApply())
-    cpg
+}
+
+class DefaultTestCpgWithRuby(withPostProcessing: Boolean, withDataFlow: Boolean)
+    extends DefaultTestCpg
+    with RubyFrontend {
+  override def applyPasses(): Unit = {
+    X2Cpg.applyDefaultOverlays(this)
+
+    if (withPostProcessing) {
+      RubySrc2Cpg.postProcessingPasses(this).foreach(_.createAndApply())
+    }
+
+    if (withDataFlow) {
+      val context = new LayerCreatorContext(this)
+      val options = new OssDataFlowOptions()
+      new OssDataFlow(options).run(context)
+    }
   }
+
 }
 
-class DefaultTestCpgWithRuby extends DefaultTestCpg with RubyFrontend
+class RubyCode2CpgFixture(withPostProcessing: Boolean = false, withDataFlow: Boolean = false)
+    extends Code2CpgFixture(() => new DefaultTestCpgWithRuby(withPostProcessing, withDataFlow)) {
 
-class RubyCode2CpgFixture extends Code2CpgFixture(() => new DefaultTestCpgWithRuby()) {
   implicit val resolver: ICallResolver           = NoResolve
   implicit lazy val engineContext: EngineContext = EngineContext()
+
 }