Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ruby] add METHOD_REF and TYPE_REF nodes for methods and class/module decls #4547

Merged
merged 6 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.joern.rubysrc2cpg.astcreation

import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.*
import io.joern.rubysrc2cpg.datastructures.{BlockScope, NamespaceScope, RubyProgramSummary, RubyScope}
import io.joern.rubysrc2cpg.datastructures.{BlockScope, NamespaceScope, RubyProgramSummary, RubyScope, RubyStubbedType}
import io.joern.rubysrc2cpg.parser.{RubyNodeCreator, RubyParser}
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
import io.joern.x2cpg.{Ast, AstCreatorBase, AstNodeBuilder, ValidationMode}
import io.shiftleft.codepropertygraph.generated.ModifierTypes
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, ModifierTypes, Operators}
import io.shiftleft.codepropertygraph.generated.nodes.*
import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
import org.slf4j.{Logger, LoggerFactory}
Expand Down Expand Up @@ -44,6 +44,8 @@ class AstCreator(
.map(_.stripPrefix(java.io.File.separator))
.getOrElse(fileName)

private def internalLineAndColNum: Option[Integer] = Option(1)

/** The relative file name, in a unix path delimited format.
*/
private def relativeUnixStyleFileName =
Expand Down Expand Up @@ -94,14 +96,81 @@ class AstCreator(
scope.pushNewScope(moduleScope)
val block = blockNode(rootNode)
scope.pushNewScope(BlockScope(block))
val statementAsts = rootNode.statements.flatMap(astsForStatement)
val statementAsts = rootNode.statements.flatMap(astsForStatement)
val internalMethodRefAsts = methodRefNodesForInternalMethods()
val internalTypeRefAsts = typeRefNodesForInternalDecls()
scope.popScope()
val bodyAst = blockAst(block, statementAsts)
val bodyAst = blockAst(block, internalTypeRefAsts ++ internalMethodRefAsts ++ statementAsts)
scope.popScope()
methodAst(methodNode_, Seq.empty, bodyAst, methodReturn, newModifierNode(ModifierTypes.MODULE) :: Nil)
}
.getOrElse(Ast())
}

private def methodRefNodesForInternalMethods(): List[Ast] = {
val typeNameForMethods = scope.surroundingTypeFullName
.map { x =>
x.stripSuffix(s":${Defines.Program}")
}
.getOrElse(Defines.Undefined)

scope.surroundingTypeFullName
.map { x =>
val typeNameForMethods = x.stripSuffix(s":${Defines.Program}")
programSummary.namespaceToType
.filter(_._1 == typeNameForMethods)
.flatMap(_._2)
.filter(!_.isInstanceOf[RubyStubbedType])
.flatMap(_.methods)
.map { method =>
val methodRefNode = NewMethodRef()
.code(s"def ${method.name} (...)")
.methodFullName(scope.surroundingTypeFullName.map { x => s"$x:${method.name}" }.getOrElse(method.name))
.typeFullName(Defines.Any)
.lineNumber(internalLineAndColNum)
.columnNumber(internalLineAndColNum)

val methodRefIdent = NewIdentifier()
.code(method.name)
.name(method.name)
.typeFullName(Defines.Any)
.lineNumber(internalLineAndColNum)
.columnNumber(internalLineAndColNum)

astForAssignment(methodRefIdent, methodRefNode, internalLineAndColNum, internalLineAndColNum)
}
.toList
}
.getOrElse(List.empty)

}

private def typeRefNodesForInternalDecls(): List[Ast] = {
scope.surroundingTypeFullName
.map { surroundingTypeFullName =>
programSummary.namespaceToType
.filter(_._1.contains(surroundingTypeFullName))
.flatMap(_._2)
.map { x =>
val typeRefName = x.name.split("[.]").takeRight(1).head
val typeRefNode = NewTypeRef()
.code(s"class ${x.name} (...)")
.typeFullName(x.name)

val typeRefIdent = NewIdentifier()
.code(typeRefName)
.name(typeRefName)
.typeFullName(x.name)
.lineNumber(internalLineAndColNum)
.columnNumber(internalLineAndColNum)

astForAssignment(typeRefIdent, typeRefNode, internalLineAndColNum, internalLineAndColNum)
}
.toList
}
.getOrElse(List.empty)

}
}

/** Determines till what depth the AST creator will parse until.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.{
}
import io.joern.rubysrc2cpg.datastructures.{BlockScope, FieldDecl}
import io.joern.x2cpg.{Ast, ValidationMode}
import io.shiftleft.codepropertygraph.generated.{EdgeTypes, Operators}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EdgeTypes, Operators}
import io.shiftleft.codepropertygraph.generated.nodes.*
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.Defines.RubyOperators
Expand Down Expand Up @@ -81,6 +81,24 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As

}

protected def astForAssignment(
lhs: NewNode,
rhs: NewNode,
lineNumber: Option[Integer],
columnNumber: Option[Integer]
): Ast = {
val code = Seq(lhs, rhs).collect { case x: AstNodeNew => x.code }.mkString(" = ")
val assignment = NewCall()
.name(Operators.assignment)
.methodFullName(Operators.assignment)
.code(code)
.dispatchType(DispatchTypes.STATIC_DISPATCH)
.lineNumber(lineNumber)
.columnNumber(columnNumber)

callAst(assignment, Seq(Ast(lhs), Ast(rhs)))
}

protected val UnaryOperatorNames: Map[String, String] = Map(
"!" -> Operators.logicalNot,
"not" -> Operators.logicalNot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package io.joern.rubysrc2cpg.querying

import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
import io.joern.x2cpg.Defines
import io.joern.rubysrc2cpg.passes.Defines as RDefines
import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, Operators}
import io.shiftleft.codepropertygraph.generated.nodes.{Call, Identifier, Literal, Return}
import io.shiftleft.codepropertygraph.generated.nodes.{Call, Identifier, Literal, MethodRef, Return, TypeRef}
import io.shiftleft.semanticcpg.language.*

class MethodTests extends RubyCode2CpgFixture {
Expand Down Expand Up @@ -538,4 +539,91 @@ class MethodTests extends RubyCode2CpgFixture {
}
}
}

"METHOD_REF and TYPE_REF nodes" should {
val cpg = code(
"""
|module A
| def foo
| end
|end
|
|class B
|end
|
|def c
|end
|""".stripMargin,
"t1.rb"
)
.moreCode(
"""
|require 't1'
|class D
|end
|
|def e
|end
|""".stripMargin,
"t2.rb"
)

"be directly under :program" in {
inside(cpg.method.name(RDefines.Program).filename("t1.rb").assignment.l) {
case moduleAssignment :: classAssignment :: methodAssignment :: Nil =>
moduleAssignment.code shouldBe "A = class t1.rb:<global>::program.A (...)"
classAssignment.code shouldBe "B = class t1.rb:<global>::program.B (...)"
methodAssignment.code shouldBe "c = def c (...)"

inside(moduleAssignment.argument.l) {
case (lhs: Identifier) :: (rhs: TypeRef) :: Nil =>
lhs.name shouldBe "A"
rhs.typeFullName shouldBe "t1.rb:<global>::program.A"
case xs => fail(s"Expected lhs and rhs, instead got ${xs.code.mkString(",")}")
}

inside(classAssignment.argument.l) {
case (lhs: Identifier) :: (rhs: TypeRef) :: Nil =>
lhs.name shouldBe "B"
rhs.typeFullName shouldBe "t1.rb:<global>::program.B"
case xs => fail(s"Expected lhs and rhs, instead got ${xs.code.mkString(",")}")
}

inside(methodAssignment.argument.l) {
case (lhs: Identifier) :: (rhs: MethodRef) :: Nil =>
lhs.name shouldBe "c"
rhs.methodFullName shouldBe "t1.rb:<global>::program:c"
rhs.typeFullName shouldBe RDefines.Any
case xs => fail(s"Expected lhs and rhs, instead got ${xs.code.mkString(",")}")
}

case xs => fail(s"Expected three assignments, got [${xs.code.mkString(",")}]")
}
}

"not be present in other files" in {
inside(cpg.method.name(RDefines.Program).filename("t2.rb").assignment.l) {
case classAssignment :: methodAssignment :: Nil =>
classAssignment.code shouldBe "D = class t2.rb:<global>::program.D (...)"
methodAssignment.code shouldBe "e = def e (...)"

inside(classAssignment.argument.l) {
case (lhs: Identifier) :: (rhs: TypeRef) :: Nil =>
lhs.name shouldBe "D"
rhs.typeFullName shouldBe "t2.rb:<global>::program.D"
case xs => fail(s"Expected lhs and rhs, instead got ${xs.code.mkString(",")}")
}

inside(methodAssignment.argument.l) {
case (lhs: Identifier) :: (rhs: MethodRef) :: Nil =>
lhs.name shouldBe "e"
rhs.methodFullName shouldBe "t2.rb:<global>::program:e"
rhs.typeFullName shouldBe RDefines.Any
case xs => fail(s"Expected lhs and rhs, instead got ${xs.code.mkString(",")}")
}

case xs => fail(s"Expected two assignments, got [${xs.code.mkString(",")}]")
}
}
}
}
Loading