diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ae8d0c..4397394 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,7 @@ jobs: run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo build --all - run: cargo test --all + - run: cargo build --features std fuzz_targets: name: Fuzz Targets @@ -23,7 +24,7 @@ jobs: # Note that building with fuzzers requires nightly since it uses unstable # flags to rustc. - run: rustup update nightly && rustup default nightly - - run: cargo install cargo-fuzz --vers "^0.10" + - run: cargo install cargo-fuzz --vers "^0.11" - run: cargo fuzz build --dev rustfmt: diff --git a/Cargo.toml b/Cargo.toml index 552e069..1deb42f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.22" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" @@ -20,6 +20,11 @@ compiler_builtins = { version = '0.1.2', optional = true } [features] rustc-dep-of-std = ['core', 'compiler_builtins'] +std = [] [profile.release] lto = true + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4b7533f..717a322 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -rustc-demangle = { path = '..' } +rustc-demangle = { path = '..', features = ["std"] } [[bin]] name = "demangle" diff --git a/fuzz/fuzz_targets/demangle.rs b/fuzz/fuzz_targets/demangle.rs index c1f7e87..e41ae00 100644 --- a/fuzz/fuzz_targets/demangle.rs +++ b/fuzz/fuzz_targets/demangle.rs @@ -11,4 +11,17 @@ fuzz_target!(|data: &str| { if let Ok(sym) = rustc_demangle::try_demangle(data) { drop(write!(s, "{}", sym)); } + + let mut output = Vec::new(); + drop(rustc_demangle::demangle_stream( + &mut s.as_bytes(), + &mut output, + true, + )); + output.clear(); + drop(rustc_demangle::demangle_stream( + &mut s.as_bytes(), + &mut output, + false, + )); }); diff --git a/src/lib.rs b/src/lib.rs index 1ecb13f..7eb1c42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,9 @@ #![no_std] #![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(test)] +#[cfg(any(test, feature = "std"))] #[macro_use] extern crate std; @@ -144,6 +145,74 @@ pub fn demangle(mut s: &str) -> Demangle { } } +#[cfg(feature = "std")] +fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow { + let mut line = std::borrow::Cow::Borrowed(line); + let mut head = 0; + loop { + // Move to the next potential match + head = match (line[head..].find("_ZN"), line[head..].find("_R")) { + (Some(idx), None) | (None, Some(idx)) => head + idx, + (Some(idx1), Some(idx2)) => head + idx1.min(idx2), + (None, None) => { + // No more matches, we can return our line. + return line; + } + }; + // Find the non-matching character. + // + // If we do not find a character, then until the end of the line is the + // thing to demangle. + let match_end = line[head..] + .find(|ch: char| !(ch == '$' || ch == '.' || ch == '_' || ch.is_ascii_alphanumeric())) + .map(|idx| head + idx) + .unwrap_or(line.len()); + + let mangled = &line[head..match_end]; + if let Ok(demangled) = try_demangle(mangled) { + let demangled = if include_hash { + format!("{}", demangled) + } else { + format!("{:#}", demangled) + }; + line.to_mut().replace_range(head..match_end, &demangled); + // Start again after the replacement. + head = head + demangled.len(); + } else { + // Skip over the full symbol. We don't try to find a partial Rust symbol in the wider + // matched text today. + head = head + mangled.len(); + } + } +} + +/// Process a stream of data from `input` into the provided `output`, demangling any symbols found +/// within. +/// +/// This currently is implemented by buffering each line of input in memory, but that may be +/// changed in the future. Symbols never cross line boundaries so this is just an implementation +/// detail. +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub fn demangle_stream( + input: &mut R, + output: &mut W, + include_hash: bool, +) -> std::io::Result<()> { + let mut buf = std::string::String::new(); + // We read in lines to reduce the memory usage at any time. + // + // demangle_line is also more efficient with relatively small buffers as it will copy around + // trailing data during demangling. In the future we might directly stream to the output but at + // least right now that seems to be less efficient. + while input.read_line(&mut buf)? > 0 { + let demangled_line = demangle_line(&buf, include_hash); + output.write_all(demangled_line.as_bytes())?; + buf.clear(); + } + Ok(()) +} + /// Error returned from the `try_demangle` function below when demangling fails. #[derive(Debug, Clone)] pub struct TryDemangleError { @@ -490,4 +559,22 @@ mod tests { "{size limit reached}" ); } + + #[test] + #[cfg(feature = "std")] + fn find_multiple() { + assert_eq!( + super::demangle_line("_ZN3fooE.llvm moocow _ZN3fooE.llvm", false), + "foo.llvm moocow foo.llvm" + ); + } + + #[test] + #[cfg(feature = "std")] + fn interleaved_new_legacy() { + assert_eq!( + super::demangle_line("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm", false), + "foo.llvm moocow ::select foo.llvm" + ); + } }