Skip to content

Commit

Permalink
[ruby] Unhandled Receiver Types (#4608)
Browse files Browse the repository at this point in the history
This PR fixes previously unhandled call receivers, which creates an issue later during dynamic call linking as the receiver would be an Unknown node.

* Handled quoted expanded regex literals
* Handled constant variable references

Additionally, this brings back warnings for `Unknown` nodes as these unhandled nodes are not syntax errors but don't necessarily have explicit warnings when unhandled.
  • Loading branch information
DavidBakerEffendi authored May 28, 2024
1 parent 038c849 commit 1803868
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -641,10 +641,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
protected def astForUnknown(node: RubyNode): Ast = {
val className = node.getClass.getSimpleName
val text = code(node)
node match {
case _: Unknown => // Unknowns are syntax errors which are logged by the parser already
case _ => logger.warn(s"Could not represent expression: $text ($className) ($relativeFileName), skipping")
}
logger.warn(s"Could not represent expression: $text ($className) ($relativeFileName), skipping")
Ast(unknownNode(node, text))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object AntlrContextHelpers {

sealed implicit class RegularExpressionLiteralContextHelper(ctx: RegularExpressionLiteralContext) {
def isStatic: Boolean = !isDynamic
def isDynamic: Boolean = ctx.regexpLiteralContent.asScala.exists(c => Option(c.compoundStatement()).isDefined)
def isDynamic: Boolean = interpolations.nonEmpty

def interpolations: List[ParserRuleContext] = ctx
.regexpLiteralContent()
Expand All @@ -102,6 +102,22 @@ object AntlrContextHelpers {
.toList
}

sealed implicit class QuotedExpandedRegularExpressionLiteralContextHelper(
ctx: QuotedExpandedRegularExpressionLiteralContext
) {

def isStatic: Boolean = !isDynamic
def isDynamic: Boolean = interpolations.nonEmpty

def interpolations: List[ParserRuleContext] = ctx
.quotedExpandedLiteralStringContent()
.asScala
.filter(ctx => Option(ctx.compoundStatement()).isDefined)
.map(ctx => ctx.compoundStatement())
.toList

}

sealed implicit class CurlyBracesBlockContextHelper(ctx: CurlyBracesBlockContext) {
def parameters: List[ParserRuleContext] = Option(ctx.blockParameter()).map(_.parameters).getOrElse(List())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.joern.rubysrc2cpg.parser

import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.*
import io.joern.rubysrc2cpg.parser.AntlrContextHelpers.*
import io.joern.rubysrc2cpg.parser.RubyParser.CommandWithDoBlockContext
import io.joern.rubysrc2cpg.parser.RubyParser.{CommandWithDoBlockContext, ConstantVariableReferenceContext}
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.Defines.getBuiltInType
import io.joern.rubysrc2cpg.utils.FreshNameGenerator
Expand Down Expand Up @@ -373,6 +373,16 @@ class RubyNodeCreator extends RubyParserBaseVisitor[RubyNode] {
}
}

override def visitQuotedExpandedRegularExpressionLiteral(
ctx: RubyParser.QuotedExpandedRegularExpressionLiteralContext
): RubyNode = {
if (ctx.isStatic) {
StaticLiteral(getBuiltInType(Defines.Regexp))(ctx.toTextSpan)
} else {
DynamicLiteral(getBuiltInType(Defines.Regexp), ctx.interpolations.map(visit))(ctx.toTextSpan)
}
}

override def visitCurlyBracesBlock(ctx: RubyParser.CurlyBracesBlockContext): RubyNode = {
val parameters = Option(ctx.blockParameter()).fold(List())(_.parameters).map(visit)
val body = visit(ctx.compoundStatement())
Expand Down Expand Up @@ -735,6 +745,12 @@ class RubyNodeCreator extends RubyParserBaseVisitor[RubyNode] {
Unknown()(ctx.toTextSpan)
}

override def visitConstantVariableReference(ctx: ConstantVariableReferenceContext): RubyNode = {
MemberAccess(SelfIdentifier()(ctx.toTextSpan.spanStart(Defines.Self)), "::", ctx.CONSTANT_IDENTIFIER().getText)(
ctx.toTextSpan
)
}

override def visitIndexingAccessExpression(ctx: RubyParser.IndexingAccessExpressionContext): RubyNode = {
IndexAccess(
visit(ctx.primaryValue()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object Defines {
val Proc: String = "proc"
val This: String = "this"
val Loop: String = "loop"
val Self: String = "self"

val Program: String = ":program"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.joern.rubysrc2cpg.querying
import io.joern.rubysrc2cpg.passes.Defines.RubyOperators
import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture
import io.joern.x2cpg.Defines
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, NodeTypes, Operators}
import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, Identifier, Literal}
import io.shiftleft.semanticcpg.language.*

Expand Down Expand Up @@ -198,11 +198,25 @@ 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 cpg = code("on in: :sequence")
val List(_, inArg) = cpg.call.argument.l: @unchecked
inArg.code shouldBe ":sequence"
inArg.argumentName shouldBe Option("in")
}

"a call with a quoted regex literal should have a literal receiver" in {
val cpg = code("%r{^/}.freeze")
val List(regexLiteral: Literal) = cpg.call.nameExact("freeze").receiver.l: @unchecked
regexLiteral.typeFullName shouldBe "__builtin.Regexp"
regexLiteral.code shouldBe "%r{^/}"
}

"a call with a double colon receiver" in {
val cpg = code("::Augeas.open { |aug| aug.get('/augeas/version') }")
val List(augeas: Call) = cpg.call.nameExact("open").receiver.l: @unchecked
// TODO: Right now this is seen as a "getter" but should _probably_ be a field access, e.g. self.Augeas
augeas.methodFullName shouldBe "Test0.rb:<global>::program:Augeas"
augeas.code shouldBe "::Augeas"
}

}

0 comments on commit 1803868

Please sign in to comment.