From 4d90eefa5a8a203899e91cfb53237efa556ab9cc Mon Sep 17 00:00:00 2001 From: Xavier Pinho Date: Mon, 6 Nov 2023 17:15:13 +0000 Subject: [PATCH] [rubysrc2cpg] dataflow tests & start measuring the gap against the previous frontend (#3798) --- .../dataflow/ConditionalTests.scala | 25 ++++ .../dataflow/ControlStructureTests.scala | 118 ++++++++++++++++++ .../dataflow/MethodReturnTests.scala | 13 ++ .../dataflow/SingleAssignmentTests.scala | 26 ++++ .../passes/ast/AssignCpgTests.scala | 21 ++-- .../passes/ast/AttributeCpgTests.scala | 7 +- .../passes/ast/BoolOpCpgTests.scala | 11 +- .../deprecated/passes/ast/CallCpgTests.scala | 8 +- .../passes/ast/CustomAssignmentTests.scala | 4 +- .../passes/ast/MethodOneTests.scala | 15 ++- .../rubysrc2cpg/querying/CallTests.scala | 17 +++ .../querying/IndexAccessTests.scala | 31 ++++- .../querying/MethodReturnTests.scala | 16 ++- .../querying/SingleAssignmentTests.scala | 5 + .../testfixtures/RubyCode2CpgFixture.scala | 9 ++ 15 files changed, 288 insertions(+), 38 deletions(-) create mode 100644 joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ConditionalTests.scala create mode 100644 joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ControlStructureTests.scala create mode 100644 joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/SingleAssignmentTests.scala diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ConditionalTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ConditionalTests.scala new file mode 100644 index 000000000000..fedc34589fb9 --- /dev/null +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ConditionalTests.scala @@ -0,0 +1,25 @@ +package io.joern.rubysrc2cpg.dataflow + +import io.joern.dataflowengineoss.language.* +import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.shiftleft.semanticcpg.language.* + +class ConditionalTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataFlow = true) { + + "flow through both branches of a ternary `.. ? .. : ..` operator" in { + val cpg = code(""" + |x = 1 + |y = 2 + |z = foo ? x : y + |puts z + |""".stripMargin) + val source = cpg.literal + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set( + List(("x = 1", 2), ("x", 2), ("foo ? x : y", 4), ("z", 4), ("puts z", 5)), + List(("y = 2", 3), ("y", 3), ("foo ? x : y", 4), ("z", 4), ("puts z", 5)) + ) + } +} diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ControlStructureTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ControlStructureTests.scala new file mode 100644 index 000000000000..c970966ae1ad --- /dev/null +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/ControlStructureTests.scala @@ -0,0 +1,118 @@ +package io.joern.rubysrc2cpg.dataflow + +import io.joern.dataflowengineoss.language.* +import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.shiftleft.semanticcpg.language.* + +class ControlStructureTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataFlow = true) { + + "flow through body of a `while-end` statement" in { + val cpg = code(""" + |x = 10 + |while x > 0 do + | x = x - 1 + |end + |puts x + |""".stripMargin) + val source = cpg.literal("10") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("x = 10", 2), ("x", 2), ("x - 1", 4), ("x = x - 1", 4), ("x > 0", 3), ("puts x", 6))) + } + + "flow through body of an `until-end` statement" in { + val cpg = code(""" + |x = 10 + |until x <= 0 do + | x = x - 1 + |end + |puts x + |""".stripMargin) + val source = cpg.literal("10") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("x = 10", 2), ("x", 2), ("x - 1", 4), ("x = x - 1", 4), ("x <= 0", 3), ("puts x", 6))) + } + + "flow through the 1st branch of an `if-end` statement" in { + val cpg = code(""" + |t = 100 + |if true + | t = t + 1 + |end + |puts t + |""".stripMargin) + val source = cpg.literal("100") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("t = 100", 2), ("t", 2), ("t + 1", 4), ("t = t + 1", 4), ("puts t", 6))) + } + + "flow through the 2nd branch of an `if-else-end` statement" in { + val cpg = code(""" + |t = 100 + |if false + | foo + |else + | t = t - 1 + |end + |puts t + |""".stripMargin) + val source = cpg.literal("100") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("t = 100", 2), ("t", 2), ("t - 1", 6), ("t = t - 1", 6), ("puts t", 8))) + } + + "flow through the 2nd branch of an `if-elsif-end` statement" in { + val cpg = code(""" + |t = 100 + |if false + | foo + |elsif true + | t = t * 2 + |end + |puts t + |""".stripMargin) + val source = cpg.literal("100") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("t = 100", 2), ("t", 2), ("t * 2", 6), ("t = t * 2", 6), ("puts t", 8))) + } + + "flow through both branches of an `if-else-end` statement" in { + val cpg = code(""" + |t = 100 + |if false + | puts t + 1 + |else + | puts t + 2 + |end + |""".stripMargin) + val source = cpg.literal("100") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("t = 100", 2), ("t", 2), ("t + 2", 6)), List(("t = 100", 2), ("t", 2), ("t + 1", 4))) + } + + "flow through an `unless-end` statement" in { + val cpg = code(""" + |x = 1 + |unless __LINE__ == 0 then + | x = x * 2 + |end + |puts x + |""".stripMargin) + val source = cpg.literal("1") + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("x = 1", 2), ("x", 2), ("x * 2", 4), ("x = x * 2", 4), ("puts x", 6))) + } +} diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/MethodReturnTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/MethodReturnTests.scala index d57aa68735bf..7f4005c5c9b4 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/MethodReturnTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/MethodReturnTests.scala @@ -43,4 +43,17 @@ class MethodReturnTests extends RubyCode2CpgFixture(withPostProcessing = true, w Set(List(("f(x)", 2), ("x", 2), ("RET", 2))) } + "flow from method parameter to implicit return via assignment to temporary variable" in { + val cpg = code(""" + |def f(x) + | y = x + |end + |""".stripMargin) + val source = cpg.method.name("f").parameter + val sink = cpg.method.name("f").methodReturn + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set(List(("f(x)", 2), ("y = x", 3), ("y", 3), ("y = x", 3), ("RET", 2))) + } + } diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/SingleAssignmentTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/SingleAssignmentTests.scala new file mode 100644 index 000000000000..92180f524514 --- /dev/null +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/dataflow/SingleAssignmentTests.scala @@ -0,0 +1,26 @@ +package io.joern.rubysrc2cpg.dataflow + +import io.joern.dataflowengineoss.language.* +import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.shiftleft.semanticcpg.language.* + +class SingleAssignmentTests extends RubyCode2CpgFixture(withPostProcessing = true, withDataFlow = true) { + + "flow through two inline assignments `z = x = y = 1`" in { + val cpg = code(""" + |z = x = y = 1 + |puts y + |puts x + |puts z + |""".stripMargin) + val source = cpg.literal + val sink = cpg.method.name("puts").callIn.argument + val flows = sink.reachableByFlows(source) + flows.map(flowToResultPairs).toSet shouldBe + Set( + List(("y = 1", 2), ("y", 2), ("y = 1", 2), ("x", 2), ("x = y = 1", 2), ("z", 2), ("puts z", 5)), + List(("y = 1", 2), ("y", 2), ("y = 1", 2), ("x", 2), ("puts x", 4)), + List(("y = 1", 2), ("y", 2), ("puts y", 3)) + ) + } +} diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AssignCpgTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AssignCpgTests.scala index 006d14d35832..95d098a62ae8 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AssignCpgTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AssignCpgTests.scala @@ -1,22 +1,23 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.joern.rubysrc2cpg.testfixtures.{DifferentInNewFrontend, RubyCode2CpgFixture, SameInNewFrontend} import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators, nodes} import io.shiftleft.semanticcpg.language.* +import org.scalatest.Tag class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { "single target assign" should { val cpg = code("""x = 2""".stripMargin) - "test local and identifier nodes" in { + "test local and identifier nodes" taggedAs SameInNewFrontend in { val localX = cpg.local.head localX.name shouldBe "x" val List(idX) = localX.referencingIdentifiers.l: @unchecked idX.name shouldBe "x" } - "test assignment node properties" in { + "test assignment node properties" taggedAs SameInNewFrontend in { val assignCall = cpg.call.methodFullName(Operators.assignment).head assignCall.code shouldBe "x = 2" assignCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH @@ -24,7 +25,7 @@ class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { assignCall.columnNumber shouldBe Some(2) } - "test assignment node ast children" in { + "test assignment node ast children" taggedAs SameInNewFrontend in { cpg.call .methodFullName(Operators.assignment) .astChildren @@ -41,7 +42,7 @@ class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { .code shouldBe "2" } - "test assignment node arguments" in { + "test assignment node arguments" taggedAs SameInNewFrontend in { cpg.call .methodFullName(Operators.assignment) .argument @@ -150,13 +151,13 @@ class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { cpg.all.collect { case block: nodes.Block if block.code != "" => block }.head } - "test block exists" in { + "test block exists" taggedAs DifferentInNewFrontend in { // Throws if block does not exist. getSurroundingBlock } // TODO: Fix the code property of the Block node - "test block node properties" ignore { + "test block node properties" taggedAs DifferentInNewFrontend ignore { val block = getSurroundingBlock block.code shouldBe """tmp0 = list @@ -166,12 +167,12 @@ class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { } // TODO: Need to fix the local variables - "test local node" ignore { + "test local node" taggedAs DifferentInNewFrontend ignore { cpg.method.name("Test0.rb::program").local.name("tmp0").headOption should not be empty } // TODO: Need to fix the code property - "test tmp variable assignment" ignore { + "test tmp variable assignment" taggedAs DifferentInNewFrontend ignore { val block = getSurroundingBlock val tmpAssignNode = block.astChildren.isCall.sortBy(_.order).head tmpAssignNode.code shouldBe "tmp0 = list" @@ -183,7 +184,7 @@ class AssignCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { "empty array assignment" should { val cpg = code("""x.y = []""".stripMargin) - "have an empty assignment" in { + "have an empty assignment" taggedAs DifferentInNewFrontend in { val List(assignment) = cpg.call.name(Operators.assignment).l assignment.argument.where(_.argumentIndex(2)).isCall.name.l shouldBe List(Operators.arrayInitializer) assignment.argument.where(_.argumentIndex(2)).isCall.argument.l shouldBe List() diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AttributeCpgTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AttributeCpgTests.scala index fc19a0fa456e..847ce13f137e 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AttributeCpgTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/AttributeCpgTests.scala @@ -1,15 +1,14 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture -import io.shiftleft.codepropertygraph.generated.{EvaluationStrategies, NodeTypes, DispatchTypes, Operators, nodes} +import io.joern.rubysrc2cpg.testfixtures.{RubyCode2CpgFixture, SameInNewFrontend} +import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators} import io.shiftleft.semanticcpg.language.* -import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal class AttributeCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { val cpg = code("""x.y""".stripMargin) // TODO: Class Modeling testcase - "test field access call node properties" ignore { + "test field access call node properties" taggedAs SameInNewFrontend ignore { val callNode = cpg.call.methodFullName(Operators.fieldAccess).head callNode.code shouldBe "x.y" callNode.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/BoolOpCpgTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/BoolOpCpgTests.scala index 46c1d7d9c907..e3e5690a2aed 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/BoolOpCpgTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/BoolOpCpgTests.scala @@ -1,14 +1,13 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture -import io.shiftleft.codepropertygraph.generated.{EvaluationStrategies, NodeTypes, DispatchTypes, Operators, nodes} +import io.joern.rubysrc2cpg.testfixtures.{DifferentInNewFrontend, RubyCode2CpgFixture, SameInNewFrontend} +import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators} import io.shiftleft.semanticcpg.language.* -import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal class BoolOpCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { val cpg = code("""x or y or z""".stripMargin) - "test boolOp 'or' call node properties" in { + "test boolOp 'or' call node properties" taggedAs SameInNewFrontend in { val orCall = cpg.call.head // val orCall = cpg.call.methodFullName(Operators.logicalOr).head orCall.code shouldBe "x or y or z" @@ -18,7 +17,7 @@ class BoolOpCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { } // TODO: Fix this multi logicalOr operation - "test boolOp 'or' ast children" ignore { + "test boolOp 'or' ast children" taggedAs DifferentInNewFrontend ignore { cpg.call .methodFullName(Operators.logicalOr) .astChildren @@ -43,7 +42,7 @@ class BoolOpCpgTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { } // TODO: Fix this multi logicalOr operation arguments - "test boolOp 'or' arguments" ignore { + "test boolOp 'or' arguments" taggedAs DifferentInNewFrontend ignore { cpg.call .methodFullName(Operators.logicalOr) .argument diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CallCpgTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CallCpgTests.scala index 7b4058555df0..cc5fde053f20 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CallCpgTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CallCpgTests.scala @@ -1,7 +1,7 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast import io.joern.rubysrc2cpg.deprecated.passes.Defines -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.joern.rubysrc2cpg.testfixtures.{RubyCode2CpgFixture, SameInNewFrontend} import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, MethodRef} import io.shiftleft.codepropertygraph.generated.{DispatchTypes, nodes} import io.shiftleft.semanticcpg.language.* @@ -10,7 +10,7 @@ class CallCpgTests extends RubyCode2CpgFixture(withPostProcessing = true, useDep "simple call method" should { val cpg = code("""foo("a", b)""".stripMargin) - "test call node properties" in { + "test call node properties" taggedAs SameInNewFrontend in { val callNode = cpg.call.name("foo").head callNode.code shouldBe """foo("a", b)""" callNode.signature shouldBe "" @@ -18,7 +18,7 @@ class CallCpgTests extends RubyCode2CpgFixture(withPostProcessing = true, useDep callNode.lineNumber shouldBe Some(1) } - "test call arguments" in { + "test call arguments" taggedAs SameInNewFrontend in { val callNode = cpg.call.name("foo").head val arg1 = callNode.argument(1) arg1.code shouldBe "\"a\"" @@ -27,7 +27,7 @@ class CallCpgTests extends RubyCode2CpgFixture(withPostProcessing = true, useDep arg2.code shouldBe "b" } - "test astChildren" in { + "test astChildren" taggedAs SameInNewFrontend in { val callNode = cpg.call.name("foo").head val children = callNode.astChildren children.size shouldBe 2 diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CustomAssignmentTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CustomAssignmentTests.scala index bdba31fddb29..429a39c47961 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CustomAssignmentTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/CustomAssignmentTests.scala @@ -1,6 +1,6 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture +import io.joern.rubysrc2cpg.testfixtures.{DifferentInNewFrontend, RubyCode2CpgFixture} import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, MethodRef, TypeRef} import io.shiftleft.semanticcpg.language.* @@ -10,7 +10,7 @@ class CustomAssignmentTests extends RubyCode2CpgFixture(withPostProcessing = tru val cpg = code(""" |puts "This is ruby" |""".stripMargin) - "be created for builtin presence" in { + "be created for builtin presence" taggedAs DifferentInNewFrontend in { val List(putsAssignmentCall, _) = cpg.call.l putsAssignmentCall.name shouldBe ".assignment" diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/MethodOneTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/MethodOneTests.scala index c764b40bc4f9..196a928191b3 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/MethodOneTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/deprecated/passes/ast/MethodOneTests.scala @@ -1,9 +1,8 @@ package io.joern.rubysrc2cpg.deprecated.passes.ast -import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture -import io.shiftleft.codepropertygraph.generated.{EvaluationStrategies, NodeTypes, Operators} +import io.joern.rubysrc2cpg.testfixtures.{DifferentInNewFrontend, RubyCode2CpgFixture, SameInNewFrontend} +import io.shiftleft.codepropertygraph.generated.Operators import io.shiftleft.semanticcpg.language.* -import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { @@ -28,7 +27,7 @@ class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { } } - "should return correct number of lines" in { + "should return correct number of lines" taggedAs SameInNewFrontend in { cpg.method.name("foo").numberOfLines.l shouldBe List(3) } @@ -41,7 +40,7 @@ class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { cpg.method.name("foo").methodReturn.typeFullName.head shouldBe "String" } - "should allow traversing to method" in { + "should allow traversing to method" taggedAs DifferentInNewFrontend in { cpg.methodReturn.method.name.l shouldBe List("foo", ":program", ".assignment") } @@ -128,7 +127,7 @@ class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { |end |""".stripMargin) - "contain empty array" in { + "contain empty array" taggedAs SameInNewFrontend in { cpg.method.name("foo").size shouldBe 1 cpg.method.name("foo").block.containsCallTo(Operators.arrayInitializer).size shouldBe 1 } @@ -145,7 +144,7 @@ class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { |end |""".stripMargin) - "contain empty array" in { + "contain empty array" taggedAs DifferentInNewFrontend in { cpg.identifier("c").astParent.isCallTo("attr_accessor").size shouldBe 1 } } @@ -174,7 +173,7 @@ class MethodOneTests extends RubyCode2CpgFixture(useDeprecatedFrontend = true) { |end |""".stripMargin) - "have function identifier as argument and function definition" ignore { + "have function identifier as argument and function definition" taggedAs DifferentInNewFrontend ignore { /* FIXME: We are capturing the prefixes but order in ast is private -> attr_reader -> LITERAL(bar) * We should duplicate the bar node and set parent as both methods */ cpg.identifier("bar").astParent.isCallTo("private").size shouldBe 1 diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallTests.scala index 7ea88dcef4d9..6af7c3db0751 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/CallTests.scala @@ -21,6 +21,23 @@ class CallTests extends RubyCode2CpgFixture { hello.lineNumber shouldBe Some(2) } + "`foo(1,2)` is represented by a CALL node" in { + val cpg = code(""" + |foo(1,2) + |""".stripMargin) + + val List(foo) = cpg.call.name("foo").l + foo.code shouldBe "foo(1,2)" + foo.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH + foo.lineNumber shouldBe Some(2) + + val one = foo.argument(1) + one.code shouldBe "1" + + val two = foo.argument(2) + two.code shouldBe "2" + } + "`x.y(1)` is represented by a `y` CALL with argument `1` and receiver `x.y`" ignore { val cpg = code(""" |x.y(1) diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IndexAccessTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IndexAccessTests.scala index 378d0b6b1ce5..795e9cf9aa09 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IndexAccessTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IndexAccessTests.scala @@ -1,12 +1,12 @@ package io.joern.rubysrc2cpg.querying import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture -import io.shiftleft.codepropertygraph.generated.Operators +import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators} import io.shiftleft.semanticcpg.language.* class IndexAccessTests extends RubyCode2CpgFixture { - "`x[1]` is represented by an `indexAccess` operator call" ignore { + "`x[1]` is represented by an `indexAccess` operator call" in { val cpg = code(""" |x[1] |""".stripMargin) @@ -16,10 +16,35 @@ class IndexAccessTests extends RubyCode2CpgFixture { indexAccess.methodFullName shouldBe Operators.indexAccess indexAccess.code shouldBe "x[1]" indexAccess.lineNumber shouldBe Some(2) + indexAccess.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH + + val List(x) = indexAccess.argument.order(1).l + val List(one) = indexAccess.argument.order(2).l + + x.code shouldBe "x" + x.lineNumber shouldBe Some(2) - val List(one) = indexAccess.argument.l one.code shouldBe "1" one.lineNumber shouldBe Some(2) } + "`x[1,2]` is represented by an `indexAccess` operator call" in { + val cpg = code(""" + |x[1,2] + |""".stripMargin) + + val List(indexAccess) = cpg.call(Operators.indexAccess).l + indexAccess.methodFullName shouldBe Operators.indexAccess + indexAccess.code shouldBe "x[1,2]" + indexAccess.lineNumber shouldBe Some(2) + + val List(x) = indexAccess.argument.order(1).l + val List(one) = indexAccess.argument.order(2).l + val List(two) = indexAccess.argument.order(3).l + + x.code shouldBe "x" + one.code shouldBe "1" + two.code shouldBe "2" + } + } diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala index b4d82dd3b56f..77ecc96ee6ac 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/MethodReturnTests.scala @@ -1,7 +1,8 @@ package io.joern.rubysrc2cpg.querying import io.joern.rubysrc2cpg.testfixtures.RubyCode2CpgFixture -import io.shiftleft.codepropertygraph.generated.nodes.Return +import io.shiftleft.codepropertygraph.generated.Operators +import io.shiftleft.codepropertygraph.generated.nodes.{Call, Return} import io.shiftleft.semanticcpg.language.* class MethodReturnTests extends RubyCode2CpgFixture { @@ -55,4 +56,17 @@ class MethodReturnTests extends RubyCode2CpgFixture { r.lineNumber shouldBe Some(3) } + "explicit RETURN node for `\"\"` exists" in { + val cpg = code(""" + |def foo + | return "" + |end + |""".stripMargin) + + val List(f) = cpg.method.name("foo").l + val List(r: Return) = f.methodReturn.cfgIn.l: @unchecked + r.code shouldBe "return \"\"" + r.lineNumber shouldBe Some(3) + } + } diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/SingleAssignmentTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/SingleAssignmentTests.scala index 301cf433fb5c..06407ee7c214 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/SingleAssignmentTests.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/SingleAssignmentTests.scala @@ -18,7 +18,12 @@ class SingleAssignmentTests extends RubyCode2CpgFixture { val List(lhs, rhs) = assignment.argument.l lhs.code shouldBe "x" + lhs.order shouldBe 1 + lhs.argumentIndex shouldBe 1 + rhs.code shouldBe "1" + rhs.order shouldBe 2 + rhs.argumentIndex shouldBe 2 } "`+=` is represented by an `assignmentPlus` operator call" in { diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala index 24e784bb165e..264a767ab2cf 100644 --- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala +++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/testfixtures/RubyCode2CpgFixture.scala @@ -10,6 +10,7 @@ import io.joern.x2cpg.{ValidationMode, X2Cpg} import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.semanticcpg.language.{ICallResolver, NoResolve} import io.shiftleft.semanticcpg.layers.LayerCreatorContext +import org.scalatest.Tag import java.io.File @@ -81,3 +82,11 @@ class RubyCfgTestCpg(useDeprecatedFrontend: Boolean = true) override val fileSuffix: String = ".rb" } + +/** Denotes a test which has been similarly ported to the new frontend. + */ +object SameInNewFrontend extends Tag("SameInNewFrontend") + +/** Denotes a test which has been ported to the new frontend, but has different expectations. + */ +object DifferentInNewFrontend extends Tag("DifferentInNewFrontend")