Skip to content

Commit

Permalink
[ruby] Alias Handling for alias_method (#5206)
Browse files Browse the repository at this point in the history
`alias_method` is a builtin call that performs a more "dynamic" version of `alias` as it makes use of symbols. This change applies the same alias lowering logic to this function as what happens with `alias` calls.

Misc additions:
* Addresses warning for `reassign` not having an exhaustive match (due to the `InClause` object)
* Strips the leading `*` from collection parameters, as these are referred to in the method body as splatted identifiers without the leading `*`.
  • Loading branch information
DavidBakerEffendi authored Jan 7, 2025
1 parent 578b422 commit 2842a88
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,8 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case EnsureClause(thenClause) => EnsureClause(reassign(lhs, op, thenClause, transform))(x.span)
case ElsIfClause(condition, thenClause) =>
ElsIfClause(condition, reassign(lhs, op, thenClause, transform))(x.span)
case ElseClause(thenClause) => ElseClause(reassign(lhs, op, thenClause, transform))(x.span)
case ElseClause(thenClause) => ElseClause(reassign(lhs, op, thenClause, transform))(x.span)
case InClause(pattern, body) => InClause(pattern, reassign(lhs, op, body, transform))(x.span)
case WhenClause(matchExpressions, matchSplatExpression, thenClause) =>
WhenClause(matchExpressions, matchSplatExpression, reassign(lhs, op, thenClause, transform))(x.span)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,16 +314,17 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
case ArrayParameter(_) => prefixAsKernelDefined("Array")
case HashParameter(_) => prefixAsKernelDefined("Hash")
}
val name = node.name.stripPrefix("*")
val parameterIn = parameterInNode(
node = node,
name = node.name,
name = name,
code = code(node),
index = index,
isVariadic = true,
evaluationStrategy = EvaluationStrategies.BY_REFERENCE,
typeFullName = Option(typeFullName)
)
scope.addToScope(node.name, parameterIn)
scope.addToScope(name, parameterIn)
Ast(parameterIn)
case node: GroupedParameter =>
val parameterIn = parameterInNode(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.joern.rubysrc2cpg.parser

import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.{RubyExpression, *}
import io.joern.rubysrc2cpg.parser.AstType.Send
import io.joern.rubysrc2cpg.parser.RubyJsonHelpers.*
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.Defines.{NilClass, RubyOperators, getBuiltInType}
Expand Down Expand Up @@ -191,9 +192,18 @@ class RubyJsonToNodeCreator(
}

private def visitAlias(obj: Obj): RubyExpression = {
val name = visit(obj(ParserKeys.Name)).text.stripPrefix(":")
val alias = visit(obj(ParserKeys.Alias)).text.stripPrefix(":")
AliasStatement(alias, name)(obj.toTextSpan)
if (AstType.fromString(obj(ParserKeys.Type).str).contains(AstType.Send)) {
obj.visitArray(ParserKeys.Arguments) match {
case name :: alias :: _ => // different order than the normal `alias` kw
AliasStatement(alias.text.stripPrefix(":"), name.text.stripPrefix(":"))(obj.toTextSpan)
case _ => defaultResult(Option(obj.toTextSpan))
}
} else {
val name = visit(obj(ParserKeys.Name)).text.stripPrefix(":")
val alias = visit(obj(ParserKeys.Alias)).text.stripPrefix(":")
AliasStatement(alias, name)(obj.toTextSpan)
}

}

private def visitAnd(obj: Obj): RubyExpression = {
Expand Down Expand Up @@ -936,6 +946,7 @@ class RubyJsonToNodeCreator(
case "[]=" => visitBracketAssignmentAsSend(obj)
case "raise" => visitRaise(obj)
case "include" => visitInclude(obj)
case "alias_method" => visitAlias(obj)
case "attr_reader" | "attr_writer" | "attr_accessor" => visitFieldDeclaration(obj)
case "private" | "public" | "protected" => visitAccessModifier(obj)
case "private_class_method" | "public_class_method" => visitMethodAccessModifier(obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class MethodTests extends RubyCode2CpgFixture {
x.name shouldBe "x"
bar.name shouldBe "bar="

bar.parameter.name.l shouldBe List("self", "*args", "&block")
bar.parameter.name.l shouldBe List("self", "args", "&block")
// bar forwards parameters to a call to the aliased method
inside(bar.call.name("x=").l) {
case barCall :: Nil =>
Expand All @@ -349,6 +349,60 @@ class MethodTests extends RubyCode2CpgFixture {
}
}

"aliased methods with `alias_method`" should {
val cpg = code("""
|class Foo
| def aliasable(bbb)
| puts bbb
| end
|
| alias_method :print_something, :aliasable
|
| def someMethod(aaa)
| print_something(aaa)
| end
|end
|
|""".stripMargin)

"similarly alias the method as if it were calling `alias`" in {
inside(cpg.typeDecl("Foo").l) {
case foo :: Nil =>
inside(foo.method.nameNot(RDefines.Initialize, RDefines.TypeDeclBody).l) {
case a :: p :: s :: Nil =>
a.name shouldBe "aliasable"
p.name shouldBe "print_something"
s.name shouldBe "someMethod"

p.parameter.name.l shouldBe List("self", "args", "&block")
// bar forwards parameters to a call to the aliased method
inside(p.call.name("aliasable").l) {
case aliasableCall :: Nil =>
inside(aliasableCall.argument.l) {
case _ :: (args: Call) :: (blockId: Identifier) :: Nil =>
args.name shouldBe RubyOperators.splat
args.code shouldBe "*args"
args.argumentIndex shouldBe 1

blockId.name shouldBe "&block"
blockId.code shouldBe "&block"
blockId.argumentIndex shouldBe 2
case xs =>
fail(
s"Expected a two arguments for the call `aliasable`, instead got [${xs.code.mkString(",")}]"
)
}
aliasableCall.code shouldBe "aliasable(*args, &block)"
case xs => fail(s"Expected a single call to `aliasable`, instead got [${xs.code.mkString(",")}]")
}
case xs => fail(s"Expected a three virtual methods under `Foo`, instead got [${xs.code.mkString(",")}]")
}
case xs => fail(s"Expected a single type decl for `Foo`, instead got [${xs.code.mkString(",")}]")
}
}

}

"Singleton Methods for module scope" should {
val cpg = code("""
|module F
Expand Down

0 comments on commit 2842a88

Please sign in to comment.