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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ bazel test --config=asan_ubsan_lsan //... # recommended default for CI
```

See [`sanitizers/README.md`](sanitizers/README.md) for setup, the full config/constraint
reference, and the v0.x migration guide.
reference, and the v0.x migration guide. See the [detailed sanitizer migration guide](docs/migration-sanitizers.md)
for the complete old-to-new API procedure.

## Clang-Tidy

Expand Down
201 changes: 201 additions & 0 deletions docs/migration-sanitizers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Migration: from the v0.x sanitizer API to the policy-owned API

See [`sanitizers/README.md`](../sanitizers/README.md) for the full feature,
constraint, suppression, and runtime reference. This guide covers the API
migration itself.

## Scope and version assumptions

The current sanitizer API is owned by `score_cpp_policies`. It uses the
`extra_known_features` toolchain registration API and the policy's
`rules_cc` feature definitions. Use a C++ toolchain that supports
`extra_known_features` and the current feature API. This repository does not
claim a minimum toolchain version for that support.

The repository tests currently use these versions:

- `score_bazel_cpp_toolchains` 1.0.2
- `rules_cc` 0.2.17
- `toolchains_llvm` 1.7.0
- GCC 12.2.0 and 15.3.0
- Clang 19.1.7

These are repository test versions, not minimum version requirements.

## Old model

The v0.x API exposed one string flag:

```text
--@score_cpp_policies//sanitizers/flags:sanitizer=<value>
```

The recognised legacy values were `asan_ubsan_lsan` and `tsan`. The old model
also used aggregate GCC feature names such as `asan_ubsan_lsan_gcc` and
`tsan_gcc`.

The current `sanitizers/flags:sanitizer` target remains only as a deprecated
compatibility shim so Bazel can parse those legacy values. Non-default use is
rejected by `sanitizer_deprecated_check`; it is not a migration path.

| Legacy invocation | Current invocation |
| --- | --- |
| `--@score_cpp_policies//sanitizers/flags:sanitizer=asan_ubsan_lsan` | `--config=asan_ubsan_lsan` |
| `--@score_cpp_policies//sanitizers/flags:sanitizer=tsan` | `--config=tsan` |

## New model

The policy defines one bool flag per sanitizer and exposes these config
aliases through [`sanitizers.bazelrc`](../sanitizers/sanitizers.bazelrc):

| Sanitizer | Current config |
| --- | --- |
| AddressSanitizer | `--config=asan` |
| UndefinedBehaviorSanitizer | `--config=ubsan` |
| LeakSanitizer | `--config=lsan` |
| ThreadSanitizer | `--config=tsan` |
| ASan + UBSan + LSan | `--config=asan_ubsan_lsan` |
| TSan + UBSan | `--config=tsan_ubsan` |

The single-sanitizer configs activate exactly their named sanitizer. Composite
configs are convenience aliases that compose those single-sanitizer configs.
The aliases also configure the policy's test wrapper and debug-symbol setup.

### Feature registration

Register the policy labels in the toolchain's `extra_known_features`. Register
the compiler-appropriate UBSan variant:

| Purpose | Label | Feature name | Toolchain |
| --- | --- | --- | --- |
| Debug symbols | `@score_cpp_policies//sanitizers/features:debug_symbols` | `debug_symbols` | GCC and Clang |
| AddressSanitizer | `@score_cpp_policies//sanitizers/features:asan` | `score_asan` | GCC and Clang |
| UBSan common flags | `@score_cpp_policies//sanitizers/features:ubsan_base` | `score_ubsan_base` | GCC and Clang |
| UBSan GCC variant | `@score_cpp_policies//sanitizers/features:ubsan_gcc` | `score_ubsan_gcc` | GCC |
| 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 |

The former aggregate GCC features `asan_ubsan_lsan_gcc` and `tsan_gcc` are
removed. Replace them with the per-sanitizer labels above. In particular,
register `asan`, `lsan`, and `tsan` directly, and register `ubsan_gcc` for GCC
or `ubsan_clang` for Clang. Do not register both UBSan variants for one
toolchain.

Example GCC registration:

```starlark
gcc.toolchain(
...,
extra_known_features = [
"@score_cpp_policies//sanitizers/features:debug_symbols",
"@score_cpp_policies//sanitizers/features:asan",
"@score_cpp_policies//sanitizers/features:ubsan_base",
"@score_cpp_policies//sanitizers/features:ubsan_gcc",
"@score_cpp_policies//sanitizers/features:lsan",
"@score_cpp_policies//sanitizers/features:tsan",
],
)
```

Example Clang registration:

```starlark
llvm.toolchain(
llvm_version = "...",
extra_known_features = [
"@score_cpp_policies//sanitizers/features:debug_symbols",
"@score_cpp_policies//sanitizers/features:asan",
"@score_cpp_policies//sanitizers/features:ubsan_base",
"@score_cpp_policies//sanitizers/features:ubsan_clang",
"@score_cpp_policies//sanitizers/features:lsan",
"@score_cpp_policies//sanitizers/features:tsan",
],
)
```

### Dependency and Bazel configuration

Add `score_cpp_policies` to the consuming workspace:

```starlark
bazel_dep(name = "score_cpp_policies")
```

Import the policy config file from the workspace `.bazelrc`:

```text
try-import %workspace%/path/to/score_cpp_policies/sanitizers/sanitizers.bazelrc
```

Use the import path appropriate for how the dependency is available in the
workspace. The config file is the source of the `--config=` aliases.

### Valid and invalid combinations

Supported combinations are:

| Combination | Current invocation |
| --- | --- |
| ASan | `--config=asan` |
| UBSan | `--config=ubsan` |
| LSan | `--config=lsan` |
| TSan | `--config=tsan` |
| ASan + UBSan + LSan | `--config=asan_ubsan_lsan` |
| TSan + UBSan | `--config=tsan_ubsan` |
| 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.

### Constraints and duplicate flags

In the current API, `no_asan_ubsan_lsan` means no ASan, UBSan, or LSan is
active: it matches when any one of those three flags is set. This differs from
the old single-flag behavior, where it was satisfied unless the combined
`asan_ubsan_lsan` preset was active. For new targets, prefer the granular
`no_asan`, `no_ubsan`, or `no_lsan` constraints. If a target must be skipped
only when all three are active, use the `asan_ubsan_lsan` match-all flag group
in a custom `config_setting`.

Remove duplicate custom sanitizer flags from `copts`, custom features, or
toolchain forks. The policy features already provide the compile and link
flags, while the config aliases select them. Duplicates can conflict with the
policy's feature composition and runtime checks.

## Migration procedure

1. Add `score_cpp_policies` as a `bazel_dep`.
2. Import `sanitizers/sanitizers.bazelrc` from the consuming workspace's
`.bazelrc`.
3. Replace legacy string-flag use with the equivalent `--config=` alias:
`asan_ubsan_lsan` maps to `--config=asan_ubsan_lsan`, and `tsan` maps to
`--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
where that expresses the intended compatibility rule.
7. Choose a supported config, run the validation commands below, and fix any
sanitizer or runtime issues exposed by the new policy.

## Validation

This repository does not provide a dedicated Markdown-link checker. Use the
Markdown diagnostics available in the editor or the documentation tooling used
by the consuming workspace to verify relative links.

From `tests/`, run the sanitizer suites:

```bash
bazel test --config=asan_ubsan_lsan //...
bazel test --config=tsan //...
```

Also validate a Clang UBSan configuration when using the Clang toolchain:

```bash
bazel test --config=ubsan //...
```
88 changes: 48 additions & 40 deletions sanitizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,33 @@ dedicated Bazel config flag. Sanitizers can be used in isolation or combined.

---

## Policy Ownership

`score_cpp_policies` is the canonical owner of sanitizer policy. Toolchain
packages provide compilers; the consuming workspace registers the policy's
feature labels with its toolchain. This policy includes the feature definitions,
configuration flags, constraints, test wrapper, runtime suppressions, and Bazel
configs.

---

## Quick Start

Add the policy dependency to your `MODULE.bazel` without pinning a version here (if you pin a version, ensure you are using >= 0.1.0, as before that the policies were defined in "bazel_cpp_toolchains"):

```python
bazel_dep(name = "score_cpp_policies")
```

Import `sanitizers.bazelrc` from your workspace `.bazelrc` using the `try-import` example:

```
try-import %workspace%/path/to/score_cpp_policies/sanitizers/sanitizers.bazelrc
```

Complete the mandatory [Toolchain Registration](#toolchain-registration) before
using a sanitizer config.

```bash
# Run tests with AddressSanitizer
bazel test --config=asan //your/target/...
Expand All @@ -23,12 +48,6 @@ bazel test --config=asan_ubsan_lsan //your/target/...
bazel test --config=tsan_ubsan //your/target/...
```

> **Note:** Import `sanitizers.bazelrc` from your workspace `.bazelrc` to make the
> above configs available:
> ```
> try-import %workspace%/path/to/score_cpp_policies/sanitizers/sanitizers.bazelrc
> ```

---

## Architecture
Expand Down Expand Up @@ -59,10 +78,11 @@ One `bool_flag` per sanitizer (`asan`, `ubsan`, `lsan`, `tsan`) and correspondin
Flags are set automatically by the `--config=` aliases in `sanitizers.bazelrc`.
Do not set them directly unless you have a non-standard composition need.

### `features/` — Compiler/linker feature definitions
### `features/` — Policy-owned compiler/linker feature definitions

One `cc_feature` per sanitizer, registered under the `score_*` namespace to
avoid collisions with toolchain built-in feature names:
The policy owns these `cc_feature` targets. Register them through your
toolchain's `extra_known_features`; their `score_*` names avoid collisions with
toolchain built-in feature names:

| Target | Feature name | Toolchain | Key flags |
|---|---|---|---|
Expand All @@ -72,7 +92,11 @@ avoid collisions with 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` |
| `debug_symbols` | `debug_symbols` | both | `-g1` + `--strip=never` |
| `debug_symbols` | `debug_symbols` | both | `-g1` |

The `with_debug_symbols` config adds `--strip=never`. Each sanitizer feature
implies `debug_symbols`; `ubsan_gcc` and `ubsan_clang` imply `ubsan_base`, which
then implies `debug_symbols`.

UBSan uses a three-target composition:
- `ubsan_base` carries the flags common to both toolchains (`-fsanitize=undefined` at compile and link time).
Expand Down Expand Up @@ -154,15 +178,15 @@ Available constraints:
| `only_lsan` | Only run when LSan is active |
| `only_tsan` | Only run when TSan is active |

> **Note — `no_asan_ubsan_lsan` semantic:**
> **Note — `no_asan_ubsan_lsan` semantic:**
> In the previous single-flag API, `no_asan_ubsan_lsan` was satisfied only when the
> combined `asan_ubsan_lsan` preset was active (i.e. all three flags simultaneously).
> In the current per-flag API it is satisfied when **any one** of the three flags is
> set, matching `any_asan_ubsan_lsan`:
>
> | Scenario | Old behaviour | New behaviour |
> |---|---|---|
> | Only `--//flags:asan=True` | Target **skipped** — combo not active, so `no_asan_ubsan_lsan` was always satisfied | Target **skipped** — `any_asan_ubsan_lsan` is satisfied |
> | Only `--//flags:asan=True` | Target **built** — combo not active, so the old `match_all` constraint was satisfied | Target **skipped** — `any_asan_ubsan_lsan` is satisfied |
> | `--//flags:asan + :ubsan + :lsan` | Target **skipped** | Target **skipped** |
> | No sanitizer | Target **built** | Target **built** |
>
Expand All @@ -174,7 +198,7 @@ Available constraints:

## Valid Sanitizer Combinations

The following table shows which flag combinations are **supported**:
The following table lists the supported presets and selected combinations:

| ASan | UBSan | LSan | TSan | Status | Preset |
|:---:|:---:|:---:|:---:|---|---|
Expand Down Expand Up @@ -207,8 +231,9 @@ Each env file sets sanitizer-specific runtime options (e.g. `ASAN_OPTIONS`,

## Toolchain Registration

To use sanitizer features, register them in your toolchain's `known_features`
or `extra_known_features`:
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
variant for the compiler package in use.

```python
# Clang toolchain (toolchains_llvm)
Expand Down Expand Up @@ -255,29 +280,12 @@ testing) rather than relying on `wrapper` at test-run time.

## Migration from v0.x

The `--@score_cpp_policies//sanitizers/flags:sanitizer=<value>` string flag has been removed.
Replace any direct flag usage with the equivalent `--config=` alias:

| Old | New |
|-----|-----|
| `--@score_cpp_policies//sanitizers/flags:sanitizer=asan_ubsan_lsan` | `--config=asan_ubsan_lsan` |
| `--@score_cpp_policies//sanitizers/flags:sanitizer=tsan` | `--config=tsan` |

`--config=asan`, `--config=ubsan`, and `--config=lsan` now activate exactly their named
sanitizer rather than the combined `asan_ubsan_lsan` mode.

### GCC-specific feature variants removed

The `asan_ubsan_lsan_gcc` and `tsan_gcc` `cc_feature` targets (which omitted
`-fsanitize-link-c++-runtime`) have been removed. The new per-sanitizer features
(`score_asan`, `score_ubsan`, etc.) work with both Clang and GCC toolchains. If you were
registering GCC-specific features explicitly in your toolchain, replace them with the new
single features (e.g. `@score_cpp_policies//sanitizers/features:asan`).

### `no_asan_ubsan_lsan` constraint semantics changed
The complete migration procedure, old-to-new mapping, toolchain registration
examples, compatibility rules, and validation commands are in the
[authoritative sanitizer migration guide](../docs/migration-sanitizers.md).

See the `constraints/` section above — in the previous
single-flag API, `no_asan_ubsan_lsan` was satisfied only when the combined
`asan_ubsan_lsan` preset was active (all three flags simultaneously). In the current
per-flag API it is satisfied when **any one** of the three flags is set. Prefer the more
granular `no_asan`, `no_ubsan`, or `no_lsan` constraints for new targets.
In short: add `score_cpp_policies`, import `sanitizers.bazelrc`, register the
per-sanitizer feature labels through `extra_known_features`, replace the
deprecated string flag and removed GCC aggregate features, and review uses of
`no_asan_ubsan_lsan` because its current meaning is "skip when any of ASan,
UBSan, or LSan is active."
Loading