diff --git a/src/tools/run-make-support/src/symbols.rs b/src/tools/run-make-support/src/symbols.rs
index e4d244e14a4a1..0e11360bd5a02 100644
--- a/src/tools/run-make-support/src/symbols.rs
+++ b/src/tools/run-make-support/src/symbols.rs
@@ -1,6 +1,7 @@
+use std::collections::BTreeSet;
use std::path::Path;
-use object::{self, Object, ObjectSymbol, SymbolIterator};
+use object::{self, Object, ObjectSymbol};
/// Given an [`object::File`], find the exported dynamic symbol names via
/// [`object::Object::exports`]. This does not distinguish between which section the symbols appear
@@ -14,47 +15,180 @@ pub fn exported_dynamic_symbol_names<'file>(file: &'file object::File<'file>) ->
.collect()
}
-/// Iterate through the symbols in an object file. See [`object::Object::symbols`].
+/// Check an object file's symbols for any matching **substrings**. That is, if an object file
+/// contains a symbol named `hello_world`, it will be matched against a provided `substrings` of
+/// `["hello", "bar"]`.
+///
+/// Returns `true` if **any** of the symbols found in the object file at `path` contain a
+/// **substring** listed in `substrings`.
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
+///
+/// # Platform-specific behavior
+///
+/// On Windows MSVC, the binary (e.g. `main.exe`) does not contain the symbols, but in the separate
+/// PDB file instead. Furthermore, you will need to use [`crate::llvm::llvm_pdbutil`] as `object`
+/// crate does not handle PDB files.
#[track_caller]
-pub fn with_symbol_iter
(path: P, func: F) -> R
+pub fn object_contains_any_symbol_substring
(path: P, substrings: &[S]) -> bool
where
P: AsRef,
- F: FnOnce(&mut SymbolIterator<'_, '_>) -> R,
+ S: AsRef,
{
let path = path.as_ref();
let blob = crate::fs::read(path);
- let f = object::File::parse(&*blob)
+ let obj = object::File::parse(&*blob)
.unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
- let mut iter = f.symbols();
- func(&mut iter)
+ let substrings = substrings.iter().map(|s| s.as_ref()).collect::>();
+ for sym in obj.symbols() {
+ for substring in &substrings {
+ if sym.name_bytes().unwrap().windows(substring.len()).any(|x| x == substring.as_bytes())
+ {
+ return true;
+ }
+ }
+ }
+ false
}
-/// Check an object file's symbols for substrings.
+/// Check an object file's symbols for any exact matches against those provided in
+/// `candidate_symbols`.
///
-/// Returns `true` if any of the symbols found in the object file at `path` contain a substring
-/// listed in `substrings`.
+/// Returns `true` if **any** of the symbols found in the object file at `path` contain an **exact
+/// match** against those listed in `candidate_symbols`. Take care to account for (1) platform
+/// differences and (2) calling convention and symbol decorations differences.
///
/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
/// parsed as a recognized object file.
+///
+/// # Platform-specific behavior
+///
+/// See [`object_contains_any_symbol_substring`].
#[track_caller]
-pub fn any_symbol_contains(path: impl AsRef, substrings: &[&str]) -> bool {
- with_symbol_iter(path, |syms| {
- for sym in syms {
- for substring in substrings {
- if sym
- .name_bytes()
- .unwrap()
- .windows(substring.len())
- .any(|x| x == substring.as_bytes())
- {
- eprintln!("{:?} contains {}", sym, substring);
- return true;
- }
+pub fn object_contains_any_symbol(path: P, candidate_symbols: &[S]) -> bool
+where
+ P: AsRef,
+ S: AsRef,
+{
+ let path = path.as_ref();
+ let blob = crate::fs::read(path);
+ let obj = object::File::parse(&*blob)
+ .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
+ let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref()).collect::>();
+ for sym in obj.symbols() {
+ for candidate_symbol in &candidate_symbols {
+ if sym.name_bytes().unwrap() == candidate_symbol.as_bytes() {
+ return true;
}
}
- false
- })
+ }
+ false
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ContainsAllSymbolSubstringsOutcome<'a> {
+ Ok,
+ MissingSymbolSubstrings(BTreeSet<&'a str>),
+}
+
+/// Check an object file's symbols for presence of all of provided **substrings**. That is, if an
+/// object file contains symbols `["hello", "goodbye", "world"]`, it will be matched against a list
+/// of `substrings` of `["he", "go"]`. In this case, `he` is a substring of `hello`, and `go` is a
+/// substring of `goodbye`, so each of `substrings` was found.
+///
+/// Returns `true` if **all** `substrings` were present in the names of symbols for the given object
+/// file (as substrings of symbol names).
+///
+/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
+/// parsed as a recognized object file.
+///
+/// # Platform-specific behavior
+///
+/// See [`object_contains_any_symbol_substring`].
+#[track_caller]
+pub fn object_contains_all_symbol_substring<'s, P, S>(
+ path: P,
+ substrings: &'s [S],
+) -> ContainsAllSymbolSubstringsOutcome<'s>
+where
+ P: AsRef,
+ S: AsRef,
+{
+ let path = path.as_ref();
+ let blob = crate::fs::read(path);
+ let obj = object::File::parse(&*blob)
+ .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
+ let substrings = substrings.iter().map(|s| s.as_ref());
+ let mut unmatched_symbol_substrings = BTreeSet::from_iter(substrings);
+ unmatched_symbol_substrings.retain(|unmatched_symbol_substring| {
+ for sym in obj.symbols() {
+ if sym
+ .name_bytes()
+ .unwrap()
+ .windows(unmatched_symbol_substring.len())
+ .any(|x| x == unmatched_symbol_substring.as_bytes())
+ {
+ return false;
+ }
+ }
+
+ true
+ });
+
+ if unmatched_symbol_substrings.is_empty() {
+ ContainsAllSymbolSubstringsOutcome::Ok
+ } else {
+ ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(unmatched_symbol_substrings)
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ContainsAllSymbolsOutcome<'a> {
+ Ok,
+ MissingSymbols(BTreeSet<&'a str>),
+}
+
+/// Check an object file contains all symbols provided in `candidate_symbols`.
+///
+/// Returns `true` if **all** of the symbols in `candidate_symbols` are found within the object file
+/// at `path` by **exact match**. Take care to account for (1) platform differences and (2) calling
+/// convention and symbol decorations differences.
+///
+/// Panics if `path` is not a valid object file readable by the current user or if `path` cannot be
+/// parsed as a recognized object file.
+///
+/// # Platform-specific behavior
+///
+/// See [`object_contains_any_symbol_substring`].
+#[track_caller]
+pub fn object_contains_all_symbols(
+ path: P,
+ candidate_symbols: &[S],
+) -> ContainsAllSymbolsOutcome<'_>
+where
+ P: AsRef,
+ S: AsRef,
+{
+ let path = path.as_ref();
+ let blob = crate::fs::read(path);
+ let obj = object::File::parse(&*blob)
+ .unwrap_or_else(|e| panic!("failed to parse `{}`: {e}", path.display()));
+ let candidate_symbols = candidate_symbols.iter().map(|s| s.as_ref());
+ let mut unmatched_symbols = BTreeSet::from_iter(candidate_symbols);
+ unmatched_symbols.retain(|unmatched_symbol| {
+ for sym in obj.symbols() {
+ if sym.name_bytes().unwrap() == unmatched_symbol.as_bytes() {
+ return false;
+ }
+ }
+
+ true
+ });
+
+ if unmatched_symbols.is_empty() {
+ ContainsAllSymbolsOutcome::Ok
+ } else {
+ ContainsAllSymbolsOutcome::MissingSymbols(unmatched_symbols)
+ }
}
diff --git a/tests/run-make/compiletest-self-test/symbols-helpers/rmake.rs b/tests/run-make/compiletest-self-test/symbols-helpers/rmake.rs
new file mode 100644
index 0000000000000..73826214aac60
--- /dev/null
+++ b/tests/run-make/compiletest-self-test/symbols-helpers/rmake.rs
@@ -0,0 +1,92 @@
+//! `run_make_support::symbols` helpers self test.
+
+// Only intended as a basic smoke test, does not try to account for platform or calling convention
+// specific symbol decorations.
+//@ only-x86_64-unknown-linux-gnu
+//@ ignore-cross-compile
+
+use std::collections::BTreeSet;
+
+use object::{Object, ObjectSymbol};
+use run_make_support::symbols::{
+ ContainsAllSymbolSubstringsOutcome, ContainsAllSymbolsOutcome,
+ object_contains_all_symbol_substring, object_contains_all_symbols, object_contains_any_symbol,
+ object_contains_any_symbol_substring,
+};
+use run_make_support::{object, rfs, rust_lib_name, rustc};
+
+fn main() {
+ rustc().input("sample.rs").emit("obj").edition("2024").run();
+
+ // `sample.rs` has two `no_mangle` functions, `eszett` and `beta`, in addition to `main`.
+ //
+ // These two symbol names and the test substrings used below are carefully picked to make sure
+ // they do not overlap with `sample` and contain non-hex characters, to avoid accidentally
+ // matching against CGU names like `sample.dad0f15d00c84e70-cgu.0`.
+
+ let obj_filename = "sample.o";
+ let blob = rfs::read(obj_filename);
+ let obj = object::File::parse(&*blob).unwrap();
+ eprintln!("found symbols:");
+ for sym in obj.symbols() {
+ eprintln!("symbol = {}", sym.name().unwrap());
+ }
+
+ // `hello` contains `hel`
+ assert!(object_contains_any_symbol_substring(obj_filename, &["zett"]));
+ assert!(object_contains_any_symbol_substring(obj_filename, &["zett", "does_not_exist"]));
+ assert!(!object_contains_any_symbol_substring(obj_filename, &["does_not_exist"]));
+
+ assert!(object_contains_any_symbol(obj_filename, &["eszett"]));
+ assert!(object_contains_any_symbol(obj_filename, &["eszett", "beta"]));
+ assert!(!object_contains_any_symbol(obj_filename, &["zett"]));
+ assert!(!object_contains_any_symbol(obj_filename, &["does_not_exist"]));
+
+ assert_eq!(
+ object_contains_all_symbol_substring(obj_filename, &["zett"]),
+ ContainsAllSymbolSubstringsOutcome::Ok
+ );
+ assert_eq!(
+ object_contains_all_symbol_substring(obj_filename, &["zett", "bet"]),
+ ContainsAllSymbolSubstringsOutcome::Ok
+ );
+ assert_eq!(
+ object_contains_all_symbol_substring(obj_filename, &["does_not_exist"]),
+ ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
+ "does_not_exist"
+ ]))
+ );
+ assert_eq!(
+ object_contains_all_symbol_substring(obj_filename, &["zett", "does_not_exist"]),
+ ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
+ "does_not_exist"
+ ]))
+ );
+ assert_eq!(
+ object_contains_all_symbol_substring(obj_filename, &["zett", "bet", "does_not_exist"]),
+ ContainsAllSymbolSubstringsOutcome::MissingSymbolSubstrings(BTreeSet::from([
+ "does_not_exist"
+ ]))
+ );
+
+ assert_eq!(
+ object_contains_all_symbols(obj_filename, &["eszett"]),
+ ContainsAllSymbolsOutcome::Ok
+ );
+ assert_eq!(
+ object_contains_all_symbols(obj_filename, &["eszett", "beta"]),
+ ContainsAllSymbolsOutcome::Ok
+ );
+ assert_eq!(
+ object_contains_all_symbols(obj_filename, &["zett"]),
+ ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
+ );
+ assert_eq!(
+ object_contains_all_symbols(obj_filename, &["zett", "beta"]),
+ ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["zett"]))
+ );
+ assert_eq!(
+ object_contains_all_symbols(obj_filename, &["does_not_exist"]),
+ ContainsAllSymbolsOutcome::MissingSymbols(BTreeSet::from(["does_not_exist"]))
+ );
+}
diff --git a/tests/run-make/compiletest-self-test/symbols-helpers/sample.rs b/tests/run-make/compiletest-self-test/symbols-helpers/sample.rs
new file mode 100644
index 0000000000000..3566d29976685
--- /dev/null
+++ b/tests/run-make/compiletest-self-test/symbols-helpers/sample.rs
@@ -0,0 +1,13 @@
+#![crate_type = "lib"]
+
+#[unsafe(no_mangle)]
+pub extern "C" fn eszett() -> i8 {
+ 42
+}
+
+#[unsafe(no_mangle)]
+pub extern "C" fn beta() -> u32 {
+ 1
+}
+
+fn main() {}
diff --git a/tests/run-make/fmt-write-bloat/rmake.rs b/tests/run-make/fmt-write-bloat/rmake.rs
index 3348651d501fd..b78e8f49683c9 100644
--- a/tests/run-make/fmt-write-bloat/rmake.rs
+++ b/tests/run-make/fmt-write-bloat/rmake.rs
@@ -20,7 +20,7 @@
use run_make_support::artifact_names::bin_name;
use run_make_support::env::no_debug_assertions;
use run_make_support::rustc;
-use run_make_support::symbols::any_symbol_contains;
+use run_make_support::symbols::object_contains_any_symbol_substring;
fn main() {
rustc().input("main.rs").opt().run();
@@ -31,5 +31,5 @@ fn main() {
// otherwise, add them to the list of symbols to deny.
panic_syms.extend_from_slice(&["panicking", "panic_fmt", "pad_integral", "Display"]);
}
- assert!(!any_symbol_contains(bin_name("main"), &panic_syms));
+ assert!(!object_contains_any_symbol_substring(bin_name("main"), &panic_syms));
}
diff --git a/tests/run-make/used/rmake.rs b/tests/run-make/used/rmake.rs
index bcdb84132d3f5..456321e2f56c3 100644
--- a/tests/run-make/used/rmake.rs
+++ b/tests/run-make/used/rmake.rs
@@ -8,9 +8,9 @@
// https://rust-lang.github.io/rfcs/2386-used.html
use run_make_support::rustc;
-use run_make_support::symbols::any_symbol_contains;
+use run_make_support::symbols::object_contains_any_symbol_substring;
fn main() {
rustc().opt_level("3").emit("obj").input("used.rs").run();
- assert!(any_symbol_contains("used.o", &["FOO"]));
+ assert!(object_contains_any_symbol_substring("used.o", &["FOO"]));
}