| author | bors <bors@rust-lang.org> 2025-01-16 02:31:22 UTC |
| committer | bors <bors@rust-lang.org> 2025-01-16 02:31:22 UTC |
| log | 5cd16b7f2bc3624f2d658aa87151279878d2652a |
| tree | 6180a5f11979ce30f0495a64c57ca6d265a4f670 |
| parent | 6fc8a27931b3806d38143c518c91c7f97b4cfb83 |
| parent | 98f673e93a47b33a451f6b2cd99e5d5b03b57563 |
tests: Port `extern-fn-reachable` to rmake.rs
Part of #121876.
## Summary
This PR ports `tests/run-make/extern-fn-reachable` to use `rmake.rs`. Notable changes:
- We now use the `object` crate and look at the exported symbols specifically.
- This test's coverage regressed against windows-msvc back in [replace dynamic library module with libloading #90716](https://github.com/rust-lang/rust/pull/90716), but since we use `object` now, we're able to claw the test coverage back.
- The checks are now stricter:
1. It no longer looks for substring symbol matches in `nm` textual outputs, it inspects the symbol names precisely.
2. We now also explicitly check for the presence of leading underscore in exported symbol names on apple vs non-apple targets.
- Added another case of `#[no_mangle] fn fun6() {}` (note the lack of `pub`) to check that Rust nameres visibility is orthogonal to symbol visibility in dylib.
## History
- Test was initially introduced as a run-pass[^run-pass] test as part of [Don't mark reachable extern fns as internal #10539](https://github.com/rust-lang/rust/pull/10539).
- Test re-introduced as a run-make test in https://github.com/rust-lang/rust/pull/13741.
- Later, the test coverage regressed in https://github.com/rust-lang/rust/pull/90716.
[^run-pass]: no longer a thing nowadays
Supersedes #128314.
Co-authored with `@lolbinarycat.`
try-job: x86_64-msvc
try-job: i686-msvc
try-job: i686-mingw
try-job: x86_64-mingw-1
try-job: x86_64-apple-1
try-job: aarch64-apple
try-job: test-various4 files changed, 61 insertions(+), 27 deletions(-)
src/tools/tidy/src/allowed_run_make_makefiles.txt-1| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | run-make/cat-and-grep-sanity-check/Makefile |
| 2 | run-make/extern-fn-reachable/Makefile | |
| 3 | 2 | run-make/jobserver-error/Makefile |
| 4 | 3 | run-make/split-debuginfo/Makefile |
| 5 | 4 | run-make/symbol-mangling-hashed/Makefile |
tests/run-make/extern-fn-reachable/Makefile deleted-26| ... | ... | @@ -1,26 +0,0 @@ |
| 1 | # ignore-cross-compile | |
| 2 | include ../tools.mk | |
| 3 | ||
| 4 | # ignore-windows-msvc | |
| 5 | ||
| 6 | NM=nm -D | |
| 7 | ||
| 8 | ifeq ($(UNAME),Darwin) | |
| 9 | NM=nm -gU | |
| 10 | endif | |
| 11 | ||
| 12 | ifdef IS_WINDOWS | |
| 13 | NM=nm -g | |
| 14 | endif | |
| 15 | ||
| 16 | # This overrides the LD_LIBRARY_PATH for RUN | |
| 17 | TARGET_RPATH_DIR:=$(TARGET_RPATH_DIR):$(TMPDIR) | |
| 18 | ||
| 19 | all: | |
| 20 | 	$(RUSTC) dylib.rs -o $(TMPDIR)/libdylib.so -C prefer-dynamic | |
| 21 | ||
| 22 | 	[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun1)" -eq "1" ] | |
| 23 | 	[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun2)" -eq "1" ] | |
| 24 | 	[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun3)" -eq "1" ] | |
| 25 | 	[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun4)" -eq "1" ] | |
| 26 | 	[ "$$($(NM) $(TMPDIR)/libdylib.so | grep -v __imp_ | grep -c fun5)" -eq "1" ] |
tests/run-make/extern-fn-reachable/dylib.rs+15| ... | ... | @@ -1,19 +1,34 @@ |
| 1 | 1 | #![crate_type = "dylib"] |
| 2 | 2 | #![allow(dead_code)] |
| 3 | 3 | |
| 4 | // `pub` extern fn here is a Rust nameres visibility concept, and should not affect symbol | |
| 5 | // visibility in the dylib. | |
| 4 | 6 | #[no_mangle] |
| 5 | 7 | pub extern "C" fn fun1() {} |
| 8 | ||
| 9 | // (Lack of) `pub` for the extern fn here is a Rust nameres visibility concept, and should not | |
| 10 | // affect symbol visibility in the dylib. | |
| 6 | 11 | #[no_mangle] |
| 7 | 12 | extern "C" fn fun2() {} |
| 8 | 13 | |
| 14 | // Modules are a Rust nameres concept, and should not affect symbol visibility in the dylib if the | |
| 15 | // extern fn is nested inside a module. | |
| 9 | 16 | mod foo { |
| 10 | 17 | #[no_mangle] |
| 11 | 18 | pub extern "C" fn fun3() {} |
| 12 | 19 | } |
| 20 | ||
| 21 | // Similarly, the Rust visibility of the containing module is a Rust nameres concept, and should not | |
| 22 | // affect symbol visibility in the dylib. | |
| 13 | 23 | pub mod bar { |
| 14 | 24 | #[no_mangle] |
| 15 | 25 | pub extern "C" fn fun4() {} |
| 16 | 26 | } |
| 17 | 27 | |
| 28 | // Non-extern `#[no_mangle]` fn should induce a symbol visible in the dylib. | |
| 18 | 29 | #[no_mangle] |
| 19 | 30 | pub fn fun5() {} |
| 31 | ||
| 32 | // The Rust visibility of the fn should not affect is symbol visibility in the dylib. | |
| 33 | #[no_mangle] | |
| 34 | fn fun6() {} |
tests/run-make/extern-fn-reachable/rmake.rs created+46| ... | ... | @@ -0,0 +1,46 @@ |
| 1 | //! Smoke test to check that that symbols of `extern "C"` functions and `#[no_mangle]` rust | |
| 2 | //! functions: | |
| 3 | //! | |
| 4 | //! 1. Are externally visible in the dylib produced. | |
| 5 | //! 2. That the symbol visibility is orthogonal to the Rust nameres visibility of the functions | |
| 6 | //! involved. | |
| 7 | ||
| 8 | //@ ignore-cross-compile | |
| 9 | ||
| 10 | use std::collections::BTreeSet; | |
| 11 | ||
| 12 | use run_make_support::object::{self, Object}; | |
| 13 | use run_make_support::{dynamic_lib_name, is_darwin, path, rfs, rustc}; | |
| 14 | ||
| 15 | fn main() { | |
| 16 | let dylib = dynamic_lib_name("dylib"); | |
| 17 | rustc().input("dylib.rs").output(&dylib).arg("-Cprefer-dynamic").run(); | |
| 18 | ||
| 19 | let expected_symbols = if is_darwin() { | |
| 20 | // Mach-O states that all exported symbols should have an underscore as prefix. At the | |
| 21 | // same time dlsym will implicitly add it, so outside of compilers, linkers and people | |
| 22 | // writing assembly, nobody needs to be aware of this. | |
| 23 | BTreeSet::from(["_fun1", "_fun2", "_fun3", "_fun4", "_fun5", "_fun6"]) | |
| 24 | } else { | |
| 25 | BTreeSet::from(["fun1", "fun2", "fun3", "fun4", "fun5", "fun6"]) | |
| 26 | }; | |
| 27 | ||
| 28 | let mut found_symbols = BTreeSet::new(); | |
| 29 | ||
| 30 | let blob = rfs::read(path(dylib)); | |
| 31 | let file = object::File::parse(&*blob).unwrap(); | |
| 32 | for export in file.exports().unwrap() { | |
| 33 | let sym_name = export.name(); | |
| 34 | let sym_name = std::str::from_utf8(sym_name).unwrap(); | |
| 35 | found_symbols.insert(sym_name); | |
| 36 | } | |
| 37 | ||
| 38 | println!("expected_symbols = {:?}", expected_symbols); | |
| 39 | println!("found_symbols = {:?}", found_symbols); | |
| 40 | if !found_symbols.is_superset(&expected_symbols) { | |
| 41 | for diff in expected_symbols.difference(&found_symbols) { | |
| 42 | eprintln!("missing symbol: {}", diff); | |
| 43 | } | |
| 44 | panic!("missing expected symbols"); | |
| 45 | } | |
| 46 | } |