Skip to content

Commit 45082b0

Browse files
committed
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 686663a commit 45082b0

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
@@ -2920,32 +2920,40 @@ impl cmp::PartialEq for Path {
29202920
impl Hash for Path {
29212921
fn hash<H: Hasher>(&self, h: &mut H) {
29222922
let bytes = self.as_u8_slice();
2923-
let prefix_len = match parse_prefix(&self.inner) {
2923+
let (prefix_len, verbatim) = match parse_prefix(&self.inner) {
29242924
Some(prefix) => {
29252925
prefix.hash(h);
2926-
prefix.len()
2926+
(prefix.len(), prefix.is_verbatim())
29272927
}
2928-
None => 0,
2928+
None => (0, false),
29292929
};
29302930
let bytes = &bytes[prefix_len..];
29312931

29322932
let mut component_start = 0;
29332933
let mut bytes_hashed = 0;
29342934

29352935
for i in 0..bytes.len() {
2936-
if is_sep_byte(bytes[i]) {
2936+
let is_sep = if verbatim { is_verbatim_sep(bytes[i]) } else { is_sep_byte(bytes[i]) };
2937+
if is_sep {
29372938
if i > component_start {
29382939
let to_hash = &bytes[component_start..i];
29392940
h.write(to_hash);
29402941
bytes_hashed += to_hash.len();
29412942
}
29422943

29432944
// skip over separator and optionally a following CurDir item
2944-
// since components() would normalize these away
2945-
component_start = i + match bytes[i..] {
2946-
[_, b'.', b'/', ..] | [_, b'.'] => 2,
2947-
_ => 1,
2948-
};
2945+
// since components() would normalize these away.
2946+
component_start = i + 1;
2947+
2948+
let tail = &bytes[component_start..];
2949+
2950+
if !verbatim {
2951+
component_start += match tail {
2952+
[b'.'] => 1,
2953+
[b'.', sep @ _, ..] if is_sep_byte(*sep) => 1,
2954+
_ => 0,
2955+
};
2956+
}
29492957
}
29502958
}
29512959

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)