From 3a98c4b40ba245d89f1fb67c68934a9ee25aef24 Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Fri, 5 Jul 2024 13:41:20 +0200 Subject: [PATCH] [ruby] Handle `super` Calls (#4740) The parser emits calls to `super` as different from simple calls, this PR handles them. --- .../rubysrc2cpg/parser/RubyNodeCreator.scala | 21 +++++++++++++++++++ .../rubysrc2cpg/querying/ClassTests.scala | 21 +++++++++++++++++++ .../querying/MethodReturnTests.scala | 8 +++---- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/parser/RubyNodeCreator.scala b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/parser/RubyNodeCreator.scala index a43cdf73ef20..4d87ab13b49b 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/parser/RubyNodeCreator.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/parser/RubyNodeCreator.scala @@ -7,6 +7,7 @@ import io.joern.rubysrc2cpg.passes.Defines import io.joern.rubysrc2cpg.passes.Defines.getBuiltInType import io.joern.rubysrc2cpg.utils.FreshNameGenerator import io.joern.x2cpg.Defines as XDefines +import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.tree.{ParseTree, RuleNode} import org.slf4j.LoggerFactory @@ -574,6 +575,26 @@ class RubyNodeCreator extends RubyParserBaseVisitor[RubyNode] { } } + override def visitSuperWithParentheses(ctx: RubyParser.SuperWithParenthesesContext): RubyNode = { + val block = Option(ctx.block()).map(visit) + val arguments = ctx.argumentWithParentheses().arguments.map(visit) + visitSuperCall(ctx, arguments, block) + } + + override def visitSuperWithoutParentheses(ctx: RubyParser.SuperWithoutParenthesesContext): RubyNode = { + val block = Option(ctx.block()).map(visit) + val arguments = ctx.argumentList().elements.map(visit) + visitSuperCall(ctx, arguments, block) + } + + private def visitSuperCall(ctx: ParserRuleContext, arguments: List[RubyNode], block: Option[RubyNode]): RubyNode = { + val callName = SimpleIdentifier()(ctx.toTextSpan.spanStart("super")) + block match { + case Some(body) => SimpleCallWithBlock(callName, arguments, body.asInstanceOf[Block])(ctx.toTextSpan) + case None => SimpleCall(callName, arguments)(ctx.toTextSpan) + } + } + override def visitIsDefinedExpression(ctx: RubyParser.IsDefinedExpressionContext): RubyNode = { SimpleCall(visit(ctx.isDefinedKeyword), visit(ctx.expressionOrCommand()) :: Nil)(ctx.toTextSpan) } diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/ClassTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/ClassTests.scala index 7d416e6c64de..89d548ecd0b6 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/ClassTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/ClassTests.scala @@ -840,4 +840,25 @@ class ClassTests extends RubyCode2CpgFixture { } } } + + "A call to super" should { + val cpg = code(""" + |class A + | def foo(a) + | end + |end + |class B < A + | def foo(a) + | super(a) + | end + |end + |""".stripMargin) + + "create a simple call" in { + val superCall = cpg.call.nameExact("super").head + superCall.code shouldBe "super(a)" + superCall.name shouldBe "super" + superCall.methodFullName shouldBe Defines.DynamicCallUnknownFullName + } + } } diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala index d2cc7fd8775a..f051ebab3562 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala @@ -339,7 +339,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withDataFlow = true) { } } - "implicit RETURN node for ASSOCIATION" in { + "implicit RETURN node for super call" in { val cpg = code(""" |def j | super(only: ["a"]) @@ -350,11 +350,11 @@ class MethodReturnTests extends RubyCode2CpgFixture(withDataFlow = true) { case jMethod :: Nil => inside(jMethod.methodReturn.toReturn.l) { case retAssoc :: Nil => - retAssoc.code shouldBe "only: [\"a\"]" + retAssoc.code shouldBe "super(only: [\"a\"])" val List(call: Call) = retAssoc.astChildren.l: @unchecked - call.name shouldBe RubyOperators.association - call.code shouldBe "only: [\"a\"]" + call.name shouldBe "super" + call.code shouldBe "super(only: [\"a\"])" case xs => fail(s"Expected exactly one return nodes, instead got [${xs.code.mkString(",")}]") } case _ => fail("Only one method expected")