From 85c3dd7037abe575eb1ccc77429b92bed9812f7a Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Tue, 2 Nov 2021 01:29:41 -0400 Subject: [PATCH 1/2] Fixed and added more tests --- macros/src/lib.rs | 8 ++--- shared/error.rs | 3 ++ shared/parser.rs | 22 +++++++++--- src/parser.rs | 4 +++ tests/ui/compile_fail/invalid_parse.rs | 7 +++- tests/ui/compile_fail/invalid_parse.stderr | 39 +++++++++++++++++----- 6 files changed, 62 insertions(+), 21 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index e7d68a01..86ef0780 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -43,15 +43,11 @@ pub fn parse_lit(input: TokenStream) -> TokenStream { s.subspan(index + 1..=index + 1).unwrap() } Error::UuidParse(error::Error( - error::ErrorKind::InvalidGroupLength { found, group, .. }, + error::ErrorKind::InvalidGroupLength { found, index, .. }, )) => { - let start = - parser::GROUP_LENS.iter().take(group).sum::() - + group - + 1; let mut s = proc_macro2::Literal::string(""); s.set_span(ts.span()); - s.subspan(start..start + found).unwrap() + s.subspan(index..index + found).unwrap() } _ => ts.span(), }; diff --git a/shared/error.rs b/shared/error.rs index 63010fcc..b2e0efa6 100644 --- a/shared/error.rs +++ b/shared/error.rs @@ -42,6 +42,8 @@ pub(crate) enum ErrorKind { found: usize, /// The segment with invalid length. group: usize, + /// The index of where the group starts + index: usize, }, /// Invalid length of the [`Uuid`] string. /// @@ -126,6 +128,7 @@ impl fmt::Display for Error { ref expected, found, group, + .. } => write!( f, "expected {}, found {} in group {}", diff --git a/shared/parser.rs b/shared/parser.rs index adc5cb84..8cc0d284 100644 --- a/shared/parser.rs +++ b/shared/parser.rs @@ -26,19 +26,24 @@ fn len_matches_any(len: usize, crits: &[usize]) -> bool { const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; // Length of each hyphenated group in hex digits. -pub(super) const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; +pub const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; + +const URN_PREFIX: &str = "urn:uuid:"; pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { // Ensure length is valid for any of the supported formats let len = input.len(); + let mut start = 0; // Check for a URN prefixed UUID - if len == 45 && input.starts_with("urn:uuid:") { - input = &input[9..]; + if len == 45 && input.starts_with(URN_PREFIX) { + input = &input[URN_PREFIX.len()..]; + start += URN_PREFIX.len(); } // Check for a Microsoft GUID wrapped in {} else if len == 38 && input.starts_with("{") && input.ends_with("}") { input = &input[1..input.len() - 1]; + start += 1; } // In other cases, check for a simple or hyphenated UUID else if !len_matches_any(len, &[36, 32]) { @@ -90,10 +95,13 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { digit }; + let index = + ACC_GROUP_LENS[group - 1] + group + 1 + start; return Err(ErrorKind::InvalidGroupLength { expected: ExpectedLength::Exact(GROUP_LENS[group]), found: found as usize, group, + index, } .into()); } @@ -106,7 +114,7 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { return Err(ErrorKind::InvalidCharacter { expected: "0123456789abcdefABCDEF-", found: input[i_char..].chars().next().unwrap(), - index: i_char, + index: i_char + start, urn: UrnPrefix::Optional, } .into()); @@ -127,10 +135,12 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { digit }; + let index = ACC_GROUP_LENS[group - 1] + group + 1 + start; return Err(ErrorKind::InvalidGroupLength { expected: ExpectedLength::Exact(GROUP_LENS[group]), found: found as usize, group, + index, } .into()); } @@ -138,7 +148,7 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { return Err(ErrorKind::InvalidCharacter { expected: "0123456789abcdefABCDEF-", found: input[i_char..].chars().next().unwrap(), - index: i_char, + index: i_char + start, urn: UrnPrefix::Optional, } .into()); @@ -151,10 +161,12 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { // Now check the last group. if ACC_GROUP_LENS[4] as u8 != digit { + let index = ACC_GROUP_LENS[group - 1] + group + 1 + start; return Err(ErrorKind::InvalidGroupLength { expected: ExpectedLength::Exact(GROUP_LENS[4]), found: (digit as usize - ACC_GROUP_LENS[3]), group, + index, } .into()); } diff --git a/src/parser.rs b/src/parser.rs index 28600635..46a7912f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -221,6 +221,7 @@ mod tests { expected: ExpectedLength::Exact(4), found: 3, group: 1, + index: 10, })) ); // (group, found, expecting) @@ -231,6 +232,7 @@ mod tests { expected: ExpectedLength::Exact(12), found: 8, group: 4, + index: 25, })) ); @@ -307,6 +309,7 @@ mod tests { expected: ExpectedLength::Exact(8), found: 6, group: 0, + index: 1, })) ); assert_eq!( @@ -315,6 +318,7 @@ mod tests { expected: ExpectedLength::Exact(4), found: 5, group: 3, + index: 20, })) ); } diff --git a/tests/ui/compile_fail/invalid_parse.rs b/tests/ui/compile_fail/invalid_parse.rs index 57beb462..27fcb932 100644 --- a/tests/ui/compile_fail/invalid_parse.rs +++ b/tests/ui/compile_fail/invalid_parse.rs @@ -12,7 +12,7 @@ const _: Uuid = uuid!("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4"); const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c88"); const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8"); -const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8"); +const _: Uuid = uuid!("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"); // Test error reporting const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c"); @@ -25,5 +25,10 @@ const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); +const _: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); +const _: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B2cBF-32BF39FA1E4"); +const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B0a75-32BF39FA1E4}"); + +const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B6BF-329Bz39FA1E4}"); fn main() {} diff --git a/tests/ui/compile_fail/invalid_parse.stderr b/tests/ui/compile_fail/invalid_parse.stderr index 44882d3f..6eff4d40 100644 --- a/tests/ui/compile_fail/invalid_parse.stderr +++ b/tests/ui/compile_fail/invalid_parse.stderr @@ -70,12 +70,6 @@ error: invalid length: expected one of [36, 32], found 33 14 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found % at 15 - --> tests/ui/compile_fail/invalid_parse.rs:15:39 - | -15 | const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8"); - | ^ - error: invalid length: expected one of [36, 32], found 31 --> tests/ui/compile_fail/invalid_parse.rs:18:23 | @@ -88,11 +82,14 @@ error: invalid character: expected an optional prefix of `urn:uuid:` followed by 19 | const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd"); | ^ -error: invalid group length: expected 8, found 6 in group 0 - --> tests/ui/compile_fail/invalid_parse.rs:20:24 +error: proc macro panicked + --> tests/ui/compile_fail/invalid_parse.rs:20:17 | 20 | const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: attempt to subtract with overflow + = note: this error originates in the macro `uuid` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid group length: expected 4, found 5 in group 3 --> tests/ui/compile_fail/invalid_parse.rs:21:43 @@ -123,3 +120,27 @@ error: invalid number of groups: expected one of [1, 5], found 4 | 27 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found G at 29 + --> tests/ui/compile_fail/invalid_parse.rs:28:53 + | +28 | const _: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); + | ^ + +error: invalid group length: expected 4, found 5 in group 3 + --> tests/ui/compile_fail/invalid_parse.rs:29:52 + | +29 | const _: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B2cBF-32BF39FA1E4"); + | ^^^^^ + +error: invalid group length: expected 4, found 5 in group 3 + --> tests/ui/compile_fail/invalid_parse.rs:30:44 + | +30 | const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B0a75-32BF39FA1E4}"); + | ^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found z at 29 + --> tests/ui/compile_fail/invalid_parse.rs:32:53 + | +32 | const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B6BF-329Bz39FA1E4}"); + | ^ From 3fc629f59db29c716084b4c6490724d188bc7d03 Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Tue, 2 Nov 2021 12:11:20 -0400 Subject: [PATCH 2/2] Fixed macro panics --- macros/src/lib.rs | 12 ++- shared/error.rs | 33 +++++++ shared/parser.rs | 101 +++++++++------------ src/parser.rs | 12 ++- tests/ui/compile_fail/invalid_parse.rs | 8 ++ tests/ui/compile_fail/invalid_parse.stderr | 33 +++++-- 6 files changed, 130 insertions(+), 69 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 86ef0780..a3d3c532 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -36,11 +36,19 @@ pub fn parse_lit(input: TokenStream) -> TokenStream { let ts = TokenStream2::from(input); let span = match e { Error::UuidParse(error::Error( - error::ErrorKind::InvalidCharacter { index, .. }, + error::ErrorKind::InvalidCharacter { found, index, .. }, )) => { + // Hack to find the byte width of the char + // so we can set the span accordingly. + let mut bytes = found as u32; + let mut width = 0; + while bytes != 0 { + bytes >>= 4; + width += 1; + } let mut s = proc_macro2::Literal::string(""); s.set_span(ts.span()); - s.subspan(index + 1..=index + 1).unwrap() + s.subspan(index + 1..index + width).unwrap() } Error::UuidParse(error::Error( error::ErrorKind::InvalidGroupLength { found, index, .. }, diff --git a/shared/error.rs b/shared/error.rs index b2e0efa6..765c73f3 100644 --- a/shared/error.rs +++ b/shared/error.rs @@ -81,6 +81,39 @@ impl fmt::Display for ExpectedLength { } } +impl Error { + pub(crate) fn character(found: char, index: usize, offset: usize) -> Self { + Error(ErrorKind::InvalidCharacter { + expected: "0123456789abcdefABCDEF-", + found, + index: index + offset, + urn: UrnPrefix::Optional, + }) + } + + pub(crate) fn group_count(expected: ExpectedLength, found: usize) -> Self { + Error(ErrorKind::InvalidGroupCount { expected, found }) + } + + pub(crate) fn group_length( + expected: ExpectedLength, + found: usize, + group: usize, + offset: usize, + ) -> Self { + Error(ErrorKind::InvalidGroupLength { + expected, + found, + group, + index: [1, 10, 15, 20, 25][group] + offset, + }) + } + + pub(crate) fn length(expected: ExpectedLength, found: usize) -> Self { + Error(ErrorKind::InvalidLength { expected, found }) + } +} + impl From for Error { fn from(kind: ErrorKind) -> Self { Error(kind) diff --git a/shared/parser.rs b/shared/parser.rs index 8cc0d284..e42a5405 100644 --- a/shared/parser.rs +++ b/shared/parser.rs @@ -36,22 +36,24 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { let mut start = 0; // Check for a URN prefixed UUID - if len == 45 && input.starts_with(URN_PREFIX) { - input = &input[URN_PREFIX.len()..]; - start += URN_PREFIX.len(); + if len == 45 { + if let Some(stripped) = input.strip_prefix(URN_PREFIX) { + input = stripped; + start = URN_PREFIX.len(); + } } // Check for a Microsoft GUID wrapped in {} - else if len == 38 && input.starts_with("{") && input.ends_with("}") { - input = &input[1..input.len() - 1]; - start += 1; + else if len == 38 { + if let Some(stripped) = + input.strip_prefix('{').and_then(|s| s.strip_suffix('}')) + { + input = stripped; + start = 1; + } } // In other cases, check for a simple or hyphenated UUID else if !len_matches_any(len, &[36, 32]) { - return Err(ErrorKind::InvalidLength { - expected: ExpectedLength::Any(&[36, 32]), - found: len, - } - .into()); + return Err(Error::length(ExpectedLength::Any(&[36, 32]), len)); } // `digit` counts only hexadecimal digits, `i_char` counts all chars. @@ -60,21 +62,17 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { let mut acc = 0; let mut buffer = [0u8; 16]; - for (i_char, chr) in input.bytes().enumerate() { + for (i_char, character) in input.char_indices() { + let chr = character as u8; if digit as usize >= 32 && group != 4 { if group == 0 { - return Err(ErrorKind::InvalidLength { - expected: ExpectedLength::Any(&[36, 32]), - found: len, - } - .into()); + return Err(Error::length(ExpectedLength::Any(&[36, 32]), len)); } - return Err(ErrorKind::InvalidGroupCount { - expected: ExpectedLength::Any(&[1, 5]), - found: group + 1, - } - .into()); + return Err(Error::group_count( + ExpectedLength::Any(&[1, 5]), + group + 1, + )); } if digit % 2 == 0 { @@ -90,20 +88,17 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { // Calculate how many digits this group consists of // in the input. let found = if group > 0 { - digit - ACC_GROUP_LENS[group - 1] as u8 + digit as usize - ACC_GROUP_LENS[group - 1] } else { - digit + digit as usize }; - let index = - ACC_GROUP_LENS[group - 1] + group + 1 + start; - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact(GROUP_LENS[group]), - found: found as usize, + return Err(Error::group_length( + ExpectedLength::Exact(GROUP_LENS[group]), + found, group, - index, - } - .into()); + start, + )); } // Next group, decrement digit, it is incremented again // at the bottom. @@ -111,13 +106,7 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { digit -= 1; } _ => { - return Err(ErrorKind::InvalidCharacter { - expected: "0123456789abcdefABCDEF-", - found: input[i_char..].chars().next().unwrap(), - index: i_char + start, - urn: UrnPrefix::Optional, - } - .into()); + return Err(Error::character(character, i_char, start)); } } } else { @@ -135,23 +124,17 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { digit }; - let index = ACC_GROUP_LENS[group - 1] + group + 1 + start; - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact(GROUP_LENS[group]), - found: found as usize, + return Err(Error::group_length( + ExpectedLength::Exact(GROUP_LENS[group]), + found as usize, group, - index, - } - .into()); + start, + )); } _ => { - return Err(ErrorKind::InvalidCharacter { - expected: "0123456789abcdefABCDEF-", - found: input[i_char..].chars().next().unwrap(), - index: i_char + start, - urn: UrnPrefix::Optional, - } - .into()); + // let found = input[i_char..].chars().next().unwrap(); + // let found = char::from(chr); + return Err(Error::character(character, i_char, start)); } } buffer[(digit / 2) as usize] = acc; @@ -161,14 +144,12 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { // Now check the last group. if ACC_GROUP_LENS[4] as u8 != digit { - let index = ACC_GROUP_LENS[group - 1] + group + 1 + start; - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact(GROUP_LENS[4]), - found: (digit as usize - ACC_GROUP_LENS[3]), + return Err(Error::group_length( + ExpectedLength::Exact(GROUP_LENS[4]), + digit as usize - ACC_GROUP_LENS[3], group, - index, - } - .into()); + start, + )); } Ok(buffer) diff --git a/src/parser.rs b/src/parser.rs index 46a7912f..4abf35a4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -209,9 +209,19 @@ mod tests { assert_eq!( Uuid::parse_str("{F9168C5E-CEB2-4faa9B6BFF329BF39FA1E41"), + Err(Error(ErrorKind::InvalidCharacter { + expected: EXPECTED_CHARS, + found: '{', + index: 0, + urn: UrnPrefix::Optional, + })) + ); + + assert_eq!( + Uuid::parse_str("{F9168C5E-CEB2-4faa9B6BFF329BF39FA1E41}"), Err(Error(ErrorKind::InvalidLength { expected: ExpectedLength::Any(&[36, 32]), - found: 38 + found: 39 })) ); diff --git a/tests/ui/compile_fail/invalid_parse.rs b/tests/ui/compile_fail/invalid_parse.rs index 27fcb932..a8c58a3e 100644 --- a/tests/ui/compile_fail/invalid_parse.rs +++ b/tests/ui/compile_fail/invalid_parse.rs @@ -31,4 +31,12 @@ const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B0a75-32BF39FA1E4}"); const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B6BF-329Bz39FA1E4}"); +// group 0 has invalid length +const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); + +const _: Uuid = uuid!("504410岡林aab1426f9247bb680e5fe0c8"); +const _: Uuid = uuid!("504410😎👍aab1426f9247bb680e5fe0c8"); + +const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-👍5-32BF39FA1E4}"); + fn main() {} diff --git a/tests/ui/compile_fail/invalid_parse.stderr b/tests/ui/compile_fail/invalid_parse.stderr index 6eff4d40..ac05c676 100644 --- a/tests/ui/compile_fail/invalid_parse.stderr +++ b/tests/ui/compile_fail/invalid_parse.stderr @@ -82,14 +82,11 @@ error: invalid character: expected an optional prefix of `urn:uuid:` followed by 19 | const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd"); | ^ -error: proc macro panicked - --> tests/ui/compile_fail/invalid_parse.rs:20:17 +error: invalid group length: expected 8, found 6 in group 0 + --> tests/ui/compile_fail/invalid_parse.rs:20:24 | 20 | const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: message: attempt to subtract with overflow - = note: this error originates in the macro `uuid` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^ error: invalid group length: expected 4, found 5 in group 3 --> tests/ui/compile_fail/invalid_parse.rs:21:43 @@ -144,3 +141,27 @@ error: invalid character: expected an optional prefix of `urn:uuid:` followed by | 32 | const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-B6BF-329Bz39FA1E4}"); | ^ + +error: invalid group length: expected 8, found 6 in group 0 + --> tests/ui/compile_fail/invalid_parse.rs:35:24 + | +35 | const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); + | ^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found 岡 at 6 + --> tests/ui/compile_fail/invalid_parse.rs:37:30 + | +37 | const _: Uuid = uuid!("504410岡林aab1426f9247bb680e5fe0c8"); + | ^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found 😎 at 6 + --> tests/ui/compile_fail/invalid_parse.rs:38:30 + | +38 | const _: Uuid = uuid!("504410😎👍aab1426f9247bb680e5fe0c8"); + | ^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found 👍 at 20 + --> tests/ui/compile_fail/invalid_parse.rs:40:44 + | +40 | const _: Uuid = uuid!("{F9168C5E-CEB2-4faa-👍5-32BF39FA1E4}"); + | ^^