{"record":{"id":"9b5cfb6db634a34d","repo":"GitoxideLabs/gitoxide","slug":"borrowed-cows-stay-borrowed","errorCode":null,"errorMessage":"borrowed cows stay borrowed","messagePattern":"borrowed cows stay borrowed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-path/src/convert.rs","lineNumber":26,"sourceCode":"\n#[derive(Debug)]\n/// The error type returned by [`into_bstr()`] and others may suffer from failed conversions from or to bytes.\npub struct Utf8Error;\n\nimpl std::fmt::Display for Utf8Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"Could not convert to UTF8 or from UTF8 due to ill-formed input\")\n    }\n}\n\nimpl std::error::Error for Utf8Error {}\n\n/// Like [`into_bstr()`], but takes `OsStr` as input for a lossless, but fallible, conversion.\npub fn os_str_into_bstr(path: &OsStr) -> Result<&BStr, Utf8Error> {\n    let path = try_into_bstr(Cow::Borrowed(path.as_ref()))?;\n    match path {\n        Cow::Borrowed(path) => Ok(path),\n        Cow::Owned(_) => unreachable!(\"borrowed cows stay borrowed\"),\n    }\n}\n\n/// Like [`into_bstr()`], but takes `OsString` as input for a lossless, but fallible, conversion.\npub fn os_string_into_bstring(path: OsString) -> Result<BString, Utf8Error> {\n    let path = try_into_bstr(Cow::Owned(path.into()))?;\n    match path {\n        Cow::Borrowed(_path) => unreachable!(\"borrowed cows stay borrowed\"),\n        Cow::Owned(path) => Ok(path),\n    }\n}\n\n/// Like [`into_bstr()`], but takes `Cow<OsStr>` as input for a lossless, but fallible, conversion.\npub fn try_os_str_into_bstr(path: Cow<'_, OsStr>) -> Result<Cow<'_, BStr>, Utf8Error> {\n    match path {\n        Cow::Borrowed(path) => os_str_into_bstr(path).map(Cow::Borrowed),\n        Cow::Owned(path) => os_string_into_bstring(path).map(Cow::Owned),\n    }","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-path/src/convert.rs#L8-L44","documentation":"This is an `unreachable!()` panic assertion inside `os_str_into_bstr`, a public API in gix-path. The function converts an `OsStr` to a `&BStr` via `try_into_bstr(Cow::Borrowed(...))` and asserts the resulting `Cow` is still `Borrowed`. The library throws it if the conversion of a borrowed OS string unexpectedly allocated an owned value, which would mean an internal invariant about platform encoding losslessness is broken.","triggerScenarios":"Calling `gix_path::os_str_into_bstr` on a Unix-like platform where the conversion always borrows; the panic fires only if `try_into_bstr` returns `Cow::Owned` for borrowed input, which indicates a bug in `try_into_bstr` (e.g. after a gix-path upgrade changing the internal conversion behavior) rather than anything the caller passed.","commonSituations":"Practically only hit during gix development or when a patched/forked version of gix-path altered `try_into_bstr` so that borrowed input is re-allocated; end users essentially never trigger it through normal path handling on supported platforms.","solutions":["Upgrade or downgrade gix-path to a released version; this indicates a bug in the crate, not in caller code","Inspect the platform: on non-Unix platforms (e.g. Windows with non-UTF-8 encodable paths) verify gix-path supports the encoding path taken and file an issue if it panics","Reproduce with a minimal `OsStr` input and report it upstream with the input value","As a workaround, use `try_os_str_into_bstr` or `into_bstr` which tolerate owned conversion"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"fn is_ascii_or_valid_os(s: &std::ffi::OsStr) -> bool { s.to_str().map(|v| v.is_character_based()).unwrap_or(true) } // unix: borrowed path is guaranteed\nlet _ = is_ascii_or_valid_os(path);","typeGuard":"fn borrowed_bstr<'a>(r: Result<&'a gix_hash::bstr::BStr, std::str::Utf8Error>) -> Option<&'a gix_hash::bstr::BStr> { r.ok() }","tryCatchPattern":"// panics are not catchable in Rust without std::panic::catch_unwind\nlet result = std::panic::catch_unwind(|| gix_path::os_str_into_bstr(path));\nmatch result { Ok(Ok(bstr)) => use(bstr), _ => fallback_to_owned_conversion(path) }","preventionTips":["Use released gix-path versions only; avoid patched forks of try_into_bstr","Prefer try_os_str_into_bstr for Cow inputs so both Cow arms are handled","On non-Unix platforms, test path conversion with non-UTF-8 encodable inputs","Wrap risky conversions in catch_unwind at process boundaries (FFI/long-running services)"],"tags":["rust","panic","paths","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}