diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15682841dd2..bb8924ea8a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: sync-files name: Fix files required to be identical - files: \.(qll?|qhelp|swift)$|^config/identical-files\.json$ + files: \.(qll?|qhelp|swift|toml)$|^config/identical-files\.json$ language: system entry: python3 config/sync-files.py --latest pass_filenames: false diff --git a/config/identical-files.json b/config/identical-files.json index 891ddd8b631..44835401e51 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -358,6 +358,7 @@ ], "shared tree-sitter extractor cargo.toml": [ "shared/tree-sitter-extractor/Cargo.toml", - "ruby/extractor/codeql-extractor-fake-crate/Cargo.toml" + "ruby/extractor/codeql-extractor-fake-crate/Cargo.toml", + "rust/extractor/codeql-extractor-fake-crate/Cargo.toml" ] } diff --git a/misc/codegen/generators/rustgen.py b/misc/codegen/generators/rustgen.py index 04a6538998f..8e0bf4c087b 100644 --- a/misc/codegen/generators/rustgen.py +++ b/misc/codegen/generators/rustgen.py @@ -13,14 +13,16 @@ from misc.codegen.loaders import schemaloader def _get_type(t: str) -> str: match t: - case None | "boolean": # None means a predicate + case None: # None means a predicate return "bool" case "string": return "String" case "int": - return "i32" + return "usize" case _ if t[0].isupper(): - return "TrapLabel" + return "trap::Label" + case "boolean": + assert False, "boolean unsupported" case _: return t diff --git a/misc/codegen/lib/rust.py b/misc/codegen/lib/rust.py index 5e6405759b8..d28a5f55a24 100644 --- a/misc/codegen/lib/rust.py +++ b/misc/codegen/lib/rust.py @@ -58,10 +58,6 @@ keywords = { } _field_overrides = [ - ( - re.compile(r"(start|end)_(line|column)|(.*_)?index|width|num_.*"), - {"base_type": "u32"}, - ), (re.compile(r"(.*)_"), lambda m: {"field_name": m[1]}), ] @@ -98,20 +94,13 @@ class Field: type = f"Vec<{type}>" return type - # using @property breaks pystache internals here - def emitter(self): - if self.type == "String": - return lambda x: f"quoted(&{x})" - else: - return lambda x: x - @property def is_single(self): return not (self.is_optional or self.is_repeated or self.is_predicate) @property def is_label(self): - return self.base_type == "TrapLabel" + return self.base_type == "trap::Label" @dataclasses.dataclass diff --git a/misc/codegen/templates/rust_classes.mustache b/misc/codegen/templates/rust_classes.mustache index a2c0aff6db8..946fb512a37 100644 --- a/misc/codegen/templates/rust_classes.mustache +++ b/misc/codegen/templates/rust_classes.mustache @@ -1,7 +1,7 @@ // generated by {{generator}} -use crate::trap::{TrapLabel, TrapId, TrapEntry, quoted}; -use std::io::Write; +use crate::trap::{TrapId, TrapEntry}; +use codeql_extractor::trap; {{#classes}} #[derive(Debug)] @@ -17,37 +17,36 @@ impl TrapEntry for {{name}} { std::mem::replace(&mut self.id, TrapId::Star) } - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()> { + fn emit(self, id: trap::Label, out: &mut trap::Writer) { {{#single_field_entries}} - write!(out, "{{table_name}}({id}{{#fields}}, {}{{/fields}})\n"{{#fields}}, {{#emitter}}self.{{field_name}}{{/emitter}}{{/fields}})?; + out.add_tuple("{{table_name}}", vec![trap::Arg::Label(id){{#fields}}, self.{{field_name}}.into(){{/fields}}]); {{/single_field_entries}} {{#fields}} {{#is_predicate}} if self.{{field_name}} { - write!(out, "{{table_name}}({id})\n")?; + out.add_tuple("{{table_name}}", vec![trap::Arg::Label(id)]); } {{/is_predicate}} {{#is_optional}} {{^is_repeated}} - if let Some(ref v) = &self.{{field_name}} { - write!(out, "{{table_name}}({id}, {})\n", {{#emitter}}v{{/emitter}})?; + if let Some(v) = self.{{field_name}} { + out.add_tuple("{{table_name}}", vec![trap::Arg::Label(id), v.into()]); } {{/is_repeated}} {{/is_optional}} {{#is_repeated}} - for (i, &ref v) in self.{{field_name}}.iter().enumerate() { + for (i, &v) in self.{{field_name}}.iter().enumerate() { {{^is_optional}} - write!(out, "{{table_name}}({id}, {{^is_unordered}}{}, {{/is_unordered}}{})\n", {{^is_unordered}}i, {{/is_unordered}}{{#emitter}}v{{/emitter}})?; + out.add_tuple("{{table_name}}", vec![trap::Arg::Label(id){{^is_unordered}}, i.into(){{/is_unordered}}, v.into()]); {{/is_optional}} {{#is_optional}} - if let Some(ref vv) = &v { - write!(out, "{{table_name}}({id}, {{^is_unordered}}{}, {{/is_unordered}}{})\n", {{^is_unordered}}i, {{/is_unordered}}{{#emitter}}vv{{/emitter}})?; + if let Some(vv) = v { + out.add_tuple("{{table_name}}", vec![trap::Arg::Label(id){{^is_unordered}}, i.into(){{/is_unordered}}, v.into()]); } {{/is_optional}} } {{/is_repeated}} {{/fields}} - Ok(()) } } {{/classes}} diff --git a/rust/extractor/.cargo/config.toml b/rust/extractor/.cargo/config.toml new file mode 100644 index 00000000000..3e8c45cb78b --- /dev/null +++ b/rust/extractor/.cargo/config.toml @@ -0,0 +1 @@ +paths = ["../../shared/tree-sitter-extractor"] diff --git a/rust/extractor/BUILD.bazel b/rust/extractor/BUILD.bazel index ffe1ee92de9..ffda10101d0 100644 --- a/rust/extractor/BUILD.bazel +++ b/rust/extractor/BUILD.bazel @@ -11,5 +11,7 @@ codeql_rust_binary( visibility = ["//rust:__subpackages__"], deps = all_crate_deps( normal = True, - ), + ) + [ + "//shared/tree-sitter-extractor:codeql-extractor", + ], ) diff --git a/rust/extractor/Cargo.lock b/rust/extractor/Cargo.lock index 98a3f590e2a..40bf5c1903d 100644 --- a/rust/extractor/Cargo.lock +++ b/rust/extractor/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "always-assert" version = "0.2.0" @@ -120,6 +135,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -132,6 +157,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "camino" version = "1.1.9" @@ -285,12 +316,35 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "codeql-extractor" +version = "0.2.0" +dependencies = [ + "chrono", + "encoding", + "flate2", + "globset", + "lazy_static", + "num_cpus", + "rand", + "rayon", + "regex", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", + "tree-sitter", + "tree-sitter-json", + "tree-sitter-ql", +] + [[package]] name = "codeql-rust" version = "0.1.0" dependencies = [ "anyhow", "clap", + "codeql-extractor", "figment", "log", "num-traits", @@ -332,6 +386,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0570650661aa447e7335f1d5e4f499d8e58796e617bedc9267d971e51c8b49d4" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -445,6 +508,70 @@ dependencies = [ "log", ] +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "equivalent" version = "1.0.1" @@ -482,6 +609,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -503,6 +640,30 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -527,6 +688,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -631,7 +798,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -698,6 +865,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3752f229dcc5a481d60f385fa479ff46818033d881d2d801aa27dffcfb5e8306" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.158" @@ -747,6 +920,15 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -762,6 +944,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -808,6 +999,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -823,6 +1024,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -835,6 +1046,12 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -922,6 +1139,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1515,6 +1741,36 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1544,6 +1800,50 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rowan" version = "0.15.15" @@ -1670,6 +1970,15 @@ dependencies = [ "syn", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1849,6 +2158,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tree-sitter" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-json" +version = "0.21.0" +source = "git+https://github.com/tree-sitter/tree-sitter-json#bdd69eb8c8a58a9f54df03de0488d9990179be46" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-ql" +version = "0.22.5" +source = "git+https://github.com/tree-sitter/tree-sitter-ql#42becd6f8f7bae82c818fa3abb1b6ff34b552310" +dependencies = [ + "cc", + "tree-sitter", ] [[package]] @@ -1900,6 +2267,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.5" @@ -1977,6 +2350,22 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -1986,6 +2375,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -2148,3 +2543,24 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rust/extractor/Cargo.toml b/rust/extractor/Cargo.toml index 8c8ef26ef2b..bb3178956d3 100644 --- a/rust/extractor/Cargo.toml +++ b/rust/extractor/Cargo.toml @@ -1,3 +1,5 @@ +[workspace] + [package] name = "codeql-rust" version = "0.1.0" @@ -21,6 +23,10 @@ serde = "1.0.209" serde_with = "3.9.0" stderrlog = "0.6.0" triomphe = "0.1.13" +# Ideally, we'd like to pull this in via a relative path. +# However, our bazel/rust tooling chokes on this, c.f. https://github.com/bazelbuild/rules_rust/issues/1525 +# Therefore, we have a pretty bad hack in place instead, see README.md in the codeql-extractor-fake-crate directory. +codeql-extractor = { path = "codeql-extractor-fake-crate" } [patch.crates-io] # patch for build script bug preventing bazel build diff --git a/rust/extractor/codeql-extractor-fake-crate/BUILD.bazel b/rust/extractor/codeql-extractor-fake-crate/BUILD.bazel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/rust/extractor/codeql-extractor-fake-crate/Cargo.toml b/rust/extractor/codeql-extractor-fake-crate/Cargo.toml new file mode 100644 index 00000000000..d51d64a3349 --- /dev/null +++ b/rust/extractor/codeql-extractor-fake-crate/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "codeql-extractor" +version = "0.2.0" +edition = "2021" +authors = ["GitHub"] + +[dependencies] +flate2 = "1.0" +globset = "0.4" +tree-sitter = ">= 0.22.6" +tracing = "0.1" +tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } +rayon = "1.5.0" +regex = "1.7.1" +encoding = "0.2" +lazy_static = "1.4.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +chrono = { version = "0.4.19", features = ["serde"] } +num_cpus = "1.14.0" + +[dev-dependencies] +tree-sitter-ql = { git = "https://github.com/tree-sitter/tree-sitter-ql" } +tree-sitter-json = {git = "https://github.com/tree-sitter/tree-sitter-json" } +rand = "0.8.5" + +[patch.crates-io] +tree-sitter = {git = "https://github.com/redsun82/tree-sitter.git", rev = "1f5c1112ceaa8fc6aff61d1852690407670d2a96"} diff --git a/rust/extractor/codeql-extractor-fake-crate/README.md b/rust/extractor/codeql-extractor-fake-crate/README.md new file mode 100644 index 00000000000..71f7ec8974b --- /dev/null +++ b/rust/extractor/codeql-extractor-fake-crate/README.md @@ -0,0 +1,7 @@ +We're presenting a fake crate in this workspace that ensures that the correct crate dependencies from the shared tree sitter +extractor can be parsed by Bazel (which doesn't resolve path dependencies outside of the cargo workspace unfortunately). + +The sync-identical-files script keeps this up-to-date. +For local development and IDEs, we override the path to `codeql-extractor` using the `.cargo/config.toml` mechanism. +Bazel doesn't actually do anything with path dependencies except to pull in their dependency tree, so we manually +specify the dependency from the ruby extractor to the shared extractor in `BUILD.bazel`. diff --git a/rust/extractor/codeql-extractor-fake-crate/src/lib.rs b/rust/extractor/codeql-extractor-fake-crate/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/rust/extractor/src/config.rs b/rust/extractor/src/config.rs index c37ed508620..521718c831a 100644 --- a/rust/extractor/src/config.rs +++ b/rust/extractor/src/config.rs @@ -1,9 +1,28 @@ use std::path::PathBuf; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer, Deserializer}; use serde_with; use figment::{Figment, providers::{Env, Serialized}}; -use clap::{Parser, ArgAction}; +use clap::{Parser, ArgAction, ValueEnum}; +use clap::builder::PossibleValue; +use codeql_extractor::trap; +#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize, Clone, Copy, ValueEnum)] +#[serde(rename_all = "lowercase")] +#[clap(rename_all = "lowercase")] +pub enum Compression { + #[default] // TODO make gzip default + None, + Gzip, +} + +impl Into for Compression { + fn into(self) -> trap::Compression { + match self { + Compression::None => trap::Compression::None, + Compression::Gzip => trap::Compression::Gzip, + } + } +} #[serde_with::apply(_ => #[serde(default)])] #[derive(Debug, Deserialize, Default)] @@ -12,6 +31,7 @@ pub struct Config { pub trap_dir: PathBuf, pub source_archive_dir: PathBuf, pub verbose: u8, + pub compression: Compression, pub inputs: Vec, } @@ -25,6 +45,8 @@ struct CliArgs { trap_dir: Option, #[arg(long)] source_archive_dir: Option, + #[arg(long)] + compression: Option, #[arg(short, long, action = ArgAction::Count)] pub verbose: u8, diff --git a/rust/extractor/src/generated/top.rs b/rust/extractor/src/generated/top.rs index d337d08c9d4..c0c7f9ddee8 100644 --- a/rust/extractor/src/generated/top.rs +++ b/rust/extractor/src/generated/top.rs @@ -1,7 +1,7 @@ // generated by codegen -use crate::trap::{TrapLabel, TrapId, TrapEntry, quoted}; -use std::io::Write; +use crate::trap::{TrapId, TrapEntry}; +use codeql_extractor::trap; #[derive(Debug)] pub struct DbFile { @@ -14,21 +14,20 @@ impl TrapEntry for DbFile { std::mem::replace(&mut self.id, TrapId::Star) } - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()> { - write!(out, "db_files({id})\n")?; - write!(out, "files({id}, {})\n", quoted(&self.name))?; - Ok(()) + fn emit(self, id: trap::Label, out: &mut trap::Writer) { + out.add_tuple("db_files", vec![trap::Arg::Label(id)]); + out.add_tuple("files", vec![trap::Arg::Label(id), self.name.into()]); } } #[derive(Debug)] pub struct DbLocation { pub id: TrapId, - pub file: TrapLabel, - pub start_line: u32, - pub start_column: u32, - pub end_line: u32, - pub end_column: u32, + pub file: trap::Label, + pub start_line: usize, + pub start_column: usize, + pub end_line: usize, + pub end_column: usize, } impl TrapEntry for DbLocation { @@ -36,17 +35,16 @@ impl TrapEntry for DbLocation { std::mem::replace(&mut self.id, TrapId::Star) } - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()> { - write!(out, "db_locations({id})\n")?; - write!(out, "locations({id}, {}, {}, {}, {}, {})\n", self.file, self.start_line, self.start_column, self.end_line, self.end_column)?; - Ok(()) + fn emit(self, id: trap::Label, out: &mut trap::Writer) { + out.add_tuple("db_locations", vec![trap::Arg::Label(id)]); + out.add_tuple("locations", vec![trap::Arg::Label(id), self.file.into(), self.start_line.into(), self.start_column.into(), self.end_line.into(), self.end_column.into()]); } } #[derive(Debug)] pub struct Function { pub id: TrapId, - pub location: Option, + pub location: Option, pub name: String, } @@ -55,20 +53,19 @@ impl TrapEntry for Function { std::mem::replace(&mut self.id, TrapId::Star) } - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()> { - write!(out, "functions({id}, {})\n", quoted(&self.name))?; - if let Some(ref v) = &self.location { - write!(out, "locatable_locations({id}, {})\n", v)?; + fn emit(self, id: trap::Label, out: &mut trap::Writer) { + out.add_tuple("functions", vec![trap::Arg::Label(id), self.name.into()]); + if let Some(v) = self.location { + out.add_tuple("locatable_locations", vec![trap::Arg::Label(id), v.into()]); } - Ok(()) } } #[derive(Debug)] pub struct Module { pub id: TrapId, - pub location: Option, - pub declarations: Vec, + pub location: Option, + pub declarations: Vec, } impl TrapEntry for Module { @@ -76,14 +73,13 @@ impl TrapEntry for Module { std::mem::replace(&mut self.id, TrapId::Star) } - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()> { - write!(out, "modules({id})\n")?; - if let Some(ref v) = &self.location { - write!(out, "locatable_locations({id}, {})\n", v)?; + fn emit(self, id: trap::Label, out: &mut trap::Writer) { + out.add_tuple("modules", vec![trap::Arg::Label(id)]); + if let Some(v) = self.location { + out.add_tuple("locatable_locations", vec![trap::Arg::Label(id), v.into()]); } - for (i, &ref v) in self.declarations.iter().enumerate() { - write!(out, "module_declarations({id}, {}, {})\n", i, v)?; + for (i, &v) in self.declarations.iter().enumerate() { + out.add_tuple("module_declarations", vec![trap::Arg::Label(id), i.into(), v.into()]); } - Ok(()) } } diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index cb8d3f2deb2..ff98d58c314 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -35,21 +35,14 @@ fn main() -> anyhow::Result<()> { prefill_caches: false, }; for input in cfg.inputs { - let path = fs::canonicalize(input)?; - { - let mut trap = traps.create("input", &path)?; - let name = String::from(path.to_string_lossy()); - trap.emit(generated::DbFile { id: name.clone().into(), name })?; - archiver.archive(&path); - } - load_workspace_at(&path, &config, &load_config, &no_progress)?; - let (db, vfs, _macro_server) = load_workspace_at(&path, &config, &load_config, &no_progress)?; + load_workspace_at(&input, &config, &load_config, &no_progress)?; + let (db, vfs, _macro_server) = load_workspace_at(&input, &config, &load_config, &no_progress)?; let crates = ::crate_graph(&db); for crate_id in crates.iter().take(1) { let krate = Crate::from(crate_id); let name = krate.display_name(&db); let crate_name = name.as_ref().map(|n| n.canonical_name().as_str()).unwrap_or(""); - let trap = traps.create("crates", &PathBuf::from(format!("/{}_{}", crate_name, crate_id.into_raw().into_u32())))?; + let trap = traps.create("crates", &PathBuf::from(format!("/{}_{}", crate_name, crate_id.into_raw().into_u32()))); translate::CrateTranslator::new( &db, trap, diff --git a/rust/extractor/src/translate.rs b/rust/extractor/src/translate.rs index b30ff5f8acc..505abf1044b 100644 --- a/rust/extractor/src/translate.rs +++ b/rust/extractor/src/translate.rs @@ -1,11 +1,10 @@ use std::collections::HashMap; use std::fs; use std::path::{PathBuf}; -use crate::trap::{TrapFile, TrapId, TrapLabel}; +use crate::trap::{TrapFile, TrapId, AsTrapKeyPart}; use crate::{generated, trap_key}; use ra_ap_hir::{Crate, Module, ModuleDef}; use anyhow; -use ra_ap_base_db::{CrateId}; use ra_ap_hir::{HasSource}; use ra_ap_vfs::{AbsPath, FileId, Vfs}; use ra_ap_syntax::ast::HasName; @@ -15,10 +14,11 @@ use triomphe::Arc; use ra_ap_ide_db::{LineIndexDatabase, RootDatabase}; use ra_ap_ide_db::line_index::LineIndex; use ra_ap_syntax::AstNode; +use codeql_extractor::trap; #[derive(Clone)] struct FileData { - label: TrapLabel, + label: trap::Label, line_index: Arc, } pub struct CrateTranslator<'a> { @@ -49,55 +49,47 @@ impl CrateTranslator<'_> { } } - fn emit_file(&mut self, file_id: FileId) -> Result> { - if let Some(abs_path) = self.vfs.file_path(file_id).as_path() { + fn emit_file(&mut self, file_id: FileId) -> Option { + self.vfs.file_path(file_id).as_path().and_then(|abs_path| { let mut canonical = PathBuf::from(abs_path.as_str()); if !self.file_labels.contains_key(&canonical) { self.archiver.archive(&canonical); canonical = fs::canonicalize(&canonical).unwrap_or(canonical); let name = canonical.to_string_lossy(); - let label = self.trap.emit(generated::DbFile { id: trap_key!["DbFile@", name.as_ref()], name: String::from(name) })?; + let label = self.trap.emit(generated::DbFile { id: trap_key!["file;", name.as_ref()], name: String::from(name) }); let line_index = ::line_index(self.db, file_id); self.file_labels.insert(canonical.clone(), FileData { label, line_index }); } - Ok(self.file_labels.get(&canonical).cloned()) - } else { - Ok(None) - } + self.file_labels.get(&canonical).cloned() + }) } - fn emit_location(&mut self, entity: T) -> Result> where T::Ast: AstNode { - if let Some(source) = entity.source(self.db) { - if let Some(file_id) = source.file_id.file_id().map(|f| f.file_id()) { - if let Some(data) = self.emit_file(file_id)? { - let range = source.value.syntax().text_range(); - let start = data.line_index.line_col(range.start()); - let end = data.line_index.line_col(range.end()); - return Ok(Some(self.trap.emit(generated::DbLocation { - id: trap_key![data.label, ":", start.line, ":", start.col, ":", end.line, ":", end.col], - file: data.label, - start_line: start.line, - start_column: start.col, - end_line: end.line, - end_column: end.col, - })?)); - } - } - } - Ok(None) + fn emit_location(&mut self, entity: T) -> Option + where + T::Ast: AstNode, + { + entity.source(self.db) + .and_then(|source| source.file_id.file_id().map(|f| (f.file_id(), source))) + .and_then(|(file_id, source)| self.emit_file(file_id).map(|data| (data, source))) + .and_then(|(data, source)| { + let range = source.value.syntax().text_range(); + let start = data.line_index.line_col(range.start()); + let end = data.line_index.line_col(range.end()); + Some(self.trap.emit_location(data.label, start, end)) + }) } - fn emit_definition(&mut self, module_label: TrapLabel, id: ModuleDef, labels: &mut Vec) -> Result<()> { + fn emit_definition(&mut self, module_label: trap::Label, id: ModuleDef, labels: &mut Vec) { match id { ModuleDef::Module(_) => {} ModuleDef::Function(function) => { let name = function.name(self.db); - let location = self.emit_location(function)?; + let location = self.emit_location(function); labels.push(self.trap.emit(generated::Function { id: trap_key![module_label, name.as_str()], location, name: name.as_str().into(), - })?); + })); } ModuleDef::Adt(_) => {} ModuleDef::Variant(_) => {} @@ -109,25 +101,23 @@ impl CrateTranslator<'_> { ModuleDef::BuiltinType(_) => {} ModuleDef::Macro(_) => {} } - Ok(()) } - fn emit_module(&mut self, label: TrapLabel, module: Module) -> Result<()> { + fn emit_module(&mut self, label: trap::Label, module: Module) { let mut children = Vec::new(); for id in module.declarations(self.db) { - self.emit_definition(label, id, &mut children)?; + self.emit_definition(label, id, &mut children); } self.trap.emit(generated::Module { id: label.into(), location: None, declarations: children, - })?; - Ok(()) + }); } - pub fn emit_crate(&mut self) -> Result<()> { - self.emit_file(self.krate.root_file(self.db))?; - let mut map = HashMap::::new(); + pub fn emit_crate(&mut self) -> std::io::Result<()> { + self.emit_file(self.krate.root_file(self.db)); + let mut map = HashMap::::new(); for module in self.krate.modules(self.db) { let mut key = String::new(); if let Some(parent) = module.parent(self.db) { @@ -137,17 +127,17 @@ impl CrateTranslator<'_> { } let def = module.definition_source(self.db); if let Some(file) = def.file_id.file_id() { - if let Some(data) = self.emit_file(file.file_id())? { + if let Some(data) = self.emit_file(file.file_id()) { key.push_str(&data.label.as_key_part()); } } if let Some(name) = module.name(self.db) { key.push_str(name.as_str()); } - let label = self.trap.label(TrapId::Key(key))?; + let label = self.trap.label(TrapId::Key(key)); map.insert(module, label); - self.emit_module(label, module)?; + self.emit_module(label, module); } - Ok(()) + self.trap.commit() } } diff --git a/rust/extractor/src/trap.rs b/rust/extractor/src/trap.rs index ea5be477370..f79be500d67 100644 --- a/rust/extractor/src/trap.rs +++ b/rust/extractor/src/trap.rs @@ -5,35 +5,39 @@ use std::io::Write; use std::path::{Path, PathBuf}; use log::{debug, trace}; use crate::{config, path}; - -#[derive(Clone, Copy)] -pub struct TrapLabel(u64); - -impl Debug for TrapLabel { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "TrapLabel({:x})", self.0) - } -} - -impl Display for TrapLabel { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "#{:x}", self.0) - } -} +use codeql_extractor::trap; +use ra_ap_ide_db::line_index::LineCol; +use crate::config::Compression; +use crate::generated; //TODO: typed labels +pub trait AsTrapKeyPart { + fn as_key_part(&self) -> String; +} -impl TrapLabel { - pub fn as_key_part(&self) -> String { +impl AsTrapKeyPart for trap::Label { + fn as_key_part(&self) -> String { format!("{{{}}}", self) } } +impl AsTrapKeyPart for String { + fn as_key_part(&self) -> String { + self.clone() + } +} + +impl AsTrapKeyPart for &str { + fn as_key_part(&self) -> String { + String::from(*self) + } +} + #[derive(Debug, Clone)] pub enum TrapId { Star, Key(String), - Label(TrapLabel), + Label(trap::Label), } impl From for TrapId { @@ -48,8 +52,8 @@ impl From<&str> for TrapId { } } -impl From for TrapId { - fn from(value: TrapLabel) -> Self { +impl From for TrapId { + fn from(value: trap::Label) -> Self { TrapId::Label(value) } } @@ -57,12 +61,6 @@ impl From for TrapId { #[macro_export] macro_rules! trap_key { ($($x:expr),+ $(,)?) => {{ - trait BlanketKeyPart: std::fmt::Display { - fn as_key_part(&self) -> String { - format!("{}", self) - } - } - impl BlanketKeyPart for T {} let mut key = String::new(); $( key.push_str(&$x.as_key_part()); @@ -71,80 +69,63 @@ macro_rules! trap_key { }}; } -impl Display for TrapId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - TrapId::Star => write!(f, "*"), - TrapId::Key(k) => write!(f, "@{}", quoted(k)), - TrapId::Label(l) => Display::fmt(&l, f) - } - } -} - -pub fn escaped(s: &str) -> String { - s.replace("\"", "\"\"") -} - -pub fn quoted(s: &str) -> String { - format!("\"{}\"", escaped(s)) -} - - pub trait TrapEntry: std::fmt::Debug { fn extract_id(&mut self) -> TrapId; - fn emit(self, id: TrapLabel, out: &mut W) -> std::io::Result<()>; + fn emit(self, id: trap::Label, out: &mut trap::Writer); } -#[derive(Debug)] pub struct TrapFile { - label_index: u64, - file: File, - trap_name: String, + path: PathBuf, + writer: trap::Writer, + compression: Compression, } impl TrapFile { - pub fn comment(&mut self, message: &str) -> std::io::Result<()> { - for part in message.split("\n") { - trace!("emit -> {}: // {part}", self.trap_name); - write!(self.file, "// {part}\n")?; - } - Ok(()) - } - - pub fn label(&mut self, mut id: TrapId) -> std::io::Result { - match id { - TrapId::Star => {} - TrapId::Key(ref s) => { - if s.is_empty() { - id = TrapId::Star; - } - } - TrapId::Label(l) => { - return Ok(l); - } - } - let ret = self.create_label(); - trace!("emit -> {}: {ret:?} = {id:?}", self.trap_name); - write!(self.file, "{ret}={id}\n")?; - Ok(ret) - } - - pub fn emit(&mut self, mut entry: T) -> std::io::Result { - trace!("emit -> {}: {entry:?}", self.trap_name); - let id = self.label(entry.extract_id())?; - entry.emit(id, &mut self.file)?; - Ok(id) - } - - fn create_label(&mut self) -> TrapLabel { - let ret = TrapLabel(self.label_index); - self.label_index += 1; + pub fn emit_location(&mut self, file_label: trap::Label, start: LineCol, end: LineCol) -> trap::Label { + let start_line = start.line as usize; + let start_column = start.col as usize; + let end_line = end.line as usize; + let end_column = end.col as usize; + let (ret, _) = self.writer.location_label(trap::Location { + start_line, + start_column, + end_line, + end_column, + }); + self.emit(generated::DbLocation { + id: ret.into(), + file: file_label, + start_line, + start_column, + end_line, + end_column, + }); ret } + + pub fn label(&mut self, id: TrapId) -> trap::Label { + match id { + TrapId::Star => self.writer.fresh_id(), + TrapId::Key(s) => self.writer.global_id(&s).0, + TrapId::Label(l) => l, + } + } + + pub fn emit(&mut self, mut e: T) -> trap::Label { + let label = self.label(e.extract_id()); + e.emit(label, &mut self.writer); + label + } + + pub fn commit(&self) -> std::io::Result<()> { + std::fs::create_dir_all(self.path.parent().unwrap())?; + self.writer.write_to_file(&self.path, self.compression.into()) + } } pub struct TrapFileProvider { trap_dir: PathBuf, + compression: Compression, } impl TrapFileProvider { @@ -152,27 +133,25 @@ impl TrapFileProvider { let trap_dir = cfg.trap_dir.clone(); std::fs::create_dir_all(&trap_dir)?; Ok(TrapFileProvider { - trap_dir + trap_dir, + compression: cfg.compression, }) } - pub fn create(&self, category: &str, key: &Path) -> std::io::Result { + pub fn create(&self, category: &str, key: &Path) -> TrapFile { let mut path = PathBuf::from(category); path.push(path::key(key)); path.set_extension(path.extension().map(|e| { - let mut o : OsString = e.to_owned(); + let mut o: OsString = e.to_owned(); o.push(".trap"); o }).unwrap_or("trap".into())); - let trap_name = String::from(path.to_string_lossy()); - debug!("creating trap file {}", trap_name); + debug!("creating trap file {}", path.display()); path = self.trap_dir.join(path); - std::fs::create_dir_all(path.parent().unwrap())?; - let file = File::create(path)?; - Ok(TrapFile { - label_index: 0, - file, - trap_name, - }) + TrapFile { + path, + writer: trap::Writer::new(), + compression: self.compression, + } } } diff --git a/shared/tree-sitter-extractor/src/trap.rs b/shared/tree-sitter-extractor/src/trap.rs index 64c06539ecb..5e0c80615e1 100644 --- a/shared/tree-sitter-extractor/src/trap.rs +++ b/shared/tree-sitter-extractor/src/trap.rs @@ -136,10 +136,16 @@ impl fmt::Display for Entry { } } -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] // Identifiers of the form #0, #1... pub struct Label(u32); +impl fmt::Debug for Label { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Label({:#x})", self.0) + } +} + impl fmt::Display for Label { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "#{:x}", self.0) @@ -170,6 +176,30 @@ impl fmt::Display for Arg { } } +impl From for Arg { + fn from(value: String) -> Self { + Arg::String(value) + } +} + +impl From<&str> for Arg { + fn from(value: &str) -> Self { + Arg::String(value.into()) + } +} + +impl From