about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/run-make-support/src/symbols.rs184
-rw-r--r--tests/run-make/compiletest-self-test/symbols-helpers/rmake.rs92
-rw-r--r--tests/run-make/compiletest-self-test/symbols-helpers/sample.rs13
3 files changed, 264 insertions, 25 deletions
diff --git a/src/tools/run-make-support/src/symbols.rs b/src/tools/run-make-support/src/symbols.rs
index e4d244e14a4..0e11360bd5a 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<P, F, R>(path: P, func: F) -> R
+pub fn object_contains_any_symbol_substring<P, S>(path: P, substrings: &[S]) -> bool
 where
     P: AsRef<Path>,
-    F: FnOnce(&mut SymbolIterator<'_, '_>) -> R,
+    S: AsRef<str>,
 {
     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::<Vec<_>>();
+    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<Path>, 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<P, S>(path: P, candidate_symbols: &[S]) -> bool
+where
+    P: AsRef<Path>,
+    S: AsRef<str>,
+{
+    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::<Vec<_>>();
+    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<Path>,
+    S: AsRef<str>,
+{
+    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<P, S>(
+    path: P,
+    candidate_symbols: &[S],
+) -> ContainsAllSymbolsOutcome<'_>
+where
+    P: AsRef<Path>,
+    S: AsRef<str>,
+{
+    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 00000000000..73826214aac
--- /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 00000000000..3566d299766
--- /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() {}