Skip to content

Commit 485397f

Browse files
committed
Add Rust ThreadSanitizer configuration and build-std
1 parent ef963c3 commit 485397f

8 files changed

Lines changed: 430 additions & 1 deletion

File tree

.bazelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ build:sanitizer-common --copt="-Og"
234234
build:sanitizer-common --copt="-g" --strip=never
235235
build:sanitizer-common --copt="-fno-optimize-sibling-calls"
236236
build:sanitizer-common --copt="-fno-omit-frame-pointer" --copt="-mno-omit-leaf-frame-pointer"
237+
# Rust's sanitizer support requires nightly
238+
build:sanitizer-common --@rules_rust//rust/toolchain/channel:channel=nightly
237239

238240
# address sanitizer (https://github.com/google/sanitizers/wiki/AddressSanitizer)
239241
build:asan --config=sanitizer-common
@@ -261,6 +263,9 @@ build:tsan --test_timeout=30,150,600,2400
261263
# not currently derive these defines from the compiler's sanitizer flags.
262264
build:tsan --copt="-DV8_IS_TSAN"
263265
build:tsan --per_file_copt='external/.*v8@-DTHREAD_SANITIZER'
266+
build:tsan --extra_toolchains=//build/rust:tsan_toolchain
267+
build:tsan --@rules_rust//:extra_rustc_flag=-Zsanitizer=thread
268+
build:tsan --@rules_rust//:extra_rustc_flag=-Zexternal-clangrt
264269

265270
# fuzzilli (https://github.com/googleprojectzero/fuzzilli/)
266271
build:fuzzilli --config=asan

build/deps/rust.MODULE.bazel

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,31 @@ rust.toolchain(
2929
RUST_NIGHTLY_VERSION,
3030
],
3131
)
32-
use_repo(rust, "rust_toolchains")
32+
use_repo(
33+
rust,
34+
"rust_toolchains",
35+
rust_nightly_linux_x86_64 = "rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools",
36+
)
3337

3438
register_toolchains("@rust_toolchains//:all")
3539

40+
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
41+
42+
http_archive(
43+
name = "rust_nightly_src",
44+
build_file_content = """
45+
filegroup(
46+
name = "rust_src",
47+
srcs = glob(["library/**"]),
48+
visibility = ["//visibility:public"],
49+
)
50+
exports_files(["library/Cargo.toml"])
51+
""",
52+
sha256 = "b8bb98533e65cb2468548b20b3575518d01b44e7738045a12de2dbb71a0d6cc2",
53+
strip_prefix = "rust-src-nightly/rust-src/lib/rustlib/src/rust",
54+
urls = ["https://static.rust-lang.org/dist/2026-07-16/rust-src-nightly.tar.xz"],
55+
)
56+
3657
# rust-based lolhtml dependency, including the API header.
3758
# Presented as a separate repository to allow overrides.
3859
new_local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository")

build/rust/BUILD.bazel

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
2+
load(":build_std.bzl", "compiler_runtime_files", "instrumented_rust_std")
3+
4+
exports_files(["build_std.sh"])
5+
6+
_RUST_TOOLS = "@rust_nightly_linux_x86_64"
7+
8+
_TARGET_TRIPLE = "x86_64-unknown-linux-gnu"
9+
10+
compiler_runtime_files(
11+
name = "nightly_rustc_lib",
12+
src = _RUST_TOOLS + "//:rustc_lib",
13+
sanitizer = "tsan",
14+
tags = ["manual"],
15+
target_compatible_with = ["//build/platforms:sanitizer_thread"],
16+
)
17+
18+
instrumented_rust_std(
19+
name = "tsan_rust_std",
20+
rust_src = "@rust_nightly_src",
21+
rust_tools = _RUST_TOOLS,
22+
sanitizer = "thread",
23+
tags = ["manual"],
24+
target_compatible_with = ["//build/platforms:sanitizer_thread"],
25+
target_triple = _TARGET_TRIPLE,
26+
visibility = ["//src/rust/tsan-test:__pkg__"],
27+
)
28+
29+
rust_toolchain(
30+
name = "tsan_toolchain_impl",
31+
allocator_library = "@rules_rust//ffi/rs:empty",
32+
binary_ext = "",
33+
cargo = _RUST_TOOLS + "//:cargo",
34+
cargo_clippy = _RUST_TOOLS + "//:cargo_clippy_bin",
35+
channel = "nightly",
36+
clippy_driver = _RUST_TOOLS + "//:clippy_driver_bin",
37+
default_edition = "2024",
38+
dylib_ext = ".so",
39+
exec_triple = _TARGET_TRIPLE,
40+
extra_exec_rustc_flags = [],
41+
extra_rustc_flags = ["-Ctarget-feature=+sse4.2,+pclmulqdq"],
42+
iso_date = "2026-07-16",
43+
linker = _RUST_TOOLS + "//:rust-lld",
44+
linker_type = "direct",
45+
llvm_cov = _RUST_TOOLS + "//:llvm_cov_bin",
46+
llvm_lib = _RUST_TOOLS + "//:llvm_lib",
47+
llvm_profdata = _RUST_TOOLS + "//:llvm_profdata_bin",
48+
rust_doc = _RUST_TOOLS + "//:rustdoc",
49+
rust_objcopy = _RUST_TOOLS + "//:rust-objcopy",
50+
rust_std = ":tsan_rust_std",
51+
rustc = _RUST_TOOLS + "//:rustc",
52+
rustc_lib = ":nightly_rustc_lib",
53+
rustfmt = _RUST_TOOLS + "//:rustfmt_bin",
54+
staticlib_ext = ".a",
55+
stdlib_linkflags = [
56+
"-ldl",
57+
"-lpthread",
58+
],
59+
tags = ["manual"],
60+
target_compatible_with = ["//build/platforms:sanitizer_thread"],
61+
target_triple = _TARGET_TRIPLE,
62+
version = "1.99.0",
63+
)
64+
65+
toolchain(
66+
name = "tsan_toolchain",
67+
exec_compatible_with = [
68+
"@platforms//cpu:x86_64",
69+
"@platforms//os:linux",
70+
],
71+
tags = ["manual"],
72+
target_compatible_with = [
73+
"@platforms//cpu:x86_64",
74+
"@platforms//os:linux",
75+
"//build/platforms:sanitizer_thread",
76+
],
77+
target_settings = ["@rules_rust//rust/toolchain/channel:nightly"],
78+
toolchain = ":tsan_toolchain_impl",
79+
toolchain_type = "@rules_rust//rust:toolchain",
80+
)

build/rust/build_std.bzl

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""Builds an instrumented Rust standard library for a rules_rust toolchain.
2+
3+
rules_rust consumes prebuilt standard-library rlibs but does not implement Cargo's
4+
-Zbuild-std. This rule invokes Cargo as a Bazel action instead of reproducing
5+
Rust's internal crate graph as Bazel targets (which would introduce a toolchain
6+
cycle).
7+
"""
8+
9+
load("@rules_rust//rust:toolchain.bzl", "rust_stdlib_filegroup")
10+
11+
# rustc_lib contains host compiler libraries plus prebuilt target metadata. A
12+
# replacement toolchain must discard that metadata while retaining the runtime
13+
# archive needed when rustc links sanitizer-instrumented execution tools.
14+
def _compiler_runtime_files_impl(ctx):
15+
sanitizer_runtime = "_rt.{}.a".format(ctx.attr.sanitizer)
16+
return [DefaultInfo(files = depset([
17+
file
18+
for file in ctx.files.src
19+
if "/lib/rustlib/" not in file.path or file.basename.endswith(sanitizer_runtime)
20+
]))]
21+
22+
compiler_runtime_files = rule(
23+
implementation = _compiler_runtime_files_impl,
24+
attrs = {
25+
"sanitizer": attr.string(mandatory = True),
26+
"src": attr.label(mandatory = True, allow_files = True),
27+
},
28+
)
29+
30+
# Bazel must know the output names before Cargo assigns metadata hashes. Keep the
31+
# expected -Zbuild-std closure in one place and copy each artifact to a stable name.
32+
_STDLIB_CRATES = [
33+
"addr2line",
34+
"adler2",
35+
"alloc",
36+
"cfg_if",
37+
"compiler_builtins",
38+
"core",
39+
"getopts",
40+
"gimli",
41+
"hashbrown",
42+
"libc",
43+
"memchr",
44+
"miniz_oxide",
45+
"object",
46+
"panic_abort",
47+
"panic_unwind",
48+
"proc_macro",
49+
"rustc_demangle",
50+
"rustc_literal_escaper",
51+
"rustc_std_workspace_alloc",
52+
"rustc_std_workspace_core",
53+
"rustc_std_workspace_std",
54+
"std",
55+
"std_detect",
56+
"test",
57+
"unwind",
58+
]
59+
60+
def _rust_build_std_impl(ctx):
61+
outputs = [
62+
ctx.actions.declare_file("{}/lib{}_{}.rlib".format(ctx.label.name, crate, crate))
63+
for crate in _STDLIB_CRATES
64+
]
65+
66+
output_dir = outputs[0].dirname
67+
ctx.actions.run(
68+
mnemonic = "RustBuildStd",
69+
progress_message = "Building the Rust standard library with {} instrumentation".format(ctx.attr.sanitizer),
70+
executable = ctx.executable._build_std,
71+
arguments = [
72+
ctx.executable.rustc.dirname.removesuffix("/bin"),
73+
ctx.file.rust_src_manifest.dirname,
74+
output_dir + "-target",
75+
output_dir,
76+
output_dir + "-work",
77+
ctx.attr.target_triple,
78+
ctx.attr.sanitizer,
79+
] + _STDLIB_CRATES,
80+
inputs = depset(
81+
ctx.files.rust_src + ctx.files.rust_toolchain_files,
82+
),
83+
outputs = outputs,
84+
tools = [ctx.executable.cargo, ctx.executable.rustc],
85+
)
86+
87+
return [DefaultInfo(files = depset(outputs))]
88+
89+
rust_build_std = rule(
90+
implementation = _rust_build_std_impl,
91+
attrs = {
92+
"cargo": attr.label(mandatory = True, executable = True, allow_files = True, cfg = "exec"),
93+
"rustc": attr.label(mandatory = True, executable = True, allow_files = True, cfg = "exec"),
94+
"rust_src": attr.label(mandatory = True, allow_files = True),
95+
"rust_src_manifest": attr.label(mandatory = True, allow_single_file = True),
96+
"rust_toolchain_files": attr.label_list(mandatory = True, allow_files = True),
97+
"sanitizer": attr.string(mandatory = True),
98+
"target_triple": attr.string(mandatory = True),
99+
"_build_std": attr.label(
100+
default = Label("//build/rust:build_std.sh"),
101+
executable = True,
102+
allow_single_file = True,
103+
cfg = "exec",
104+
),
105+
},
106+
)
107+
108+
def instrumented_rust_std(
109+
name,
110+
sanitizer,
111+
target_triple,
112+
rust_tools,
113+
rust_src,
114+
tags = None,
115+
target_compatible_with = None,
116+
visibility = None):
117+
"""Defines a rules_rust standard library instrumented by `sanitizer`."""
118+
rust_build_std(
119+
name = name + "_build",
120+
cargo = rust_tools + "//:cargo",
121+
rustc = rust_tools + "//:rustc",
122+
rust_src = rust_src + "//:rust_src",
123+
rust_src_manifest = rust_src + "//:library/Cargo.toml",
124+
rust_toolchain_files = [rust_tools + "//:rustc_lib"],
125+
sanitizer = sanitizer,
126+
target_triple = target_triple,
127+
tags = tags,
128+
target_compatible_with = target_compatible_with,
129+
)
130+
rust_stdlib_filegroup(
131+
name = name,
132+
srcs = [":" + name + "_build"],
133+
tags = tags,
134+
target_compatible_with = target_compatible_with,
135+
visibility = visibility,
136+
)

build/rust/build_std.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env bash
2+
3+
# Builds a sanitizer-instrumented Rust standard library for rules_rust.
4+
#
5+
# Arguments:
6+
# RUST_TOOLCHAIN_ROOT Toolchain containing rustc and Cargo.
7+
# RUST_LIBRARY Root of Rust's library source tree.
8+
# TARGET_DIR Cargo target directory.
9+
# OUTPUT_DIR Directory for stable-named rlibs declared to Bazel.
10+
# WORK_DIR Scratch directory for the generated Cargo package.
11+
# TARGET_TRIPLE Rust target triple.
12+
# SANITIZER rustc -Zsanitizer value.
13+
# CRATE... Standard-library crates declared as Bazel outputs.
14+
15+
set -euo pipefail
16+
17+
if [[ $# -lt 9 ]]; then
18+
echo "usage: $0 RUST_TOOLCHAIN_ROOT RUST_LIBRARY TARGET_DIR OUTPUT_DIR WORK_DIR TARGET_TRIPLE SANITIZER CRATE..." >&2
19+
exit 2
20+
fi
21+
22+
rust_root=$(realpath "$1")
23+
rust_library=$(realpath "$2")
24+
target_dir=$(realpath -m "$3")
25+
output_dir=$(realpath -m "$4")
26+
work=$(realpath -m "$5")
27+
target_triple=$6
28+
sanitizer=$7
29+
shift 7
30+
crates=("$@")
31+
32+
rm -rf "$target_dir" "$output_dir" "$work"
33+
mkdir -p "$work/src" "$work/cargo-home" "$output_dir"
34+
35+
cat > "$work/Cargo.toml" <<'EOF'
36+
[package]
37+
name = "workerd-build-std"
38+
version = "0.0.0"
39+
edition = "2024"
40+
41+
[profile.release]
42+
debug = true
43+
EOF
44+
45+
cat > "$work/src/lib.rs" <<'EOF'
46+
#[test]
47+
fn build_std() {}
48+
EOF
49+
50+
# rust-src includes the exact third-party sources selected by the standard
51+
# library's lockfile. Offline mode keeps this action hermetic.
52+
cat > "$work/cargo-home/config.toml" <<EOF
53+
[source.crates-io]
54+
replace-with = "vendored-sources"
55+
[source.vendored-sources]
56+
directory = "$rust_library/vendor"
57+
EOF
58+
59+
export PATH="$rust_root/bin:$PATH"
60+
export RUSTC="$rust_root/bin/rustc"
61+
export CARGO_HOME="$work/cargo-home"
62+
export CARGO_NET_OFFLINE=true
63+
export CARGO_TARGET_DIR="$target_dir"
64+
# Cargo has no public override for rust-src's location. This test-only hook is
65+
# what Cargo itself uses to exercise -Zbuild-std against a separate source tree.
66+
export __CARGO_TESTS_ONLY_SRC_ROOT="$rust_library"
67+
68+
# Cargo applies target-specific flags to the generated package and every
69+
# build-std dependency. The temporary test binary may use Rust's sanitizer
70+
# runtime; Bazel-built targets use -Zexternal-clangrt to share Clang's runtime
71+
# with C++ at their final link.
72+
env_name="CARGO_TARGET_${target_triple^^}_RUSTFLAGS"
73+
env_name=${env_name//-/_}
74+
export "$env_name=-Zsanitizer=$sanitizer -Zexternal-clangrt -Clinker=clang -Clink-arg=-fsanitize=$sanitizer"
75+
76+
"$rust_root/bin/cargo" test \
77+
--manifest-path "$work/Cargo.toml" \
78+
--no-run \
79+
--release \
80+
--target "$target_triple" \
81+
-Zbuild-std=std,panic_unwind,test \
82+
-Zbuild-std-features=backtrace,panic-unwind
83+
84+
deps="$target_dir/$target_triple/release/deps"
85+
copy_unique_rlib() {
86+
local crate=$1
87+
local destination=$2
88+
local sources=()
89+
mapfile -t sources < <(find "$deps" -maxdepth 1 -name "lib$crate-*.rlib" -print)
90+
if [[ ${#sources[@]} -ne 1 ]]; then
91+
echo "Cargo produced ${#sources[@]} rlibs for $crate; expected exactly one" >&2
92+
printf ' %s\n' "${sources[@]}" >&2
93+
exit 1
94+
fi
95+
cp "${sources[0]}" "$destination"
96+
}
97+
98+
for crate in "${crates[@]}"; do
99+
copy_unique_rlib "$crate" "$output_dir/lib${crate}_${crate}.rlib"
100+
done

src/rust/tsan-test/BUILD.bazel

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
load("@rules_rust//rust:defs.bzl", "rust_binary")
2+
load("@rules_shell//shell:sh_test.bzl", "sh_test")
3+
4+
rust_binary(
5+
name = "race",
6+
srcs = ["race.rs"],
7+
edition = "2024",
8+
target_compatible_with = select({
9+
"//build/platforms:sanitizer_thread": [],
10+
"//conditions:default": ["@platforms//:incompatible"],
11+
}),
12+
)
13+
14+
sh_test(
15+
name = "tsan-integration-test",
16+
srcs = ["verify.sh"],
17+
data = [
18+
":race",
19+
"//build/rust:tsan_rust_std",
20+
],
21+
target_compatible_with = select({
22+
"//build/platforms:sanitizer_thread": [],
23+
"//conditions:default": ["@platforms//:incompatible"],
24+
}),
25+
)

0 commit comments

Comments
 (0)