Skip to content

Commit d521a81

Browse files
the8472Mark-Simulacrum
authored andcommitted
Fix hashing for windows paths containing a CurDir component
* the logic only checked for / but not for \ * verbatim paths shouldn't skip items at all since they don't get normalized * the extra branches get optimized out on unix since is_sep_byte is a trivial comparison and is_verbatim is always-false * tests lacked windows coverage for these cases That lead to equal paths not having equal hashes and to unnecessary collisions.
1 parent 8b2c687 commit d521a81

File tree

2 files changed

+52
-9
lines changed

2 files changed

+52
-9
lines changed

library/std/src/path.rs

+17-9
Original file line numberDiff line numberDiff line change
@@ -2899,32 +2899,40 @@ impl cmp::PartialEq for Path {
28992899
impl Hash for Path {
29002900
fn hash<H: Hasher>(&self, h: &mut H) {
29012901
let bytes = self.as_u8_slice();
2902-
let prefix_len = match parse_prefix(&self.inner) {
2902+
let (prefix_len, verbatim) = match parse_prefix(&self.inner) {
29032903
Some(prefix) => {
29042904
prefix.hash(h);
2905-
prefix.len()
2905+
(prefix.len(), prefix.is_verbatim())
29062906
}
2907-
None => 0,
2907+
None => (0, false),
29082908
};
29092909
let bytes = &bytes[prefix_len..];
29102910

29112911
let mut component_start = 0;
29122912
let mut bytes_hashed = 0;
29132913

29142914
for i in 0..bytes.len() {
2915-
if is_sep_byte(bytes[i]) {
2915+
let is_sep = if verbatim { is_verbatim_sep(bytes[i]) } else { is_sep_byte(bytes[i]) };
2916+
if is_sep {
29162917
if i > component_start {
29172918
let to_hash = &bytes[component_start..i];
29182919
h.write(to_hash);
29192920
bytes_hashed += to_hash.len();
29202921
}
29212922

29222923
// skip over separator and optionally a following CurDir item
2923-
// since components() would normalize these away
2924-
component_start = i + match bytes[i..] {
2925-
[_, b'.', b'/', ..] | [_, b'.'] => 2,
2926-
_ => 1,
2927-
};
2924+
// since components() would normalize these away.
2925+
component_start = i + 1;
2926+
2927+
let tail = &bytes[component_start..];
2928+
2929+
if !verbatim {
2930+
component_start += match tail {
2931+
[b'.'] => 1,
2932+
[b'.', sep @ _, ..] if is_sep_byte(*sep) => 1,
2933+
_ => 0,
2934+
};
2935+
}
29282936
}
29292937
}
29302938

library/std/src/path/tests.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,20 @@ pub fn test_compare() {
14981498
relative_from: Some("")
14991499
);
15001500

1501+
tc!("foo/.", "foo",
1502+
eq: true,
1503+
starts_with: true,
1504+
ends_with: true,
1505+
relative_from: Some("")
1506+
);
1507+
1508+
tc!("foo/./bar", "foo/bar",
1509+
eq: true,
1510+
starts_with: true,
1511+
ends_with: true,
1512+
relative_from: Some("")
1513+
);
1514+
15011515
tc!("foo/bar", "foo",
15021516
eq: false,
15031517
starts_with: true,
@@ -1541,6 +1555,27 @@ pub fn test_compare() {
15411555
ends_with: true,
15421556
relative_from: Some("")
15431557
);
1558+
1559+
tc!(r"C:\foo\.\bar.txt", r"C:\foo\bar.txt",
1560+
eq: true,
1561+
starts_with: true,
1562+
ends_with: true,
1563+
relative_from: Some("")
1564+
);
1565+
1566+
tc!(r"C:\foo\.", r"C:\foo",
1567+
eq: true,
1568+
starts_with: true,
1569+
ends_with: true,
1570+
relative_from: Some("")
1571+
);
1572+
1573+
tc!(r"\\?\C:\foo\.\bar.txt", r"\\?\C:\foo\bar.txt",
1574+
eq: false,
1575+
starts_with: false,
1576+
ends_with: false,
1577+
relative_from: None
1578+
);
15441579
}
15451580
}
15461581

0 commit comments

Comments
 (0)