Skip to content

Commit

Permalink
Merge pull request #554 from QnnOkabayashi/fix-uuid-macro-diagnostics
Browse files Browse the repository at this point in the history
Fixed `uuid!("")` macro diagnostics when using `urn:uuid` prefix.
  • Loading branch information
KodrAus committed Nov 6, 2021
2 parents e6a6dbd + 3fc629f commit fa28942
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 68 deletions.
20 changes: 12 additions & 8 deletions macros/src/lib.rs
Expand Up @@ -36,22 +36,26 @@ 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, group, .. },
error::ErrorKind::InvalidGroupLength { found, index, .. },
)) => {
let start =
parser::GROUP_LENS.iter().take(group).sum::<usize>()
+ 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(),
};
Expand Down
36 changes: 36 additions & 0 deletions shared/error.rs
Expand Up @@ -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.
///
Expand Down Expand Up @@ -79,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<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Error(kind)
Expand Down Expand Up @@ -126,6 +161,7 @@ impl fmt::Display for Error {
ref expected,
found,
group,
..
} => write!(
f,
"expected {}, found {} in group {}",
Expand Down
97 changes: 45 additions & 52 deletions shared/parser.rs
Expand Up @@ -26,27 +26,34 @@ 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 {
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];
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.
Expand All @@ -55,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 {
Expand All @@ -85,31 +88,25 @@ 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
};

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,
}
.into());
start,
));
}
// Next group, decrement digit, it is incremented again
// at the bottom.
group += 1;
digit -= 1;
}
_ => {
return Err(ErrorKind::InvalidCharacter {
expected: "0123456789abcdefABCDEF-",
found: input[i_char..].chars().next().unwrap(),
index: i_char,
urn: UrnPrefix::Optional,
}
.into());
return Err(Error::character(character, i_char, start));
}
}
} else {
Expand All @@ -127,21 +124,17 @@ pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> {
digit
};

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,
}
.into());
start,
));
}
_ => {
return Err(ErrorKind::InvalidCharacter {
expected: "0123456789abcdefABCDEF-",
found: input[i_char..].chars().next().unwrap(),
index: i_char,
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;
Expand All @@ -151,12 +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 {
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,
}
.into());
start,
));
}

Ok(buffer)
Expand Down
16 changes: 15 additions & 1 deletion src/parser.rs
Expand Up @@ -210,9 +210,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
}))
);

Expand All @@ -222,6 +232,7 @@ mod tests {
expected: ExpectedLength::Exact(4),
found: 3,
group: 1,
index: 10,
}))
);
// (group, found, expecting)
Expand All @@ -232,6 +243,7 @@ mod tests {
expected: ExpectedLength::Exact(12),
found: 8,
group: 4,
index: 25,
}))
);

Expand Down Expand Up @@ -308,6 +320,7 @@ mod tests {
expected: ExpectedLength::Exact(8),
found: 6,
group: 0,
index: 1,
}))
);
assert_eq!(
Expand All @@ -316,6 +329,7 @@ mod tests {
expected: ExpectedLength::Exact(4),
found: 5,
group: 3,
index: 20,
}))
);
}
Expand Down
15 changes: 14 additions & 1 deletion tests/ui/compile_fail/invalid_parse.rs
Expand Up @@ -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");
Expand All @@ -25,5 +25,18 @@ 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}");

// 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() {}
54 changes: 48 additions & 6 deletions tests/ui/compile_fail/invalid_parse.stderr
Expand Up @@ -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
|
Expand Down Expand Up @@ -123,3 +117,51 @@ 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}");
| ^

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}");
| ^^

0 comments on commit fa28942

Please sign in to comment.