Skip to content

Commit

Permalink
Rollup merge of rust-lang#127060 - Oneirical:testificate, r=jieyouxu
Browse files Browse the repository at this point in the history
Migrate `symbol-visibility` `run-make` test to rmake

Part of rust-lang#121876 and the associated [Google Summer of Code project](https://blog.rust-lang.org/2024/05/01/gsoc-2024-selected-projects.html).

Pretty scary!

- The expected number of symbols on each check has been changed slightly to reflect the differences between `llvm_readobj` and `nm`, as I think the former will print hidden symbols once and visible symbols twice, while the latter will only print visible symbols.
- The original test ran the same exact checks on `cdylib` twice, for seemingly no reason. I have removed it.
- This may be possible to optimize some more? `llvm_readobj` could get called only once for each library type, and the regex could avoid being created repeatedly. I am not sure if these kinds of considerations are important for a `run-make` test.

Demands a Windows try-job.

try-job: x86_64-mingw
  • Loading branch information
tgross35 authored Jul 31, 2024
2 parents 13cfcb2 + ea04b0a commit f562771
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 124 deletions.
1 change: 1 addition & 0 deletions src/tools/compiletest/src/command-list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"ignore-watchos",
"ignore-windows",
"ignore-windows-gnu",
"ignore-windows-msvc",
"ignore-x32",
"ignore-x86",
"ignore-x86_64",
Expand Down
8 changes: 8 additions & 0 deletions src/tools/run-make-support/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,19 @@ pub struct CompletedProcess {

impl CompletedProcess {
#[must_use]
#[track_caller]
pub fn stdout_utf8(&self) -> String {
String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
}

#[must_use]
#[track_caller]
pub fn invalid_stdout_utf8(&self) -> String {
String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
}

#[must_use]
#[track_caller]
pub fn stderr_utf8(&self) -> String {
String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
}
Expand Down
1 change: 0 additions & 1 deletion src/tools/tidy/src/allowed_run_make_makefiles.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ run-make/split-debuginfo/Makefile
run-make/stable-symbol-names/Makefile
run-make/staticlib-dylib-linkage/Makefile
run-make/symbol-mangling-hashed/Makefile
run-make/symbol-visibility/Makefile
run-make/sysroot-crates-are-unstable/Makefile
run-make/thumb-none-cortex-m/Makefile
run-make/thumb-none-qemu/Makefile
Expand Down
123 changes: 0 additions & 123 deletions tests/run-make/symbol-visibility/Makefile

This file was deleted.

179 changes: 179 additions & 0 deletions tests/run-make/symbol-visibility/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Dynamic libraries on Rust used to export a very high amount of symbols,
// going as far as filling the output with mangled names and generic function
// names. After the rework of #38117, this test checks that no mangled Rust symbols
// are exported, and that generics are only shown if explicitely requested.
// See https://github.com/rust-lang/rust/issues/37530

//@ ignore-windows-msvc

//FIXME(Oneirical): This currently uses llvm-nm for symbol detection. However,
// the custom Rust-based solution of #128314 may prove to be an interesting alternative.

use run_make_support::{bin_name, dynamic_lib_name, is_darwin, is_windows, llvm_nm, regex, rustc};

fn main() {
let cdylib_name = dynamic_lib_name("a_cdylib");
let rdylib_name = dynamic_lib_name("a_rust_dylib");
let exe_name = bin_name("an_executable");
let combined_cdylib_name = dynamic_lib_name("combined_rlib_dylib");
rustc().arg("-Zshare-generics=no").input("an_rlib.rs").run();
rustc().arg("-Zshare-generics=no").input("a_cdylib.rs").run();
rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run();
rustc().arg("-Zshare-generics=no").input("a_proc_macro.rs").run();
rustc().arg("-Zshare-generics=no").input("an_executable.rs").run();
rustc()
.arg("-Zshare-generics=no")
.input("a_cdylib.rs")
.crate_name("combined_rlib_dylib")
.crate_type("rlib,cdylib")
.run();

// Check that a cdylib exports its public #[no_mangle] functions
symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_cdylib"), true);
// Check that a cdylib exports the public #[no_mangle] functions of dependencies
symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
// Check that a cdylib DOES NOT export any public Rust functions
symbols_check(&cdylib_name, SymbolCheckType::AnyRustSymbol, false);

// Check that a Rust dylib exports its monomorphic functions
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_c_function_from_rust_dylib"),
true,
);
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_rust_function_from_rust_dylib"),
true,
);
// Check that a Rust dylib does not export generics if -Zshare-generics=no
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_generic_function_from_rust_dylib"),
false,
);

// Check that a Rust dylib exports the monomorphic functions from its dependencies
symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_rust_function_from_rlib"), true);
// Check that a Rust dylib does not export generics if -Zshare-generics=no
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_generic_function_from_rlib"),
false,
);

// FIXME(nbdd0121): This is broken in MinGW, see https://github.com/rust-lang/rust/pull/95604#issuecomment-1101564032
// if is_windows() {
// // Check that an executable does not export any dynamic symbols
// symbols_check(&exe_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib")
//, false);
// symbols_check(
// &exe_name,
// SymbolCheckType::StrSymbol("public_rust_function_from_exe"),
// false,
// );
// }

// Check the combined case, where we generate a cdylib and an rlib in the same
// compilation session:
// Check that a cdylib exports its public #[no_mangle] functions
symbols_check(
&combined_cdylib_name,
SymbolCheckType::StrSymbol("public_c_function_from_cdylib"),
true,
);
// Check that a cdylib exports the public #[no_mangle] functions of dependencies
symbols_check(
&combined_cdylib_name,
SymbolCheckType::StrSymbol("public_c_function_from_rlib"),
true,
);
// Check that a cdylib DOES NOT export any public Rust functions
symbols_check(&combined_cdylib_name, SymbolCheckType::AnyRustSymbol, false);

rustc().arg("-Zshare-generics=yes").input("an_rlib.rs").run();
rustc().arg("-Zshare-generics=yes").input("a_cdylib.rs").run();
rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run();
rustc().arg("-Zshare-generics=yes").input("an_executable.rs").run();

// Check that a cdylib exports its public #[no_mangle] functions
symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_cdylib"), true);
// Check that a cdylib exports the public #[no_mangle] functions of dependencies
symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
// Check that a cdylib DOES NOT export any public Rust functions
symbols_check(&cdylib_name, SymbolCheckType::AnyRustSymbol, false);

// Check that a Rust dylib exports its monomorphic functions, including generics this time
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_c_function_from_rust_dylib"),
true,
);
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_rust_function_from_rust_dylib"),
true,
);
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_generic_function_from_rust_dylib"),
true,
);

// Check that a Rust dylib exports the monomorphic functions from its dependencies
symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_rust_function_from_rlib"), true);
symbols_check(
&rdylib_name,
SymbolCheckType::StrSymbol("public_generic_function_from_rlib"),
true,
);

// FIXME(nbdd0121): This is broken in MinGW, see https://github.com/rust-lang/rust/pull/95604#issuecomment-1101564032
// if is_windows() {
// // Check that an executable does not export any dynamic symbols
// symbols_check(&exe_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib")
//, false);
// symbols_check(
// &exe_name,
// SymbolCheckType::StrSymbol("public_rust_function_from_exe"),
// false,
// );
// }
}

#[track_caller]
fn symbols_check(path: &str, symbol_check_type: SymbolCheckType, exists_once: bool) {
let mut nm = llvm_nm();
if is_windows() {
nm.arg("--extern-only");
} else if is_darwin() {
nm.arg("--extern-only").arg("--defined-only");
} else {
nm.arg("--dynamic");
}
let out = nm.input(path).run().stdout_utf8();
assert_eq!(
out.lines()
.filter(|&line| !line.contains("__imp_") && has_symbol(line, symbol_check_type))
.count()
== 1,
exists_once
);
}

fn has_symbol(line: &str, symbol_check_type: SymbolCheckType) -> bool {
if let SymbolCheckType::StrSymbol(expected) = symbol_check_type {
line.contains(expected)
} else {
let regex = regex::Regex::new(r#"_ZN.*h.*E\|_R[a-zA-Z0-9_]+"#).unwrap();
regex.is_match(line)
}
}

#[derive(Clone, Copy)]
enum SymbolCheckType {
StrSymbol(&'static str),
AnyRustSymbol,
}

0 comments on commit f562771

Please sign in to comment.