Skip to content

std::Path parses UNC with forward slashes incorrectly on x86_64-pc-cygwin #154164

@briansmith

Description

@briansmith

in briansmith/ring#2730 (comment), @500-internal-server-error wrote:

This code:

fn  main() {
   println!("{:?}", std::path::Path::new("//server/path").components());
}

prints Components([RootDir, Normal("server"), Normal("path")]) on x86_64-pc-cygwin and x86_64-unknown-linux-gnu but Components([Prefix(PrefixComponent { raw: "//server/path", parsed: UNC("server", "path") }), RootDir]) on x86_64-pc-windows-gnu. However, the Cygwin runtime will treat it like a Windows UNC path. So it's probably the Rust std::path implementation 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.

Details
#[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, &[]);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.O-cygwinTarget: *-pc-cygwinT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions