rust-lang/rust · error

Path component {:?} of path {} is an invalid filename

Error message

Path component {:?} of path {} is an invalid filename

What it means

Panics inside split_path_dir_and_file while generating .debug_line info when the last component of a source path is not a Component::Normal (i.e. it is RootDir, CurDir, ParentDir, or a Windows Prefix). gimli's LineProgram requires an ordinary file-name OsStr, so an unusual path aborts debuginfo emission.

Source

Thrown at compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs:24

use cranelift_codegen::MachSrcLoc;
use cranelift_codegen::binemit::CodeOffset;
use gimli::write::{FileId, FileInfo, LineProgram, LineString, LineStringTable};
use rustc_span::{
    FileName, Pos, RemapPathScopeComponents, SourceFile, SourceFileAndLine,
    SourceFileHashAlgorithm, hygiene,
};

use crate::debuginfo::FunctionDebugContext;
use crate::debuginfo::emit::address_for_func;
use crate::prelude::*;

// OPTIMIZATION: It is cheaper to do this in one pass than using `.parent()` and `.file_name()`.
fn split_path_dir_and_file(path: &Path) -> (&Path, &OsStr) {
    let mut iter = path.components();
    let file_name = match iter.next_back() {
        Some(Component::Normal(p)) => p,
        component => {
            panic!(
                "Path component {:?} of path {} is an invalid filename",
                component,
                path.display()
            );
        }
    };
    let parent = iter.as_path();
    (parent, file_name)
}

// OPTIMIZATION: Avoid UTF-8 validation on UNIX.
fn osstr_as_utf8_bytes(path: &OsStr) -> &[u8] {
    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;
        path.as_bytes()
    }
    #[cfg(not(unix))]

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Inspect the failing path printed in the panic (the `{}` is path.display()) and identify which crate/file produces it.
  2. Audit --remap-path-prefix / RUSTFLAGS remapping rules to ensure no rule maps a source root to `/`, `.`, or an empty string.
  3. Reproduce with `--remap-path-prefix=<bad>=<good>` inverted to find the offending rule, then fix the rule.
  4. If the path comes from a generated/build-script file, ensure the generator emits a real filename rather than a bare root or sentinel.

Example fix

// before
let file_name = match iter.next_back() {
    Some(Component::Normal(p)) => p,
    component => {
        panic!("Path component {:?} of path {} is an invalid filename", component, path.display());
    }
};
// after
let file_name = match iter.next_back() {
    Some(Component::Normal(p)) => p,
    Some(other) => {
        // Fall back to the OsStr of whatever component we got so debuginfo can still emit.
        eprintln!("cg_clif: path component {:?} of {} is not Normal; using as-is", other, path.display());
        other.as_os_str()
    }
    None => panic!("Path {} has no components", path.display()),
};
Defensive patterns

Strategy: validation

Validate before calling

use std::path::{Path, Component};
fn validate_path(p: &Path) -> Result<(), String> {
    for c in p.components() {
        if let Component::Normal(os_str) = c {
            if os_str.to_str().is_none() {
                return Err(format!("path component {:?} in {} is not valid Unicode / contains illegal chars", os_str, p.display()));
            }
            let s = os_str.to_str().unwrap();
            if s.is_empty() || s.contains('\0') {
                return Err(format!("path component {:?} is empty or contains NUL", s));
            }
            #[cfg(windows)]
            for bad in ['<', '>', ':', '"', '/', '\\', '|', '?', '*'] {
                if s.contains(bad) { return Err(format!("component {:?} has illegal char {:?}", s, bad)); }
            }
        }
    }
    Ok(())
}

Type guard

use std::path::Path;
fn path_components_are_valid_filenames(p: &Path) -> bool {
    p.components().all(|c| match c {
        std::path::Component::Normal(os) => os.to_str().map(|s| !s.is_empty() && !s.contains('\0')).unwrap_or(false),
        _ => true,
    })
}

Prevention

When it happens

Trigger: Reached during debuginfo line-program construction (line_info.rs ~107 calls split_path_dir_and_file on each source file path). The panic fires for any source file whose path, after remapping, ends in something that is not a normal filename component.

Common situations: A --remap-path-prefix mapping collapses a path to `/` or `.` so the final component becomes RootDir/CurDir; proc-macro or macro-expanded spans synthesize a virtual filename like `<anon>` or `<macro>` mapped to a bare root; a Windows UNC or disk prefix leaks through as the last component on non-Windows; source paths produced by out-of-tree builds with trailing separators.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/7c1610d892b5497e.json. Report an issue: GitHub.