Skip to content

Commit 99f223a

Browse files
authored
[rust2cpg] lower struct attributes as annotations. (#6227)
* [rust2cpg] lower struct attributes as annotations. * retry ci
1 parent 93d96ea commit 99f223a

2 files changed

Lines changed: 189 additions & 3 deletions

File tree

joern-cli/frontends/rust2cpg/src/main/scala/io/joern/rust2cpg/astcreation/RustVisitor.scala

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,12 +1399,13 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
13991399
val structFullName = typeFullNameForStruct(struct)
14001400
val inheritsFrom = implementedTraits.map(traitFullName => s"<$structFullName as $traitFullName>")
14011401
val typeDecl = typeDeclForStruct(struct, inheritsFrom)
1402+
val attributes = struct.attr.map(visitAttr)
14021403

14031404
contextStack.pushTypeDecl(typeDecl)
14041405
val ctorAst = structCtorMethodAst(struct, typeDecl, Nil)
14051406
contextStack.pop()
14061407

1407-
Ast(typeDecl).withChild(ctorAst)
1408+
Ast(typeDecl).withChild(ctorAst).withChildren(attributes)
14081409
}
14091410

14101411
// `struct Foo { x: T, ... }` becomes:
@@ -1420,12 +1421,15 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
14201421
val structFullName = typeFullNameForStruct(struct)
14211422
val inheritsFrom = implementedTraits.map(traitFullName => s"<$structFullName as $traitFullName>")
14221423
val typeDecl = typeDeclForStruct(struct, inheritsFrom)
1424+
val attributes = struct.attr.map(visitAttr)
14231425

14241426
contextStack.pushTypeDecl(typeDecl)
14251427
val ctorAst = structCtorMethodAst(struct, typeDecl, recordFieldData(recordFieldList))
14261428
contextStack.pop()
14271429

1428-
Ast(typeDecl).withChildren(visitRecordFieldList(recordFieldList) :+ ctorAst)
1430+
Ast(typeDecl)
1431+
.withChildren(visitRecordFieldList(recordFieldList) :+ ctorAst)
1432+
.withChildren(attributes)
14291433
}
14301434

14311435
// `struct Foo(T1, T2, ...);` becomes:
@@ -1451,12 +1455,16 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
14511455
val inheritsFrom = implementedTraits.map(traitFullName => s"<$structFullName as $traitFullName>")
14521456
val typeDecl = typeDeclForStruct(struct, inheritsFrom)
14531457
val fields = tupleStructFieldData(tupleFieldList)
1458+
val attributes = struct.attr.map(visitAttr)
14541459

14551460
contextStack.pushTypeDecl(typeDecl)
14561461
val ctorAst = structCtorMethodAst(struct, typeDecl, fields)
14571462
contextStack.pop()
14581463

1459-
val typeDeclAst = Ast(typeDecl).withChildren(visitTupleFieldList(tupleFieldList) :+ ctorAst)
1464+
val typeDeclAst =
1465+
Ast(typeDecl)
1466+
.withChildren(visitTupleFieldList(tupleFieldList) :+ ctorAst)
1467+
.withChildren(attributes)
14601468
val ctorWrapperAst = tupleStructCtorWrapperAst(
14611469
struct,
14621470
typeDecl,
@@ -1871,4 +1879,20 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
18711879
private def mkImport(use: Use, path: Seq[Path], importedAs: String): NewImport = {
18721880
newImportNode(code(use), path.map(code).mkString(PathSep), importedAs, use)
18731881
}
1882+
1883+
// Attr =
1884+
// '#' '!'? '[' Meta ']'
1885+
private def visitAttr(attr: Attr): Ast = {
1886+
val path = attr.meta match {
1887+
case pathMeta: PathMeta => code(pathMeta.path)
1888+
case keyValueMeta: KeyValueMeta => code(keyValueMeta.path)
1889+
case tokenTreeMeta: TokenTreeMeta => code(tokenTreeMeta.path)
1890+
case cfgMeta: CfgMeta => code(cfgMeta.cfgKwToken)
1891+
case cfgAttrMeta: CfgAttrMeta => code(cfgAttrMeta.cfgAttrKwToken)
1892+
case unsafeMeta: UnsafeMeta => code(unsafeMeta.unsafeKwToken)
1893+
}
1894+
val name = path.split(PathSep).lastOption.getOrElse(path)
1895+
val fullName = path
1896+
Ast(annotationNode(attr, code(attr), name, fullName))
1897+
}
18741898
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package io.joern.rust2cpg.passes.ast
2+
3+
import io.joern.rust2cpg.testfixtures.Rust2CpgSuite
4+
import io.shiftleft.semanticcpg.language.*
5+
6+
class AnnotationTests extends Rust2CpgSuite(noSysRoot = true) {
7+
8+
"struct with path attribute" should {
9+
val cpg = code("""
10+
|#[non_exhaustive]
11+
|struct Foo;
12+
|""".stripMargin)
13+
14+
"have correct annotation" in {
15+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
16+
attr.name shouldBe "non_exhaustive"
17+
attr.fullName shouldBe "non_exhaustive"
18+
attr.code shouldBe "#[non_exhaustive]"
19+
}
20+
}
21+
}
22+
23+
"struct with token tree attribute" should {
24+
val cpg = code("""
25+
|#[derive(Debug)]
26+
|struct Foo;
27+
|""".stripMargin)
28+
29+
"have correct annotation" in {
30+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
31+
attr.name shouldBe "derive"
32+
attr.fullName shouldBe "derive"
33+
attr.code shouldBe "#[derive(Debug)]"
34+
}
35+
}
36+
}
37+
38+
"struct with key-value attribute" should {
39+
val cpg = code("""
40+
|#[doc = "something"]
41+
|struct Foo {}
42+
|""".stripMargin)
43+
44+
"have correct annotation" in {
45+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
46+
attr.name shouldBe "doc"
47+
attr.fullName shouldBe "doc"
48+
attr.code shouldBe """#[doc = "something"]"""
49+
}
50+
}
51+
}
52+
53+
"struct with cfg attribute" should {
54+
val cpg = code("""
55+
|#[cfg(not(feature = "unstable"))]
56+
|struct Foo;
57+
|""".stripMargin)
58+
59+
"have correct annotation" in {
60+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
61+
attr.name shouldBe "cfg"
62+
attr.fullName shouldBe "cfg"
63+
attr.code shouldBe """#[cfg(not(feature = "unstable"))]"""
64+
}
65+
}
66+
}
67+
68+
"struct with cfg_attr attribute" should {
69+
val cpg = code("""
70+
|#[cfg_attr(not(feature = "unstable"), derive(Debug))]
71+
|struct Foo;
72+
|""".stripMargin)
73+
74+
"have correct annotation" in {
75+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
76+
attr.name shouldBe "cfg_attr"
77+
attr.fullName shouldBe "cfg_attr"
78+
attr.code shouldBe """#[cfg_attr(not(feature = "unstable"), derive(Debug))]"""
79+
}
80+
}
81+
}
82+
83+
"struct with unsafe attribute" should {
84+
val cpg = code("""
85+
|#[unsafe(no_mangle)]
86+
|struct Foo;
87+
|""".stripMargin)
88+
89+
"have correct annotation" in {
90+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
91+
attr.name shouldBe "unsafe"
92+
attr.fullName shouldBe "unsafe"
93+
attr.code shouldBe "#[unsafe(no_mangle)]"
94+
}
95+
}
96+
}
97+
98+
"struct with qualified attribute path" should {
99+
val cpg = code("""
100+
|#[some::attr]
101+
|struct Foo;
102+
|""".stripMargin)
103+
104+
"have correct annotation" in {
105+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
106+
attr.name shouldBe "attr"
107+
attr.fullName shouldBe "some::attr"
108+
attr.code shouldBe "#[some::attr]"
109+
}
110+
}
111+
}
112+
113+
"struct with qualified token tree attribute path" should {
114+
val cpg = code("""
115+
|#[some::attr(1)]
116+
|struct Foo;
117+
|""".stripMargin)
118+
119+
"have correct annotation" in {
120+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
121+
attr.name shouldBe "attr"
122+
attr.fullName shouldBe "some::attr"
123+
attr.code shouldBe "#[some::attr(1)]"
124+
}
125+
}
126+
}
127+
128+
"struct with qualified key-value attribute path" should {
129+
val cpg = code("""
130+
|#[some::attr = 1]
131+
|struct Foo;
132+
|""".stripMargin)
133+
134+
"have correct annotation" in {
135+
inside(cpg.typeDecl.nameExact("Foo").annotation.l) { case attr :: Nil =>
136+
attr.name shouldBe "attr"
137+
attr.fullName shouldBe "some::attr"
138+
attr.code shouldBe "#[some::attr = 1]"
139+
}
140+
}
141+
}
142+
143+
"struct with two attributes" should {
144+
val cpg = code("""
145+
|#[derive(Debug)]
146+
|#[doc = "foobar"]
147+
|struct Foo;
148+
|""".stripMargin)
149+
150+
"have correct annotations" in {
151+
inside(cpg.typeDecl.nameExact("Foo").annotation.sortBy(_.lineNumber).l) { case attr1 :: attr2 :: Nil =>
152+
attr1.name shouldBe "derive"
153+
attr1.fullName shouldBe "derive"
154+
attr1.code shouldBe "#[derive(Debug)]"
155+
156+
attr2.name shouldBe "doc"
157+
attr2.fullName shouldBe "doc"
158+
attr2.code shouldBe """#[doc = "foobar"]"""
159+
}
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)