Skip to content

Commit

Permalink
[rubysrc2cpg] Interpolated double-quoted string literal support (#3252)
Browse files Browse the repository at this point in the history
* Added interpolated double-quoted string literal support

* symbol fix
  • Loading branch information
ankit-privado authored Jul 27, 2023
1 parent 214ad17 commit cf5f809
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ class AstCreator(
val undef = "<operator>.undef"
val superKeyword = "<operator>.super"
val stringConcatenation = "<operator>.stringConcatenation"
val formattedString = "<operator>.formatString"
val formattedValue = "<operator>.formatValue"
}
private def getOperatorName(token: Token): String = token.getType match {
case ASSIGNMENT_OPERATOR => Operators.assignment
Expand Down Expand Up @@ -308,7 +310,19 @@ class AstCreator(
.interpolatedStringSequence()
.asScala
.flatMap(inter => {
astForStatements(inter.compoundStatement().statements(), false, false)
Seq(
Ast(
NewCall()
.code(inter.getText)
.name(RubyOperators.formattedValue)
.methodFullName(RubyOperators.formattedValue)
.lineNumber(line(ctx))
.columnNumber(column(ctx))
.typeFullName(Defines.Any)
.dispatchType(DispatchTypes.STATIC_DISPATCH)
)
) ++
astForStatements(inter.compoundStatement().statements(), false, false)
})
.toSeq

Expand Down Expand Up @@ -356,7 +370,7 @@ class AstCreator(
astForChainedScopedConstantReferencePrimaryContext(ctx)
case ctx: ArrayConstructorPrimaryContext => astForArrayConstructorPrimaryContext(ctx)
case ctx: HashConstructorPrimaryContext => astForHashConstructorPrimaryContext(ctx)
case ctx: LiteralPrimaryContext => Seq(astForLiteralPrimaryExpression(ctx))
case ctx: LiteralPrimaryContext => astForLiteralPrimaryExpression(ctx)
case ctx: StringExpressionPrimaryContext => astForStringExpression(ctx.stringExpression)
case ctx: RegexInterpolationPrimaryContext =>
astForRegexInterpolationPrimaryContext(ctx.regexInterpolation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package io.joern.rubysrc2cpg.astcreation

import io.joern.rubysrc2cpg.parser.RubyParser.*
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.Defines.getBuiltInType
import io.joern.x2cpg.Ast
import io.shiftleft.codepropertygraph.generated.nodes.{NewFieldIdentifier, NewJumpTarget, NewLiteral, NewNode}
import io.shiftleft.codepropertygraph.generated.nodes.{NewCall, NewFieldIdentifier, NewJumpTarget, NewLiteral, NewNode}
import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, DispatchTypes, ModifierTypes, Operators}
import org.antlr.v4.runtime.ParserRuleContext
import io.joern.x2cpg.utils._
Expand Down Expand Up @@ -101,10 +102,29 @@ trait AstForExpressionsCreator { this: AstCreator =>
callAst(call, argsAst.toList)
}

protected def astForLiteralPrimaryExpression(ctx: LiteralPrimaryContext): Ast = ctx.literal() match {
case ctx: NumericLiteralLiteralContext => astForNumericLiteral(ctx.numericLiteral())
case ctx: SymbolLiteralContext => astForSymbolLiteral(ctx.symbol())
case ctx: RegularExpressionLiteralContext => astForRegularExpressionLiteral(ctx)
protected def astForLiteralPrimaryExpression(ctx: LiteralPrimaryContext): Seq[Ast] = ctx.literal() match {
case ctx: NumericLiteralLiteralContext => Seq(astForNumericLiteral(ctx.numericLiteral()))
case ctx: SymbolLiteralContext => astForSymbol(ctx.symbol())
case ctx: RegularExpressionLiteralContext => Seq(astForRegularExpressionLiteral(ctx))
}

protected def astForSymbol(ctx: SymbolContext): Seq[Ast] = {
if (
ctx.stringExpression() != null && ctx.stringExpression().children.get(0).isInstanceOf[StringInterpolationContext]
) {
val node = NewCall()
.name(RubyOperators.formattedString)
.methodFullName(RubyOperators.formattedString)
.code(ctx.getText)
.lineNumber(line(ctx))
.columnNumber(column(ctx))
.typeFullName(Defines.Any)
.dispatchType(DispatchTypes.STATIC_DISPATCH)

astForStringExpression(ctx.stringExpression()) ++ Seq(Ast(node))
} else {
Seq(astForSymbolLiteral(ctx))
}
}

// TODO: Return Ast instead of Seq[Ast]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2487,4 +2487,18 @@ class DataFlowTests extends RubyCode2CpgFixture(withPostProcessing = true, withD
sink.reachableByFlows(source).size shouldBe 2
}
}

"flow through interpolated double-quoted string literal " should {
val cpg = code("""
|x = "foo"
|y = :"bar #{x}"
|puts y
|""".stripMargin)

"find flows to the sink" in {
val source = cpg.identifier.name("x").l
val sink = cpg.call.name("puts").l
sink.reachableByFlows(source).size shouldBe 2
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1019,20 +1019,59 @@ class SimpleAstCreationPassTest extends RubyCode2CpgFixture {
implicitParameter.name shouldBe "this"
actualParameter.name shouldBe "param_0"
}
}

"have correct structure when regular expression literal passed after `when`" in {
val cpg = code("""
|case foo
| when /^ch/
| bar
|end
|""".stripMargin)
"have correct structure when regular expression literal passed after `when`" in {
val cpg = code("""
|case foo
| when /^ch/
| bar
|end
|""".stripMargin)

val List(literalArg) = cpg.literal.l
literalArg.typeFullName shouldBe Defines.Regexp
literalArg.code shouldBe "/^ch/"
literalArg.lineNumber shouldBe Some(3)
}

"have correct structure when have interpolated double-quoted string literal" in {
val cpg = code("""
|v = :"w x #{y} z"
|""".stripMargin)

cpg.call.size shouldBe 4
cpg.call.name("<operator>.formatString").head.code shouldBe """:"w x #{y} z""""
cpg.call.name("<operator>.formatValue").head.code shouldBe "#{y}"

val List(literalArg) = cpg.literal.l
literalArg.typeFullName shouldBe Defines.Regexp
literalArg.code shouldBe "/^ch/"
literalArg.lineNumber shouldBe Some(3)
cpg.literal.size shouldBe 2
cpg.literal.code("w x ").size shouldBe 1
cpg.literal.code(" z").size shouldBe 1

cpg.identifier.name("y").size shouldBe 1
cpg.identifier.name("v").size shouldBe 1
}

"have correct structure when have non-interpolated double-quoted string literal" in {
val cpg = code("""
|x = :"y z"
|""".stripMargin)

cpg.call.size shouldBe 1
val List(literal) = cpg.literal.l
literal.code shouldBe ":\"y z\""
literal.typeFullName shouldBe Defines.Symbol
}

"have correct structure when have symbol " in {
val cpg = code(s"""
|x = :"${10}"
|""".stripMargin)

cpg.call.size shouldBe 1
val List(literal) = cpg.literal.l
literal.typeFullName shouldBe Defines.Symbol
literal.code shouldBe ":\"10\""
}
}

"have correct structure when no RHS for a mandatory parameter is provided" in {
Expand Down

0 comments on commit cf5f809

Please sign in to comment.