Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -243,16 +243,36 @@ build:sanitizer-common --copt="-fno-optimize-sibling-calls"
build:sanitizer-common --copt="-fno-omit-frame-pointer" --copt="-mno-omit-leaf-frame-pointer"
# Rust's sanitizer support requires nightly
build:sanitizer-common --@rules_rust//rust/toolchain/channel:channel=nightly
# Rust and C++ share Clang's sanitizer runtime, linked by the C++ driver.
build:sanitizer-common --@rules_rust//:extra_rustc_flag=-Zexternal-clangrt

# address sanitizer (https://github.com/google/sanitizers/wiki/AddressSanitizer)
#
# Instruments both C++ and Rust (nightly rustc with -Zsanitizer=address plus an ASan-built standard
# library, see //build/rust). Supported hosts: Linux x86_64 and aarch64, macOS arm64.
#
# Also enables UndefinedBehaviorSanitizer for C++ (https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html).
# Unlike ASan and TSan, UBSan can share a build with either, so it rides along here instead of
# needing its own CI lane. It instruments C++ only: rustc has no UBSan, and safe Rust already checks
# bounds and (in debug builds) integer overflow.
build:asan --config=sanitizer-common
build:asan --platforms=//build/platforms:sanitizer_address_platform
build:asan --copt="-fsanitize=address" --linkopt="-fsanitize=address"
build:asan --copt="-fsanitize=address,undefined" --linkopt="-fsanitize=address,undefined"
build:asan --test_env=ASAN_OPTIONS=abort_on_error=true
build:asan --test_env=UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
# capnp's lexer memcpy()s from a null source when a backtick-quoted line is empty, which the
# nonnull-attribute check reports. The tarball rule for capnp-cpp cannot carry patches, so drop
# that one check for that one file.
# TODO(cleanup): Remove once https://github.com/capnproto/capnproto/pull/2832 is merged and
# capnp-cpp has been bumped past it.
build:asan --per_file_copt='external/.*capnp-cpp/src/capnp/compiler/lexer[.]c[+][+]@-fno-sanitize=nonnull-attribute'
build:asan --test_env=KJ_CLEAN_SHUTDOWN=1
# Enable ASan, LSan support in V8
build:asan --copt="-DV8_USE_ADDRESS_SANITIZER"
build:asan --per_file_copt='external/.*v8@-DADDRESS_SANITIZER,-DLEAK_SANITIZER'
# One toolchain per host; resolution picks the one matching the (host-parented) asan platform.
build:asan --extra_toolchains=//build/rust:asan_toolchain_linux_x86_64,//build/rust:asan_toolchain_linux_aarch64,//build/rust:asan_toolchain_macos_aarch64
build:asan --@rules_rust//:extra_rustc_flag=-Zsanitizer=address

# thread sanitizer (https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual)
#
Expand All @@ -278,7 +298,6 @@ build:tsan-common --per_file_copt='external/.*v8@-DTHREAD_SANITIZER'
# One toolchain per host; resolution picks the one matching the (host-parented) tsan platform.
build:tsan-common --extra_toolchains=//build/rust:tsan_toolchain_linux_x86_64,//build/rust:tsan_toolchain_macos_aarch64
build:tsan-common --@rules_rust//:extra_rustc_flag=-Zsanitizer=thread
build:tsan-common --@rules_rust//:extra_rustc_flag=-Zexternal-clangrt

# Linux x86_64.
build:tsan --config=tsan-common
Expand Down
1 change: 1 addition & 0 deletions build/deps/rust.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ rust.toolchain(
use_repo(
rust,
"rust_toolchains",
rust_nightly_linux_aarch64 = "rust_linux_aarch64__aarch64-unknown-linux-gnu__nightly_tools",
rust_nightly_linux_x86_64 = "rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools",
rust_nightly_macos_aarch64 = "rust_macos_aarch64__aarch64-apple-darwin__nightly_tools",
)
Expand Down
60 changes: 29 additions & 31 deletions build/rust/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
load(":tsan_toolchain.bzl", "tsan_rust_toolchain")
load(":sanitizer_toolchain.bzl", "sanitizer_rust_toolchain")

exports_files(["build_std.sh"])

# One TSan toolchain per supported host. `--config=tsan` / `--config=tsan-macos` (.bazelrc) list
# all of them in --extra_toolchains; toolchain resolution picks the one matching the host.
# stdlib_linkflags mirror rules_rust's own generated toolchain for each triple.
tsan_rust_toolchain(
# One toolchain per sanitizer and supported host. `--config=asan` and `--config=tsan` /
# `--config=tsan-macos` (.bazelrc) list all of a sanitizer's toolchains in --extra_toolchains;
# toolchain resolution picks the one matching the host.

sanitizer_rust_toolchain(
name = "asan_toolchain_linux_aarch64",
host = "linux_aarch64",
sanitizer = "address",
)

sanitizer_rust_toolchain(
name = "asan_toolchain_linux_x86_64",
host = "linux_x86_64",
sanitizer = "address",
)

sanitizer_rust_toolchain(
name = "asan_toolchain_macos_aarch64",
host = "macos_aarch64",
sanitizer = "address",
)

sanitizer_rust_toolchain(
name = "tsan_toolchain_linux_x86_64",
cpu = "@platforms//cpu:x86_64",
dylib_ext = ".so",
extra_rustc_flags = ["-Ctarget-feature=+sse4.2,+pclmulqdq"],
os = "@platforms//os:linux",
rust_tools = "@rust_nightly_linux_x86_64",
stdlib_linkflags = [
"-ldl",
"-lpthread",
],
target_triple = "x86_64-unknown-linux-gnu",
host = "linux_x86_64",
sanitizer = "thread",
)

tsan_rust_toolchain(
sanitizer_rust_toolchain(
name = "tsan_toolchain_macos_aarch64",
cpu = "@platforms//cpu:aarch64",
dylib_ext = ".dylib",
# rustc passes -nodefaultlibs to the linker driver, and unlike Linux, Darwin's clang then also
# omits the sanitizer runtime that -Zexternal-clangrt (.bazelrc) relies on it to provide.
# Letting clang add its default libraries restores libclang_rt.tsan_osx_dynamic.dylib (and
# its rpath) at every Rust-driven link, so rust_binary/rust_test targets share Clang's
# runtime with the C++ they link, exactly as on Linux.
extra_rustc_flags = ["-Cdefault-linker-libraries=yes"],
os = "@platforms//os:macos",
rust_tools = "@rust_nightly_macos_aarch64",
stdlib_linkflags = [
"-lSystem",
"-lresolv",
],
target_triple = "aarch64-apple-darwin",
host = "macos_aarch64",
sanitizer = "thread",
)
9 changes: 8 additions & 1 deletion build/rust/build_std.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def _rust_build_std_impl(ctx):
output_dir + "-work",
ctx.attr.target_triple,
ctx.attr.sanitizer,
" ".join(ctx.attr.rustflags),
str(len(patches)),
] + [patch.path for patch in patches] + _STDLIB_CRATES,
inputs = depset(
Expand All @@ -102,6 +103,7 @@ rust_build_std = rule(
"rust_src": attr.label(mandatory = True, allow_files = True),
"rust_src_manifest": attr.label(mandatory = True, allow_single_file = True),
"rust_toolchain_files": attr.label_list(mandatory = True, allow_files = True),
"rustflags": attr.string_list(),
"sanitizer": attr.string(mandatory = True),
"target_triple": attr.string(mandatory = True),
"_build_std": attr.label(
Expand All @@ -123,17 +125,22 @@ def instrumented_rust_std(
target_triple,
rust_tools,
rust_src,
rustflags = [],
tags = None,
target_compatible_with = None,
visibility = None):
"""Defines a rules_rust standard library instrumented by `sanitizer`."""
"""Defines a rules_rust standard library instrumented by `sanitizer`.

`rustflags` are extra rustc flags every standard-library crate is compiled with.
"""
rust_build_std(
name = name + "_build",
cargo = rust_tools + "//:cargo",
rustc = rust_tools + "//:rustc",
rust_src = rust_src + "//:rust_src",
rust_src_manifest = rust_src + "//:library/Cargo.toml",
rust_toolchain_files = [rust_tools + "//:rustc_lib"],
rustflags = rustflags,
sanitizer = sanitizer,
target_triple = target_triple,
tags = tags,
Expand Down
12 changes: 7 additions & 5 deletions build/rust/build_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
# WORK_DIR Scratch directory for the generated Cargo package.
# TARGET_TRIPLE Rust target triple.
# SANITIZER rustc -Zsanitizer value.
# EXTRA_RUSTFLAGS Space-separated additional rustc flags (may be empty).
# PATCH_COUNT Number of standard-library patch paths that follow.
# PATCH... Patches to apply to the Rust standard-library sources.
# CRATE... Standard-library crates declared as Bazel outputs.

set -euo pipefail

if [[ $# -lt 9 ]]; then
echo "usage: $0 RUST_TOOLCHAIN_ROOT RUST_LIBRARY TARGET_DIR OUTPUT_DIR WORK_DIR TARGET_TRIPLE SANITIZER PATCH_COUNT [PATCH...] CRATE..." >&2
if [[ $# -lt 10 ]]; then
echo "usage: $0 RUST_TOOLCHAIN_ROOT RUST_LIBRARY TARGET_DIR OUTPUT_DIR WORK_DIR TARGET_TRIPLE SANITIZER EXTRA_RUSTFLAGS PATCH_COUNT [PATCH...] CRATE..." >&2
exit 2
fi

Expand All @@ -37,8 +38,9 @@ output_dir=$(abs_path "$4")
work=$(abs_path "$5")
target_triple=$6
sanitizer=$7
patch_count=$8
shift 8
extra_rustflags=$8
patch_count=$9
shift 9
if ! [[ $patch_count =~ ^[0-9]+$ ]] || ((patch_count > $#)); then
echo "invalid PATCH_COUNT: $patch_count" >&2
exit 2
Expand Down Expand Up @@ -106,7 +108,7 @@ export __CARGO_TESTS_ONLY_SRC_ROOT="$rust_library"
# action only builds rlibs; sanitizer runtime selection is deferred to the final Bazel link, where
# Rust and C++ share Clang's runtime.
env_name="CARGO_TARGET_$(printf '%s' "$target_triple" | tr '[:lower:]-' '[:upper:]_')_RUSTFLAGS"
export "$env_name=-Zsanitizer=$sanitizer"
export "$env_name=-Zsanitizer=$sanitizer${extra_rustflags:+ $extra_rustflags}"

"$rust_root/bin/cargo" build \
--manifest-path "$work/Cargo.toml" \
Expand Down
149 changes: 149 additions & 0 deletions build/rust/sanitizer_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Copyright (c) 2026 Cloudflare, Inc.
# Licensed under the Apache 2.0 license found in the LICENSE file or at:
# https://opensource.org/licenses/Apache-2.0

"""Per-host sanitizer-instrumented Rust toolchains (see //build/rust:BUILD.bazel)."""

load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
load("//build/deps:gen/build_deps.bzl", "RUST_NIGHTLY_DATE", "RUST_NIGHTLY_VERSION")
load(":build_std.bzl", "compiler_runtime_files", "instrumented_rust_std")

# rustc -Zsanitizer value => (runtime library stem, platform constraint).
_SANITIZERS = {
"address": ("asan", "//build/platforms:sanitizer_address"),
"thread": ("tsan", "//build/platforms:sanitizer_thread"),
}

# stdlib_linkflags mirror rules_rust's own generated toolchain for each triple, and
# extra_rustc_flags mirror rust.toolchain's extra_rustc_flags_triples (//build/deps:rust.MODULE.bazel).
_HOSTS = {
"linux_aarch64": struct(
cpu = "@platforms//cpu:aarch64",
dylib_ext = ".so",
extra_rustc_flags = ["-Ctarget-feature=+crc"],
os = "@platforms//os:linux",
rust_tools = "@rust_nightly_linux_aarch64",
sanitizer_rustc_flags = {},
stdlib_linkflags = [
"-ldl",
"-lpthread",
],
target_triple = "aarch64-unknown-linux-gnu",
),
"linux_x86_64": struct(
cpu = "@platforms//cpu:x86_64",
dylib_ext = ".so",
extra_rustc_flags = ["-Ctarget-feature=+sse4.2,+pclmulqdq"],
os = "@platforms//os:linux",
rust_tools = "@rust_nightly_linux_x86_64",
sanitizer_rustc_flags = {},
stdlib_linkflags = [
"-ldl",
"-lpthread",
],
target_triple = "x86_64-unknown-linux-gnu",
),
"macos_aarch64": struct(
cpu = "@platforms//cpu:aarch64",
dylib_ext = ".dylib",
# rustc passes -nodefaultlibs to the linker driver, and unlike Linux, Darwin's clang then
# also omits the sanitizer runtime that -Zexternal-clangrt (.bazelrc) relies on it to
# provide. Letting clang add its default libraries restores
# libclang_rt.<sanitizer>_osx_dynamic.dylib (and its rpath) at every Rust-driven link, so
# rust_binary/rust_test targets share Clang's runtime with the C++ they link, exactly as on
# Linux.
extra_rustc_flags = ["-Cdefault-linker-libraries=yes"],
os = "@platforms//os:macos",
rust_tools = "@rust_nightly_macos_aarch64",
sanitizer_rustc_flags = {
# The C++ toolchain is Apple Clang, whose ASan runtime exports
# __asan_version_mismatch_check_apple_clang_<version> in place of upstream LLVM's
# __asan_version_mismatch_check_v8 that rustc's module constructors reference.
"address": ["-Cllvm-args=-asan-guard-against-version-mismatch=false"],
},
stdlib_linkflags = [
"-lSystem",
"-lresolv",
],
target_triple = "aarch64-apple-darwin",
),
}

def sanitizer_rust_toolchain(name, sanitizer, host):
"""One host platform's sanitizer-instrumented Rust toolchain.

Defines `<name>_rustc_lib`, `<name>_std` (the instrumented standard library),
`<name>_impl` (the rust_toolchain) and `<name>` (the registrable toolchain),
all constrained to the host's cpu/os plus the sanitizer's platform constraint.

Args:
name: toolchain name.
sanitizer: rustc -Zsanitizer value, "address" or "thread".
host: key of _HOSTS.
"""
runtime, constraint = _SANITIZERS[sanitizer]
h = _HOSTS[host]
host_constraints = [h.cpu, h.os]
sanitizer_rustc_flags = h.sanitizer_rustc_flags.get(sanitizer, [])

compiler_runtime_files(
name = name + "_rustc_lib",
src = h.rust_tools + "//:rustc_lib",
sanitizer = runtime,
tags = ["manual"],
target_compatible_with = host_constraints,
)

instrumented_rust_std(
name = name + "_std",
rust_src = "@rust_nightly_src",
rust_tools = h.rust_tools,
rustflags = sanitizer_rustc_flags,
sanitizer = sanitizer,
tags = ["manual"],
target_compatible_with = host_constraints + [constraint],
target_triple = h.target_triple,
)

rust_toolchain(
name = name + "_impl",
allocator_library = "@rules_rust//ffi/rs:empty",
binary_ext = "",
cargo = h.rust_tools + "//:cargo",
cargo_clippy = h.rust_tools + "//:cargo_clippy_bin",
channel = "nightly",
clippy_driver = h.rust_tools + "//:clippy_driver_bin",
default_edition = "2024",
dylib_ext = h.dylib_ext,
exec_triple = h.target_triple,
extra_exec_rustc_flags = [],
extra_rustc_flags = h.extra_rustc_flags + sanitizer_rustc_flags,
iso_date = RUST_NIGHTLY_DATE,
linker = h.rust_tools + "//:rust-lld",
linker_type = "direct",
llvm_cov = h.rust_tools + "//:llvm_cov_bin",
llvm_lib = h.rust_tools + "//:llvm_lib",
llvm_profdata = h.rust_tools + "//:llvm_profdata_bin",
rust_doc = h.rust_tools + "//:rustdoc",
rust_objcopy = h.rust_tools + "//:rust-objcopy",
rust_std = ":" + name + "_std",
rustc = h.rust_tools + "//:rustc",
rustc_lib = ":" + name + "_rustc_lib",
rustfmt = h.rust_tools + "//:rustfmt_bin",
staticlib_ext = ".a",
stdlib_linkflags = h.stdlib_linkflags,
tags = ["manual"],
target_compatible_with = host_constraints + [constraint],
target_triple = h.target_triple,
version = RUST_NIGHTLY_VERSION,
)

native.toolchain(
name = name,
exec_compatible_with = host_constraints,
tags = ["manual"],
target_compatible_with = host_constraints + [constraint],
target_settings = ["@rules_rust//rust/toolchain/channel:nightly"],
toolchain = ":" + name + "_impl",
toolchain_type = "@rules_rust//rust:toolchain",
)
Loading
Loading