diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 329617339b4a3..d339cdabf8b8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,9 +16,6 @@ permissions: defaults: run: shell: bash -concurrency: - group: build - cancel-in-progress: true jobs: fmt: runs-on: macos-latest @@ -34,18 +31,15 @@ jobs: build: needs: [fmt] strategy: - fail-fast: true + fail-fast: false matrix: config: - arch: x86 Linux os: ubuntu-latest - install_cmd: sudo apt-get install ninja-build - arch: ARM64 macOS os: macos-latest - install_cmd: brew install ninja - - arch: ARM64 Linux - os: ubuntu-24.04-arm - install_cmd: sudo apt-get install ninja-build + # - arch: ARM64 Linux + # os: ubuntu-24.04-arm runs-on: '${{ matrix.config.os }}' name: 'Build (${{ matrix.config.arch }})' steps: @@ -54,7 +48,6 @@ jobs: fetch-depth: 0 - name: Install dependencies run: | - ${{matrix.config.install_cmd}} rustup component add llvm-tools-preview cp src/bootstrap/defaults/config.bsan.dev.toml config.toml - name: Upstream @@ -64,5 +57,5 @@ jobs: - name: Unit Tests run: ./x.py test --stage 1 src/tools/bsan/bsan-rt - name: UI Tests - run: ./x.py test --stage 1 src/tools/bsan/bsan-driver + run: ./x.py test --stage 2 src/tools/bsan/bsan-driver \ No newline at end of file diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs index ed4d3713fc642..416d826c25ee4 100644 --- a/compiler/rustc_codegen_ssa/src/mir/statement.rs +++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs @@ -1,9 +1,10 @@ -use rustc_middle::mir::{self, NonDivergingIntrinsic /* , PlaceKind*/}; +use rustc_middle::mir::{self, NonDivergingIntrinsic, PlaceKind}; use rustc_middle::span_bug; use tracing::instrument; -//use super::operand::OperandValue; +use super::operand::OperandValue; use super::{FunctionCx, LocalRef}; +use crate::mir::place::PlaceValue; use crate::traits::*; impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { @@ -88,9 +89,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let src = src_val.immediate(); bx.memcpy(dst, align, src, align, bytes, crate::MemFlags::empty()); } - - /*mir::StatementKind::Retag(retag_kind, box ref place) => { - + mir::StatementKind::Retag(retag_kind, box ref place) => { if self.cx.sess().emit_retags() { let place_value = if let Some(index) = place.as_local() { match self.locals[index] { @@ -103,27 +102,31 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { place ); } - LocalRef::Operand(op) => match op.val { - OperandValue::Ref(r) => r, - OperandValue::Immediate(_) | OperandValue::Pair(_, _) => { - let mono_ty = self.monomorphized_place_ty(place.as_ref()); - if mono_ty.is_any_ptr() { - op.deref(bx.cx()).val - } else { - return; + LocalRef::Operand(op) => { + let mono_ty = self.monomorphized_place_ty(place.as_ref()); + if mono_ty.is_any_ptr() { + match op.val { + OperandValue::Ref(r) => r, + OperandValue::Immediate(llval) => { + PlaceValue::new_sized(llval, op.layout.align.abi) + } + OperandValue::Pair(llptr, _) => { + PlaceValue::new_sized(llptr, op.layout.align.abi) + } + OperandValue::ZeroSized => return, } + } else { + return; } - OperandValue::ZeroSized => return, - }, + } } } else { self.codegen_place(bx, place.as_ref()).val }; bx.retag(place_value, PlaceKind::Default, retag_kind); } - }*/ + } mir::StatementKind::FakeRead(..) - | mir::StatementKind::Retag(..) | mir::StatementKind::AscribeUserType(..) | mir::StatementKind::ConstEvalCounter | mir::StatementKind::PlaceMention(..) diff --git a/src/bootstrap/defaults/config.bsan.ci.toml b/src/bootstrap/defaults/config.bsan.ci.toml new file mode 100644 index 0000000000000..6267725b6d17a --- /dev/null +++ b/src/bootstrap/defaults/config.bsan.ci.toml @@ -0,0 +1,21 @@ +change-id = 132494 + +[build] +build-stage = 1 +test-stage = 1 +docs = false +extended = true +tools = [ + "bsan-rt", + "bsan", + "cargo-bsan", +] + +[rust] +description = "BorrowSanitizer" +llvm-tools = true + +[llvm] +download-ci-llvm = false +clang = true +link-shared = true \ No newline at end of file diff --git a/src/bootstrap/defaults/config.bsan.dev.toml b/src/bootstrap/defaults/config.bsan.dev.toml index 6267725b6d17a..b5832846a7be7 100644 --- a/src/bootstrap/defaults/config.bsan.dev.toml +++ b/src/bootstrap/defaults/config.bsan.dev.toml @@ -7,8 +7,8 @@ docs = false extended = true tools = [ "bsan-rt", - "bsan", "cargo-bsan", + "bsan-driver" ] [rust] diff --git a/src/bootstrap/src/core/build_steps/bsan.rs b/src/bootstrap/src/core/build_steps/bsan.rs index c1403e2869779..56383c457e769 100644 --- a/src/bootstrap/src/core/build_steps/bsan.rs +++ b/src/bootstrap/src/core/build_steps/bsan.rs @@ -19,7 +19,7 @@ pub struct BsanRT { pub target: TargetSelection, } impl Step for BsanRT { - type Output = Option; + type Output = PathBuf; const DEFAULT: bool = true; const ONLY_HOSTS: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -53,26 +53,34 @@ impl Step for BsanRT { exit!(1); } - let bindir = builder.sysroot_target_bindir(compiler, target); let out_dir = builder.native_dir(self.target).join("sanitizers"); let cpp_runtime = supports_bsan(&out_dir, self.target, &builder.config.channel); if cpp_runtime.is_none() { - return None; + eprintln!("ERROR: BorrowSanitizer is not supported for target {}.", self.target); + exit!(1); } + let cpp_runtime = cpp_runtime.unwrap(); let LlvmResult { llvm_config, .. } = builder.ensure(Llvm { target: builder.config.build }); let compiler_rt_dir = builder.src.join("src/llvm-project/compiler-rt"); - if !compiler_rt_dir.exists() { - return None; - } + let lib_name = cpp_runtime.name; + + // Like other sanitizer runtimes, we want to install this runtime into + // both rustlib and the root of the sysroot libdir let rust_runtime_path = builder.ensure(BsanRTCore { compiler, target }); let rust_runtime_parent_dir = rust_runtime_path.parent().unwrap(); + let libdir = builder.sysroot_target_libdir(compiler, target); + let rustc_libdir = builder.rustc_libdir(compiler); + + let dst = libdir.join(&lib_name); + let rustc_dst = rustc_libdir.join(&lib_name); + if builder.config.dry_run() { - return Some(cpp_runtime); + return rustc_dst; } // On targets that build BSAN as a static runtime, we need to manually add in the object files // for the Rust runtime using llvm-ar (see below). If the C++ sources haven't changed, then CMake @@ -136,13 +144,15 @@ impl Step for BsanRT { // way to declare an external static archive (libbsan_rt.a) as a build dependency // of another static archive (libclang-rt-.bsan.a) in CMake—at least, not if // the external archive contains an unknown, varying quantity of object files. - if !is_dylib(&cpp_runtime.name) { + if !is_dylib(&lib_name) { let temp_dir = builder.build.tempdir().join("bsan-rt"); if temp_dir.exists() { fs::remove_dir_all(&temp_dir).unwrap(); } fs::create_dir_all(&temp_dir).unwrap(); + let bindir = builder.sysroot_target_bindir(compiler, target); + // Since our Rust runtime depends on core, // we need to remove all global symbols except for // our API endpoints to avoid clashing with users' programs. @@ -176,22 +186,17 @@ impl Step for BsanRT { .run(builder); } - let libdir = builder.sysroot_target_libdir(compiler, target); - let dst = libdir.join(&cpp_runtime.name); builder.copy_link(&cpp_runtime.path, &dst); if target.contains("-apple-") { // Update the library’s install name to reflect that it has been renamed. - apple_darwin_update_library_name( - builder, - &dst, - &format!("@rpath/{}", &cpp_runtime.name), - ); + apple_darwin_update_library_name(builder, &dst, &format!("@rpath/{}", &lib_name)); // Upon renaming the install name, the code signature of the file will invalidate, // so we will sign it again. apple_darwin_sign_file(builder, &dst); } - Some(cpp_runtime) + builder.copy_link(&dst, &rustc_dst); + rustc_dst } } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index d9c70e4c1a681..994dfb975009e 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -17,6 +17,7 @@ use std::{env, fs}; use object::BinaryFormat; use object::read::archive::ArchiveFile; +use crate::core::build_steps::bsan::BsanRT; use crate::core::build_steps::doc::DocumentationFormat; use crate::core::build_steps::tool::{self, Tool}; use crate::core::build_steps::vendor::default_paths_to_vendor; @@ -1256,6 +1257,60 @@ impl Step for Clippy { } } +#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +pub struct Bsan { + pub compiler: Compiler, + pub target: TargetSelection, +} + +impl Step for Bsan { + type Output = Option; + const DEFAULT: bool = true; + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + let default = should_build_extended_tool(run.builder, "bsan-driver"); + run.alias("bsan").default_condition(default) + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(Bsan { + compiler: run.builder.compiler_for( + run.builder.top_stage, + run.builder.config.build, + run.target, + ), + target: run.target, + }); + } + + fn run(self, builder: &Builder<'_>) -> Option { + // This prevents bsan from being built for "dist" or "install" + // on the stable/beta channels. It is a nightly-only tool and should + // not be included. + if !builder.build.unstable_features() { + return None; + } + let compiler = self.compiler; + let target = self.target; + + let bsanrt = builder.ensure(BsanRT { compiler, target }); + let bsandriver = + builder.ensure(tool::BsanDriver { compiler, target, extra_features: Vec::new() }); + let cargobsan = + builder.ensure(tool::CargoBsan { compiler, target, extra_features: Vec::new() }); + + let mut tarball = Tarball::new(builder, "bsan", &target.triple); + tarball.set_overlay(OverlayKind::Bsan); + tarball.is_preview(true); + tarball.add_file(bsandriver, "bin", 0o755); + tarball.add_file(cargobsan, "bin", 0o755); + tarball.add_file(bsanrt, "lib", 0o755); + tarball.add_legal_and_readme_to("share/doc/bsan"); + Some(tarball.generate()) + } +} + #[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] pub struct Miri { pub compiler: Compiler, @@ -1504,6 +1559,7 @@ impl Step for Extended { add_component!("llvm-components" => LlvmTools { target }); add_component!("clippy" => Clippy { compiler, target }); add_component!("miri" => Miri { compiler, target }); + add_component!("bsan" => Bsan { compiler, target }); add_component!("analysis" => Analysis { compiler, target }); add_component!("rustc-codegen-cranelift" => CodegenBackend { compiler: builder.compiler(stage, target), @@ -1909,7 +1965,7 @@ impl Step for Extended { cmd.arg("-dMiriDir=miri"); } if built_tools.contains("bsan") { - cmd.arg("-dBsanDir=miri"); + cmd.arg("-dBsanDir=bsan"); } if target.is_windows_gnu() { cmd.arg("-dGccDir=rust-mingw"); diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 063361be1333a..622b283ef7ddd 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -9,6 +9,7 @@ use std::{env, fs, iter}; use clap_complete::shells; +use crate::core::build_steps::bsan::BsanRT; use crate::core::build_steps::doc::DocumentationFormat; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; use crate::core::build_steps::tool::{self, SourceType, Tool}; @@ -469,11 +470,11 @@ pub struct BsanDriver { } impl BsanDriver { - /// Run `cargo miri setup` for the given target, return where the Miri sysroot was put. pub fn build_bsan_sysroot( builder: &Builder<'_>, compiler: Compiler, target: TargetSelection, + bsan_runtime_dir: &PathBuf, ) -> PathBuf { let bsan_sysroot = builder.out.join(compiler.host).join("bsan-sysroot"); let mut cargo = builder::Cargo::new( @@ -489,6 +490,7 @@ impl BsanDriver { cargo.env("BSAN_LIB_SRC", builder.src.join("library")); // Tell it where to put the sysroot. cargo.env("BSAN_SYSROOT", &bsan_sysroot); + cargo.env("BSAN_RT_SYSROOT", bsan_runtime_dir); let mut cargo = BootstrapCommand::from(cargo); let _guard = @@ -528,38 +530,57 @@ impl Step for BsanDriver { let host = builder.build.build; let target = self.target; let stage = builder.top_stage; - if stage == 0 { - eprintln!("bsan cannot be tested at stage 0"); + + // Miri can only be tested at stage N for N > 0 because it relies on having + // the stage N-1 compiler built to ensure that rustc and rustdoc are the + // same. We have the additional restriction that the BSAN runtime must be built, + // and the earliest this happens is in stage 1. This means that we are limited to testing + // at stage 2, since that's the only way we can ensure that the Stage N-1 sysroot contains + // the bsan runtime. + if stage < 2 { + eprintln!("BSAN can only be tested at stage 2"); std::process::exit(1); } - // This compiler runs on the host, we'll just use it for the target. let target_compiler = builder.compiler(stage, host); + // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise // we'd have stageN/bin/rustc and stageN/bin/rustdoc be effectively different stage // compilers, which isn't what we want. Rustdoc should be linked in the same way as the // rustc compiler it's paired with, so it must be built with the previous stage compiler. let host_compiler = builder.compiler(stage - 1, host); + // We need the BSAN runtime to be available so that we can + // build our instrumented sysroot. + builder.ensure(BsanRT { compiler: host_compiler, target: host }); + // Build our tools. let bsan_driver = builder.ensure(tool::BsanDriver { compiler: host_compiler, target: host, extra_features: Vec::new(), }); - // the ui tests also assume cargo-miri has been built + + // the ui tests also assume cargo-bsan has been built builder.ensure(tool::CargoBsan { compiler: host_compiler, target: host, extra_features: Vec::new(), }); - // We also need sysroots, for Miri and for the host (the latter for build scripts). + // We also need sysroots, for BSAN and for the host (the latter for build scripts). // This is for the tests so everything is done with the target compiler. - let bsan_sysroot = BsanDriver::build_bsan_sysroot(builder, target_compiler, target); + let bsan_sysroot = BsanDriver::build_bsan_sysroot( + builder, + target_compiler, + target, + &builder.sysroot(host_compiler), + ); + builder.ensure(compile::Std::new(target_compiler, host)); let host_sysroot = builder.sysroot(target_compiler); - // Miri has its own "target dir" for ui test dependencies. Make sure it gets cleared when + + // BSAN has its own "target dir" for ui test dependencies. Make sure it gets cleared when // the sysroot gets rebuilt, to avoid "found possibly newer version of crate `std`" errors. if !builder.config.dry_run() { let ui_test_dep_dir = builder.stage_out(host_compiler, Mode::ToolStd).join("bsan_ui"); @@ -570,7 +591,7 @@ impl Step for BsanDriver { } // Run `cargo test`. - // This is with the Miri crate, so it uses the host compiler. + // This is with the bsan-driver crate, so it uses the host compiler. let mut cargo = tool::prepare_tool_cargo( builder, host_compiler, @@ -584,13 +605,23 @@ impl Step for BsanDriver { cargo.add_rustc_lib_path(builder); - // We can NOT use `run_cargo_test` since Miri's integration tests do not use the usual test + // We can NOT use `run_cargo_test` since BSAN's integration tests do not use the usual test // harness and therefore do not understand the flags added by `add_flags_and_try_run_test`. let mut cargo = prepare_cargo_test(cargo, &[], &[], "bsan", host_compiler, host, builder); - // miri tests need to know about the stage sysroot + // bsan tests need to know about the stage sysroot + cargo.env("BSAN_SYSROOT", &bsan_sysroot); cargo.env("BSAN_HOST_SYSROOT", &host_sysroot); + + // Since our runtime is build with the Stage N-1 compiler, + // we need to tell BSAN to search for it separately in the StageN-1 + // sysroot. This is only necessary for platforms where the runtime + // is a dynamically linked library; otherwise, the StageN-1 compiler + // used by bsan-driver will link against the runtime in the that + // sysroot automatically. + + cargo.env("BSAN_RT_SYSROOT", &builder.sysroot(host_compiler)); cargo.env("BSAN", &bsan_driver); // Set the target. @@ -600,28 +631,6 @@ impl Step for BsanDriver { let _time = helpers::timeit(builder); cargo.run(builder); } - /* - if builder.config.test_args().is_empty() { - cargo.env("MIRIFLAGS", "-O -Zmir-opt-level=4 -Cdebug-assertions=yes"); - // Optimizations can change backtraces - cargo.env("MIRI_SKIP_UI_CHECKS", "1"); - // `MIRI_SKIP_UI_CHECKS` and `RUSTC_BLESS` are incompatible - cargo.env_remove("RUSTC_BLESS"); - // Optimizations can change error locations and remove UB so don't run `fail` tests. - cargo.args(["tests/pass", "tests/panic"]); - - { - let _guard = builder.msg_sysroot_tool( - Kind::Test, - stage, - "miri (mir-opt-level 4)", - host, - target, - ); - let _time = helpers::timeit(builder); - cargo.run(builder); - } - }*/ } } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 551e7fe0925ad..27bb91b5f5054 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -244,6 +244,18 @@ impl Cargo { }; if let Some(rpath) = rpath { self.rustflags.arg(&format!("-Clink-args={rpath}")); + // If we are linking against a prebuilt, external LLVM as a shared + // library, then we need to ensure that the path to libLLVM is added + // to rpath, otherwise it will not be available. + if builder.llvm_link_shared() && builder.is_system_llvm(target) { + if let Some(llvm_config) = builder.llvm_config(target) { + let llvm_libdir = command(llvm_config) + .arg("--libdir") + .run_capture_stdout(builder) + .stdout(); + self.rustflags.arg(&format!("-Clink-args=-Wl,-rpath,{llvm_libdir}")); + } + } } } diff --git a/src/bootstrap/src/utils/tarball.rs b/src/bootstrap/src/utils/tarball.rs index 3c6c7a7fa180a..5a6070e00446e 100644 --- a/src/bootstrap/src/utils/tarball.rs +++ b/src/bootstrap/src/utils/tarball.rs @@ -26,6 +26,7 @@ pub(crate) enum OverlayKind { RustAnalyzer, RustcCodegenCranelift, LlvmBitcodeLinker, + Bsan, } impl OverlayKind { @@ -73,6 +74,7 @@ impl OverlayKind { "LICENSE-MIT", "src/tools/llvm-bitcode-linker/README.md", ], + OverlayKind::Bsan => &["COPYRIGHT", "LICENSE-APACHE", "LICENSE-MIT"], } } @@ -96,6 +98,7 @@ impl OverlayKind { .version(builder, &builder.release_num("rust-analyzer/crates/rust-analyzer")), OverlayKind::RustcCodegenCranelift => builder.rust_version(), OverlayKind::LlvmBitcodeLinker => builder.rust_version(), + OverlayKind::Bsan => builder.rust_version(), } } } diff --git a/src/llvm-project b/src/llvm-project index 9ba72184e90d8..726f06435db05 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 9ba72184e90d87ae9002b2144a4c92deb073f813 +Subproject commit 726f06435db05ba03a26cd8a6c3aa53d0cf7cca9 diff --git a/src/tools/bsan/bsan-driver/Cargo.toml b/src/tools/bsan/bsan-driver/Cargo.toml index f95b04d80547b..3320cebb7984f 100644 --- a/src/tools/bsan/bsan-driver/Cargo.toml +++ b/src/tools/bsan/bsan-driver/Cargo.toml @@ -29,4 +29,4 @@ rustc_private = true [[test]] name = "ui" -harness = false +harness = false \ No newline at end of file diff --git a/src/tools/bsan/bsan-driver/cargo-bsan/src/phases.rs b/src/tools/bsan/bsan-driver/cargo-bsan/src/phases.rs index a9493d52aa36c..8f357967b4664 100644 --- a/src/tools/bsan/bsan-driver/cargo-bsan/src/phases.rs +++ b/src/tools/bsan/bsan-driver/cargo-bsan/src/phases.rs @@ -54,7 +54,7 @@ pub fn phase_cargo_bsan(mut args: impl Iterator) { setup(&subcommand, &rustc_version.host.as_str(), &rustc_version, verbose, quiet); - let bsan_sysroot = get_sysroot_dir(); + let bsan_sysroot = get_target_sysroot_dir(); let bsan_path = find_bsan(); let cargo_cmd = match subcommand { @@ -113,6 +113,10 @@ pub fn phase_cargo_bsan(mut args: impl Iterator) { cmd.env("BSAN_VERBOSE", verbose.to_string()); // This makes the other phases verbose. } + if env::var_os("BSAN_RT_SYSROOT").is_none() { + cmd.env("BSAN_RT_SYSROOT", get_host_sysroot_dir(verbose)); + } + // Run cargo. debug_cmd("[cargo-bsan rustc]", verbose, &cmd); exec(cmd) diff --git a/src/tools/bsan/bsan-driver/cargo-bsan/src/setup.rs b/src/tools/bsan/bsan-driver/cargo-bsan/src/setup.rs index b07189133c691..529f4601fc372 100644 --- a/src/tools/bsan/bsan-driver/cargo-bsan/src/setup.rs +++ b/src/tools/bsan/bsan-driver/cargo-bsan/src/setup.rs @@ -1,7 +1,6 @@ //! Implements `cargo bsan setup`. //! This was copied directly from cargo-miri, with only small changes //! to comments and the names of environment variables. - use std::ffi::OsStr; use std::path::PathBuf; use std::process::{self, Command}; @@ -71,7 +70,7 @@ pub fn setup( } // Determine where to put the sysroot. - let sysroot_dir = get_sysroot_dir(); + let sysroot_dir = get_target_sysroot_dir(); // Sysroot configuration and build details. let no_std = match std::env::var_os("BSAN_NO_STD") { @@ -113,6 +112,7 @@ pub fn setup( } else { command.env("RUSTC", &cargo_bsan_path); } + command.env("BSAN_CALLED_FROM_SETUP", "1"); // Miri expects `BSAN_SYSROOT` to be set when invoked in target mode. Even if that directory is empty. command.env("BSAN_SYSROOT", &sysroot_dir); @@ -167,7 +167,7 @@ pub fn setup( // Do the build. let status = SysrootBuilder::new(&sysroot_dir, target) - .build_mode(BuildMode::Check) + .build_mode(BuildMode::Build) .rustc_version(rustc_version.clone()) .sysroot_config(sysroot_config) .rustflags(rustflags) diff --git a/src/tools/bsan/bsan-driver/cargo-bsan/src/util.rs b/src/tools/bsan/bsan-driver/cargo-bsan/src/util.rs index c3db495852c35..9f1e3cd7434fe 100644 --- a/src/tools/bsan/bsan-driver/cargo-bsan/src/util.rs +++ b/src/tools/bsan/bsan-driver/cargo-bsan/src/util.rs @@ -97,6 +97,15 @@ pub fn exec(mut cmd: Command) -> ! { } } +pub fn exec_stdout(mut cmd: Command) -> String { + let output = cmd.output().expect("failed to run command"); + if output.status.success() { + String::from_utf8(output.stdout).expect("output bytes should be valid utf8") + } else { + panic!("failed to run command: {:?}", output) + } +} + #[allow(unused)] pub fn exec_with_pipe

(mut cmd: Command, input: &[u8], path: P) -> ! where @@ -133,10 +142,19 @@ where } } +/// Determines where the host sysroot of this execution is +pub fn get_host_sysroot_dir(verbose: usize) -> PathBuf { + let mut cmd = bsan_for_host(); + cmd.args(["--print", "sysroot"]); + debug_cmd("[cargo-bsan rustc]", verbose, &cmd); + let libdir = exec_stdout(cmd); + PathBuf::from(libdir.trim()) +} + /// Determines where the sysroot of this execution is /// /// Either in a user-specified spot by an envar, or in a default cache location. -pub fn get_sysroot_dir() -> PathBuf { +pub fn get_target_sysroot_dir() -> PathBuf { match std::env::var_os("BSAN_SYSROOT") { Some(dir) => PathBuf::from(dir), None => { @@ -212,7 +230,7 @@ pub fn get_cargo_metadata() -> Metadata { } pub fn clean_sysroot_dir() { - let sysroot = get_sysroot_dir(); + let sysroot = get_target_sysroot_dir(); if sysroot.exists() { std::fs::remove_dir_all(&sysroot).unwrap(); } diff --git a/src/tools/bsan/bsan-driver/src/bin/bsan-driver.rs b/src/tools/bsan/bsan-driver/src/bin/bsan-driver.rs index 92566e6fdf6e6..0cc057c52fb6a 100644 --- a/src/tools/bsan/bsan-driver/src/bin/bsan-driver.rs +++ b/src/tools/bsan/bsan-driver/src/bin/bsan-driver.rs @@ -19,17 +19,13 @@ fn main() { let args = rustc_driver::args::raw_args(&early_dcx) .unwrap_or_else(|_| std::process::exit(rustc_driver::EXIT_FAILURE)); - // We need to determine the subset of arguments to pass to the rustc invocation, - // as well as whether we need to add our BorrowSanitizer instrumentation when - // compiling the crate. - let (args, target_crate) = { // If the `BSAN_BE_RUSTC` environment variable is set, we are being invoked as // rustc to build a crate for either the "target" architecture, or the "host" - // architecture. In this case "target" and "host" are the same, since we do not - // support cross-compilation. However "target" indicates that the program needs + // architecture. In this case, "target" and "host" are the same platform, since we do not + // support cross-compilation. However, "target" also indicates that the program needs // to be instrumented, while "host" indicates that it is a build script or procedural - // macro, which we can skip instrumenting. + // macro, which we can skip. if let Some(crate_kind) = env::var("BSAN_BE_RUSTC").ok() { let is_target = match crate_kind.as_str() { @@ -46,7 +42,7 @@ fn main() { // after the "--" separator, since these will be passed to the compiled binary // when it executes. let mut rustc_args = vec![]; - for arg in args.iter().skip(1) { + for arg in args.iter() { if arg == "--" { break; } else { diff --git a/src/tools/bsan/bsan-driver/src/lib.rs b/src/tools/bsan/bsan-driver/src/lib.rs index 3c94d0a8b3810..54322fc6a61a6 100644 --- a/src/tools/bsan/bsan-driver/src/lib.rs +++ b/src/tools/bsan/bsan-driver/src/lib.rs @@ -3,6 +3,7 @@ extern crate rustc_driver; +use std::env; use std::sync::Arc; pub const BSAN_BUG_REPORT_URL: &str = "https://github.com/BorrowSanitizer/rust/issues/new"; @@ -21,9 +22,13 @@ pub fn run_compiler( using_internal_features: Arc, ) -> ! { if target_crate { - // Some options have different defaults in Miri than in plain rustc; apply those by making - // them the first arguments after the binary name (but later arguments can overwrite them). - args.splice(1..1, BSAN_DEFAULT_ARGS.iter().map(ToString::to_string)); + let mut additional_args = + BSAN_DEFAULT_ARGS.iter().map(ToString::to_string).collect::>(); + if let Some(runtime) = env::var_os("BSAN_RT_SYSROOT") { + let rt = runtime.to_string_lossy(); + additional_args.push(format!("-L{}/lib", rt)); + } + args.splice(1..1, additional_args); } let exit_code = rustc_driver::catch_with_exit_code(move || { rustc_driver::RunCompiler::new(&args, callbacks) diff --git a/src/tools/bsan/bsan-driver/test_dependencies/Cargo.toml b/src/tools/bsan/bsan-driver/test_dependencies/Cargo.toml new file mode 100644 index 0000000000000..060c3fbae466d --- /dev/null +++ b/src/tools/bsan/bsan-driver/test_dependencies/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["BSAN Team"] +description = "dependencies that unit tests can have" +license = "MIT OR Apache-2.0" +name = "bsan-test-deps" +repository = "https://github.com/borrowsanitizer/rust" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/src/tools/bsan/bsan-driver/test_dependencies/src/main.rs b/src/tools/bsan/bsan-driver/test_dependencies/src/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/src/tools/bsan/bsan-driver/test_dependencies/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/bsan/bsan-driver/tests/pass/hello.rs b/src/tools/bsan/bsan-driver/tests/pass/hello.rs new file mode 100644 index 0000000000000..7075c6de03fa4 --- /dev/null +++ b/src/tools/bsan/bsan-driver/tests/pass/hello.rs @@ -0,0 +1,4 @@ +//@run +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/bsan/bsan-driver/tests/pass/hello.run.stdout b/src/tools/bsan/bsan-driver/tests/pass/hello.run.stdout new file mode 100644 index 0000000000000..af5626b4a114a --- /dev/null +++ b/src/tools/bsan/bsan-driver/tests/pass/hello.run.stdout @@ -0,0 +1 @@ +Hello, world! diff --git a/src/tools/bsan/bsan-driver/tests/ui.rs b/src/tools/bsan/bsan-driver/tests/ui.rs index c1e5c8819808f..8e41640e2008a 100644 --- a/src/tools/bsan/bsan-driver/tests/ui.rs +++ b/src/tools/bsan/bsan-driver/tests/ui.rs @@ -1,3 +1,246 @@ -fn main() -> Result<(), ()> { +use std::env; +use std::num::NonZero; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; + +use colored::*; +use regex::bytes::Regex; +use ui_test::color_eyre::eyre::{Context, Result}; +use ui_test::custom_flags::edition::Edition; +use ui_test::dependencies::DependencyBuilder; +use ui_test::spanned::Spanned; +use ui_test::{CommandBuilder, Config, Format, Match, OutputConflictHandling, status_emitter}; + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +enum Mode { + Pass, + /// Requires annotations + Fail, + Panic, +} + +fn bsan_path() -> PathBuf { + PathBuf::from(env::var("BSAN").unwrap_or_else(|_| env!("CARGO_BIN_EXE_bsan-driver").into())) +} + +#[allow(dead_code)] +enum Dependencies { + WithDependencies, + WithoutDependencies, +} +use Dependencies::*; + +macro_rules! regexes { + ($name:ident: $($regex:expr => $replacement:expr,)*) => { + fn $name() -> &'static [(Match, &'static [u8])] { + static S: OnceLock> = OnceLock::new(); + S.get_or_init(|| vec![ + $((Regex::new($regex).unwrap().into(), $replacement.as_bytes()),)* + ]) + } + }; +} + +regexes! { + stdout_filters: + // Windows file paths + r"\\" => "/", + // erase borrow tags + "<[0-9]+>" => "", + "<[0-9]+=" => " ".rs:LL:CC", + // erase alloc ids + "alloc[0-9]+" => "ALLOC", + // erase thread ids + r"unnamed-[0-9]+" => "unnamed-ID", + // erase borrow tags + "<[0-9]+>" => "", + "<[0-9]+=" => " "$1", + // erase whitespace that differs between platforms + r" +at (.*\.rs)" => " at $1", + // erase generics in backtraces + "([0-9]+: .*)::<.*>" => "$1", + // erase long hexadecimals + r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX", + // erase specific alignments + "alignment [0-9]+" => "alignment ALIGN", + "[0-9]+ byte alignment but found [0-9]+" => "ALIGN byte alignment but found ALIGN", + // erase thread caller ids + r"call [0-9]+" => "call ID", + // erase platform module paths + "sys::pal::[a-z]+::" => "sys::pal::PLATFORM::", + // Windows file paths + r"\\" => "/", + // erase Rust stdlib path + "[^ \n`]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/", + // erase platform file paths + "sys/pal/[a-z]+/" => "sys/pal/PLATFORM/", + // erase paths into the crate registry + r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)" => "CARGO_REGISTRY/.../$1", +} + +/// Does *not* set any args or env vars, since it is shared between the test runner and +/// run_dep_mode. +fn bsan_config(path: &str, target: &str, mode: Mode, with_dependencies: bool) -> Config { + // BSAN is rustc-like, so we create a default builder for rustc and modify it + let mut program = CommandBuilder::rustc(); + program.program = bsan_path(); + + let mut config = Config { + target: Some(target.to_owned()), + program, + out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap()).join("bsan_ui"), + threads: std::env::var("BSAN_TEST_THREADS") + .ok() + .map(|threads| NonZero::new(threads.parse().unwrap()).unwrap()), + ..Config::rustc(path) + }; + + config.comment_defaults.base().exit_status = match mode { + Mode::Pass => Some(0), + Mode::Fail => Some(1), + Mode::Panic => Some(101), + } + .map(Spanned::dummy) + .into(); + + config.comment_defaults.base().require_annotations = + Spanned::dummy(matches!(mode, Mode::Fail)).into(); + + config.comment_defaults.base().normalize_stderr = + stderr_filters().iter().map(|(m, p)| (m.clone(), p.to_vec())).collect(); + config.comment_defaults.base().normalize_stdout = + stdout_filters().iter().map(|(m, p)| (m.clone(), p.to_vec())).collect(); + + config.comment_defaults.base().add_custom("edition", Edition("2021".into())); + + if with_dependencies { + config.comment_defaults.base().set_custom("dependencies", DependencyBuilder { + program: CommandBuilder { + // Set the `cargo-bsan` binary, which we expect to be in the same folder as the `bsan` binary. + // (It's a separate crate, so we don't get an env var from cargo.) + program: bsan_path() + .with_file_name(format!("cargo-bsan{}", env::consts::EXE_SUFFIX)), + // There is no `cargo bsan build` so we just use `cargo bsan run`. + args: ["bsan", "run"].into_iter().map(Into::into).collect(), + // Reset `RUSTFLAGS` to work around . + envs: vec![("RUSTFLAGS".into(), None)], + ..CommandBuilder::cargo() + }, + crate_manifest_path: Path::new("test_dependencies").join("Cargo.toml"), + build_std: None, + }); + } + config +} + +fn run_tests( + mode: Mode, + path: &str, + target: &str, + with_dependencies: bool, + tmpdir: &Path, +) -> Result<()> { + let mut config = bsan_config(path, target, mode, with_dependencies); + + // Add a test env var to do environment communication tests. + config.program.envs.push(("BSAN_ENV_VAR_TEST".into(), Some("0".into()))); + // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find). + config.program.envs.push(("BSAN_TEMP".into(), Some(tmpdir.to_owned().into()))); + // If a test ICEs, we want to see a backtrace. + config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into()))); + + // Add some flags we always want. + config.program.args.push( + format!( + "--sysroot={}", + env::var("BSAN_SYSROOT").expect("BSAN_SYSROOT must be set to run the ui test suite") + ) + .into(), + ); + config.program.args.push("-Dwarnings".into()); + config.program.args.push("-Dunused".into()); + config.program.args.push("-Ainternal_features".into()); + if let Ok(extra_flags) = env::var("BSANFLAGS") { + for flag in extra_flags.split_whitespace() { + config.program.args.push(flag.into()); + } + } + config.program.args.push("-Zui-testing".into()); + + // Handle command-line arguments. + let mut args = ui_test::Args::test()?; + args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); + config.with_args(&args); + config.bless_command = Some("./bsan test --bless".into()); + + if env::var_os("BSAN_SKIP_UI_CHECKS").is_some() { + assert!(!args.bless, "cannot use RUSTC_BLESS and BSAN_SKIP_UI_CHECKS at the same time"); + config.output_conflict_handling = OutputConflictHandling::Ignore; + } + eprintln!(" Compiler: {}", config.program.display()); + ui_test::run_tests_generic( + // Only run one test suite. In the future we can add all test suites to one `Vec` and run + // them all at once, making best use of systems with high parallelism. + vec![config], + // The files we're actually interested in (all `.rs` files). + ui_test::default_file_filter, + // This could be used to overwrite the `Config` on a per-test basis. + |_, _| {}, + ( + match args.format { + Format::Terse => status_emitter::Text::quiet(), + Format::Pretty => status_emitter::Text::verbose(), + }, + status_emitter::Gha:: { + name: format!("{mode:?} {path}"), + }, + ), + ) +} + +fn get_target() -> String { + rustc_version::VersionMeta::for_command(std::process::Command::new(bsan_path())) + .expect("failed to parse rustc version info") + .host +} + +fn ui( + mode: Mode, + path: &str, + target: &str, + with_dependencies: Dependencies, + tmpdir: &Path, +) -> Result<()> { + let msg = format!("## Running ui tests in {path}"); + eprintln!("{}", msg.green().bold()); + + let with_dependencies = match with_dependencies { + WithDependencies => true, + WithoutDependencies => false, + }; + run_tests(mode, path, target, with_dependencies, tmpdir) + .with_context(|| format!("ui tests in {path} failed")) +} + +fn main() -> Result<()> { + ui_test::color_eyre::install()?; + + let target = get_target(); + + let tmpdir = tempfile::Builder::new().prefix("bsan-uitest-").tempdir()?; + ui(Mode::Pass, "tests/pass", &target, WithoutDependencies, tmpdir.path())?; + ui(Mode::Pass, "tests/pass-dep", &target, WithDependencies, tmpdir.path())?; + ui(Mode::Panic, "tests/panic", &target, WithDependencies, tmpdir.path())?; + ui(Mode::Fail, "tests/fail", &target, WithoutDependencies, tmpdir.path())?; + ui(Mode::Fail, "tests/fail-dep", &target, WithDependencies, tmpdir.path())?; Ok(()) }