From d2afe00f7b6ad03488e08316f06f95f356f7edc5 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender <55576758+jschwe@users.noreply.github.com> Date: Sun, 11 May 2025 09:27:40 +0200 Subject: [PATCH] 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 --- Cargo.lock | 6 ++-- python/servo/build_commands.py | 58 ++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12433965aa8..0dee2b21944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4645,7 +4645,7 @@ dependencies = [ [[package]] name = "mozjs" version = "0.14.1" -source = "git+https://github.com/servo/mozjs#d1525dfaee22cc1ea9ee16c552cdeedaa9f20741" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", @@ -4656,8 +4656,8 @@ dependencies = [ [[package]] name = "mozjs_sys" -version = "0.128.9-1" -source = "git+https://github.com/servo/mozjs#d1525dfaee22cc1ea9ee16c552cdeedaa9f20741" +version = "0.128.9-2" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index b21f89980c0..42988debf35 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -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