Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ build:ferrocene-coverage --remote_download_all
# to be removed
build:build_qnx8 --config=arm64-qnx

import %workspace%/quality/sanitizer/sanitizer.bazelrc

## default is a stdout logger which looks like dlt logs
## uncomment below to use score::mw::log instead of the stdout logger
# build --cxxopt=-DLC_LOG_SCORE_MW_LOG
52 changes: 52 additions & 0 deletions .github/workflows/address_undefined_behavior_leak_sanitizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
name: Test with address, UB, and leak sanitizer

on:
pull_request:
types: [opened, reopened, synchronize]
merge_group:
types: [checks_requested]
push:
branches:
- main

concurrency:
group: asan_ubsan_lsan-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build_and_test_asan_ubsan_lsan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.18.0
with:
bazelisk-cache: true
disk-cache: build_and_test_asan_ubsan_lsan
repository-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Bazel info (discover paths)
run: |
echo "BAZEL_OUTPUT_BASE=$(bazel info output_base)" >> $GITHUB_ENV
bazel info

- name: Run tests with ASan + UBSan + LSan
run: |
bazel test --lockfile_mode=error --config=x86_64-linux --config=asan_ubsan_lsan \
--test_output=errors \
//... --build_tests_only
52 changes: 52 additions & 0 deletions .github/workflows/thread_sanitizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
name: Test with thread sanitizer

on:
pull_request:
types: [opened, reopened, synchronize]
merge_group:
types: [checks_requested]
push:
branches:
- main

concurrency:
group: tsan-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build_and_test_tsan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.18.0
with:
bazelisk-cache: true
disk-cache: build_and_test_tsan
repository-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Bazel info (discover paths)
run: |
echo "BAZEL_OUTPUT_BASE=$(bazel info output_base)" >> $GITHUB_ENV
bazel info

- name: Run tests with TSan
run: |
bazel test --lockfile_mode=error --config=x86_64-linux --config=tsan \
--test_output=errors \
//... --build_tests_only
15 changes: 0 additions & 15 deletions config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,3 @@ config_setting(
"config": "host",
},
)

config_setting(
name = "thread_sanitizer_enabled",
define_values = {"sanitize": "thread"},
)

config_setting(
name = "address_sanitizer_enabled",
define_values = {"sanitize": "address"},
)

config_setting(
name = "ub_sanitizer_enabled",
define_values = {"sanitize": "undefined"},
)
21 changes: 21 additions & 0 deletions config/common_cc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# *******************************************************************************
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//config:common_opts.bzl", "SANITIZER_CXXOPTS", "SANITIZER_LINKOPTS")

_def_or_empty = lambda v: v if v else []
Expand All @@ -38,6 +39,26 @@ def cc_library_with_common_opts(
**kwargs
)

def cc_test_with_common_opts(
name,
srcs = None,
deps = None,
cxxopts = None,
linkopts = None,
omit_common_cxxopts = False,
omit_common_linkopts = False,
**kwargs):
base_cxx = [] if omit_common_cxxopts else SANITIZER_CXXOPTS
base_link = [] if omit_common_linkopts else SANITIZER_LINKOPTS
cc_test(
name = name,
srcs = _def_or_empty(srcs),
deps = _def_or_empty(deps),
cxxopts = base_cxx + _def_or_empty(cxxopts),
linkopts = base_link + _def_or_empty(linkopts),
**kwargs
)

def cc_binary_with_common_opts(
name,
srcs = None,
Expand Down
23 changes: 6 additions & 17 deletions config/common_opts.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,10 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
SANITIZER_CXXOPTS = select({
"//config:thread_sanitizer_enabled": ["-fsanitize=thread", "-O0", "-g"],
"//config:ub_sanitizer_enabled": ["-fsanitize=undefined", "-O0", "-g"],
"//config:address_sanitizer_enabled": [
"-fsanitize=address",
"-fno-omit-frame-pointer",
"-O1",
"-g",
],
"//conditions:default": [],
})

SANITIZER_LINKOPTS = select({
"//config:thread_sanitizer_enabled": ["-fsanitize=thread"],
"//config:ub_sanitizer_enabled": ["-fsanitize-link-c++-runtime", "-fsanitize=undefined"],
"//config:address_sanitizer_enabled": ["-fsanitize=address"],
"//conditions:default": [],
})
# Sanitizer flags are applied globally via quality/sanitizer/sanitizer.bazelrc
# using --cxxopt / --linkopt, which instruments all C++ targets in the build.
# These constants are kept as empty lists for backward compatibility with
# cc_*_with_common_opts wrappers, which pass them through to each target.
SANITIZER_CXXOPTS = []
SANITIZER_LINKOPTS = []
59 changes: 59 additions & 0 deletions quality/sanitizer/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Selects the env template matching the active sanitizer.
filegroup(
name = "env_template",
srcs = select({
"//quality/sanitizer/flags:tsan": ["tsan.env.template"],
"//quality/sanitizer/flags:asan_ubsan_lsan": ["asan_ubsan_lsan.env.template"],
}),
target_compatible_with = ["//quality/sanitizer/constraints:any_sanitizer"],
)

# Generates the env file with %ROOT% substituted for a relative path ("./").
# Used by wrapper.sh at test runtime to locate suppression files.
genrule(
name = "relative_env",
srcs = [":env_template"],
outs = ["relative_sanitizer.env"],
cmd = "sed 's|%ROOT%|./|g' $(location :env_template) > $@",
target_compatible_with = ["//quality/sanitizer/constraints:any_sanitizer"],
visibility = ["//:__subpackages__"],
)

# Bundles the suppression files that match the active sanitizer.
filegroup(
name = "suppressions",
srcs = select({
"//quality/sanitizer/flags:tsan": ["tsan.supp"],
"//quality/sanitizer/flags:asan_ubsan_lsan": [
"asan.supp",
"lsan.supp",
"ubsan.supp",
],
}),
target_compatible_with = ["//quality/sanitizer/constraints:any_sanitizer"],
)

# Shell wrapper used as --run_under in sanitizer test configs.
# Exports sanitizer runtime options before executing the test binary.
sh_binary(
name = "wrapper",
srcs = ["wrapper.sh"],
data = [
":relative_env",
":suppressions",
],
target_compatible_with = ["//quality/sanitizer/constraints:any_sanitizer"],
)
17 changes: 17 additions & 0 deletions quality/sanitizer/asan.supp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Suppressions for the AddressSanitizer
# See: https://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression
# Every suppression requires a justification.
# Suppressions that share the same justification may be organized in a single block.
3 changes: 3 additions & 0 deletions quality/sanitizer/asan_ubsan_lsan.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ASAN_OPTIONS=exitcode=55 allow_addr2line=1 detect_stack_use_after_return=1 halt_on_error=0 suppressions=%ROOT%quality/sanitizer/asan.supp
UBSAN_OPTIONS=exitcode=55 print_stacktrace=1 halt_on_error=1 suppressions=%ROOT%quality/sanitizer/ubsan.supp
LSAN_OPTIONS=exitcode=55 suppressions=%ROOT%quality/sanitizer/lsan.supp
55 changes: 55 additions & 0 deletions quality/sanitizer/constraints/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# A dummy constraint setting whose default value is always satisfied by any
# platform. The aliases below resolve to either :always_true (compatible) or
# @platforms//:incompatible depending on the active sanitizer flag, giving
# targets a way to express sanitizer-only or sanitizer-excluded compatibility.

constraint_setting(
name = "dummy_setting",
default_constraint_value = ":always_true",
)

constraint_value(
name = "always_true",
constraint_setting = ":dummy_setting",
)

alias(
name = "any_sanitizer",
actual = select({
"//quality/sanitizer/flags:asan_ubsan_lsan": ":always_true",
"//quality/sanitizer/flags:tsan": ":always_true",
"//conditions:default": "@platforms//:incompatible",
}),
visibility = ["//:__subpackages__"],
)

alias(
name = "no_asan_ubsan_lsan",
actual = select({
"//quality/sanitizer/flags:asan_ubsan_lsan": "@platforms//:incompatible",
"//conditions:default": ":always_true",
}),
visibility = ["//:__subpackages__"],
)

alias(
name = "no_tsan",
actual = select({
"//quality/sanitizer/flags:tsan": "@platforms//:incompatible",
"//conditions:default": ":always_true",
}),
visibility = ["//:__subpackages__"],
)
27 changes: 27 additions & 0 deletions quality/sanitizer/flags/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Sanitizer flag: set via --define sanitizer=<value> in .bazelrc configs.
# Valid values: "asan_ubsan_lsan", "tsan"

config_setting(
name = "asan_ubsan_lsan",
define_values = {"sanitizer": "asan_ubsan_lsan"},
visibility = ["//:__subpackages__"],
)

config_setting(
name = "tsan",
define_values = {"sanitizer": "tsan"},
visibility = ["//:__subpackages__"],
)
24 changes: 24 additions & 0 deletions quality/sanitizer/lsan.supp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Suppressions for the LeakSanitizer
# See: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions
# Every suppression requires a justification.
# Suppressions that share the same justification may be organized in a single block.

# Rust's standard runtime (std::rt::lang_start) allocates thread-local storage (TLS) that is not
# cleaned up at the time LSan runs its atexit sweep. LSan reports this as a memory leak because
# TLS destructors are invoked after LSan's atexit handler, so the memory appears live but
# unreachable at leak-check time. This affects all Rust binaries regardless of async runtime used.
# Rust sanitizer support upstream: https://github.com/rust-lang/rust/issues/39699.
leak:std::rt::lang_start
Loading
Loading