Skip to content

Commit 332c4da

Browse files
Support searching for and demangling symbols
This adds an API for demangling a stream of data which consists of a mix of mangled and non-mangled symbols. This is gated behind a new Cargo feature ("std") since the new API uses I/O traits and allocates memory. This API is broadly useful for any downstream users that are processing streams with interleaved symbols (e.g., perf script output) and don't want to filter down ahead of time. Previously the best option for this (to my knowledge) was either shelling out to the `rustfilt` binary or copying an implementation much like it. The implementation added in this commit is roughly 2x faster than that in the rustfilt binary today; if this is accepted moving rustfilt to use this will make sense.
1 parent 2811a1a commit 332c4da

File tree

5 files changed

+102
-2
lines changed

5 files changed

+102
-2
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
1515
- run: cargo build --all
1616
- run: cargo test --all
17+
- run: cargo build --features std
1718

1819
fuzz_targets:
1920
name: Fuzz Targets

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ compiler_builtins = { version = '0.1.2', optional = true }
2020

2121
[features]
2222
rustc-dep-of-std = ['core', 'compiler_builtins']
23+
std = []
2324

2425
[profile.release]
2526
lto = true
27+
28+
[package.metadata.docs.rs]
29+
features = ["std"]
30+
rustdoc-args = ["--cfg", "docsrs"]

fuzz/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ cargo-fuzz = true
1010

1111
[dependencies]
1212
libfuzzer-sys = "0.4"
13-
rustc-demangle = { path = '..' }
13+
rustc-demangle = { path = '..', features = ["std"] }
1414

1515
[[bin]]
1616
name = "demangle"

fuzz/fuzz_targets/demangle.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,17 @@ fuzz_target!(|data: &str| {
1111
if let Ok(sym) = rustc_demangle::try_demangle(data) {
1212
drop(write!(s, "{}", sym));
1313
}
14+
15+
let mut output = Vec::new();
16+
drop(rustc_demangle::demangle_stream(
17+
&mut s.as_bytes(),
18+
&mut output,
19+
true,
20+
));
21+
output.clear();
22+
drop(rustc_demangle::demangle_stream(
23+
&mut s.as_bytes(),
24+
&mut output,
25+
false,
26+
));
1427
});

src/lib.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
2626
#![no_std]
2727
#![deny(missing_docs)]
28+
#![cfg_attr(docsrs, feature(doc_cfg))]
2829

29-
#[cfg(test)]
30+
#[cfg(any(test, feature = "std"))]
3031
#[macro_use]
3132
extern crate std;
3233

@@ -144,6 +145,68 @@ pub fn demangle(mut s: &str) -> Demangle {
144145
}
145146
}
146147

148+
#[cfg(feature = "std")]
149+
fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
150+
let mut line = std::borrow::Cow::Borrowed(line);
151+
let mut head = 0;
152+
loop {
153+
// Move to the next potential match
154+
head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
155+
(Some(idx), None) | (None, Some(idx)) => head + idx,
156+
(Some(idx1), Some(idx2)) => head + idx1.min(idx2),
157+
(None, None) => {
158+
// No more matches, we can return our line.
159+
return line;
160+
}
161+
};
162+
// Find the non-matching character.
163+
//
164+
// If we do not find a character, then until the end of the line is the
165+
// thing to demangle.
166+
let match_end = line[head..]
167+
.find(|ch: char| !(ch == '$' || ch == '.' || ch == '_' || ch.is_ascii_alphanumeric()))
168+
.map(|idx| head + idx)
169+
.unwrap_or(line.len());
170+
171+
let mangled = &line[head..match_end];
172+
let demangled = if include_hash {
173+
format!("{}", demangle(mangled))
174+
} else {
175+
format!("{:#}", demangle(mangled))
176+
};
177+
line.to_mut().replace_range(head..match_end, &demangled);
178+
// Start again after the replacement.
179+
head = head + demangled.len();
180+
}
181+
}
182+
183+
/// Process a stream of data from `input` into the provided `output`, demangling any symbols found
184+
/// within.
185+
///
186+
/// This currently is implemented by buffering each line of input in memory, but that may be
187+
/// changed in the future. Symbols never cross line boundaries so this is just an implementation
188+
/// detail.
189+
#[cfg(feature = "std")]
190+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
191+
pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
192+
input: &mut R,
193+
output: &mut W,
194+
include_hash: bool,
195+
) -> std::io::Result<()> {
196+
let mut buf = std::string::String::new();
197+
// We read in lines to reduce the memory usage at any time.
198+
//
199+
// demangle_line is also more efficient with relatively small buffers as it will copy around
200+
// trailing data during demangling. In the future we might directly stream to the output but at
201+
// least right now that seems to be less efficient.
202+
while input.read_line(&mut buf)? > 0 {
203+
let demangled_line = demangle_line(&buf, include_hash);
204+
output.write_all(demangled_line.as_bytes())?;
205+
buf.clear();
206+
}
207+
Ok(())
208+
}
209+
147210
/// Error returned from the `try_demangle` function below when demangling fails.
148211
#[derive(Debug, Clone)]
149212
pub struct TryDemangleError {
@@ -490,4 +553,22 @@ mod tests {
490553
"{size limit reached}"
491554
);
492555
}
556+
557+
#[test]
558+
#[cfg(feature = "std")]
559+
fn find_multiple() {
560+
assert_eq!(
561+
super::demangle_line("_ZN3fooE.llvm moocow _ZN3fooE.llvm", false),
562+
"foo.llvm moocow foo.llvm"
563+
);
564+
}
565+
566+
#[test]
567+
#[cfg(feature = "std")]
568+
fn interleaved_new_legacy() {
569+
assert_eq!(
570+
super::demangle_line("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm", false),
571+
"foo.llvm moocow <mio::sys::unix::selector::epoll::Selector>::select foo.llvm"
572+
);
573+
}
493574
}

0 commit comments

Comments
 (0)