diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc93821..a7edc44 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: test-sanitizers: name: Test ${{ matrix.config }} runs-on: ubuntu-24.04 - + strategy: fail-fast: false matrix: @@ -48,7 +48,7 @@ jobs: description: "ThreadSanitizer" - config: tsan_ubsan description: "TSan + UBSan" - + steps: - name: Checkout uses: actions/checkout@v6 @@ -99,6 +99,11 @@ jobs: compile_target: feature_injection_tsan positive_target: feature_injection_positive runtime_target: feature_injection_tsan_runtime + - label: tysan + config: feature_only_tysan + compile_target: feature_injection_tysan + positive_target: feature_injection_positive + runtime_target: feature_injection_tysan_runtime - label: asan+ubsan+lsan config: feature_only_asan_ubsan_lsan compile_target: feature_injection_asan_ubsan_lsan diff --git a/README.md b/README.md index 20ca257..7c76e69 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Planned: clang-format, code coverage policies. ## What This Provides -- **[`sanitizers/`](sanitizers/README.md)** — ASan/UBSan/LSan/TSan Bazel `cc_feature`s, ready-to-use `--config=` aliases, suppression files, and `target_compatible_with` constraints. +- **[`sanitizers/`](sanitizers/README.md)** — ASan/UBSan/LSan/TSan/TySan Bazel `cc_feature`s, ready-to-use `--config=` aliases, suppression files, and `target_compatible_with` constraints. - **[`clang_tidy/`](clang_tidy/README.md)** — centralized `.clang-tidy` baseline (conservative, tailorable per module) and a `--config=clang-tidy` Bazel integration. ## Sanitizers @@ -19,9 +19,13 @@ Planned: clang-format, code coverage policies. | `--config=ubsan` | UndefinedBehaviorSanitizer | Integer overflow, null deref | | `--config=lsan` | LeakSanitizer | Memory leaks | | `--config=tsan` | ThreadSanitizer | Data races, deadlocks — cannot combine with ASan/LSan | +| `--config=tysan` | TypeSanitizer | Type confusion — Clang/LLVM only; cannot combine with ASan/LSan/TSan | | `--config=asan_ubsan_lsan` | ASan + UBSan + LSan | **Recommended default for CI** | | `--config=tsan_ubsan` | TSan + UBSan | Threading + undefined behavior | +TySan is experimental and still under development. Run it separately from UBSan +for now. + ## Sanitizer Combination Compatibility | Combination | Valid? | Notes | @@ -31,13 +35,16 @@ Planned: clang-format, code coverage policies. | TSan + UBSan | ✅ Yes | Use `--config=tsan_ubsan` | | ASan + TSan | ❌ No | Incompatible runtime libraries (`libasan` vs `libtsan`) | | LSan + TSan | ❌ No | TSan has built-in leak detection; enabling both causes runtime conflicts | +| TySan + ASan | ❌ No | Incompatible shadow-memory runtimes | +| TySan + LSan | ❌ No | Incompatible shadow-memory runtimes | +| TySan + TSan | ❌ No | Incompatible shadow-memory runtimes | Invalid combinations are enforced at three layers, strongest first: 1. **Feature level (primary)** — the sanitizer `cc_feature`s declare - `mutually_exclusive` categories (`asan_tsan`, `lsan_tsan`), so - enabling `score_asan`+`score_tsan` or `score_lsan`+`score_tsan` through the - toolchain fails at **analysis time** with an explicit error + `mutually_exclusive` categories (`asan_tsan`, `lsan_tsan`, + `asan_tysan`, `lsan_tysan`, `tsan_tysan`), so enabling any invalid pair + through the toolchain fails at **analysis time** with an explicit error (`Symbol ...:asan_tsan is provided by all of the following features: score_asan score_tsan`). This protection is intrinsic to feature resolution and applies to every consumer automatically — no extra build-graph dependency required. diff --git a/docs/migration-sanitizers.md b/docs/migration-sanitizers.md index 87e6d5d..8af20ea 100644 --- a/docs/migration-sanitizers.md +++ b/docs/migration-sanitizers.md @@ -16,7 +16,7 @@ The repository tests currently use these versions: - `score_bazel_cpp_toolchains` 1.0.2 - `rules_cc` 0.2.17 -- `toolchains_llvm` 1.7.0 +- `toolchains_llvm` 1.8.0 - GCC 12.2.0 and 15.3.0 - Clang 19.1.7 @@ -54,6 +54,7 @@ aliases through [`sanitizers.bazelrc`](../sanitizers/sanitizers.bazelrc): | UndefinedBehaviorSanitizer | `--config=ubsan` | | LeakSanitizer | `--config=lsan` | | ThreadSanitizer | `--config=tsan` | +| TypeSanitizer | `--config=tysan` (Clang/LLVM only) | | ASan + UBSan + LSan | `--config=asan_ubsan_lsan` | | TSan + UBSan | `--config=tsan_ubsan` | @@ -75,6 +76,7 @@ the compiler-appropriate UBSan variant: | UBSan Clang variant | `@score_cpp_policies//sanitizers/features:ubsan_clang` | `score_ubsan_clang` | Clang | | LeakSanitizer | `@score_cpp_policies//sanitizers/features:lsan` | `score_lsan` | GCC and Clang | | ThreadSanitizer | `@score_cpp_policies//sanitizers/features:tsan` | `score_tsan` | GCC and Clang | +| TypeSanitizer | `@score_cpp_policies//sanitizers/features:tysan` | `score_tysan` | Clang/LLVM only | The former aggregate GCC features `asan_ubsan_lsan_gcc` and `tsan_gcc` are removed. Replace them with the per-sanitizer labels above. In particular, @@ -110,10 +112,14 @@ llvm.toolchain( "@score_cpp_policies//sanitizers/features:ubsan_clang", "@score_cpp_policies//sanitizers/features:lsan", "@score_cpp_policies//sanitizers/features:tsan", + "@score_cpp_policies//sanitizers/features:tysan", ], ) ``` +The `tysan` feature is for Clang/LLVM only. It injects `-fsanitize=type`; do +not register it with a GCC toolchain. + ### Dependency and Bazel configuration Add `score_cpp_policies` to the consuming workspace: @@ -143,12 +149,21 @@ Supported combinations are: | TSan | `--config=tsan` | | ASan + UBSan + LSan | `--config=asan_ubsan_lsan` | | TSan + UBSan | `--config=tsan_ubsan` | +| TySan | `--config=tysan` (Clang/LLVM only) | | ASan + LSan | `--config=asan --config=lsan` | ASan + TSan and LSan + TSan are invalid because their runtime libraries are -incompatible; TSan also provides its own leak detection. The feature layer -declares these pairs mutually exclusive, and the flag layer provides a -secondary combination check. Do not create configs for these pairs. +incompatible; TSan also provides its own leak detection. TySan is incompatible +with ASan, LSan, and TSan because their shadow-memory runtimes cannot be +combined. The feature layer declares these pairs mutually exclusive, and the +flag layer provides a secondary combination check. Do not create configs for +these pairs. + +TySan is experimental and still under development. Run it separately from UBSan +for now. + +TySan runtime options are loaded through `TYSAN_OPTIONS`, including the policy +suppression file at `sanitizers/suppressions/tysan.supp`. ### Constraints and duplicate flags @@ -175,10 +190,13 @@ policy's feature composition and runtime checks. `--config=tsan`. 4. Replace registrations of `asan_ubsan_lsan_gcc` and `tsan_gcc` with the per-sanitizer labels, including the matching `ubsan_gcc` or `ubsan_clang`. -5. Remove duplicate sanitizer flags from project options and custom features. -6. Review `no_asan_ubsan_lsan` uses and replace them with granular constraints +5. For Clang/LLVM consumers using TypeSanitizer, register + `@score_cpp_policies//sanitizers/features:tysan` and use `--config=tysan`. +6. Remove duplicate sanitizer flags from project options and custom features. +7. Review `no_asan_ubsan_lsan` uses and replace them with granular constraints where that expresses the intended compatibility rule. -7. Choose a supported config, run the validation commands below, and fix any +8. Review `no_tysan` and `only_tysan` uses when migrating TySan-specific tests. +9. Choose a supported config, run the validation commands below, and fix any sanitizer or runtime issues exposed by the new policy. ## Validation diff --git a/sanitizers/BUILD.bazel b/sanitizers/BUILD.bazel index 6b215f0..d8092b8 100644 --- a/sanitizers/BUILD.bazel +++ b/sanitizers/BUILD.bazel @@ -24,6 +24,7 @@ filegroup( "suppressions/asan.supp", "suppressions/lsan.supp", "suppressions/tsan.supp", + "suppressions/tysan.supp", "suppressions/ubsan.supp", ], visibility = ["//visibility:public"], @@ -34,6 +35,7 @@ _SANITIZERS = { "ubsan": "templates/ubsan.env.template", "lsan": "templates/lsan.env.template", "tsan": "templates/tsan.env.template", + "tysan": "templates/tysan.env.template", } _ROOTS = { @@ -81,6 +83,11 @@ sh_binary( ":tsan_relative_env", ], "//conditions:default": [], + }) + select({ + "//sanitizers/flags:tysan_on": [ + ":tysan_relative_env", + ], + "//conditions:default": [], }), target_compatible_with = ["//sanitizers/constraints:any_sanitizer"], visibility = ["//visibility:public"], diff --git a/sanitizers/README.md b/sanitizers/README.md index a682e0f..7d6e52d 100644 --- a/sanitizers/README.md +++ b/sanitizers/README.md @@ -2,7 +2,7 @@ Centralized sanitizer infrastructure for S-CORE C++ modules. -Each sanitizer (ASan, UBSan, LSan, TSan) is independently configurable via a +Each sanitizer (ASan, UBSan, LSan, TSan, TySan) is independently configurable via a dedicated Bazel config flag. Sanitizers can be used in isolation or combined. --- @@ -46,15 +46,21 @@ bazel test --config=asan_ubsan_lsan //your/target/... # Run tests with TSan + UBSan bazel test --config=tsan_ubsan //your/target/... + +# Run tests with TypeSanitizer (Clang/LLVM only) +bazel test --config=tysan //your/target/... ``` +TySan is experimental and still under development. Run it separately from UBSan +for now. + --- ## Architecture ``` sanitizers/ -├── sanitizers.bazelrc # --config=asan/ubsan/lsan/tsan/asan_ubsan_lsan/tsan_ubsan +├── sanitizers.bazelrc # --config=asan/ubsan/lsan/tsan/tysan/asan_ubsan_lsan/tsan_ubsan ├── flags/ # bool_flag per sanitizer; config_setting_group for combinations ├── features/ # cc_feature per sanitizer (score_asan, score_ubsan, ...) ├── constraints/ # no_*/only_* target_compatible_with aliases @@ -66,12 +72,12 @@ sanitizers/ ### `flags/` — Build-time boolean flags -One `bool_flag` per sanitizer (`asan`, `ubsan`, `lsan`, `tsan`) and corresponding +One `bool_flag` per sanitizer (`asan`, `ubsan`, `lsan`, `tsan`, `tysan`) and corresponding `config_setting`s (`asan_on`, `ubsan_on`, ...). Composite groups: | Group | Meaning | |---|---| -| `any_sanitizer` | True if any of the four flags is set | +| `any_sanitizer` | True if any of the five flags is set | | `any_asan_ubsan_lsan` | True if ASan **or** UBSan **or** LSan is set | | `asan_ubsan_lsan` | True only if **all three** of ASan, UBSan, LSan are set | @@ -92,6 +98,7 @@ toolchain built-in feature names: | `ubsan_clang` | `score_ubsan_clang` | Clang | implies `ubsan_base` + `-fsanitize-link-c++-runtime` (link) | | `lsan` | `score_lsan` | both | `-fsanitize=leak` | | `tsan` | `score_tsan` | both | `-fsanitize=thread`, `-O1` | +| `tysan` | `score_tysan` | Clang/LLVM | `-fsanitize=type` | | `debug_symbols` | `debug_symbols` | both | `-g1` | The `with_debug_symbols` config adds `--strip=never`. Each sanitizer feature @@ -108,13 +115,18 @@ Register the toolchain-appropriate target (`ubsan_gcc` or `ubsan_clang`) in `ext #### Mutually exclusive runtimes (primary enforcement) TSan uses a different runtime library than ASan/LSan, so those pairs cannot be -enabled together. This incompatibility is modeled directly in the feature layer -via `cc_mutually_exclusive_category` targets: +enabled together. TySan is also incompatible with ASan, LSan, and TSan because +their shadow-memory runtimes cannot be combined. These incompatibilities are +modeled directly in the feature layer via `cc_mutually_exclusive_category` +targets: | Category | Members | |---|---| | `asan_tsan` | `score_asan`, `score_tsan` | | `lsan_tsan` | `score_lsan`, `score_tsan` | +| `asan_tysan` | `score_asan`, `score_tysan` | +| `lsan_tysan` | `score_lsan`, `score_tysan` | +| `tsan_tysan` | `score_tsan`, `score_tysan` | When a toolchain enables both features in a mutually exclusive category, Bazel fails at **analysis time** with an explicit error, e.g.: @@ -172,11 +184,13 @@ Available constraints: | `no_ubsan` | Skip when UBSan is active | | `no_lsan` | Skip when LSan is active | | `no_tsan` | Skip when TSan is active | +| `no_tysan` | Skip when TySan is active | | `no_asan_ubsan_lsan` | Skip when **any** of ASan, UBSan, or LSan is active (see note below) | | `only_asan` | Only run when ASan is active | | `only_ubsan` | Only run when UBSan is active | | `only_lsan` | Only run when LSan is active | | `only_tsan` | Only run when TSan is active | +| `only_tysan` | Only run when TySan is active | > **Note — `no_asan_ubsan_lsan` semantic:** > In the previous single-flag API, `no_asan_ubsan_lsan` was satisfied only when the @@ -200,16 +214,19 @@ Available constraints: The following table lists the supported presets and selected combinations: -| ASan | UBSan | LSan | TSan | Status | Preset | -|:---:|:---:|:---:|:---:|---|---| -| ✓ | | | | ✅ Supported | `--config=asan` | -| | ✓ | | | ✅ Supported | `--config=ubsan` | -| ✓ | | ✓ | | ✅ Supported | `--config=asan` + `--config=lsan` | -| ✓ | ✓ | ✓ | | ✅ Supported | `--config=asan_ubsan_lsan` (**recommended**) | -| | | | ✓ | ✅ Supported | `--config=tsan` | -| | ✓ | | ✓ | ✅ Supported | `--config=tsan_ubsan` | -| ✓ | | | ✓ | ❌ **Invalid** | ASan+TSan: incompatible runtime libraries | -| | | ✓ | ✓ | ❌ **Invalid** | LSan+TSan: TSan has built-in leak detection | +| ASan | UBSan | LSan | TSan | TySan | Status | Preset | +|:---:|:---:|:---:|:---:|:---:|---|---| +| ✓ | | | | | ✅ Supported | `--config=asan` | +| | ✓ | | | | ✅ Supported | `--config=ubsan` | +| ✓ | | ✓ | | | ✅ Supported | `--config=asan` + `--config=lsan` | +| ✓ | ✓ | ✓ | | | ✅ Supported | `--config=asan_ubsan_lsan` (**recommended**) | +| | | | ✓ | | ✅ Supported | `--config=tsan` | +| | ✓ | | ✓ | | ✅ Supported | `--config=tsan_ubsan` | +| ✓ | | | ✓ | | ❌ **Invalid** | ASan+TSan: incompatible runtime libraries | +| | | ✓ | ✓ | | ❌ **Invalid** | LSan+TSan: TSan has built-in leak detection | +| ✓ | | | | ✓ | ❌ **Invalid** | ASan+TySan: incompatible shadow-memory runtimes | +| | | ✓ | | ✓ | ❌ **Invalid** | LSan+TySan: incompatible shadow-memory runtimes | +| | | | ✓ | ✓ | ❌ **Invalid** | TSan+TySan: incompatible shadow-memory runtimes | Invalid combinations are enforced primarily at the **feature level**: the `score_asan`/`score_lsan` and `score_tsan` features declare mutually exclusive @@ -225,14 +242,16 @@ these at build time (the CI test suite depends on this target automatically via The test runner script. It is set via `--run_under` in `sanitizers.bazelrc` and sources all `*_relative_sanitizer.env` files present in its directory. Each env file sets sanitizer-specific runtime options (e.g. `ASAN_OPTIONS`, -`TSAN_OPTIONS`) and points to the corresponding suppression file. +`TSAN_OPTIONS`, `TYSAN_OPTIONS`) and points to the corresponding suppression +file. TySan uses [`suppressions/tysan.supp`](suppressions/tysan.supp). --- ## Toolchain Registration Users of `score_bazel_cpp_toolchains` pass these policy targets through -`extra_known_features`/`extra_enabled_features`. The policy is toolchain-neutral: use the matching UBSan +`extra_known_features`/`extra_enabled_features`. The policy is toolchain-neutral +except for TySan, which is supported only by Clang/LLVM; use the matching UBSan variant for the compiler package in use. ```python @@ -246,6 +265,7 @@ llvm.toolchain( "@score_cpp_policies//sanitizers/features:ubsan_clang", "@score_cpp_policies//sanitizers/features:lsan", "@score_cpp_policies//sanitizers/features:tsan", + "@score_cpp_policies//sanitizers/features:tysan", ], ) @@ -271,7 +291,7 @@ Runtime suppression files live in `suppressions/`. Add suppressions for known false positives in your module's `.bazelrc` or by passing the suppression file path in the `*_OPTIONS` environment variable. -`@score_cpp_policies//sanitizers:suppressions` exposes all four files as a +`@score_cpp_policies//sanitizers:suppressions` exposes all five files as a `filegroup`, for consumers that need to package them alongside their own repo-specific suppressions (e.g. into an OCI/Docker image for integration testing) rather than relying on `wrapper` at test-run time. diff --git a/sanitizers/constraints/BUILD.bazel b/sanitizers/constraints/BUILD.bazel index bed952e..d3df2bd 100644 --- a/sanitizers/constraints/BUILD.bazel +++ b/sanitizers/constraints/BUILD.bazel @@ -66,6 +66,15 @@ alias( visibility = ["//visibility:public"], ) +alias( + name = "no_tysan", + actual = select({ + "//sanitizers/flags:tysan_on": "@platforms//:incompatible", + "//conditions:default": ":always_true", + }), + visibility = ["//visibility:public"], +) + alias( name = "no_asan_ubsan_lsan", actual = select({ @@ -111,3 +120,12 @@ alias( }), visibility = ["//visibility:public"], ) + +alias( + name = "only_tysan", + actual = select({ + "//sanitizers/flags:tysan_on": ":always_true", + "//conditions:default": "@platforms//:incompatible", + }), + visibility = ["//visibility:public"], +) diff --git a/sanitizers/features/BUILD.bazel b/sanitizers/features/BUILD.bazel index f217f5f..f42d00c 100644 --- a/sanitizers/features/BUILD.bazel +++ b/sanitizers/features/BUILD.bazel @@ -25,6 +25,21 @@ cc_mutually_exclusive_category( visibility = ["//visibility:public"], ) +cc_mutually_exclusive_category( + name = "asan_tysan", + visibility = ["//visibility:public"], +) + +cc_mutually_exclusive_category( + name = "lsan_tysan", + visibility = ["//visibility:public"], +) + +cc_mutually_exclusive_category( + name = "tsan_tysan", + visibility = ["//visibility:public"], +) + cc_args( name = "debug_symbols_args", actions = [ @@ -63,7 +78,10 @@ cc_feature( ], feature_name = "score_asan", implies = ["debug_symbols"], - mutually_exclusive = [":asan_tsan"], + mutually_exclusive = [ + ":asan_tsan", + ":asan_tysan", + ], visibility = ["//visibility:public"], ) @@ -133,7 +151,10 @@ cc_feature( ], feature_name = "score_lsan", implies = ["debug_symbols"], - mutually_exclusive = [":lsan_tsan"], + mutually_exclusive = [ + ":lsan_tsan", + ":lsan_tysan", + ], visibility = ["//visibility:public"], ) @@ -165,6 +186,37 @@ cc_feature( mutually_exclusive = [ ":asan_tsan", ":lsan_tsan", + ":tsan_tysan", + ], + visibility = ["//visibility:public"], +) + +cc_args( + name = "tysan_compile_args", + actions = ["@rules_cc//cc/toolchains/actions:compile_actions"], + args = ["-fsanitize=type"], + visibility = ["//visibility:public"], +) + +cc_args( + name = "tysan_link_args", + actions = ["@rules_cc//cc/toolchains/actions:link_actions"], + args = ["-fsanitize=type"], + visibility = ["//visibility:public"], +) + +cc_feature( + name = "tysan", + args = [ + ":tysan_compile_args", + ":tysan_link_args", + ], + feature_name = "score_tysan", + implies = ["debug_symbols"], + mutually_exclusive = [ + ":asan_tysan", + ":lsan_tysan", + ":tsan_tysan", ], visibility = ["//visibility:public"], ) diff --git a/sanitizers/flags/BUILD.bazel b/sanitizers/flags/BUILD.bazel index c09f271..f472d13 100644 --- a/sanitizers/flags/BUILD.bazel +++ b/sanitizers/flags/BUILD.bazel @@ -62,6 +62,18 @@ config_setting( visibility = ["//visibility:public"], ) +bool_flag( + name = "tysan", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "tysan_on", + flag_values = {":tysan": "True"}, + visibility = ["//visibility:public"], +) + selects.config_setting_group( name = "any_sanitizer", match_any = [ @@ -69,6 +81,7 @@ selects.config_setting_group( ":ubsan_on", ":lsan_on", ":tsan_on", + ":tysan_on", ], visibility = ["//visibility:public"], ) @@ -105,6 +118,23 @@ selects.config_setting_group( match_all = [":lsan_on", ":tsan_on"], ) +# Invalid-combination guards (TySan vs ASan/LSan/TSan — incompatible shadow-memory runtimes). + +selects.config_setting_group( + name = "_invalid_asan_tysan", + match_all = [":asan_on", ":tysan_on"], +) + +selects.config_setting_group( + name = "_invalid_lsan_tysan", + match_all = [":lsan_on", ":tysan_on"], +) + +selects.config_setting_group( + name = "_invalid_tsan_tysan", + match_all = [":tsan_on", ":tysan_on"], +) + genrule( name = "sanitizer_combination_check", srcs = [], @@ -121,6 +151,18 @@ genrule( " used together. TSan includes its own leak detection. Use --config=tsan_ubsan" + " instead.' >&2; exit 1" ), + ":_invalid_asan_tysan": ( + "echo 'ERROR: --//sanitizers/flags:asan and --//sanitizers/flags:tysan cannot be" + + " used together. They require incompatible shadow-memory runtimes.' >&2; exit 1" + ), + ":_invalid_lsan_tysan": ( + "echo 'ERROR: --//sanitizers/flags:lsan and --//sanitizers/flags:tysan cannot be" + + " used together. They require incompatible shadow-memory runtimes.' >&2; exit 1" + ), + ":_invalid_tsan_tysan": ( + "echo 'ERROR: --//sanitizers/flags:tsan and --//sanitizers/flags:tysan cannot be" + + " used together. They require incompatible shadow-memory runtimes.' >&2; exit 1" + ), "//conditions:default": "echo 'sanitizer combination valid' > $@", }), visibility = ["//visibility:public"], diff --git a/sanitizers/sanitizers.bazelrc b/sanitizers/sanitizers.bazelrc index 2f64841..6a207d6 100644 --- a/sanitizers/sanitizers.bazelrc +++ b/sanitizers/sanitizers.bazelrc @@ -46,6 +46,12 @@ build:tsan --platform_suffix=tsan build:tsan --config=with_debug_symbols test:tsan --run_under=@score_cpp_policies//sanitizers:wrapper +build:tysan --features=score_tysan +build:tysan --@score_cpp_policies//sanitizers/flags:tysan=True +build:tysan --platform_suffix=tysan +build:tysan --config=with_debug_symbols +test:tysan --run_under=@score_cpp_policies//sanitizers:wrapper + # ============================================================================== # Composite configs — convenience aliases for common combinations # ============================================================================== diff --git a/sanitizers/suppressions/tysan.supp b/sanitizers/suppressions/tysan.supp new file mode 100644 index 0000000..c602a0b --- /dev/null +++ b/sanitizers/suppressions/tysan.supp @@ -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 +# ******************************************************************************* + +# Compile-time ignorelist for the LLVM TypeSanitizer (-fsanitize-ignorelist=). +# See: https://clang.llvm.org/docs/SanitizerSpecialCaseList.html +# Every entry requires a justification. Currently empty — add entries here as +# false positives are discovered. diff --git a/sanitizers/templates/tysan.env.template b/sanitizers/templates/tysan.env.template new file mode 100644 index 0000000..ec8a2ef --- /dev/null +++ b/sanitizers/templates/tysan.env.template @@ -0,0 +1 @@ +TYSAN_OPTIONS=print_stacktrace=1 suppressions=%ROOT%sanitizers/suppressions/tysan.supp diff --git a/tests/.bazelrc b/tests/.bazelrc index aec84aa..76832be 100644 --- a/tests/.bazelrc +++ b/tests/.bazelrc @@ -23,6 +23,7 @@ build:asan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-lin build:ubsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux build:lsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux build:tsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux +build:tysan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux build:asan_ubsan_lsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux build:tsan_ubsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux @@ -43,6 +44,11 @@ build:feature_only_tsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_6 build:feature_only_tsan --features=score_tsan test:feature_only_tsan --test_env=TSAN_OPTIONS=exitcode=55:halt_on_error=1 +# TySan (Clang/LLVM only — no GCC variant available). +build:feature_only_tysan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux +build:feature_only_tysan --features=score_tysan +test:feature_only_tysan --test_env=TYSAN_OPTIONS=exitcode=55:halt_on_error=1 + # Combination configs — parallel to asan_ubsan_lsan / tsan_ubsan but cc_feature only. build:feature_only_asan_ubsan_lsan --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux build:feature_only_asan_ubsan_lsan --features=score_asan diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index a3e5602..024483a 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -58,6 +58,12 @@ cc_test( tags = ["manual"], ) +cc_test( + name = "feature_injection_tysan", + srcs = ["feature_injection/tysan_injection_test.cpp"], + tags = ["manual"], +) + cc_test( name = "feature_injection_positive", srcs = ["feature_injection/sanitizer_positive_test.cpp"], @@ -120,6 +126,16 @@ sh_test( tags = ["manual"], ) +sh_test( + name = "feature_injection_tysan_runtime", + srcs = ["negative/verify_tysan_failure.sh"], + args = [ + "$(location :tysan_fail_test)", + ], + data = [":tysan_fail_test"], + tags = ["manual"], +) + # Exercises ASan violation detection in the combo. UBSan runtime is covered # separately by feature_injection_ubsan_runtime. sh_test( @@ -151,7 +167,10 @@ sh_test( cc_test( name = "sample_test", srcs = ["sample_test.cpp"], - target_compatible_with = ["@score_cpp_policies//sanitizers/constraints:no_tsan"], # GoogleTest has known TSan false positives + target_compatible_with = [ + "@score_cpp_policies//sanitizers/constraints:no_tsan", + "@score_cpp_policies//sanitizers/constraints:no_tysan", + ], # GoogleTest has known TSan false positives deps = [ "@googletest//:gtest", "@googletest//:gtest_main", @@ -180,6 +199,12 @@ cc_binary( srcs = ["negative/tsan_fail.cpp"], ) +cc_binary( + name = "tysan_fail_test", + testonly = True, + srcs = ["negative/tysan_fail.cpp"], +) + cc_binary( name = "ubsan_fail_test", testonly = True, @@ -223,6 +248,16 @@ sh_test( target_compatible_with = ["@score_cpp_policies//sanitizers/constraints:only_tsan"], ) +sh_test( + name = "tysan_fail", + srcs = ["negative/verify_tysan_failure.sh"], + args = [ + "$(location :tysan_fail_test)", + ], + data = [":tysan_fail_test"], + target_compatible_with = ["@score_cpp_policies//sanitizers/constraints:only_tysan"], +) + sh_test( name = "ubsan_fail", srcs = ["negative/verify_sanitizer_failure.sh"], diff --git a/tests/MODULE.bazel b/tests/MODULE.bazel index 8747aca..d2f84d2 100644 --- a/tests/MODULE.bazel +++ b/tests/MODULE.bazel @@ -15,7 +15,7 @@ module(name = "score_cpp_policies_tests") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "rules_cc", version = "0.2.17") -bazel_dep(name = "toolchains_llvm", version = "1.7.0") +bazel_dep(name = "toolchains_llvm", version = "1.8.0") bazel_dep(name = "score_bazel_platforms", version = "1.0.0") bazel_dep(name = "score_cpp_policies") local_path_override( @@ -72,7 +72,8 @@ llvm.toolchain( "@score_cpp_policies//sanitizers/features:ubsan_clang", "@score_cpp_policies//sanitizers/features:lsan", "@score_cpp_policies//sanitizers/features:tsan", + "@score_cpp_policies//sanitizers/features:tysan", ], - llvm_version = "19.1.7", + llvm_version = "22.1.7", ) use_repo(llvm, "llvm_toolchain") diff --git a/tests/feature_injection/tysan_injection_test.cpp b/tests/feature_injection/tysan_injection_test.cpp new file mode 100644 index 0000000..7e63de4 --- /dev/null +++ b/tests/feature_injection/tysan_injection_test.cpp @@ -0,0 +1,18 @@ +// ******************************************************************************* +// 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 +// ******************************************************************************* + +#if !__has_feature(type_sanitizer) +#error "score_tysan did not inject -fsanitize=type: check extra_known_features in MODULE.bazel" +#endif + +int main() { return 0; } diff --git a/tests/negative/tysan_fail.cpp b/tests/negative/tysan_fail.cpp new file mode 100644 index 0000000..6bc4d96 --- /dev/null +++ b/tests/negative/tysan_fail.cpp @@ -0,0 +1,21 @@ +// ******************************************************************************* +// 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 +// ******************************************************************************* + +int main(int argc, char **argv) { + int x = 100; + float *y = (float*)&x; + *y += 2.0f; // Strict aliasing violation + return 0; +} + +// this example was copied from the TypeSanitizer documentation (https://clang.llvm.org/docs/TypeSanitizer.html) \ No newline at end of file diff --git a/tests/negative/verify_tysan_failure.sh b/tests/negative/verify_tysan_failure.sh new file mode 100755 index 0000000..a4fac74 --- /dev/null +++ b/tests/negative/verify_tysan_failure.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# ******************************************************************************* +# 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 +# ******************************************************************************* + +set -euo pipefail + +# Unlike other sanitizers, TypeSanitizer's runtime does not honor +# halt_on_error/exitcode (unimplemented in the runtime): the instrumented +# binary always exits 0 even when it reports violations. So we verify +# detection by scanning output for the error marker instead of the exit code. + +BINARY="$1" + +echo "Running: $BINARY" + +OUTPUT="$("$BINARY" 2>&1)" || true +echo "$OUTPUT" + +if echo "$OUTPUT" | grep -q "TypeSanitizer: type-aliasing-violation"; then + echo "✓ PASS: TypeSanitizer reported a type-aliasing violation" + exit 0 +else + echo "✗ FAIL: No TypeSanitizer violation reported in output" + exit 1 +fi