From c160f9a946042c9e6f3cb56476864e5d5eb1306b Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 14 Aug 2024 10:28:36 +0200 Subject: [PATCH 1/9] refactor(go): change the result of `Scanner.Scan` to `ScanResults` `Scanner.Scan` was returning a slice with the matching rules, but this is not flexible enough if we want the results to include additional information like non-matching rules or module outputs. The `ScanResults` object provides access to the matching rules and to any other information that we want to return in the future. --- go/compiler_test.go | 44 ++++++++++++++++++++++---------------------- go/example_test.go | 8 ++++---- go/main.go | 2 +- go/scanner.go | 20 +++++++++++++++----- go/scanner_test.go | 35 +++++++++++++++++++---------------- 5 files changed, 61 insertions(+), 48 deletions(-) diff --git a/go/compiler_test.go b/go/compiler_test.go index e1fcfe110..826a2581e 100644 --- a/go/compiler_test.go +++ b/go/compiler_test.go @@ -15,8 +15,8 @@ func TestNamespaces(t *testing.T) { c.AddSource("rule test { condition: true }") s := NewScanner(c.Build()) - matchingRules, _ := s.Scan([]byte{}) - assert.Len(t, matchingRules, 2) + scanResults, _ := s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 2) } func TestUnsupportedModules(t *testing.T) { @@ -26,8 +26,8 @@ func TestUnsupportedModules(t *testing.T) { IgnoreModule("unsupported_module")) assert.NoError(t, err) - matchingRules, _ := r.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ := r.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) } func TestRelaxedReSyntax(t *testing.T) { @@ -35,8 +35,8 @@ func TestRelaxedReSyntax(t *testing.T) { rule test { strings: $a = /\Release/ condition: $a }`, RelaxedReSyntax(true)) assert.NoError(t, err) - matchingRules, _ := r.Scan([]byte("Release")) - assert.Len(t, matchingRules, 1) + scanResults, _ := r.Scan([]byte("Release")) + assert.Len(t, scanResults.MatchingRules(), 1) } @@ -55,9 +55,9 @@ func TestSerialization(t *testing.T) { r, _ = Deserialize(b) s := NewScanner(r) - matchingRules, _ := s.Scan([]byte{}) + scanResults, _ := s.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + assert.Len(t, scanResults.MatchingRules(), 1) } func TestVariables(t *testing.T) { @@ -65,41 +65,41 @@ func TestVariables(t *testing.T) { "rule test { condition: var == 1234 }", Globals(map[string]interface{}{"var": 1234})) - matchingRules, _ := NewScanner(r).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ := NewScanner(r).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) c, err := NewCompiler() assert.NoError(t, err) c.DefineGlobal("var", 1234) c.AddSource("rule test { condition: var == 1234 }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) c.DefineGlobal("var", -1234) c.AddSource("rule test { condition: var == -1234 }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) c.DefineGlobal("var", true) c.AddSource("rule test { condition: var }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) c.DefineGlobal("var", false) c.AddSource("rule test { condition: var }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 0) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 0) c.DefineGlobal("var", "foo") c.AddSource("rule test { condition: var == \"foo\" }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) c.DefineGlobal("var", 3.4) c.AddSource("rule test { condition: var == 3.4 }") - matchingRules, _ = NewScanner(c.Build()).Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = NewScanner(c.Build()).Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) err = c.DefineGlobal("var", struct{}{}) assert.EqualError(t, err, "variable `var` has unsupported type: struct {}") diff --git a/go/example_test.go b/go/example_test.go index 2b19efe91..4c58525df 100644 --- a/go/example_test.go +++ b/go/example_test.go @@ -20,10 +20,10 @@ rule bar { }`) // Use the compiled rules for scanning some data. - matchingRules, _ := rules.Scan([]byte("foobar")) + scanResults, _ := rules.Scan([]byte("foobar")) // Iterate over the matching rules. - for _, r := range matchingRules { + for _, r := range scanResults.MatchingRules() { fmt.Printf("rule %s matched\n", r.Identifier()) } @@ -62,10 +62,10 @@ func Example_compilerAndScanner() { scanner := NewScanner(rules) // Use the scanner for scanning some data. - matchingRules, _ := scanner.Scan([]byte("foobar")) + scanResults, _ := scanner.Scan([]byte("foobar")) // Iterate over the matching rules. - for _, r := range matchingRules { + for _, r := range scanResults.MatchingRules() { fmt.Printf("rule %s matched\n", r.Identifier()) } diff --git a/go/main.go b/go/main.go index 56f950b9b..54fab7a58 100644 --- a/go/main.go +++ b/go/main.go @@ -71,7 +71,7 @@ type Rules struct{ cRules *C.YRX_RULES } // Scan some data with the compiled rules. // // Returns a slice with the rules that matched. -func (r *Rules) Scan(data []byte) ([]*Rule, error) { +func (r *Rules) Scan(data []byte) (*ScanResults, error) { scanner := NewScanner(r) return scanner.Scan(data) } diff --git a/go/scanner.go b/go/scanner.go index 6ead9b04e..0b91fb168 100644 --- a/go/scanner.go +++ b/go/scanner.go @@ -45,7 +45,16 @@ type Scanner struct { matchingRules []*Rule } -type ScanResults struct{} + +// ScanResults contains the results of a Scanner.Scan. +type ScanResults struct{ + matchingRules []*Rule +} + +// MatchingRules returns the rules that matched during the scan. +func (s ScanResults) MatchingRules() []*Rule { + return s.matchingRules +} // NewScanner creates a Scanner that will use the provided YARA rules. // @@ -192,7 +201,7 @@ func (s *Scanner) SetModuleOutput(data proto.Message) error { } // Scan scans the provided data with the Rules associated to the Scanner. -func (s *Scanner) Scan(buf []byte) ([]*Rule, error) { +func (s *Scanner) Scan(buf []byte) (*ScanResults, error) { var ptr *C.uint8_t // When `buf` is an empty slice `ptr` will be nil. That's ok, because // yrx_scanner_scan allows the data pointer to be null as long as the data @@ -201,8 +210,6 @@ func (s *Scanner) Scan(buf []byte) ([]*Rule, error) { ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0]))) } - s.matchingRules = nil - runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -216,7 +223,10 @@ func (s *Scanner) Scan(buf []byte) ([]*Rule, error) { err = errors.New(C.GoString(C.yrx_last_error())) } - return s.matchingRules, err + scanResults := &ScanResults{ s.matchingRules } + s.matchingRules = nil + + return scanResults, err } // Destroy destroys the scanner. diff --git a/go/scanner_test.go b/go/scanner_test.go index c2d2b58f2..b3ebef7e6 100644 --- a/go/scanner_test.go +++ b/go/scanner_test.go @@ -12,7 +12,8 @@ import ( func TestScanner1(t *testing.T) { r, _ := Compile("rule t { condition: true }") s := NewScanner(r) - matchingRules, _ := s.Scan([]byte{}) + scanResults, _ := s.Scan([]byte{}) + matchingRules := scanResults.MatchingRules() assert.Len(t, matchingRules, 1) assert.Equal(t, "t", matchingRules[0].Identifier()) @@ -23,7 +24,8 @@ func TestScanner1(t *testing.T) { func TestScanner2(t *testing.T) { r, _ := Compile(`rule t { strings: $bar = "bar" condition: $bar }`) s := NewScanner(r) - matchingRules, _ := s.Scan([]byte("foobar")) + scanResults, _ := s.Scan([]byte("foobar")) + matchingRules := scanResults.MatchingRules() assert.Len(t, matchingRules, 1) assert.Equal(t, "t", matchingRules[0].Identifier()) @@ -44,12 +46,12 @@ func TestScanner3(t *testing.T) { Globals(map[string]interface{}{"var_bool": true})) s := NewScanner(r) - matchingRules, _ := s.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ := s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) s.SetGlobal("var_bool", false) - matchingRules, _ = s.Scan([]byte{}) - assert.Len(t, matchingRules, 0) + scanResults, _ = s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 0) } func TestScanner4(t *testing.T) { @@ -58,20 +60,20 @@ func TestScanner4(t *testing.T) { Globals(map[string]interface{}{"var_int": 0})) s := NewScanner(r) - matchingRules, _ := s.Scan([]byte{}) - assert.Len(t, matchingRules, 0) + scanResults, _ := s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 0) assert.NoError(t, s.SetGlobal("var_int", 1)) - matchingRules, _ = s.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) assert.NoError(t, s.SetGlobal("var_int", int32(1))) - matchingRules, _ = s.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) assert.NoError(t, s.SetGlobal("var_int", int64(1))) - matchingRules, _ = s.Scan([]byte{}) - assert.Len(t, matchingRules, 1) + scanResults, _ = s.Scan([]byte{}) + assert.Len(t, scanResults.MatchingRules(), 1) } func TestScannerTimeout(t *testing.T) { @@ -94,7 +96,8 @@ func TestScannerMetadata(t *testing.T) { true }`) s := NewScanner(r) - matchingRules, _ := s.Scan([]byte{}) + scanResults, _ := s.Scan([]byte{}) + matchingRules := scanResults.MatchingRules() assert.Len(t, matchingRules, 1) assert.Equal(t, "some_int", matchingRules[0].Metadata()[0].Identifier) @@ -106,5 +109,5 @@ func TestScannerMetadata(t *testing.T) { assert.Equal(t, "some_string", matchingRules[0].Metadata()[3].Identifier) assert.Equal(t, "hello", matchingRules[0].Metadata()[3].Value) assert.Equal(t, "some_bytes", matchingRules[0].Metadata()[4].Identifier) - assert.Equal(t, []byte{0, 1, 2}, matchingRules[0].Metadata()[4].Value) + assert.Equal(t, []byte{0, 1, 2}, matchingRules[0].Metadata()[4].Value) } From 8076b0ec739363a2a55da4f6284a6eed7687b385 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 14 Aug 2024 12:32:24 +0200 Subject: [PATCH 2/9] chore: fix clippy warnings --- capi/include/yara_x.h | 3 ++- capi/src/scanner.rs | 5 +++-- lib/src/compiler/ir/mod.rs | 2 +- lib/src/re/thompson/instr.rs | 14 +++++++------- lib/src/scanner/mod.rs | 4 ++-- lib/src/types/structure.rs | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index deeb91118..f86924abe 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -380,7 +380,8 @@ enum YRX_RESULT yrx_scanner_on_matching_rule(struct YRX_SCANNER *scanner, // // 1) When the module does not produce any output on its own. // 2) When you already know the output of the module for the upcoming file to -// be scanned, and you prefer to reuse this data instead of generating it again. +// be scanned, and you prefer to reuse this data instead of generating it +// again. // // Case 1) applies to certain modules lacking a main function, thus incapable of // producing any output on their own. For such modules, you must set the output diff --git a/capi/src/scanner.rs b/capi/src/scanner.rs index b41dbacf4..4c94a5dd7 100644 --- a/capi/src/scanner.rs +++ b/capi/src/scanner.rs @@ -118,7 +118,7 @@ pub unsafe extern "C" fn yrx_scanner_scan( /// callback function is being executed, but it may be freed after the callback /// function returns, so you cannot use the pointer outside the callback. /// -/// It also receives the `user_data` pointer that was passed to the +/// It also receives the `user_data` pointer that was passed to the /// [`yrx_scanner_on_matching_rule`] function, which can point to arbitrary /// data owned by the user. pub type YRX_ON_MATCHING_RULE = extern "C" fn( @@ -161,7 +161,8 @@ pub unsafe extern "C" fn yrx_scanner_on_matching_rule( /// /// 1) When the module does not produce any output on its own. /// 2) When you already know the output of the module for the upcoming file to -/// be scanned, and you prefer to reuse this data instead of generating it again. +/// be scanned, and you prefer to reuse this data instead of generating it +/// again. /// /// Case 1) applies to certain modules lacking a main function, thus incapable of /// producing any output on their own. For such modules, you must set the output diff --git a/lib/src/compiler/ir/mod.rs b/lib/src/compiler/ir/mod.rs index 8adbdeb90..92735597e 100644 --- a/lib/src/compiler/ir/mod.rs +++ b/lib/src/compiler/ir/mod.rs @@ -22,7 +22,7 @@ crate parses regular expressions and produce the corresponding [Hir]. For hex patterns the [Hir] is generated from the AST by the [`hex2hir`] module. Using a common representation for both regular expressions and hex patterns -allows using the same regexp engine for matching both types of patterns. +allows using the same regex engine for matching both types of patterns. [Rules]: crate::compiler::Rules [regex_syntax]: https://docs.rs/regex-syntax/latest/regex_syntax/ diff --git a/lib/src/re/thompson/instr.rs b/lib/src/re/thompson/instr.rs index 0d4139b1e..3692af675 100644 --- a/lib/src/re/thompson/instr.rs +++ b/lib/src/re/thompson/instr.rs @@ -151,16 +151,16 @@ pub enum Instr<'a> { ClassRanges(ClassRanges<'a>), /// Creates a new thread that starts at the current instruction pointer - /// + offset while the current thread continues at the next instruction. - /// The name comes from the fact that this instruction splits the execution - /// flow in two. + /// plus an offset, while the current thread continues at the next + /// instruction. The name comes from the fact that this instruction splits + /// the execution flow in two. SplitA(SplitId, Offset), /// Similar to SplitA, but the current thread continues at instruction - /// pointer + offset while the new thread continues at the next instruction. - /// This difference is important because the newly created thread has lower - /// priority than the existing one, and priority affects the greediness of - /// the regular expression. + /// pointer plus an offset while the new thread continues at the next + /// instruction. This difference is important because the newly created + /// thread has lower priority than the existing one, and priority affects + /// the greediness of the regular expression. SplitB(SplitId, Offset), /// Continues executing the code at N different locations. The current diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index 5700e6cba..5027d31e9 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -406,8 +406,8 @@ impl<'r> Scanner<'r> { /// /// 1) When the module does not produce any output on its own. /// 2) When you already know the output of the module for the upcoming file - /// to be scanned, and you prefer to reuse this data instead of generating - /// it again. + /// to be scanned, and you prefer to reuse this data instead of generating + /// it again. /// /// Case 1) applies to certain modules lacking a main function, thus /// incapable of producing any output on their own. For such modules, you diff --git a/lib/src/types/structure.rs b/lib/src/types/structure.rs index e5612c5b3..d9b40231d 100644 --- a/lib/src/types/structure.rs +++ b/lib/src/types/structure.rs @@ -551,8 +551,8 @@ impl Struct { /// } /// ``` /// - /// In this enum the value of `ITEM_0` is 0, and the value of `ITEM_1` is - /// 1. The tag number associated to each item determines its value. However, + /// In this enum the value of `ITEM_0` is 0 and the value of `ITEM_1` is 1. + /// The tag number associated to each item determines its value. However, /// this approach has one limitation, tag number are of type `i32` and /// therefore they are limited to the range `-2147483648,2147483647`. For /// larger values you need to use the second approach: From 205b29d05933e10d591b79a08bcff60ad2e6c86c Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 15 Aug 2024 00:37:59 +0200 Subject: [PATCH 3/9] refactor: migrate macros to syn v2 and do some improvements to errors and warnings. Errors and warnings now support more label types, like info, note and help. --- Cargo.lock | 80 ++-- lib/src/compiler/errors.rs | 107 +++--- lib/src/compiler/ir/ast2ir.rs | 142 +++---- lib/src/compiler/report.rs | 2 +- lib/src/compiler/tests/testdata/errors/46.out | 4 +- lib/src/compiler/warnings.rs | 29 +- macros/Cargo.toml | 6 +- macros/src/error.rs | 351 +++++++----------- macros/src/lib.rs | 82 ++-- macros/src/module_export.rs | 8 +- macros/src/module_main.rs | 8 +- macros/src/wasm_export.rs | 33 +- 12 files changed, 401 insertions(+), 451 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92aba6915..76fca17db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,7 +207,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "synstructure", ] @@ -219,7 +219,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -230,7 +230,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -606,7 +606,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1014,9 +1014,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -1024,27 +1024,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 1.0.109", + "strsim 0.11.1", + "syn 2.0.74", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.74", ] [[package]] @@ -1116,7 +1116,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1195,7 +1195,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1294,7 +1294,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2037,7 +2037,7 @@ checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2089,7 +2089,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.4", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2378,7 +2378,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2559,7 +2559,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2618,7 +2618,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2681,7 +2681,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2949,7 +2949,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2962,7 +2962,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3316,7 +3316,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3549,7 +3549,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3560,7 +3560,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3579,7 +3579,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3640,9 +3640,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -3657,7 +3657,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3786,7 +3786,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4036,7 +4036,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-shared", ] @@ -4058,7 +4058,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4228,7 +4228,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -4355,7 +4355,7 @@ checksum = "de5a9bc4f44ceeb168e9e8e3be4e0b4beb9095b468479663a9e24c667e36826f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4939,7 +4939,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.74", ] [[package]] @@ -5027,7 +5027,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5038,7 +5038,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5058,7 +5058,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] diff --git a/lib/src/compiler/errors.rs b/lib/src/compiler/errors.rs index 2f4652840..d9da19528 100644 --- a/lib/src/compiler/errors.rs +++ b/lib/src/compiler/errors.rs @@ -53,7 +53,7 @@ pub enum Error { #[non_exhaustive] pub enum CompileError { #[error("E001", "syntax error")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] SyntaxError { detailed_report: String, error_msg: String, @@ -61,7 +61,7 @@ pub enum CompileError { }, #[error("E002", "wrong type")] - #[label( + #[label_error( "expression should be {expected_types}, but is `{actual_type}`", expression_span )] @@ -73,8 +73,8 @@ pub enum CompileError { }, #[error("E003", "mismatching types")] - #[label("this expression is `{type1}`", type1_span)] - #[label("this expression is `{type2}`", type2_span)] + #[label_error("this expression is `{type1}`", type1_span)] + #[label_error("this expression is `{type2}`", type2_span)] MismatchingTypes { detailed_report: String, type1: String, @@ -84,7 +84,7 @@ pub enum CompileError { }, #[error("E004", "wrong arguments")] - #[label("wrong arguments in this call", args_span)] + #[label_error("wrong arguments in this call", args_span)] #[note(note)] WrongArguments { detailed_report: String, @@ -93,8 +93,8 @@ pub enum CompileError { }, #[error("E005", "assignment mismatch")] - #[label("this expects {expected_values} value(s)", error_span)] - #[label("this produces {actual_values} value(s)", iterable_span)] + #[label_error("this expects {expected_values} value(s)", error_span)] + #[label_error("this produces {actual_values} value(s)", iterable_span)] AssignmentMismatch { detailed_report: String, expected_values: u8, @@ -104,11 +104,14 @@ pub enum CompileError { }, #[error("E006", "unexpected negative number")] - #[label("this number can not be negative", span)] + #[label_error("this number can not be negative", span)] UnexpectedNegativeNumber { detailed_report: String, span: SourceRef }, #[error("E007", "number out of range")] - #[label("this number is out of the allowed range [{min}-{max}]", span)] + #[label_error( + "this number is out of the allowed range [{min}-{max}]", + span + )] NumberOutOfRange { detailed_report: String, min: i64, @@ -117,7 +120,7 @@ pub enum CompileError { }, #[error("E008", "unknown field or method `{identifier}`")] - #[label("this field or method doesn't exist", span)] + #[label_error("this field or method doesn't exist", span)] UnknownField { detailed_report: String, identifier: String, @@ -125,7 +128,7 @@ pub enum CompileError { }, #[error("E009", "unknown identifier `{identifier}`")] - #[label("this identifier has not been declared", span)] + #[label_error("this identifier has not been declared", span)] #[note(note)] UnknownIdentifier { detailed_report: String, @@ -135,7 +138,7 @@ pub enum CompileError { }, #[error("E010", "unknown module `{identifier}`")] - #[label("module `{identifier}` not found", span)] + #[label_error("module `{identifier}` not found", span)] UnknownModule { detailed_report: String, identifier: String, @@ -143,7 +146,7 @@ pub enum CompileError { }, #[error("E011", "invalid range")] - #[label("{error_msg}", span)] + #[label_error("{error_msg}", span)] InvalidRange { detailed_report: String, error_msg: String, @@ -151,12 +154,11 @@ pub enum CompileError { }, #[error("E012", "duplicate rule `{new_rule}`")] - #[label( + #[label_note( "`{new_rule}` declared here for the first time", - existing_rule_span, - style = "note" + existing_rule_span )] - #[label("duplicate declaration of `{new_rule}`", new_rule_span)] + #[label_error("duplicate declaration of `{new_rule}`", new_rule_span)] DuplicateRule { detailed_report: String, new_rule: String, @@ -165,7 +167,7 @@ pub enum CompileError { }, #[error("E013", "rule `{ident}` conflicts with an existing identifier")] - #[label( + #[label_error( "identifier already in use by a module or global variable", ident_span )] @@ -176,7 +178,7 @@ pub enum CompileError { }, #[error("E014", "invalid regular expression")] - #[label("{error}", span)] + #[label_error("{error}", span)] #[note(note)] InvalidRegexp { detailed_report: String, @@ -189,8 +191,8 @@ pub enum CompileError { "E015", "mixing greedy and non-greedy quantifiers in regular expression" )] - #[label("this is {quantifier1_greediness}", quantifier1_span)] - #[label("this is {quantifier2_greediness}", quantifier2_span)] + #[label_error("this is {quantifier1_greediness}", quantifier1_span)] + #[label_error("this is {quantifier2_greediness}", quantifier2_span)] MixedGreediness { detailed_report: String, quantifier1_greediness: String, @@ -200,7 +202,7 @@ pub enum CompileError { }, #[error("E016", "no matching patterns")] - #[label("there's no pattern in this set", span)] + #[label_error("there's no pattern in this set", span)] #[note(note)] EmptyPatternSet { detailed_report: String, @@ -209,20 +211,19 @@ pub enum CompileError { }, #[error("E017", "`entrypoint` is unsupported`")] - #[label("the `entrypoint` keyword is not supported anymore", span)] - #[note(note)] - EntrypointUnsupported { - detailed_report: String, - span: SourceRef, - note: Option, - }, + #[label_error("the `entrypoint` keyword is not supported anymore", span)] + #[label_help( + "use `pe.entry_point` or `elf.entry_point` or `macho.entry_point`", + span + )] + EntrypointUnsupported { detailed_report: String, span: SourceRef }, #[error("E018", "slow pattern")] - #[label("this pattern may slow down the scan", span)] + #[label_error("this pattern may slow down the scan", span)] SlowPattern { detailed_report: String, span: SourceRef }, #[error("E117", "invalid pattern modifier")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] InvalidModifier { detailed_report: String, error_msg: String, @@ -233,8 +234,8 @@ pub enum CompileError { "E019", "invalid modifier combination: `{modifier1}` `{modifier2}`" )] - #[label("`{modifier1}` modifier used here", modifier1_span)] - #[label("`{modifier2}` modifier used here", modifier2_span)] + #[label_error("`{modifier1}` modifier used here", modifier1_span)] + #[label_error("`{modifier2}` modifier used here", modifier2_span)] #[note(note)] InvalidModifierCombination { detailed_report: String, @@ -246,15 +247,18 @@ pub enum CompileError { }, #[error("E020", "duplicate pattern modifier")] - #[label("duplicate modifier", modifier_span)] + #[label_error("duplicate modifier", modifier_span)] DuplicateModifier { detailed_report: String, modifier_span: SourceRef }, #[error("E021", "duplicate tag `{tag}`")] - #[label("duplicate tag", tag_span)] + #[label_error("duplicate tag", tag_span)] DuplicateTag { detailed_report: String, tag: String, tag_span: SourceRef }, #[error("E022", "unused pattern `{pattern_ident}`")] - #[label("this pattern was not used in the condition", pattern_ident_span)] + #[label_error( + "this pattern was not used in the condition", + pattern_ident_span + )] UnusedPattern { detailed_report: String, pattern_ident: String, @@ -262,11 +266,13 @@ pub enum CompileError { }, #[error("E023", "duplicate pattern `{pattern_ident}`")] - #[label("duplicate declaration of `{pattern_ident}`", new_pattern_span)] - #[label( + #[label_error( + "duplicate declaration of `{pattern_ident}`", + new_pattern_span + )] + #[label_note( "`{pattern_ident}` declared here for the first time", - existing_pattern_span, - style = "note" + existing_pattern_span )] DuplicatePattern { detailed_report: String, @@ -276,7 +282,7 @@ pub enum CompileError { }, #[error("E024", "invalid pattern `{pattern_ident}`")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] #[note(note)] InvalidPattern { detailed_report: String, @@ -287,7 +293,7 @@ pub enum CompileError { }, #[error("E025", "unknown pattern `{pattern_ident}`")] - #[label( + #[label_error( "this pattern is not declared in the `strings` section", pattern_ident_span )] @@ -298,7 +304,7 @@ pub enum CompileError { }, #[error("E026", "invalid base64 alphabet")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] InvalidBase64Alphabet { detailed_report: String, error_msg: String, @@ -306,7 +312,7 @@ pub enum CompileError { }, #[error("E027", "invalid integer")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] InvalidInteger { detailed_report: String, error_msg: String, @@ -314,7 +320,7 @@ pub enum CompileError { }, #[error("E028", "invalid float")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] InvalidFloat { detailed_report: String, error_msg: String, @@ -322,7 +328,7 @@ pub enum CompileError { }, #[error("E029", "invalid escape sequence")] - #[label("{error_msg}", error_span)] + #[label_error("{error_msg}", error_span)] InvalidEscapeSequence { detailed_report: String, error_msg: String, @@ -330,7 +336,7 @@ pub enum CompileError { }, #[error("E030", "invalid regexp modifier `{modifier}`")] - #[label("invalid modifier", error_span)] + #[label_error("invalid modifier", error_span)] InvalidRegexpModifier { detailed_report: String, modifier: String, @@ -338,11 +344,14 @@ pub enum CompileError { }, #[error("E031", "unexpected escape sequence")] - #[label("escape sequences are not allowed in this string", error_span)] + #[label_error( + "escape sequences are not allowed in this string", + error_span + )] UnexpectedEscapeSequence { detailed_report: String, error_span: SourceRef }, #[error("E032", "invalid UTF-8")] - #[label("invalid UTF-8 character", error_span)] + #[label_error("invalid UTF-8 character", error_span)] InvalidUTF8 { detailed_report: String, error_span: SourceRef }, } diff --git a/lib/src/compiler/ir/ast2ir.rs b/lib/src/compiler/ir/ast2ir.rs index 4c0c564d1..73c080e62 100644 --- a/lib/src/compiler/ir/ast2ir.rs +++ b/lib/src/compiler/ir/ast2ir.rs @@ -377,7 +377,6 @@ pub(in crate::compiler) fn expr_from_ast( Err(Box::new(CompileError::entrypoint_unsupported( ctx.report_builder, span.into(), - Some("use `pe.entry_point`, `elf.entry_point` or `macho.entry_point`".to_string()), ))) } ast::Expr::Filesize { .. } => Ok(Expr::Filesize), @@ -390,26 +389,29 @@ pub(in crate::compiler) fn expr_from_ast( Ok(Expr::Const(TypeValue::const_bool_from(false))) } - ast::Expr::LiteralInteger(literal) => Ok(Expr::Const( - TypeValue::const_integer_from(literal.value))), + ast::Expr::LiteralInteger(literal) => { + Ok(Expr::Const(TypeValue::const_integer_from(literal.value))) + } - ast::Expr::LiteralFloat(literal) => Ok(Expr::Const( - TypeValue::const_float_from(literal.value))), + ast::Expr::LiteralFloat(literal) => { + Ok(Expr::Const(TypeValue::const_float_from(literal.value))) + } ast::Expr::LiteralString(literal) => Ok(Expr::Const( - TypeValue::const_string_from(literal.value.as_bytes()))), + TypeValue::const_string_from(literal.value.as_bytes()), + )), ast::Expr::Regexp(regexp) => { re::parser::Parser::new() .relaxed_re_syntax(ctx.relaxed_re_syntax) .parse(regexp.as_ref()) - .map_err(|err| { re_error_to_compile_error(ctx.report_builder, regexp, err) - })?; + .map_err(|err| { + re_error_to_compile_error(ctx.report_builder, regexp, err) + })?; Ok(Expr::Const(TypeValue::Regexp(Some(Regexp::new( - regexp.literal, - ))), - )) + regexp.literal, + ))))) } ast::Expr::Defined(expr) => defined_expr_from_ast(ctx, expr), @@ -455,35 +457,49 @@ pub(in crate::compiler) fn expr_from_ast( (TypeValue::Bool(_), TypeValue::Integer(Value::Const(0))) => { Some(( Expr::Not { operand: Box::new(lhs) }, - format!("not {}", ctx.report_builder.get_snippet(&lhs_span.into())) + format!( + "not {}", + ctx.report_builder.get_snippet(&lhs_span.into()) + ), )) } (TypeValue::Integer(Value::Const(0)), TypeValue::Bool(_)) => { Some(( Expr::Not { operand: Box::new(rhs) }, - format!("not {}", ctx.report_builder.get_snippet(&rhs_span.into())) + format!( + "not {}", + ctx.report_builder.get_snippet(&rhs_span.into()) + ), )) } (TypeValue::Bool(_), TypeValue::Integer(Value::Const(1))) => { - Some((lhs, ctx.report_builder.get_snippet(&lhs_span.into()))) + Some(( + lhs, + ctx.report_builder.get_snippet(&lhs_span.into()), + )) } (TypeValue::Integer(Value::Const(1)), TypeValue::Bool(_)) => { - Some((rhs, ctx.report_builder.get_snippet(&rhs_span.into()))) + Some(( + rhs, + ctx.report_builder.get_snippet(&rhs_span.into()), + )) } - _ => None + _ => None, }; if let Some((expr, msg)) = replacement { - ctx.warnings.add(|| Warning::boolean_integer_comparison( - ctx.report_builder, - span.into(), - msg, - )); + ctx.warnings.add(|| { + Warning::boolean_integer_comparison( + ctx.report_builder, + span.into(), + msg, + ) + }); Ok(expr) } else { eq_expr_from_ast(ctx, expr) } - }, + } ast::Expr::Ne(expr) => ne_expr_from_ast(ctx, expr), ast::Expr::Gt(expr) => gt_expr_from_ast(ctx, expr), ast::Expr::Ge(expr) => ge_expr_from_ast(ctx, expr), @@ -561,8 +577,7 @@ pub(in crate::compiler) fn expr_from_ast( ident.span().into(), // Add a note about the missing import statement if // the unknown identifier is a module name. - if BUILTIN_MODULES.contains_key(ident.name) - { + if BUILTIN_MODULES.contains_key(ident.name) { Some(format!( "there is a module named `{}`, but the `import \"{}\"` statement is missing", ident.name, @@ -578,7 +593,7 @@ pub(in crate::compiler) fn expr_from_ast( ident.name.to_string(), ident.span().into(), ))) - } + }; } let symbol = symbol.unwrap(); @@ -616,9 +631,10 @@ pub(in crate::compiler) fn expr_from_ast( symbol: ctx.symbol_table.lookup("$").unwrap(), anchor, }) - }, + } _ => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.identifier)?; pattern.mark_as_used(); @@ -628,10 +644,7 @@ pub(in crate::compiler) fn expr_from_ast( pattern.make_non_anchorable(); } - Ok(Expr::PatternMatch { - pattern: pattern_idx, - anchor, - }) + Ok(Expr::PatternMatch { pattern: pattern_idx, anchor }) } } } @@ -658,20 +671,18 @@ pub(in crate::compiler) fn expr_from_ast( }), // Cases where the identifier is not `#`. (_, Some(range)) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternCount { pattern: pattern_idx, range: Some(range_from_ast(ctx, range)?), }) } (_, None) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternCount { pattern: pattern_idx, range: None, @@ -706,10 +717,9 @@ pub(in crate::compiler) fn expr_from_ast( }), // Cases where the identifier is not `@`. (_, Some(index)) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternOffset { pattern: pattern_idx, index: Some(Box::new(integer_in_range_from_ast( @@ -720,10 +730,9 @@ pub(in crate::compiler) fn expr_from_ast( }) } (_, None) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternOffset { pattern: pattern_idx, index: None, @@ -758,10 +767,9 @@ pub(in crate::compiler) fn expr_from_ast( }), // Cases where the identifier is not `!`. (_, Some(index)) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternLength { pattern: pattern_idx, index: Some(Box::new(integer_in_range_from_ast( @@ -772,10 +780,9 @@ pub(in crate::compiler) fn expr_from_ast( }) } (_, None) => { - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.ident)?; - pattern - .make_non_anchorable() - .mark_as_used(); + let (pattern_idx, pattern) = + ctx.get_pattern_mut(&p.ident)?; + pattern.make_non_anchorable().mark_as_used(); Ok(Expr::PatternLength { pattern: pattern_idx, index: None, @@ -818,12 +825,11 @@ pub(in crate::compiler) fn expr_from_ast( // with the type of the map's keys. if key_ty != ty { return Err(Box::new(CompileError::wrong_type( - ctx.report_builder, - format!("`{}`", key_ty), - ty.to_string(), - expr.index.span().into(), - ), - )); + ctx.report_builder, + format!("`{}`", key_ty), + ty.to_string(), + expr.index.span().into(), + ))); } Ok(Expr::Lookup(Box::new(Lookup { @@ -832,14 +838,12 @@ pub(in crate::compiler) fn expr_from_ast( index, }))) } - type_value => { - Err(Box::new(CompileError::wrong_type( - ctx.report_builder, - format!("`{}` or `{}`", Type::Array, Type::Map), - type_value.ty().to_string(), - expr.primary.span().into(), - ))) - } + type_value => Err(Box::new(CompileError::wrong_type( + ctx.report_builder, + format!("`{}` or `{}`", Type::Array, Type::Map), + type_value.ty().to_string(), + expr.primary.span().into(), + ))), } } } diff --git a/lib/src/compiler/report.rs b/lib/src/compiler/report.rs index 7d1c7817a..167d0a785 100644 --- a/lib/src/compiler/report.rs +++ b/lib/src/compiler/report.rs @@ -22,7 +22,7 @@ pub struct SourceId(u32); /// /// The [`SourceId`] is optional, if it is [`None`] it means that the [`Span`] /// is relative to the current source file. -#[derive(PartialEq, Clone, Eq, Default)] +#[derive(PartialEq, Debug, Clone, Eq, Default)] pub struct SourceRef { source_id: Option, span: Span, diff --git a/lib/src/compiler/tests/testdata/errors/46.out b/lib/src/compiler/tests/testdata/errors/46.out index b17ca4f3a..e07c0a140 100644 --- a/lib/src/compiler/tests/testdata/errors/46.out +++ b/lib/src/compiler/tests/testdata/errors/46.out @@ -3,5 +3,5 @@ error[E017]: `entrypoint` is unsupported` | 3 | entrypoint == 0x1000 | ^^^^^^^^^^ the `entrypoint` keyword is not supported anymore - | - = note: use `pe.entry_point`, `elf.entry_point` or `macho.entry_point` \ No newline at end of file + | ---------- help: use `pe.entry_point` or `elf.entry_point` or `macho.entry_point` + | \ No newline at end of file diff --git a/lib/src/compiler/warnings.rs b/lib/src/compiler/warnings.rs index 5a0986845..618c3852e 100644 --- a/lib/src/compiler/warnings.rs +++ b/lib/src/compiler/warnings.rs @@ -14,7 +14,7 @@ use crate::compiler::report::{ReportBuilder, SourceRef}; #[derive(DeriveError)] pub enum Warning { #[warning("consecutive_jumps", "consecutive jumps in hex pattern `{pattern_ident}`")] - #[label("these consecutive jumps will be treated as {coalesced_jump}", jumps_span)] + #[label_warn("these consecutive jumps will be treated as {coalesced_jump}", jumps_span)] ConsecutiveJumps { detailed_report: String, pattern_ident: String, @@ -23,8 +23,8 @@ pub enum Warning { }, #[warning("unsatisfiable_expr", "potentially unsatisfiable expression")] - #[label("this implies that multiple patterns must match", quantifier_span)] - #[label("but they must match at the same offset", at_span)] + #[label_warn("this implies that multiple patterns must match", quantifier_span)] + #[label_warn("but they must match at the same offset", at_span)] PotentiallyUnsatisfiableExpression { detailed_report: String, quantifier_span: SourceRef, @@ -32,7 +32,7 @@ pub enum Warning { }, #[warning("invariant_expr", "invariant boolean expression")] - #[label("this expression is always {value}", span)] + #[label_warn("this expression is always {value}", span)] #[note(note)] InvariantBooleanExpression { detailed_report: String, @@ -42,7 +42,7 @@ pub enum Warning { }, #[warning("non_bool_expr", "non-boolean expression used as boolean")] - #[label("this expression is `{expression_type}` but is being used as `bool`", span)] + #[label_warn("this expression is `{expression_type}` but is being used as `bool`", span)] #[note(note)] NonBooleanAsBoolean { detailed_report: String, @@ -52,7 +52,7 @@ pub enum Warning { }, #[warning("bool_int_comparison", "comparison between boolean and integer")] - #[label("this comparison can be replaced with: `{replacement}`", span)] + #[label_warn("this comparison can be replaced with: `{replacement}`", span)] BooleanIntegerComparison { detailed_report: String, span: SourceRef, @@ -60,14 +60,13 @@ pub enum Warning { }, #[warning("duplicate_import", "duplicate import statement")] - #[label( + #[label_warn( "duplicate import", new_import_span )] - #[label( + #[label_note( "`{module_name}` imported here for the first time", - existing_import_span, - style="note" + existing_import_span )] DuplicateImport { detailed_report: String, @@ -77,8 +76,8 @@ pub enum Warning { }, #[warning("redundant_modifier", "redundant case-insensitive modifier")] - #[label("the `i` suffix indicates that the pattern is case-insensitive", i_span)] - #[label("the `nocase` modifier does the same", nocase_span)] + #[label_warn("the `i` suffix indicates that the pattern is case-insensitive", i_span)] + #[label_warn("the `nocase` modifier does the same", nocase_span)] RedundantCaseModifier { detailed_report: String, nocase_span: SourceRef, @@ -86,14 +85,14 @@ pub enum Warning { }, #[warning("slow_pattern", "slow pattern")] - #[label("this pattern may slow down the scan", span)] + #[label_warn("this pattern may slow down the scan", span)] SlowPattern { detailed_report: String, span: SourceRef, }, #[warning("unsupported_module", "module `{module_name}` is not supported")] - #[label("module `{module_name}` used here", span)] + #[label_warn("module `{module_name}` used here", span)] #[note(note)] IgnoredModule { detailed_report: String, @@ -106,7 +105,7 @@ pub enum Warning { "ignored_rule", "rule `{ignored_rule}` will be ignored due to an indirect dependency on module `{module_name}`" )] - #[label("this other rule depends on module `{module_name}`, which is unsupported", span)] + #[label_warn("this other rule depends on module `{module_name}`, which is unsupported", span)] IgnoredRule { detailed_report: String, ignored_rule: String, diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 2a1dde4da..eb826d51f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,8 +12,8 @@ rust-version.workspace = true proc-macro = true [dependencies] -darling = "0.14.2" -syn = { version = "1.0", features = ["full", "derive", "parsing", "visit"] } +darling = "0.20.10" +syn = { version = "2.0.74", features = ["full", "derive", "parsing", "visit"] } quote = "1.0" -proc-macro2 = "1.0.43" +proc-macro2 = "1.0.86" convert_case = "0.6.0" diff --git a/macros/src/error.rs b/macros/src/error.rs index 85dc4ee24..562cbb29a 100644 --- a/macros/src/error.rs +++ b/macros/src/error.rs @@ -2,25 +2,27 @@ extern crate proc_macro; use convert_case::{Case, Casing}; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::{quote, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::Comma; use syn::{ - Attribute, DataEnum, DeriveInput, Ident, Lit, Meta, NestedMeta, Variant, + Attribute, Data, DataEnum, DeriveInput, Error, Expr, Fields, Ident, + LitStr, Result, Variant, }; -pub(crate) fn impl_error_macro( - input: DeriveInput, -) -> syn::Result { +pub(crate) fn impl_error_macro(input: DeriveInput) -> Result { let name = &input.ident; let (codes, variants, funcs) = match &input.data { - syn::Data::Struct(_) | syn::Data::Union(_) => { - return Err(syn::Error::new( + Data::Struct(_) | Data::Union(_) => { + return Err(Error::new( name.span(), "macros macro Error can be used with only with enum types" .to_string(), )) } - syn::Data::Enum(data_enum) => impl_enum_error_macro(data_enum)?, + Data::Enum(data_enum) => impl_enum_error_macro(data_enum)?, }; let (impl_generics, ty_generics, where_clause) = @@ -87,7 +89,7 @@ pub(crate) fn impl_error_macro( fn impl_enum_error_macro( data_enum: &DataEnum, -) -> syn::Result<(Vec, Vec<&Ident>, Vec)> { +) -> Result<(Vec, Vec<&Ident>, Vec)> { // Generate a function for each variant in the enum labelled // with #[error(...)] or #[warning(...)]. let mut funcs = Vec::new(); @@ -97,93 +99,82 @@ fn impl_enum_error_macro( for variant in &data_enum.variants { // ...look for #[error(...)] or #[warning(...)] attribute. for attr in &variant.attrs { - if let Some((kind, code, description)) = parse_attr(attr)? { + if let Some((kind, error_attr)) = parse_attr(attr)? { variants.push(&variant.ident); funcs.push(gen_build_func( kind, - &code, - &description, + &error_attr.code, + &error_attr.description, variant, )?); - codes.push(code); + codes.push(error_attr.code); } } } Ok((codes, variants, funcs)) } +#[derive(Debug)] +struct ErrorArgs { + code: LitStr, + description: LitStr, +} + +impl Parse for ErrorArgs { + fn parse(input: ParseStream) -> Result { + let mut args = Punctuated::::parse_terminated(input)?; + + if args.len() != 2 { + return Err( + Error::new_spanned( + args, + "#[error(...)] must have exactly 2 arguments: code and description".to_string(), + )); + } + + let description = args.pop().unwrap().into_value(); + let code = args.pop().unwrap().into_value(); + + Ok(ErrorArgs { code, description }) + } +} + // Checks if an attribute is #[error(...)] or #[warning(...)] and returns its // arguments. Otherwise, it returns None. -fn parse_attr( - attr: &Attribute, -) -> syn::Result> { - let meta = attr.parse_meta()?; - - let kind = if meta.path().is_ident("error") { +fn parse_attr(attr: &Attribute) -> Result> { + let kind = if attr.path().is_ident("error") { "error" - } else if meta.path().is_ident("warning") { + } else if attr.path().is_ident("warning") { "warning" } else { return Ok(None); }; - let mut attr_args = match meta { - // `error` and `warning` must be list-style attributes, as in - // #[error(...)] - Meta::List(list) => list.nested, - // any other syntax, like #[error] or #[error = "..."] is not - // supported. - _ => { - return Err(syn::Error::new_spanned( - meta, - format!( - "expected a list-style attribute (e.g. #[{}(...)])", - kind - ), - )) - } - }; - - // There must be exactly 2 arguments, the first one is the error/warning - // code, and the second one is its description. - if attr_args.len() != 2 { - return Err( - syn::Error::new_spanned( - attr, - format!( - "#[{}(...)] must have exactly 2 arguments: code and description", - kind - ), - )); - } - - // Arguments are popped in reverse order. - let description = attr_args.pop().unwrap().into_value(); - let code = attr_args.pop().unwrap().into_value(); + let args = attr.meta.require_list()?.parse_args::()?; - Ok(Some((kind, code, description))) + Ok(Some((kind, args))) } // Given an error or warning variant, generates the function that builds // an instance of this error or warning. fn gen_build_func( kind: &str, - code: &NestedMeta, - description: &NestedMeta, + code: &LitStr, + description: &LitStr, variant: &Variant, -) -> syn::Result { +) -> Result { match &variant.fields { - syn::Fields::Named(fields) => { + Fields::Named(fields) => { // Each error variant has one or more labels (e.g. #[label(...)]), // get the labels for this variant. - let labels = get_labels(kind, variant)?; + let labels = get_labels(variant)?; // The variant can also have a note (e.g. #[note(...)]). let note = get_note(variant)?; // The main label is the first label in the tuple. let main_label = &labels.first().ok_or_else(|| { - syn::Error::new_spanned( + Error::new_spanned( variant, "#[error(...)] must be accompanied by at least one instance of #[label(...)}", )})?; @@ -245,8 +236,8 @@ fn gen_build_func( } )) } - syn::Fields::Unnamed(_) | syn::Fields::Unit => { - Err(syn::Error::new_spanned( + Fields::Unnamed(_) | Fields::Unit => { + Err(Error::new_spanned( variant, format!( "{} not a struct variant, #[error(...)] can be used only with struct variants", @@ -257,183 +248,105 @@ fn gen_build_func( } } -fn get_note(variant: &Variant) -> syn::Result { - // Iterate over the attributes of this variant, looking for #[note(...)] - for attr in &variant.attrs { - let meta = attr.parse_meta()?; - // This attribute is not #[note(...)], skip it and continue iterating. - if !meta.path().is_ident("note") { - continue; - } - // This is a note attribute, let's check that it has a list of - // arguments... - let attr_args = match meta { - Meta::List(list) => list, - _ => { - return Err(syn::Error::new_spanned( - meta, - "expected a list-style attribute (e.g. #[note(...)])", - )) - } +fn get_note(variant: &Variant) -> Result { + // Try to find a #[note(...)] attribute. + let note_attr = + match variant.attrs.iter().find(|attr| attr.path().is_ident("note")) { + Some(attr) => attr, + None => return Ok(quote!(None)), }; - // It should have exactly one argument, which is the field that - // contains the note. - if attr_args.nested.len() != 1 { - return Err(syn::Error::new_spanned( - attr_args.nested, - "#[note(...)] must receive exactly one argument", - )); - } - // Get the argument of #[note(...)], which should be the field that - // contains the note. - let note_field = &attr_args.nested.first().unwrap(); + // Let's check that it has a list of arguments... + let args = note_attr.meta.require_list()?; - // Make sure that it's an identifier. - let note_field = match note_field { - NestedMeta::Meta(Meta::Path(path)) => path.get_ident(), - _ => None, - }; + // The arguments are a comma-separated list of expressions. + let args = + args.parse_args_with(Punctuated::::parse_terminated)?; - let note_field = note_field.ok_or_else(|| { - syn::Error::new_spanned( - note_field, - format!( - "the argument for #[note(...)] must be a field in `{}`", - variant.ident - ), - ) - })?; - - return Ok(note_field.to_token_stream()); + // It should have exactly one argument, which is the field that + // contains the note. + if args.len() != 1 { + return Err(Error::new_spanned( + args, + "#[note(...)] must receive exactly one argument", + )); } - Ok(quote!(None)) + let node_field = &args[0]; + + // Make sure that label_span_field it's an identifier. + let node_field = match node_field { + Expr::Path(expr) => expr.path.get_ident(), + _ => None, + }; + + let node_field = node_field.ok_or_else(|| { + Error::new_spanned( + node_field, + format!( + "the argument for #[note(...)] must be a field in `{}`", + variant.ident + ), + ) + })?; + + Ok(quote!(#node_field)) } -fn get_labels( - kind: &str, - variant: &Variant, -) -> syn::Result> { +fn get_labels(variant: &Variant) -> Result> { let mut labels = Vec::new(); - // Iterate over the attributes of this variant, looking for #[label(...)] - for attr in &variant.attrs { - let meta = attr.parse_meta()?; - // This attribute is not #[label(...)], skip it and continue iterating. - if !meta.path().is_ident("label") { - continue; - } - // This is a label attribute, let's check that it has a list of - // arguments... - let attr_args = match meta { - Meta::List(list) => list, - _ => { - return Err(syn::Error::new_spanned( - meta, - "expected a list-style attribute (e.g. #[label(...)])", - )) - } - }; - // It should have at least two arguments, the first argument should be - // the label's text, which can contain placeholders that are filled - // with the arguments that follows. The last argument should be the + // Iterate over the #[label_xxxx(...)] attributes. + for attr in variant.attrs.iter().filter(|attr| { + attr.path().is_ident("label_error") + || attr.path().is_ident("label_warn") + || attr.path().is_ident("label_info") + || attr.path().is_ident("label_note") + || attr.path().is_ident("label_help") + }) { + // Check that the attribute has a list of arguments. + let args = attr.meta.require_list()?; + + // The arguments are a comma-separated list of expressions. + let args = + args.parse_args_with(Punctuated::::parse_terminated)?; + + // It should have two arguments, the first argument should be the + // label's format string, and the second argument is the name of the // field that contains the span for the label. - if attr_args.nested.len() < 2 { - return Err(syn::Error::new_spanned( - attr_args.nested, - "#[label(...)] must receive at least two arguments", + if args.len() != 2 { + return Err(Error::new_spanned( + args, + "#[label_xxxx(...)] must receive two arguments", )); } - let mut args = attr_args.nested.pairs(); - - // The default label style depends on the type of report. It's red - // for errors and yellow for warnings. - let mut level = match kind { - "error" => quote!(Level::Error), - "warning" => quote!(Level::Warning), - _ => unreachable!(), - }; + let label_fmt = &args[0]; + let label_span_field = &args[1]; - // Take the last argument, which should be either the style or the - // name of the field containing the span for the label. - let last_arg = args.next_back().unwrap(); - - // If the last argument is a named value (e.g. foo = "bar") it can't be - // the name of the span field, so it should be the style. - let label_span_field = - if let NestedMeta::Meta(Meta::NameValue(value)) = last_arg.value() - { - // The argument is a named value, but return an error if the - // name is not "style". - if !value.path.is_ident("style") { - return Err(syn::Error::new_spanned( - value, - format!( - "unknown argument {}", - value.path.get_ident().unwrap() - ), - )); - } - // Make sure that the style is a literal string. - let style_name = match &value.lit { - Lit::Str(l) => l.value(), - _ => { - return Err(syn::Error::new_spanned( - value, - "argument style must be a string literal", - )); - } - }; - - // Override the label style with the one specified as an - // argument. (e.g. #[label(..., style="