Extend --with-asan to support C/C++ code (#36873)

`--with-asan` now also will instrument C/C++ code. This also increases
the requirements on the user environment, since a clang compiler with
the same major version as the LLVM version rustc is using must be in
PATH.
ASAN without C/C++ code is IMHO not so interesting, so I'm not sure if
it would be worth adding a separate flag.

Testing: Manual testing with `--with-asan`. I believe we don't run ASAN
in CI yet, although perhaps we should.

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-05-11 09:27:40 +02:00 committed by GitHub
parent e702bde9bf
commit d2afe00f7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 9 deletions

View file

@ -13,10 +13,11 @@ import os.path as path
import pathlib
import shutil
import stat
import subprocess
import sys
from time import time
from typing import Optional
from typing import Optional, List
import notifypy
@ -40,6 +41,32 @@ SUPPORTED_ASAN_TARGETS = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu",
"x86_64-apple-darwin", "x86_64-unknown-linux-gnu"]
def get_rustc_llvm_version() -> Optional[List[int]]:
"""Determine the LLVM version of `rustc` and return it as a List[major, minor, patch, ...]
In some cases we want to ensure that the LLVM version of rustc and clang match, e.g.
when using ASAN for both C/C++ and Rust code, we want to use the same ASAN implementation.
This function assumes that rustc points to the rust compiler we are interested in, which should
be valid in both rustup managed environment and on nix.
"""
try:
result = subprocess.run(['rustc', '--version', '--verbose'], encoding='utf-8', capture_output=True)
result.check_returncode()
for line in result.stdout.splitlines():
line_lowercase = line.lower()
if line_lowercase.startswith("llvm version:"):
llvm_version = line_lowercase.strip("llvm version:")
llvm_version = llvm_version.strip()
version = llvm_version.split('.')
print(f"Info: rustc is using LLVM version {'.'.join(version)}")
return version
else:
print(f"Error: Couldn't find LLVM version in output of `rustc --version --verbose`: `{result.stdout}`")
except Exception as e:
print(f"Error: Failed to determine rustc version: {e}")
return None
@CommandProvider
class MachCommands(CommandBase):
@Command('build', description='Build Servo', category='build')
@ -99,11 +126,30 @@ class MachCommands(CommandBase):
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -Zsanitizer=address"
opts += ["-Zbuild-std"]
kwargs["target_override"] = target_triple
# TODO: Investigate sanitizers in C/C++ code:
# env.setdefault("CFLAGS", "")
# env.setdefault("CXXFLAGS", "")
# env["CFLAGS"] += " -fsanitize=address"
# env["CXXFLAGS"] += " -fsanitize=address"
# Note: We want to use the same clang/LLVM version as rustc.
rustc_llvm_version = get_rustc_llvm_version()
if rustc_llvm_version is None:
raise RuntimeError("Unable to determine necessary clang version for ASAN support")
llvm_major: int = rustc_llvm_version[0]
target_clang = f"clang-{llvm_major}"
target_cxx = f"clang++-{llvm_major}"
if shutil.which(target_clang) is None or shutil.which(target_cxx) is None:
raise RuntimeError(f"--with-asan requires `{target_clang}` and `{target_cxx}` to be in PATH")
env.setdefault("TARGET_CC", target_clang)
env.setdefault("TARGET_CXX", target_cxx)
# TODO: We should also parse the LLVM version from the clang compiler we chose.
# It's unclear if the major version being the same is sufficient.
# We need to use `TARGET_CFLAGS`, since we don't want to compile host dependencies with ASAN,
# since that causes issues when building build-scripts / proc macros.
env.setdefault("TARGET_CFLAGS", "")
env.setdefault("TARGET_CXXFLAGS", "")
env["TARGET_CFLAGS"] += " -fsanitize=address"
env["TARGET_CXXFLAGS"] += " -fsanitize=address"
env["TARGET_LDFLAGS"] = "-static-libasan"
# By default build mozjs from source to enable ASAN with mozjs.
env.setdefault("MOZJS_FROM_SOURCE", "1")
# asan replaces system allocator with asan allocator
# we need to make sure that we do not replace it with jemalloc