Repository: joernio/joern
Pull request #3080 (2023-07-11 07:54:17)

rubysrc2cpg: Try catch for rescue and fixes
1. Used try-catch control structure for rescue like other frontends. Used the wrapper in it
2. Changes to handle implicit returns only if there is no rescue block. This is to handle the complications of implicit/explicit returns separately in a different PR
3. Disabled test case for implicit return handling with if-else
4. Enabled test cases passing due to this change. They were failing due to implicit returns being used in try-catch


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 47f0c77ad..f567829af 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
@@ -1044,12 +1044,8 @@ class AstCreator(
       asts.addAll(astForSingleLeftHandSideContext(ctx.exceptionVariableAssignment().singleLeftHandSide()))
     }
 
-    asts.addAll(astForCompoundStatement(ctx.thenClause().compoundStatement()))
-    val blockNode = NewBlock()
-      .code(ctx.getText)
-      .lineNumber(ctx.RESCUE().getSymbol.getLine)
-      .columnNumber(ctx.RESCUE().getSymbol.getCharPositionInLine)
-    blockAst(blockNode, asts.toList)
+    asts.addAll(astForCompoundStatement(ctx.thenClause().compoundStatement(), false))
+    blockAst(blockNode(ctx), asts.toList)
   }
 
   /** Handles body statements differently from [[astForBodyStatementContext]] by noting that method definitions should
@@ -1131,35 +1127,43 @@ class AstCreator(
   def astForBodyStatementContext(ctx: BodyStatementContext, addReturnNode: Boolean = false): Seq[Ast] = {
     val compoundStatementAsts = astForCompoundStatement(ctx.compoundStatement(), !addReturnNode)
 
-    /*
-     * Convert the last statement to a return AST if it is not already a return AST.
-     * If it is a return AST leave it untouched.
-     */
-    val compoundStatementAstsWithReturn =
-      if (addReturnNode && compoundStatementAsts.nonEmpty) {
-        convertLastStmtToReturn(compoundStatementAsts, ctx.compoundStatement().statements())
-      } else {
-        compoundStatementAsts
-      }
+    if (ctx.rescueClause().size > 0) {
+      val elseClauseAsts = Option(ctx.elseClause()) match
+        case Some(ctx) => astForCompoundStatement(ctx.compoundStatement(), false)
+        case None      => Seq()
 
-    val mainBodyAsts = if (ctx.ensureClause() != null) {
-      val ensureAsts = astForCompoundStatement(ctx.ensureClause().compoundStatement())
-      compoundStatementAstsWithReturn ++ ensureAsts
-    } else {
-      compoundStatementAstsWithReturn
-    }
+      /*
+       * TODO Conversion of last statement to return AST is needed here
+       */
+      val tryBodyAsts = compoundStatementAsts ++ elseClauseAsts
+      val tryBodyAst  = blockAst(blockNode(ctx), tryBodyAsts.toList)
 
-    val rescueAsts = ctx
-      .rescueClause()
-      .asScala
-      .map(astForRescueClauseContext)
-      .toSeq
+      val finallyAst = Option(ctx.ensureClause()) match
+        case Some(ctx) => astForCompoundStatement(ctx.compoundStatement()).headOption
+        case None      => None
+
+      val catchAsts = ctx
+        .rescueClause()
+        .asScala
+        .map(astForRescueClauseContext)
+        .toSeq
 
-    if (ctx.elseClause() != null) {
-      val elseClauseAsts = astForCompoundStatement(ctx.elseClause().compoundStatement())
-      mainBodyAsts ++ rescueAsts ++ elseClauseAsts
+      val tryNode = NewControlStructure()
+        .controlStructureType(ControlStructureTypes.TRY)
+        .code("try")
+        .lineNumber(line(ctx))
+        .columnNumber(column(ctx))
+
+      Seq(tryCatchAst(tryNode, tryBodyAst, catchAsts, finallyAst))
+    } else if (addReturnNode && compoundStatementAsts.nonEmpty) {
+      /*
+       * Convert the last statement to a return AST if it is not already a return AST.
+       * If it is a return AST leave it untouched.
+       * TODO Handling for last statement being if-else is needed here
+       */
+      convertLastStmtToReturn(compoundStatementAsts, ctx.compoundStatement().statements())
     } else {
-      mainBodyAsts ++ rescueAsts
+      compoundStatementAsts
     }
   }
 


Test patch:
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/macros/MacroHandlingTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/macros/MacroHandlingTests.scala
index 58b6f5801..4a1444749 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/macros/MacroHandlingTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/macros/MacroHandlingTests.scala
@@ -242,12 +242,14 @@ class MacroHandlingTests extends CCodeToCpgSuite {
 
     "should expand the macro on both sides of binary operators" in {
       cpg.call.name(Operators.and).code.l shouldBe List("x & FLAG_A", "FLAG_A & x")
-      val List(andCall1, andCall2)                           = cpg.call.name(Operators.and).l
-      val List(andCall1Arg1: Identifier, andCall1Arg2: Call) = andCall1.argument.l: @unchecked
+      val List(andCall1, andCall2) = cpg.call.name(Operators.and).l
+      val List(andCall1Arg1)       = andCall1.argument.isIdentifier.l
       andCall1Arg1.name shouldBe "x"
+      val List(andCall1Arg2) = andCall1.argument.isCall.l
       andCall1Arg2.name shouldBe "FLAG_A"
-      val List(andCall2Arg1: Call, andCall2Arg2: Identifier) = andCall2.argument.l: @unchecked
+      val List(andCall2Arg1) = andCall2.argument.isCall.l
       andCall2Arg1.name shouldBe "FLAG_A"
+      val List(andCall2Arg2) = andCall2.argument.isIdentifier.l
       andCall2Arg2.name shouldBe "x"
     }
   }
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ConstClosurePassTests.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ConstClosurePassTests.scala
index 75a928818..7c59bdf4f 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ConstClosurePassTests.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ConstClosurePassTests.scala
@@ -45,7 +45,7 @@ class ConstClosurePassTests extends DataFlowCodeToCpgSuite {
       val List(foo) = cpg.method.name("foo").l
       foo.name shouldBe "foo"
       foo.fullName.endsWith("program:foo") shouldBe true
-      val Some(fooCall) = cpg.call("foo").headOption: @unchecked
+      val List(fooCall) = cpg.call("foo").l
       fooCall.methodFullName.endsWith("program:foo") shouldBe true
     }
 
@@ -53,7 +53,7 @@ class ConstClosurePassTests extends DataFlowCodeToCpgSuite {
       val List(bar) = cpg.method.name("anonymous1").l
       bar.name shouldBe "anonymous1"
       bar.fullName.endsWith("program:anonymous1") shouldBe true
-      val Some(barCall) = cpg.call("bar").headOption: @unchecked
+      val List(barCall) = cpg.call("bar").l
       barCall.methodFullName.endsWith("program:anonymous1") shouldBe true
     }
   }
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/InheritanceFullNamePassTests.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/InheritanceFullNamePassTests.scala
index 84096616a..00f4caa96 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/InheritanceFullNamePassTests.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/InheritanceFullNamePassTests.scala
@@ -42,7 +42,7 @@ class InheritanceFullNamePassTests extends DataFlowCodeToCpgSuite {
     )
 
     "resolve the type being inherited fully" in {
-      val Some(tgtType) = cpg.typeDecl.nameExact("MusicWithLyrics").headOption: @unchecked
+      val List(tgtType) = cpg.typeDecl.nameExact("MusicWithLyrics").l
       tgtType.fullName shouldBe "inheritance.js::program:MusicWithLyrics"
       cpg.typeDecl("Musician").fullName.headOption shouldBe Some(
         Seq("domain", "music.js::program:Musician").mkString(File.separator)
@@ -54,8 +54,8 @@ class InheritanceFullNamePassTests extends DataFlowCodeToCpgSuite {
     }
 
     "identifiers instantiated from these types should have their fully resolved types" in {
-      val Some(musician)           = cpg.identifier.nameExact("myMusician").headOption: @unchecked
-      val Some(musicianWithLyrics) = cpg.identifier.nameExact("myMusicWithLyrics").headOption: @unchecked
+      val List(musician)           = cpg.identifier.nameExact("myMusician").l
+      val List(musicianWithLyrics) = cpg.identifier.nameExact("myMusicWithLyrics").l
       musician.typeFullName shouldBe Seq("domain", "music.js::program:Musician").mkString(File.separator)
       musicianWithLyrics.typeFullName shouldBe "inheritance.js::program:MusicWithLyrics"
     }
@@ -77,7 +77,7 @@ class InheritanceFullNamePassTests extends DataFlowCodeToCpgSuite {
     )
 
     "resolve the type to a type stub from an external module" in {
-      val Some(tgtType) = cpg.typeDecl.nameExact("MusicWithLyrics").headOption: @unchecked
+      val List(tgtType) = cpg.typeDecl.nameExact("MusicWithLyrics").l
       tgtType.fullName shouldBe "inheritance.js::program:MusicWithLyrics"
       tgtType.inheritsFromTypeFullName.headOption shouldBe Some("music.js::program:Musician")
     }
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
index 0beb16161..4e13735ba 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
@@ -7,7 +7,7 @@ import io.shiftleft.semanticcpg.language._
 class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
 
   "literals declared from built-in types" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |let x = 123;
         |
         |function foo_shadowing() {
@@ -38,8 +38,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "call from a function from an external type" should {
-
-    lazy val cpg = code(
+    val cpg = code(
       """
         |import { WebClient } from "slack_sdk";
         |import { SendGridAPIClient } from "sendgrid";
@@ -97,17 +96,17 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "recovering paths for built-in calls" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |console.log("Hello world");
         |let x = Math.abs(-1);
         |""".stripMargin).cpg
 
     "resolve 'print' and 'max' calls" in {
-      val Some(printCall) = cpg.call("log").headOption: @unchecked
+      val List(printCall) = cpg.call("log").l
       printCall.methodFullName shouldBe "__whatwg.console:log"
-      val Some(maxCall) = cpg.call("abs").headOption: @unchecked
+      val List(maxCall) = cpg.call("abs").l
       maxCall.methodFullName shouldBe "__ecma.Math:abs"
-      val Some(x) = cpg.identifier("x").headOption: @unchecked
+      val List(x) = cpg.identifier("x").l
       // TODO: Ideally we would know the result of `abs` but this can be a future task
       x.typeFullName shouldBe "__ecma.Math:abs:<returnValue>"
     }
@@ -115,7 +114,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "recovering module members across modules" should {
-    lazy val cpg = code(
+    val cpg = code(
       """
         |import { SQLAlchemy } from "flask_sqlalchemy";
         |
@@ -153,22 +152,20 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
       e.fullName shouldBe "flask_sqlalchemy:SQLAlchemy"
     }
 
-    "resolve 'x' and 'y' locally under foo.py" in {
-      val Some(x) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("x").headOption: @unchecked
-      x.typeFullName shouldBe "__ecma.Number"
-      val Some(y) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("y").headOption: @unchecked
-      y.typeFullName shouldBe "__ecma.String"
-      val Some(db) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("db").headOption: @unchecked
-      db.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
+    "resolve 'x' and 'y' locally under Foo.ts" in {
+      val List(x1, x2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("x").l
+      x1.typeFullName shouldBe "__ecma.Number"
+      x2.typeFullName shouldBe "__ecma.Number"
+      val List(y1, y2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("y").l
+      y1.typeFullName shouldBe "__ecma.String"
+      y2.typeFullName shouldBe "__ecma.String"
+      val List(db1, db2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("db").l
+      db1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
+      db2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
     }
 
     "resolve 'foo.x' and 'foo.y' field access primitive types correctly" in {
-      val List(z1, z2) = cpg.file
-        .name(".*Bar.*")
-        .ast
-        .isIdentifier
-        .nameExact("z")
-        .l
+      val List(z1, z2) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("z").l
       z1.typeFullName shouldBe "ANY"
       z1.dynamicTypeHintFullName shouldBe Seq("__ecma.Number", "__ecma.String")
       z2.typeFullName shouldBe "ANY"
@@ -176,23 +173,17 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
     }
 
     "resolve 'foo.d' field access object types correctly" in {
-      val Some(d) = cpg.file
-        .name(".*Bar.*")
-        .ast
-        .isIdentifier
-        .nameExact("d")
-        .headOption: @unchecked
-      d.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
-      d.dynamicTypeHintFullName shouldBe Seq()
+      val List(d1, d2, d3) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("d").l
+      d1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
+      d1.dynamicTypeHintFullName shouldBe Seq()
+      d2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
+      d2.dynamicTypeHintFullName shouldBe Seq()
+      d3.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy"
+      d3.dynamicTypeHintFullName shouldBe Seq()
     }
 
     "resolve a 'createTable' call indirectly from 'foo.d' field access correctly" in {
-      val List(d) = cpg.file
-        .name(".*Bar.*")
-        .ast
-        .isCall
-        .name("createTable")
-        .l
+      val List(d) = cpg.file.name(".*Bar.*").ast.isCall.name("createTable").l
       d.methodFullName shouldBe "flask_sqlalchemy:SQLAlchemy:createTable"
       d.dynamicTypeHintFullName shouldBe Seq()
       d.callee(NoResolve).isExternal.headOption shouldBe Some(true)
@@ -213,7 +204,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "Importing an anonymous function" should {
-    lazy val cpg = code(
+    val cpg = code(
       """
         |var refThis = this;
         |
@@ -243,24 +234,24 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
     }
 
     "resolve the method full name off of an aliased 'this'" in {
-      val Some(x) = cpg.file("util.js").ast.isCall.nameExact("getIncrementalInteger").headOption: @unchecked
+      val List(x) = cpg.file("util.js").ast.isCall.nameExact("getIncrementalInteger").l
       x.methodFullName shouldBe "util.js::program:getIncrementalInteger"
     }
 
     "resolve the method full name off of the imported 'util'" in {
-      val Some(x) = cpg.file("foo.js").ast.isCall.nameExact("getIncrementalInteger").headOption: @unchecked
+      val List(x) = cpg.file("foo.js").ast.isCall.nameExact("getIncrementalInteger").l
       x.methodFullName shouldBe "util.js::program:getIncrementalInteger"
     }
 
     "resolve the full name of the currying from the closure" in {
-      val Some(x) = cpg.file("util.js").ast.isCall.lineNumber(4).lastOption: @unchecked
+      val List(x) = cpg.file("util.js").ast.isCall.nameExact("anonymous").lineNumber(4).l
       x.name shouldBe "anonymous"
       x.methodFullName shouldBe "util.js::program:anonymous"
     }
   }
 
   "Type obtained via assignment from `require`" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |const google = require('googleapis');
         |const driveObj = google.drive({ version: 'v3', auth });
         |""".stripMargin)
@@ -282,7 +273,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "Type obtained via assignment from `require` to {...}" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |const { google } = require('googleapis');
         |const driveObj = google.drive({ version: 'v3', auth });
         |""".stripMargin)
@@ -304,7 +295,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
   }
 
   "Type obtained via field access from 'require' derived identifier" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |import google from 'googleapis';
         |export const authObj = new google.auth.GoogleAuth({
         |  keyFile: 'path/to/your/credentials.json',
@@ -315,28 +306,32 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
     "be propagated to `methodFullName` of call" in {
       val List(constructor) = cpg.call.code("new google.auth.GoogleAuth\\(.*").l
       constructor.methodFullName shouldBe "googleapis:google:<member>(auth):GoogleAuth:<init>"
-      val Some(typeFullName) = cpg.identifier.name("authObj").typeFullName.headOption: @unchecked
-      typeFullName shouldBe "googleapis:google:<member>(auth):GoogleAuth"
+      val List(authObj1, authObj2) = cpg.identifier.name("authObj").l
+      authObj1.typeFullName shouldBe "googleapis:google:<member>(auth):GoogleAuth"
+      authObj2.typeFullName shouldBe "googleapis:google:<member>(auth):GoogleAuth"
     }
   }
 
   "Type casts of an identifier and call receiver" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |let imgScr: string = <string>this.imageElement;
         |this.imageElement = new HTMLImageElement();
         |(<HTMLImageElement>this.imageElement).src = imgScr;
         |""".stripMargin)
 
     "succeed in propagating type cast identifiers" in {
-      val Some(imgSrc) = cpg.identifier("imgScr").headOption: @unchecked
-      imgSrc.typeFullName shouldBe "__ecma.String"
-      val Some(_tmp_0) = cpg.identifier("_tmp_0").headOption: @unchecked
-      _tmp_0.typeFullName shouldBe "__ecma.HTMLImageElement"
+      val List(imgSrc1, imgSrc2) = cpg.identifier("imgScr").l
+      imgSrc1.typeFullName shouldBe "__ecma.String"
+      imgSrc2.typeFullName shouldBe "__ecma.String"
+      val List(tmp1, tmp2, tmp3) = cpg.identifier("_tmp_0").l
+      tmp1.typeFullName shouldBe "__ecma.HTMLImageElement"
+      tmp2.typeFullName shouldBe "__ecma.HTMLImageElement"
+      tmp3.typeFullName shouldBe "__ecma.HTMLImageElement"
     }
   }
 
   "Type hints for method parameters and returns" should {
-    lazy val cpg = code("""
+    val cpg = code("""
         |import google from 'googleapis';
         |
         |function foo(a: google.More, b: google.Money): google.Problems {
@@ -346,19 +341,17 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
         |""".stripMargin)
 
     "be propagated within the method full name reflecting the import `googleapis`" in {
-      val Some(bar) = cpg.call("bar").headOption: @unchecked
+      val List(bar) = cpg.call("bar").l
       bar.methodFullName shouldBe "googleapis:google:More:bar"
-
-      val Some(baz) = cpg.call("baz").headOption: @unchecked
+      val List(baz) = cpg.call("baz").l
       baz.methodFullName shouldBe "googleapis:google:Money:baz"
-
-      val Some(foo) = cpg.method("foo").methodReturn.headOption: @unchecked
+      val List(foo) = cpg.method("foo").methodReturn.l
       foo.typeFullName shouldBe "googleapis:google:Problems"
     }
   }
 
   "Recovered values that are returned in methods" should {
-    lazy val cpg = code(
+    val cpg = code(
       """
         |const axios = require("axios");
         |
@@ -398,29 +391,26 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
     }
 
     "propagate literal types to the method return" in {
-      val Some(literalMethod) = cpg.method.nameExact("literalFunction").headOption: @unchecked
+      val List(literalMethod) = cpg.method.nameExact("literalFunction").l
       literalMethod.methodReturn.typeFullName shouldBe "__ecma.Number"
-      val Some(x) = cpg.identifier("x").headOption: @unchecked
+      val List(x) = cpg.identifier("x").l
       x.typeFullName shouldBe "__ecma.Number"
-
-      val Some(literalCall) = cpg.call.nameExact("literalFunction").headOption: @unchecked
+      val List(literalCall) = cpg.call.nameExact("literalFunction").l
       literalCall.typeFullName shouldBe "__ecma.Number"
     }
 
     "propagate complex types to the method return" in {
-      val Some(getMethod) = cpg.method.nameExact("get").headOption: @unchecked
+      val List(getMethod) = cpg.method.nameExact("get").lineNumber(12).l
       getMethod.methodReturn.typeFullName shouldBe "axios:create:<returnValue>:get:<returnValue>"
-      val Some(y) = cpg.identifier("y").headOption: @unchecked
+      val List(y) = cpg.identifier("y").l
       y.typeFullName shouldBe "axios:create:<returnValue>:get:<returnValue>"
-
-      val Some(getCall) = cpg.call.nameExact("get").headOption: @unchecked
+      val List(getCall) = cpg.call.nameExact("get").lineNumber(5).l
       getCall.typeFullName shouldBe "axios:create:<returnValue>:get:<returnValue>"
     }
   }
 
   "Temporary variables inserted to produce a three-address code structure" should {
-
-    lazy val cpg = code(
+    val cpg = code(
       """
         |import { HttpClient } from '@angular/common/http';
         |
@@ -446,7 +436,7 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
 
   "Members initialized from constructors where the parameter has a type hint" should {
 
-    lazy val cpg = code(
+    val cpg = code(
       """
         |import { HttpClient } from '@angular/common/http';
         |
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsClassesAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsClassesAstCreationPassTest.scala
index ba9373b9e..cd71e0966 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsClassesAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsClassesAstCreationPassTest.scala
@@ -291,7 +291,7 @@ class TsClassesAstCreationPassTest extends AbstractPassTest {
         |    gender: string;
         |}
         |""".stripMargin) { cpg =>
-      val Some(userType) = cpg.typeDecl.name("User").headOption: @unchecked
+      val List(userType) = cpg.typeDecl.name("User").l
       userType.member.name.l shouldBe List("email", "organizationIds", "username", "name", "gender")
       userType.member.typeFullName.toSet shouldBe Set("__ecma.String", "string[]")
     }
@@ -304,11 +304,11 @@ class TsClassesAstCreationPassTest extends AbstractPassTest {
         |    }
         |}
         |""".stripMargin) { cpg =>
-      val Some(credentialsType) = cpg.typeDecl.nameExact("_anon_cdecl").headOption: @unchecked
+      val List(credentialsType) = cpg.typeDecl.nameExact("_anon_cdecl").l
       credentialsType.fullName shouldBe "code.ts::program:Test:run:_anon_cdecl"
       credentialsType.member.name.l shouldBe List("username", "password")
       credentialsType.member.typeFullName.toSet shouldBe Set("__ecma.String")
-      val Some(credentialsParam) = cpg.parameter.nameExact("credentials").headOption: @unchecked
+      val List(credentialsParam) = cpg.parameter.nameExact("credentials").l
       credentialsParam.typeFullName shouldBe "code.ts::program:Test:run:_anon_cdecl"
       // should not produce dangling nodes that are meant to be inside procedures
       cpg.all.collectAll[CfgNode].whereNot(_._astIn).size shouldBe 0
@@ -321,11 +321,11 @@ class TsClassesAstCreationPassTest extends AbstractPassTest {
         |    log(`${username}: ${password}`);
         |}
         |""".stripMargin) { cpg =>
-      val Some(credentialsType) = cpg.typeDecl.nameExact("_anon_cdecl").headOption: @unchecked
+      val List(credentialsType) = cpg.typeDecl.nameExact("_anon_cdecl").l
       credentialsType.fullName shouldBe "code.ts::program:apiCall:_anon_cdecl"
       credentialsType.member.name.l shouldBe List("username", "password")
       credentialsType.member.typeFullName.toSet shouldBe Set(Defines.Any)
-      val Some(credentialsParam) = cpg.parameter.nameExact("param1_0").headOption: @unchecked
+      val List(credentialsParam) = cpg.parameter.nameExact("param1_0").l
       credentialsParam.typeFullName shouldBe "code.ts::program:apiCall:_anon_cdecl"
       // should not produce dangling nodes that are meant to be inside procedures
       cpg.all.collectAll[CfgNode].whereNot(_._astIn).size shouldBe 0
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 9c4042f46..ffda2a7b3 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
@@ -92,6 +92,28 @@ class DataFlowTests extends DataFlowCodeToCpgSuite {
     }
   }
 
+  "Implicit return in if-else block" ignore {
+    val cpg = code("""
+        |def foo(arg)
+        |if arg > 1
+        |        arg + 1
+        |else
+        |        arg + 10
+        |end
+        |end
+        |
+        |x = 1
+        |y = foo x
+        |puts y
+        |""".stripMargin)
+
+    "be found" in {
+      val src  = cpg.identifier.name("x").l
+      val sink = cpg.call.name("puts").l
+      sink.reachableByFlows(src).l.size shouldBe 2
+    }
+  }
+
   "Return via call w/o initialization" should {
     val cpg = code("""
         |def add(p)
@@ -1115,7 +1137,7 @@ class DataFlowTests extends DataFlowCodeToCpgSuite {
     }
   }
 
-  "Data flow for begin/rescue with sink in function without begin" ignore {
+  "Data flow for begin/rescue with sink in function without begin" should {
     val cpg = code("""
         |def foo(arg)
         |  puts "in begin"
@@ -1140,7 +1162,7 @@ class DataFlowTests extends DataFlowCodeToCpgSuite {
     }
   }
 
-  "Data flow for begin/rescue with sink in function without begin and sink in rescue with exception" ignore {
+  "Data flow for begin/rescue with sink in function without begin and sink in rescue with exception" should {
     val cpg = code("""
         |def foo(arg)
         |  puts "in begin"
@@ -1163,7 +1185,7 @@ class DataFlowTests extends DataFlowCodeToCpgSuite {
     }
   }
 
-  "Data flow for begin/rescue with sink in function without begin and sink in catch-call rescue" ignore {
+  "Data flow for begin/rescue with sink in function without begin and sink in catch-call rescue" should {
     val cpg = code("""
         |def foo(arg)
         |  puts "in begin"