Skip to content
Merged
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
9 changes: 7 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
test-sanitizers:
name: Test ${{ matrix.config }}
runs-on: ubuntu-24.04

strategy:
fail-fast: false
matrix:
Expand All @@ -48,7 +48,7 @@ jobs:
description: "ThreadSanitizer"
- config: tsan_ubsan
description: "TSan + UBSan"

steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 |
Expand All @@ -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 |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't refer the UBSan+TySan because they work together. But as TySan is recent, and not even the exitcode or halt_on_error works correctly, we will keep it running separated from UBSan.


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.
Expand Down
32 changes: 25 additions & 7 deletions docs/migration-sanitizers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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` |

Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions sanitizers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ filegroup(
"suppressions/asan.supp",
"suppressions/lsan.supp",
"suppressions/tsan.supp",
"suppressions/tysan.supp",
"suppressions/ubsan.supp",
],
visibility = ["//visibility:public"],
Expand All @@ -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 = {
Expand Down Expand Up @@ -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"],
Expand Down
58 changes: 39 additions & 19 deletions sanitizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---
Expand Down Expand Up @@ -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
Expand All @@ -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 |

Expand All @@ -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
Expand All @@ -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.:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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",
],
)

Expand All @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions sanitizers/constraints/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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"],
)
Loading
Loading