Skip to content

Commit

Permalink
[ruby] Handle super Calls (#4740)
Browse files Browse the repository at this point in the history
The parser emits calls to `super` as different from simple calls, this PR handles them.
  • Loading branch information
DavidBakerEffendi authored Jul 5, 2024
1 parent cf8d139 commit 3a98c4b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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")
Expand Down

0 comments on commit 3a98c4b

Please sign in to comment.