std::Path parses UNC with forward slashes incorrectly on x86_64-pc-cygwin
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
in https://github.com/briansmith/ring/pull/2730#issuecomment-4102613443, @500-internal-server-error wrote:
This code:
fn main() { println!("{:?}", std::path::Path::new("//server/path").components()); }prints
Components([RootDir, Normal("server"), Normal("path")])onx86_64-pc-cygwinandx86_64-unknown-linux-gnubutComponents([Prefix(PrefixComponent { raw: "//server/path", parsed: UNC("server", "path") }), RootDir])onx86_64-pc-windows-gnu. However, the Cygwin runtime will treat it like a Windows UNC path. So it's probably the Ruststd::pathimplementation for Cygwin that is wrong/incomplete.
rustc -vV:
rustc 1.94.0 (4a4ef493e 2026-03-02) (Rev4, Built by MSYS2 project)
binary: rustc
commit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db
commit-date: 2026-03-02
host: x86_64-pc-cygwin
release: 1.94.0
LLVM version: 21.1.8
Interestingly, It seems to parse the backslash form like Windows, which seems like a bug as well; presumably Cygwin shouldn't be accepting backslashes as a path separator.
I noticed that it processes the input r#"//foo/bar"# correctly!
Reading the source code, I speculate that this is related to the 8-byte "prefix" buffer In the prefix parser, as "//server/path" has the third slash as the 9th byte whereas "//foo/bar" has it within 8 bytes.
Edit: I misread the test results. r#"//foo/bar"# is also parsed incorrectly.
Here are several test vectors that you are happy to modify and incorporate under the standard Apache/MIT license. Note that these tests are actually testing a wrapper that replaces path separators with '/' so they can't be used as-is, but these tests are what found the issue.
#[test]
fn join_components_with_forward_slashes_tests() {
struct Case {
input: &'static str,
expected: &'static str,
}
const VALID_TEST_CASES: &[Case] = &[
Case {
input: r#"/"#,
expected: "/",
},
Case {
input: r#"//"#,
expected: "/",
},
Case {
input: r#"\"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"/"
} else {
r#"\"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"/"
} else {
r#"\\"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"/foo"
} else {
r#"\\foo"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\foo\bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//foo/bar/" // UNC with implied root
} else {
r#"\\foo\bar"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"//foo"#,
expected: "/foo", // Redundant slash removed.
},
Case {
input: r#"//foo/bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//foo/bar/" // UNC with implied root
} else {
"/foo/bar"
},
},
Case {
input: r#"\\server\share"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC with implied root
} else {
r#"\\server\share"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\server\share\"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC
} else {
r#"\\server\share\"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\server\share\foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo" // UNC
} else {
r#"\\server\share\foo"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"\\server\share\foo\bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
r#"\\server\share\foo\bar"# // Backslash not interpreted as a separator
},
},
Case {
// XXX: trailing slash stripped
input: r#"\\server\share\foo\bar\"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
r#"\\server\share\foo\bar\"# // Backslash not interpreted as a separator
},
},
Case {
input: r#"//server/share"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC with implied root
} else {
r#"/server/share"# // Redundant slash removed.
},
},
Case {
input: r#"//server/share/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC
} else {
r#"/server/share"# // Redundant slash removed. XXX: trailing slash stripped.
},
},
Case {
input: r#"//server/share/foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo" // UNC
} else {
// Redundant slash removed.
"/server/share/foo"
},
},
Case {
input: r#"//server/share/foo/bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
r#"/server/share/foo/bar"#
}, // Redundant slash removed.
},
Case {
// XXX: trailing slash stripped
input: r#"//server/share/foo/bar/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
r#"/server/share/foo/bar"# // Redundant slash removed.
},
},
Case {
input: r#"//server\share"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC with implied root
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server\share"#
},
},
Case {
input: r#"//server\share/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
// XXX: trailing slash stripped.
r#"/server\share"#
},
},
Case {
input: r#"//server\share/foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server\share/foo"#
},
},
Case {
input: r#"//server\share/foo/bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server\share/foo/bar"#
},
},
Case {
// XXX: trailing slash stripped
input: r#"//server\share/foo/bar/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server\share/foo/bar"#
},
},
Case {
input: r#"\\server\share"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC with implied root
} else {
// Backslash not interpreted as a separator.
r#"\\server\share"#
},
},
Case {
input: r#"\\server\share/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC
} else {
// Backslash not interpreted as a separator.
// XXX: trailing slash stripped.
r#"\\server\share"#
},
},
Case {
input: r#"\\server\share/foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo" // UNC
} else {
// Backslash not interpreted as a separator.
r#"\\server\share/foo"#
},
},
Case {
input: r#"\\server\share/foo/bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
// Backslash not interpreted as a separator.
r#"\\server\share/foo/bar"#
},
},
Case {
// XXX: trailing slash stripped
input: r#"\\server\share/foo/bar/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
// Backslash not interpreted as a separator.
r#"\\server\share/foo/bar"#
},
},
Case {
input: r#"//server/share\"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server/share\"#
},
},
Case {
input: r#"//server/share\foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server/share\foo"#
},
},
Case {
input: r#"//server/share/foo\bar"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar" // UNC
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server/share/foo\bar"#
},
},
Case {
// XXX: trailing slash stripped
input: r#"//server/share\foo/bar/"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"//server/share/foo/bar"
} else {
// Redundant slash removed. Backslash not interpreted as a separator.
r#"/server/share\foo/bar"#
},
},
Case {
input: r#"C:foo"#,
expected: "C:foo",
},
Case {
input: r#"C:\foo"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"C:/foo"
} else {
r#"C:\foo"# // Backslash not interpreted as a separator.
},
},
Case {
input: r#"C:/foo"#,
expected: "C:/foo",
},
Case {
input: r#"a\b"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"a/b"
} else {
r#"a\b"# // Backslash not interpreted as a separator.
},
},
Case {
input: r#"/a/b"#,
expected: "/a/b",
},
Case {
input: r#"\a\b"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"/a/b"
} else {
r#"\a\b"# // Backslash not interpreted as a separator.
},
},
Case {
input: r#".\b"#,
expected: if TARGET_SUPPORTS_UNCS_AND_BACKSLASHES {
"./b"
} else {
r#".\b"# // Backslash not interpreted as a separator.
},
},
Case {
input: r#"../b"#,
expected: "../b",
},
Case {
input: r#"a/../b"#,
expected: "a/../b",
},
Case {
// XXX: Trailing slash is skipped.
input: r#"a/./b/"#,
expected: "a/b",
},
];
let failures = VALID_TEST_CASES
.iter()
.filter_map(|Case { input, expected }| {
let actual = join_components_with_forward_slashes(Path::new(input)).unwrap();
if actual == AsRef::<OsStr>::as_ref(expected) {
None
} else {
Some((input, actual, expected))
}
})
.collect::<Vec<_>>();
assert_eq!(failures, &[]);
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with std::path::Path component parsing on x86_64-pc-cygwin and compare it with x86_64-pc-windows-gnu using the supplied forward- and backslash test vectors. Done means Cygwin's UNC and path-separator behavior is corrected and the relevant regression coverage is incorporated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100