-
Notifications
You must be signed in to change notification settings - Fork 418
[rust2cpg] support impl X blocks.
#6031
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| package io.joern.rust2cpg.passes.ast | ||
|
|
||
| import io.joern.rust2cpg.testfixtures.Rust2CpgSuite | ||
| import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EvaluationStrategies} | ||
| import io.shiftleft.codepropertygraph.generated.nodes.* | ||
| import io.shiftleft.semanticcpg.language.* | ||
| import io.shiftleft.semanticcpg.utils.FileUtil.PathExt | ||
|
|
||
| import java.nio.file.Paths | ||
|
|
||
| class ImplTests extends Rust2CpgSuite(noSysRoot = true) { | ||
|
|
||
| "an inherent method in an impl block" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn bar(&self) {} | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "have correct fullName" in { | ||
| cpg.method.nameExact("bar").fullName.l shouldBe List("rust2cpgtest::Foo::bar") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test fixture creates at least 2 files:
So,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was aware of that. I was just suggesting that if you name the module |
||
| } | ||
|
|
||
| "have correct return typeFullName" in { | ||
| cpg.method.nameExact("bar").methodReturn.typeFullName.l shouldBe List("()") | ||
| } | ||
|
|
||
| "have correct self properties" in { | ||
| inside(cpg.method.nameExact("bar").parameter.nameExact("self").l) { case self :: Nil => | ||
| self.index shouldBe 0 | ||
| self.order shouldBe 0 | ||
| self.evaluationStrategy shouldBe EvaluationStrategies.BY_SHARING | ||
| self.typeFullName shouldBe "&rust2cpgtest::Foo" | ||
| } | ||
| } | ||
|
|
||
| "be an AST child of the corresponding TYPE_DECL" in { | ||
| cpg.typeDecl.nameExact("Foo").method.nameExact("bar").fullName.l shouldBe List("rust2cpgtest::Foo::bar") | ||
| } | ||
|
|
||
| "not create a duplicate TYPE_DECL" in { | ||
| cpg.typeDecl.fullNameExact("rust2cpgtest::Foo").size shouldBe 1 | ||
| } | ||
| } | ||
|
|
||
| "an inherent method with a `&mut self` and an explicit parameter" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn bar(&mut self, x: i32) {} | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "have correct self properties" in { | ||
| inside(cpg.method.nameExact("bar").parameter.nameExact("self").l) { case self :: Nil => | ||
| self.index shouldBe 0 | ||
| self.order shouldBe 0 | ||
| self.evaluationStrategy shouldBe EvaluationStrategies.BY_SHARING | ||
| self.typeFullName shouldBe "&mut rust2cpgtest::Foo" | ||
| } | ||
| } | ||
|
|
||
| "have correct explicit parameter properties" in { | ||
| inside(cpg.method.nameExact("bar").parameter.nameExact("x").l) { case param :: Nil => | ||
| param.index shouldBe 1 | ||
| param.order shouldBe 1 | ||
| param.typeFullName shouldBe "i32" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| "an inherent method with a by-value `self` receiver" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn bar(self) {} | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "have correct self properties" in { | ||
| inside(cpg.method.nameExact("bar").parameter.nameExact("self").l) { case self :: Nil => | ||
| self.index shouldBe 0 | ||
| self.order shouldBe 0 | ||
| self.evaluationStrategy shouldBe EvaluationStrategies.BY_VALUE | ||
| self.typeFullName shouldBe "rust2cpgtest::Foo" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| "an associated function without a receiver" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn new() -> Foo { Foo } | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "have no parameters" in { | ||
| cpg.method.nameExact("new").parameter shouldBe empty | ||
| } | ||
|
|
||
| "have correct return typeFullName" in { | ||
| cpg.method.nameExact("new").methodReturn.typeFullName.l shouldBe List("rust2cpgtest::Foo") | ||
| } | ||
| } | ||
|
|
||
| "multiple methods in a single impl block" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn a(&self) {} | ||
| | fn b(&self) {} | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "lower each as an AST child of the corresponding TYPE_DECL" in { | ||
| cpg.typeDecl.nameExact("Foo").method.fullName.sorted.l shouldBe List( | ||
| "rust2cpgtest::Foo::a", | ||
| "rust2cpgtest::Foo::b" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| "multiple impl blocks for the same type" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { | ||
| | fn a(&self) {} | ||
| |} | ||
| |impl Foo { | ||
| | fn b(&self) {} | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "merge all methods under the same TYPE_DECL" in { | ||
| cpg.typeDecl.nameExact("Foo").method.fullName.sorted.l shouldBe List( | ||
| "rust2cpgtest::Foo::a", | ||
| "rust2cpgtest::Foo::b" | ||
| ) | ||
| } | ||
|
|
||
| "not create a duplicate TYPE_DECL" in { | ||
| cpg.typeDecl.fullNameExact("rust2cpgtest::Foo").size shouldBe 1 | ||
| } | ||
| } | ||
|
|
||
| "multiple impl blocks for the same type spread across files" should { | ||
| val cpg = code( | ||
| """ | ||
| |struct Foo; | ||
| |mod a; | ||
| |mod b; | ||
| |""".stripMargin, | ||
| fileName = (Paths.get("src") / "lib.rs").toString | ||
| ).moreCode( | ||
| """ | ||
| |impl crate::Foo { | ||
| | fn a(&self) {} | ||
| |} | ||
| |""".stripMargin, | ||
| fileName = (Paths.get("src") / "a.rs").toString | ||
| ).moreCode( | ||
| """ | ||
| |impl crate::Foo { | ||
| | fn b(&self) {} | ||
| |}""".stripMargin, | ||
| fileName = (Paths.get("src") / "b.rs").toString | ||
| ) | ||
|
|
||
| "merge all methods under the same TYPE_DECL" in { | ||
| cpg.typeDecl.nameExact("Foo").method.fullName.sorted.l shouldBe List( | ||
| "rust2cpgtest::Foo::a", | ||
| "rust2cpgtest::Foo::b" | ||
| ) | ||
| } | ||
|
|
||
| "not create a duplicate TYPE_DECL" in { | ||
| cpg.typeDecl.fullNameExact("rust2cpgtest::Foo").size shouldBe 1 | ||
| } | ||
| } | ||
|
|
||
| "a call to an inherent method" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { fn bar(&self) {} } | ||
| |fn run(f: Foo) { f.bar(); } | ||
| |""".stripMargin) | ||
|
|
||
| "have correct properties and callee" in { | ||
| inside(cpg.call.nameExact("bar").l) { case call :: Nil => | ||
| implicit val callResolver: NoResolve.type = NoResolve | ||
| call.methodFullName shouldBe "rust2cpgtest::Foo::bar" | ||
| call.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH | ||
| call.callee.l shouldBe cpg.method.fullNameExact("rust2cpgtest::Foo::bar").l | ||
| } | ||
| } | ||
|
|
||
| "have correct arguments" in { | ||
| inside(cpg.call.nameExact("bar").argument.l) { case (base: Identifier) :: Nil => | ||
| base.name shouldBe "f" | ||
| base.argumentIndex shouldBe 0 | ||
| base.typeFullName shouldBe "&rust2cpgtest::Foo" | ||
| } | ||
| } | ||
|
|
||
| "have correct self typeFullName" in { | ||
| cpg.method.nameExact("bar").parameter.nameExact("self").typeFullName.l shouldBe List("&rust2cpgtest::Foo") | ||
| } | ||
| } | ||
|
|
||
| "a call to an associated function" should { | ||
| val cpg = code(""" | ||
| |struct Foo; | ||
| |impl Foo { fn new() -> Foo { Foo } } | ||
| |fn run() { Foo::new(); } | ||
| |""".stripMargin) | ||
|
|
||
| "have correct properties and callee" in { | ||
| inside(cpg.call.nameExact("new").l) { case call :: Nil => | ||
| implicit val callResolver: NoResolve.type = NoResolve | ||
| call.methodFullName shouldBe "rust2cpgtest::Foo::new" | ||
| call.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH | ||
| call.callee.l shouldBe cpg.method.fullNameExact("rust2cpgtest::Foo::new").l | ||
| } | ||
| } | ||
|
|
||
| "have no arguments" in { | ||
| cpg.call.nameExact("new").argument shouldBe empty | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does rust allow to specify multiple
impl Xdecls for the same type?If so, this implementation here would create multiple TypeDecls with the same fullName.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does -- and I don't know why I can no longer paste a proper link, but please see the
"multiple impl blocks for the same type"in the current diff.But that correctly reminds me: the TypeDecl creation actually happens outside (see the last paragraph of #6028). Well, not for
structsdeclared in the source-code, but for external ones. Anyway, for theimpl X for YI was thinking of having an extra pass à la swifsrc2cpg's ExtensionPass so that these kind of changes only happen in one place, rather than potentially scattered inside RustVisitor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, just one more thing came into my mind: is it possible to specify multiple
impl Xdecls for the same type but spread over different files? Does that lead to multiple TypeDecls with the same fullName?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also fine (no duplicates), but it's good to have a test for it.