Skip to content

Commit

Permalink
[ruby] loop Do Block & Break Expressions (#4524)
Browse files Browse the repository at this point in the history
* Handles `break` statements on an expression level
* Lowering `loop` do-block statements as `do-while` loops
* Fixed missing transform match & `loop` block handling

Resolves #4508
  • Loading branch information
DavidBakerEffendi authored May 2, 2024
1 parent 3d4c58a commit deb8842
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case node: ProcOrLambdaExpr => astForProcOrLambdaExpr(node)
case node: RubyCallWithBlock[_] => astsForCallWithBlockInExpr(node)
case node: SelfIdentifier => astForSelfIdentifier(node)
case node: BreakStatement => astForBreakStatement(node)
case node: StatementList => astForStatementList(node)
case node: DummyNode => Ast(node.node)
case _ => astForUnknown(node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { t
returnAst(returnNode(node, code(node)), List(astForMemberCall(node)))
}

private def astForBreakStatement(node: BreakStatement): Ast = {
protected def astForBreakStatement(node: BreakStatement): Ast = {
val _node = NewControlStructure()
.controlStructureType(ControlStructureTypes.BREAK)
.lineNumber(line(node))
Expand Down Expand Up @@ -386,8 +386,9 @@ trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { t
elseClause.map(transform).orElse(defaultElseBranch(node.span)),
ensureClause
)(node.span)
case WhileExpression(condition, body) => WhileExpression(condition, transform(body))(node.span)
case UntilExpression(condition, body) => UntilExpression(condition, transform(body))(node.span)
case WhileExpression(condition, body) => WhileExpression(condition, transform(body))(node.span)
case DoWhileExpression(condition, body) => DoWhileExpression(condition, transform(body))(node.span)
case UntilExpression(condition, body) => UntilExpression(condition, transform(body))(node.span)
case IfExpression(condition, thenClause, elsifClauses, elseClause) =>
IfExpression(
condition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.joern.rubysrc2cpg.astcreation
import io.joern.rubysrc2cpg.passes.Defines
import io.shiftleft.codepropertygraph.generated.nodes.NewNode

import scala.annotation.tailrec

object RubyIntermediateAst {

case class TextSpan(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,19 @@ class RubyNodeCreator extends RubyParserBaseVisitor[RubyNode] {
override def visitMethodCallWithBlockExpression(ctx: RubyParser.MethodCallWithBlockExpressionContext): RubyNode = {
ctx.methodIdentifier().getText match {
case Defines.Proc | Defines.Lambda => ProcOrLambdaExpr(visit(ctx.block()).asInstanceOf[Block])(ctx.toTextSpan)
case Defines.Loop =>
DoWhileExpression(
SimpleIdentifier(Option(Defines.getBuiltInType(Defines.TrueClass)))(
ctx.methodIdentifier().toTextSpan.spanStart("true")
),
ctx.block() match {
case b: RubyParser.DoBlockBlockContext =>
visit(b.doBlock().bodyStatement())
case y =>
logger.warn(s"Unexpected loop block body ${y.getClass}")
visit(ctx.block())
}
)(ctx.toTextSpan)
case _ =>
SimpleCallWithBlock(visit(ctx.methodIdentifier()), List(), visit(ctx.block()).asInstanceOf[Block])(
ctx.toTextSpan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ object Defines {
val Lambda: String = "lambda"
val Proc: String = "proc"
val This: String = "this"
val Loop: String = "loop"

val Program: String = ":program"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ class MethodTests extends RubyCode2CpgFixture(withPostProcessing = true, withDat
sink.reachableByFlows(src).l.size shouldBe 2
}

// Works in deprecated
"Data flow through do-while loop" ignore {
"Data flow through do-while loop" in {
val cpg = code("""
|x = 0
|num = -1
Expand All @@ -42,7 +41,7 @@ class MethodTests extends RubyCode2CpgFixture(withPostProcessing = true, withDat

val source = cpg.identifier.name("x").l
val sink = cpg.call.name("puts").l
sink.reachableByFlows(source).l.size shouldBe 2
sink.reachableByFlows(source).size shouldBe 5
}

"Data flow through methodOnlyIdentifier usage" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ class ControlStructureTests extends RubyCode2CpgFixture {
assignment.lineNumber shouldBe Some(4)
}

"a break expression nested in a control structure should be represented" in {
val cpg = code("""
|x = 0
|num = -1
|loop do
| num = x + 1
| x = x + 1
| if x > 10
| break
| end
|end
|puts num
|""".stripMargin)

val List(breakNode) = cpg.break.l
breakNode.code shouldBe "break"
breakNode.lineNumber shouldBe Some(8)
}

"`if-end` statement is represented by an `IF` CONTROL_STRUCTURE node" in {
val cpg = code("""
|if __LINE__ > 1 then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ class MethodTests extends RubyCode2CpgFixture {
"break unless statement" should {
val cpg = code("""
| def foo
| loop do
| bar do
| break unless 1 < 2
| end
| end
Expand Down

0 comments on commit deb8842

Please sign in to comment.