Skip to content

Commit

Permalink
[ruby] Add self Base to Simple Calls (#4637)
Browse files Browse the repository at this point in the history
Added implicit self receiver to simple calls.
  • Loading branch information
DavidBakerEffendi committed Jun 5, 2024
1 parent 3c1eefd commit f408501
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As

private def astForFieldInstance(name: String, node: RubyNode & RubyFieldIdentifier): Ast = {
val identName = node match {
case _: InstanceFieldIdentifier => Defines.This
case _: InstanceFieldIdentifier => Defines.Self
case _: ClassFieldIdentifier => scope.surroundingTypeFullName.map(_.split("[.]").last).getOrElse(Defines.Any)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import io.shiftleft.codepropertygraph.generated.{

trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>

val tmpGen = FreshNameGenerator(i => s"<tmp-$i>")
val tmpGen: FreshNameGenerator[String] = FreshNameGenerator(i => s"<tmp-$i>")

protected def astForExpression(node: RubyNode): Ast = node match
case node: StaticLiteral => astForStaticLiteral(node)
Expand Down Expand Up @@ -402,11 +402,15 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
astForSimpleCall(node.asSimpleCall)
}

/** A yield in Ruby could either return the result of the block, or simply call the block, depending on runtime
* conditions. Thus we embed this in a conditional expression where the condition itself is some non-deterministic
* placeholder.
*/
protected def astForYield(node: YieldExpr): Ast = {
scope.useProcParam match {
case Some(param) =>
val call = astForExpression(
SimpleCall(SimpleIdentifier()(node.span.spanStart(param)), node.arguments)(node.span)
SimpleCall(SimpleIdentifier()(node.span.spanStart(param)), node.arguments)(node.span.spanStart(param))
)
val ret = returnAst(returnNode(node, code(node)))
val cond = astForExpression(
Expand Down Expand Up @@ -677,11 +681,24 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
scope.typeForMethod(m).map(t => t.name -> s"${t.name}:${m.name}").getOrElse(defaultResult)
case None => defaultResult
}
val argumentAst = node.arguments.map(astForMethodCallArgument)
val call = callNode(node, code(node), methodName, methodFullName, DispatchTypes.DYNAMIC_DISPATCH)
val receiverCallName = identifierNode(node, call.name, call.name, receiverType)

callAst(call, argumentAst, Option(Ast(receiverCallName)))
val argumentAst = node.arguments.map(astForMethodCallArgument)
val call = callNode(node, code(node), methodName, methodFullName, DispatchTypes.DYNAMIC_DISPATCH)
val receiverAst = {
val fi = Ast(fieldIdentifierNode(node, call.name, call.name))
val self = Ast(identifierNode(node, Defines.Self, Defines.Self, receiverType))
val baseAccess = callNode(
node,
s"${Defines.Self}.${call.name}",
Operators.fieldAccess,
Operators.fieldAccess,
DispatchTypes.STATIC_DISPATCH,
None,
Option(Defines.Any)
)
callAst(baseAccess, Seq(self, fi))
}
val baseAst = Ast(identifierNode(node, Defines.Self, Defines.Self, receiverType))
callAst(call, argumentAst, Option(baseAst), Option(receiverAst))
}

private def astForProcOrLambdaExpr(node: ProcOrLambdaExpr): Ast = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th

val thisParameterAst = Ast(
newThisParameterNode(
code = Defines.This,
code = Defines.Self,
typeFullName = scope.surroundingTypeFullName.getOrElse(Defines.Any),
line = method.lineNumber,
column = method.columnNumber
Expand Down Expand Up @@ -302,7 +302,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th

val (astParentType, astParentFullName, thisParamCode, addEdge) = targetNode match {
case _: SelfIdentifier =>
(scope.surroundingAstLabel, scope.surroundingScopeFullName, Defines.This, false)
(scope.surroundingAstLabel, scope.surroundingScopeFullName, Defines.Self, false)
case _: SimpleIdentifier =>
val baseType = node.target.span.text
scope.surroundingTypeFullName.map(_.split("[.]").last) match {
Expand All @@ -312,9 +312,9 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
scope.tryResolveTypeReference(baseType) match {
case Some(typ) =>
(Option(NodeTypes.TYPE_DECL), Option(typ.name), baseType, true)
case None => (None, None, Defines.This, false)
case None => (None, None, Defines.Self, false)
}
case None => (None, None, Defines.This, false)
case None => (None, None, Defines.Self, false)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.*
import io.joern.rubysrc2cpg.datastructures.{BlockScope, MethodScope, ModuleScope, TypeScope}
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.x2cpg.{Ast, ValidationMode, Defines as XDefines}
import io.shiftleft.codepropertygraph.generated.nodes.{NewLocal, NewMethodParameterIn}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EvaluationStrategies, Operators}

import scala.collection.immutable.List
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ object Defines {
val Regexp: String = "Regexp"
val Lambda: String = "lambda"
val Proc: String = "proc"
val This: String = "this"
val Loop: String = "loop"
val Self: String = "self"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ class CallTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataF
| .bar(1)""".stripMargin,
11
),
("bar(this, x)", 3),
("bar(self, x)", 3),
("return x", 4),
("RET", 3),
(
Expand Down Expand Up @@ -340,7 +340,7 @@ class CallTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataF
| bar(1)""".stripMargin,
11
),
("bar(this, x)", 3),
("bar(self, x)", 3),
("return x", 4),
("RET", 3),
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
val sink = cpg.method.name("f").methodReturn
val flows = sink.reachableByFlows(source)
flows.map(flowToResultPairs).toSet shouldBe
Set(List(("f(this, x)", 2), ("return x", 3), ("RET", 2)))
Set(List(("f(self, x)", 2), ("return x", 3), ("RET", 2)))
}

"flow from method parameter to implicit return of the same variable" in {
Expand All @@ -29,7 +29,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
val sink = cpg.method.name("f").methodReturn
val flows = sink.reachableByFlows(source)
flows.map(flowToResultPairs).toSet shouldBe
Set(List(("f(this, x)", 2), ("x", 3), ("RET", 2)))
Set(List(("f(self, x)", 2), ("x", 3), ("RET", 2)))
}

"flow from endless method parameter to implicit return of the same variable" in {
Expand All @@ -40,7 +40,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
val sink = cpg.method.name("f").methodReturn
val flows = sink.reachableByFlows(source)
flows.map(flowToResultPairs).toSet shouldBe
Set(List(("f(this, x)", 2), ("x", 2), ("RET", 2)))
Set(List(("f(self, x)", 2), ("x", 2), ("RET", 2)))
}

"flow from method parameter to implicit return via assignment to temporary variable" in {
Expand All @@ -53,7 +53,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
val sink = cpg.method.name("f").methodReturn
val flows = sink.reachableByFlows(source)
flows.map(flowToResultPairs).toSet shouldBe
Set(List(("f(this, x)", 2), ("y = x", 3), ("RET", 2)))
Set(List(("f(self, x)", 2), ("y = x", 3), ("RET", 2)))
}

"Implicit return in if-else block" in {
Expand All @@ -77,7 +77,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
flows.map(flowToResultPairs).toSet shouldBe
Set(
List(
("foo(this, arg)", 2),
("foo(self, arg)", 2),
("arg > 1", 3),
("arg + 1", 4),
("RET", 2),
Expand Down Expand Up @@ -114,10 +114,10 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
flows.map(flowToResultPairs).toSet shouldBe
Set(
List(
("foo(this, arg)", 6),
("foo(self, arg)", 6),
("arg > 1", 7),
("add(arg)", 8),
("add(this, arg)", 2),
("add(self, arg)", 2),
("arg + 100", 3),
("RET", 2),
("add(arg)", 8),
Expand Down Expand Up @@ -147,7 +147,7 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w
flows.map(flowToResultPairs).toSet shouldBe
Set(
List(
("add(this, p)", 2),
("add(self, p)", 2),
("q = p", 3),
("return q", 4),
("RET", 2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,28 +128,25 @@ class MethodTests extends RubyCode2CpgFixture(withPostProcessing = true, withDat
sink.reachableByFlows(source).size shouldBe 2
}

"Data flow through blockExprAssocTypeArguments with block argument in the wrapper function" in {
"Data flow through blockExprAssocTypeArguments with block argument in the wrapper function" ignore {
val cpg = code("""
|def foo (blockArg,&block)
|block.call(blockArg)
|def foo(blockArg, &block)
| block.call(blockArg)
|end
|
|def foo_wrap (blockArg,&block)
|foo(blockArg,&block)
|def foo_wrap(blockArg, &block)
| foo(blockArg, &block)
|end
|
|
|x = 10
|foo_wrap x do |arg|
| y = 100 + arg
| puts y
|end
|
|
|""".stripMargin)

val source = cpg.identifier.name("x").l
val sink = cpg.call.name("puts").l
val source = cpg.literal.code("10").l
val sink = cpg.call.name("puts").argument(1).l
sink.reachableByFlows(source).size shouldBe 2
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class SingleAssignmentTests extends RubyCode2CpgFixture(withPostProcessing = tru
val flows = sink.reachableByFlows(source).map(flowToResultPairs).distinct.sortBy(_.length).l
val List(flow1, flow2, flow3, flow4, flow5) = flows
flow1 shouldBe List(("y = 1", 2), ("puts y", 3))
flow2 shouldBe List(("y = 1", 2), ("puts y", 3), ("puts x", 4))
flow3 shouldBe List(("y = 1", 2), ("x = y = 1", 2), ("puts x", 4))
flow2 shouldBe List(("y = 1", 2), ("x = y = 1", 2), ("puts x", 4))
flow3 shouldBe List(("y = 1", 2), ("puts y", 3), ("puts x", 4))
flow4 shouldBe List(("y = 1", 2), ("x = y = 1", 2), ("z = x = y = 1", 2), ("puts z", 5))
flow5 shouldBe List(("y = 1", 2), ("x = y = 1", 2), ("puts x", 4), ("puts z", 5))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.joern.rubysrc2cpg.querying

import io.joern.rubysrc2cpg.passes.Defines as RubyDefines
import io.joern.rubysrc2cpg.passes.Defines.RubyOperators
import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
import io.joern.x2cpg.Defines
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, NodeTypes, Operators}
import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, Identifier, Literal}
import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, FieldIdentifier, Identifier, Literal}
import io.shiftleft.semanticcpg.language.*

class CallTests extends RubyCode2CpgFixture {
Expand All @@ -18,13 +19,26 @@ class CallTests extends RubyCode2CpgFixture {
puts.lineNumber shouldBe Some(2)
puts.code shouldBe "puts 'hello'"

val List(rec: Identifier, hello: Literal) = puts.argument.l: @unchecked
rec.argumentIndex shouldBe 0
rec.name shouldBe "puts"
val List(selfReceiver: Identifier, hello: Literal) = puts.argument.l: @unchecked
selfReceiver.argumentIndex shouldBe 0
selfReceiver.name shouldBe RubyDefines.Self
selfReceiver.code shouldBe RubyDefines.Self

hello.argumentIndex shouldBe 1
hello.code shouldBe "'hello'"

val List(callBase: Call) = puts.receiver.l: @unchecked
callBase.argumentIndex shouldBe -1
callBase.methodFullName shouldBe Operators.fieldAccess
callBase.name shouldBe Operators.fieldAccess
callBase.code shouldBe "self.puts"

val List(baseSelf: Identifier, baseProperty: FieldIdentifier) = callBase.argument.l: @unchecked
baseSelf.argumentIndex shouldBe 1
baseSelf.name shouldBe RubyDefines.Self
hello.lineNumber shouldBe Some(2)
baseProperty.argumentIndex shouldBe 2
baseProperty.canonicalName shouldBe "puts"
}

"`foo(1,2)` is represented by a CALL node" in {
Expand Down Expand Up @@ -80,9 +94,9 @@ class CallTests extends RubyCode2CpgFixture {

"contain they keyword in the argumentName property" in {
inside(cpg.call.nameExact("foo").argument.l) {
case (foo: Identifier) :: (hello: Literal) :: (baz: Literal) :: Nil =>
foo.name shouldBe "foo"
foo.argumentIndex shouldBe 0
case (self: Identifier) :: (hello: Literal) :: (baz: Literal) :: Nil =>
self.name shouldBe RubyDefines.Self
self.argumentIndex shouldBe 0

hello.code shouldBe "\"hello\""
hello.argumentIndex shouldBe 1
Expand Down Expand Up @@ -199,7 +213,7 @@ class CallTests extends RubyCode2CpgFixture {

"named parameters in parenthesis-less call to a symbol value should create a correctly named argument" in {
val cpg = code("on in: :sequence")
val List(_, inArg) = cpg.call.argument.l: @unchecked
val List(_, inArg) = cpg.call.nameExact("on").argument.l: @unchecked
inArg.code shouldBe ":sequence"
inArg.argumentName shouldBe Option("in")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,12 +473,12 @@ class ClassTests extends RubyCode2CpgFixture {

inside(aAssignment.argument.l) {
case (lhs: Call) :: (rhs: Literal) :: Nil =>
lhs.code shouldBe "this.@a"
lhs.code shouldBe s"${RubyDefines.Self}.@a"
lhs.methodFullName shouldBe Operators.fieldAccess

inside(lhs.argument.l) {
case (identifier: Identifier) :: (fieldIdentifier: FieldIdentifier) :: Nil =>
identifier.code shouldBe RubyDefines.This
identifier.code shouldBe RubyDefines.Self
fieldIdentifier.code shouldBe "@a"
case _ => fail("Expected identifier and fieldIdentifier for fieldAccess")
}
Expand Down Expand Up @@ -624,8 +624,8 @@ class ClassTests extends RubyCode2CpgFixture {
scopeCall.code shouldBe "scope :published, -> { where(status: \"Published\") }"

inside(scopeCall.argument.l) {
case (scopeIdent: Identifier) :: (literalArg: Literal) :: unknownArg :: Nil =>
scopeIdent.code shouldBe "scope"
case (self: Identifier) :: (literalArg: Literal) :: unknownArg :: Nil =>
self.code shouldBe "self"
literalArg.code shouldBe ":published"
case xs => fail(s"Expected three arguments, got ${xs.code.mkString(", ")} instead")
}
Expand Down Expand Up @@ -658,8 +658,9 @@ class ClassTests extends RubyCode2CpgFixture {
inside(methodBlock.astChildren.l) {
case methodCall :: Nil =>
inside(methodCall.astChildren.l) {
case (identifier: Identifier) :: (literal: Literal) :: (methodRef: MethodRef) :: Nil =>
identifier.code shouldBe "scope"
case (base: Call) :: (self: Identifier) :: (literal: Literal) :: (methodRef: MethodRef) :: Nil =>
base.code shouldBe "self.scope"
self.name shouldBe "self"
literal.code shouldBe ":hits_by_ip"
methodRef.methodFullName shouldBe "Test0.rb:<global>::program.Foo:<init>:<lambda>0"
methodRef.referencedMethod.parameter.indexGt(0).name.l shouldBe List("ip", "col")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class DoBlockTests extends RubyCode2CpgFixture {
}

"have the call under the closure" in {
inside(cpg.method("<lambda>0").call.l) {
inside(cpg.method("<lambda>0").call.nameExact("puts").l) {
case puts :: Nil =>
puts.name shouldBe "puts"
puts.code shouldBe "puts item"
Expand Down Expand Up @@ -173,7 +173,7 @@ class DoBlockTests extends RubyCode2CpgFixture {
}

"have the calls under the closure" in {
inside(cpg.method("<lambda>0").call.l) {
inside(cpg.method("<lambda>0").call.nameExact("puts").l) {
case puts1 :: puts2 :: Nil =>
puts1.name shouldBe "puts"
puts1.code shouldBe "puts key"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class MethodTests extends RubyCode2CpgFixture {
case funcF :: Nil =>
inside(funcF.parameter.l) {
case thisParam :: xParam :: Nil =>
thisParam.code shouldBe "this"
thisParam.code shouldBe RDefines.Self
thisParam.typeFullName shouldBe "Test0.rb:<global>::program.C"
thisParam.index shouldBe 0
thisParam.isVariadic shouldBe false
Expand Down Expand Up @@ -220,7 +220,7 @@ class MethodTests extends RubyCode2CpgFixture {
case funcF :: Nil =>
inside(funcF.parameter.l) {
case thisParam :: xParam :: Nil =>
thisParam.code shouldBe "this"
thisParam.code shouldBe RDefines.Self
thisParam.typeFullName shouldBe "Test0.rb:<global>::program.C"
thisParam.index shouldBe 0
thisParam.isVariadic shouldBe false
Expand Down
Loading

0 comments on commit f408501

Please sign in to comment.