# abicheck

[![CI](https://github.com/napetrov/abicheck/actions/workflows/ci.yml/badge.svg)](https://github.com/napetrov/abicheck/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/napetrov/abicheck/branch/main/graph/badge.svg)](https://codecov.io/gh/napetrov/abicheck)

**abicheck** is a command-line tool that detects breaking changes in C/C++ shared libraries before they reach production. It compares two versions of a `.so` library — along with their public headers — and reports whether existing binaries will continue to work or break at runtime.

Typical problems it catches: removed or renamed symbols, changed function signatures, struct layout drift, vtable reordering, enum value reassignment, and dozens of other ABI/API incompatibilities that cause crashes, silent data corruption, or linker failures after a library upgrade.

> **Platform:** Linux (ELF binaries + DWARF debug info + C/C++ headers). Windows PE and macOS Mach-O are not yet supported.

---

## Installation

> **Note:** abicheck is not yet published to PyPI or conda-forge. Install from source for now.

### Prerequisites

| Requirement | Notes |
|-------------|-------|
| **Linux** | ELF/DWARF + header-based analysis |
| **Python >= 3.10** | |
| **`castxml`** | Clang-based C/C++ AST parser for header analysis |
| **`g++` or `clang++`** | Must be accessible to castxml |

```bash
# Ubuntu / Debian
sudo apt install castxml g++
```

```bash
# conda
conda install -c conda-forge castxml
```

### Install from source

```bash
git clone https://github.com/napetrov/abicheck.git
cd abicheck
pip install -e .
```

For development (includes test & lint dependencies):

```bash
pip install -e ".[dev]"
```

---

## Quick start

### Compare two library versions

The simplest way to check ABI compatibility — pass two `.so` files and their public headers:

```bash
abicheck compare libfoo.so.1 libfoo.so.2 \
  --old-header include/v1/foo.h --new-header include/v2/foo.h
```

If the header file is the same for both versions, use the shorthand:

```bash
abicheck compare libfoo.so.1 libfoo.so.2 -H include/foo.h
```

### Use saved snapshots (for CI baselines)

Save an ABI snapshot once per release, then compare against new builds:

```bash
# Save baseline
abicheck dump libfoo.so -H include/foo.h --version 1.0 -o baseline.json
```

```bash
# Compare new build against saved baseline
abicheck compare baseline.json ./build/libfoo.so --new-header include/foo.h
```

### Key flags

| Flag | Description |
|------|-------------|
| `--old-header` / `--new-header` | Public headers for each version |
| `-H` | Same header for both versions (shorthand) |
| `--format` | Output format: `markdown` (default), `json`, `sarif`, `html` |
| `-o` | Write report to file |
| `--policy` | Verdict policy: `strict_abi` (default), `sdk_vendor`, `plugin_abi` |
| `--policy-file` | Custom YAML policy overrides |
| `--lang` | Language mode: `c++` (default) or `c` |
| `-v` / `--verbose` | Debug output |

For full CLI reference and advanced options (cross-compilation, suppression files, symbol filtering), see the [documentation](https://napetrov.github.io/abicheck/getting_started/).

---

## Output formats and reports

abicheck supports four output formats:

```bash
# Markdown (default) — human-readable, printed to stdout
abicheck compare old.so new.so -H foo.h
```

```bash
# JSON — machine-readable, includes precise verdict field for CI parsing
abicheck compare old.so new.so -H foo.h --format json -o result.json
```

```bash
# SARIF — for GitHub Code Scanning integration
abicheck compare old.so new.so -H foo.h --format sarif -o abi.sarif
```

```bash
# HTML — standalone report for review
abicheck compare old.so new.so -H foo.h --format html -o report.html
```

### Example report (markdown output)

```text
# ABI Report: libfoo.so

| | |
|---|---|
| **Old version** | `1.0` |
| **New version** | `2.0` |
| **Verdict** | ❌ `BREAKING` |
| Breaking changes | 2 |
| Source-level breaks | 0 |
| Deployment risk changes | 0 |
| Compatible additions | 1 |

## ❌ Breaking Changes

- **func_removed**: Public function removed: helper (`helper`)
  > Old binaries call a symbol that no longer exists; dynamic linker will refuse to load or crash at call site.

- **type_size_changed**: Size changed: Point (64 → 96 bits) (`64` → `96`)
  > Old code allocates or copies the type with the old size; heap/stack corruption, out-of-bounds access.

## ✅ Compatible Additions

- Field added: Point::z

---
## Legend

| Verdict | Meaning |
|---------|---------|
| ✅ NO_CHANGE | Identical ABI |
| ✅ COMPATIBLE | Only additions (backward compatible) |
| ⚠️ COMPATIBLE_WITH_RISK | Binary-compatible; verify target environment |
| ⚠️ API_BREAK | Source-level API change — recompilation required |
| ❌ BREAKING | Binary ABI break — recompilation required |

_Generated by [abicheck](https://github.com/napetrov/abicheck)_
```

---

## Policy profiles

Policies control how detected changes are classified. A change that is `BREAKING` under `strict_abi` might be downgraded to `COMPATIBLE` under `sdk_vendor`.

**Built-in profiles:**

| Profile | Use case | Behavior |
|---------|----------|----------|
| `strict_abi` (default) | System libraries, public SDKs | Every ABI change at maximum severity |
| `sdk_vendor` | Vendor SDKs, optional extensions | Source-only changes (renames, access) downgraded to COMPATIBLE |
| `plugin_abi` | Plugins rebuilt with host | Calling-convention changes downgraded to COMPATIBLE |

```bash
abicheck compare old.so new.so -H foo.h --policy sdk_vendor
```

### Custom policy file

Create a YAML file to override classification of specific change kinds:

```yaml
base_policy: strict_abi
overrides:
  enum_member_renamed: ignore   # break | warn | ignore
  field_renamed: ignore
```

```bash
abicheck compare old.so new.so -H foo.h --policy-file project_policy.yaml
```

Semantics: `break` = BREAKING (exit 4), `warn` = API_BREAK (exit 2), `ignore` = COMPATIBLE (exit 0). Kinds not listed in `overrides` use the `base_policy`.

See [Policy Profiles](https://napetrov.github.io/abicheck/policies/) for full details.

---

## Exit codes

| Exit code | Verdict | Meaning |
|-----------|---------|---------|
| `0` | `NO_CHANGE`, `COMPATIBLE`, `COMPATIBLE_WITH_RISK` | Safe — no binary ABI break (risk report may have warnings) |
| `1` | — | Tool/runtime error |
| `2` | `API_BREAK` | Source-level break (recompile needed, binary may work) |
| `4` | `BREAKING` | Binary ABI break (old binaries will crash or misbehave) |

Use exit codes directly in CI gates. For precise verdicts, parse `--format json` output.

---

## GitHub Actions integration

A typical CI flow: dump the ABI snapshot once at release time, then compare every new build against that saved baseline.

```yaml
name: ABI check
on: [push, pull_request]

jobs:
  abi-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install abicheck
        run: pip install git+https://github.com/napetrov/abicheck.git

      # ── Release step (run once when cutting a release) ─────────────
      # Dump the ABI baseline and upload it as a release artifact:
      #
      #   abicheck dump ./build/libfoo.so -H include/foo.h \
      #     --version ${{ github.ref_name }} -o abi-baseline.json
      #
      # Then download it in CI (e.g. from a release asset or artifact).

      - name: Download ABI baseline
        uses: actions/download-artifact@v4
        with:
          name: abi-baseline
          # abi-baseline.json saved from the last release

      - name: Build current library
        run: make -C src/   # produces ./build/libfoo.so

      # ── CI step: compare new build against saved baseline ──────────
      - name: Compare ABI
        run: |
          abicheck compare abi-baseline.json ./build/libfoo.so \
            --new-header include/foo.h \
            --format sarif -o abi.sarif

      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: abi.sarif
```

Exit codes for CI gates: `0` = compatible, `1` = tool error, `2` = API break, `4` = breaking ABI change.

---

## ABICC drop-in replacement

For teams migrating from [ABI Compliance Checker (ABICC)](https://lvc.github.io/abi-compliance-checker/) — swap one command, keep your existing XML descriptors:

```bash
# Before (ABICC):
abi-compliance-checker -lib libfoo -old old.xml -new new.xml -report-path r.html
```

```bash
# After (abicheck — same flags):
abicheck compat check -lib libfoo -old old.xml -new new.xml -report-path r.html
```

When ready, migrate to the simpler native workflow:

```bash
abicheck compare libfoo.so.1 libfoo.so.2 -H include/foo.h
```

See [ABICC Migration Guide](https://napetrov.github.io/abicheck/migration/from_abicc/) for full flag reference, exit code differences, and migration checklist.

---

## Examples and ABI breakage catalog

The `examples/` directory contains **48 real-world ABI break scenarios** — each with paired `v1`/`v2` source code, a consumer app that demonstrates the actual failure, and a Makefile to reproduce it.

### Try an example

```bash
cd examples/case01_symbol_removal
make
abicheck compare libv1.so libv2.so --old-header v1.h --new-header v2.h
# Verdict: BREAKING — symbol 'helper' was removed
```

### What the examples cover

**Breaking changes** — changes that crash or corrupt existing binaries:

| Category | Cases | Examples |
|----------|-------|---------|
| Symbol removal / rename | 01, 12 | Function removed from export table |
| Type/signature changes | 02, 10, 33 | Parameter type, return type, pointer level changed |
| Struct/class layout | 07, 14, 40, 43, 44 | Field added/reordered, class size changed |
| Enum changes | 08, 19, 20 | Value reassigned, member removed |
| C++ vtable / virtual | 09, 23, 38 | Vtable reorder, pure virtual added |
| Qualifiers / binding | 21, 22, 30, 39 | Method became static, const changed |
| Templates / typedefs | 17, 28, 45, 46, 48 | Template layout, typedef opaque |
| Complex types | 24, 26, 35, 36, 37 | Union field removed, field renamed, base class changed |

**Compatible changes** — safe, no binary break:

| Cases | Examples |
|-------|---------|
| 03, 25, 26b | New symbol added, enum member appended, union in reserved space |
| 04, 32 | No change, parameter defaults changed |
| 05, 06 | SONAME policy, visibility leak (bad practice but binary-safe) |
| 13, 27, 29, 47 | Symbol versioning, weak binding, IFUNC, inline-to-outlined |
| 16 | Inline to non-inline (ODR concern, not binary break) |

**API-only breaks** — source-level break, binary still works:

| Cases | Examples |
|-------|---------|
| 31, 34 | Enum renamed, access level changed |

### Benchmarks

abicheck detects 100+ change types across ELF, AST, and DWARF layers. The `examples/` directory contains 48 representative test cases with expected verdicts in `examples/ground_truth.json`. Cross-tool comparison on 42 of these cases:

| Tool | Correct / Scored | Accuracy |
|------|-----------------|----------|
| **abicheck (compare)** | **42/42** | **100%** |
| abicheck (compat) | 40/42 | 95% |
| ABICC (xml) | 25/41 | 61% |
| ABICC (abi-dumper) | 20/30 | 66% |
| abidiff | 11/42 | 26% |

abicheck passes all 48 cases. Run `python3 scripts/benchmark_comparison.py` to reproduce.

See [Benchmark & Tool Comparison](https://napetrov.github.io/abicheck/tool_comparison/) for per-case results, methodology, and timing data.

---

## ABI compatibility guide

Understanding what breaks ABI and what doesn't is essential for library maintainers. Here is a quick reference:

### Changes that break binary compatibility

- **Removing or renaming** an exported function or variable
- **Changing function signature** — parameter types, return type, calling convention
- **Modifying struct/class layout** — adding/removing/reordering fields, changing field types
- **Changing enum values** — reassigning numeric values, removing members
- **C++ vtable changes** — reordering virtual methods, adding pure virtuals
- **Changing method qualifiers** — `const`, `static`, `noexcept` (when it affects mangling)
- **Changing global variable type** — size/alignment mismatch

### Changes that are safe (binary compatible)

- **Adding** new exported functions or variables
- **Adding** new enum members at the end (without shifting existing values)
- **Weakening** symbol binding (GLOBAL to WEAK)
- **Adding** IFUNC resolvers
- **Adding** symbol version tags

### Best practices for library maintainers

1. **Treat public headers as ABI contracts** — any change is potentially breaking
2. **Use SONAME versioning** — bump major version on incompatible changes
3. **Hide implementation details** — use Pimpl pattern, opaque handles, `-fvisibility=hidden`
4. **Add, don't modify** — introduce `foo_v2()` instead of changing `foo()`
5. **Freeze enum values** — never renumber released constants
6. **Don't expose third-party types** in public API — wrap them behind stable project-owned types

See [Examples Breakage Guide](https://napetrov.github.io/abicheck/examples_breakage_guide/) for detailed code examples and failure demonstrations for each case.

---

## Architecture

abicheck uses a 3-layer comparison pipeline:

1. **ELF metadata** (via pyelftools) — exported symbols, SONAME, visibility, binding, symbol versions
2. **Header AST** (via castxml/Clang) — function signatures, classes, fields, typedefs, enums, vtable layout
3. **DWARF cross-check** (optional) — validates actual compiled struct sizes, member offsets, vtable slots

Each layer provides independent signals. Combining all three gives abicheck higher accuracy than tools that rely on only one or two sources (e.g., abidiff uses only DWARF; ABICC uses only GCC AST).

### Key modules

| Module | Responsibility |
|--------|---------------|
| `cli.py` | CLI entrypoint (`dump`, `compare`, `compat check/dump`) |
| `dumper.py` | Snapshot generation from `.so` + headers |
| `checker.py` | Diff orchestration and change collection |
| `checker_policy.py` | Change classification, built-in policies, verdict logic |
| `detectors.py` | ABI change detection rules |
| `reporter.py` | Output formatting (markdown, JSON, SARIF, HTML) |
| `suppression.py` | Suppression rules and symbol filtering |
| `policy_file.py` | Custom YAML policy file parsing |

See [Architecture reference](https://napetrov.github.io/abicheck/reference/architecture/) for the full design documentation.

---

## Documentation

Full documentation is available at **[napetrov.github.io/abicheck](https://napetrov.github.io/abicheck/)**.

**Getting started:**
- [Installation & first check](https://napetrov.github.io/abicheck/getting_started/)
- [Exit codes reference](https://napetrov.github.io/abicheck/exit_codes/)

**Concepts:**
- [Verdicts explained](https://napetrov.github.io/abicheck/concepts/verdicts/) — NO_CHANGE / COMPATIBLE / COMPATIBLE_WITH_RISK / API_BREAK / BREAKING
- [Limitations](https://napetrov.github.io/abicheck/concepts/limitations/)
- [Troubleshooting](https://napetrov.github.io/abicheck/concepts/troubleshooting/)

**User guide:**
- [Policy profiles](https://napetrov.github.io/abicheck/policies/)
- [SARIF output](https://napetrov.github.io/abicheck/sarif_output/)
- [Examples & breakage guide](https://napetrov.github.io/abicheck/examples_breakage_guide/)
- [ABICC migration](https://napetrov.github.io/abicheck/migration/from_abicc/)

**Reference:**
- [Architecture](https://napetrov.github.io/abicheck/reference/architecture/)
- [Change kind reference](https://napetrov.github.io/abicheck/reference/change_kinds/)
- [Benchmark & tool comparison](https://napetrov.github.io/abicheck/tool_comparison/)
- [ABICC compatibility reference](https://napetrov.github.io/abicheck/abicc_compat/)

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, testing, code style, and PR workflow.

## License

Apache-2.0. See [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md).
