This guide covers what a Map2Check maintainer or a C program needs to use
the Rust map2check-library 2.0.0-rc.1: what the release ships, how to
install the 21 bitcode modules in place of the C runtime's, the Map2Check
commit they were validated against, how to reproduce the validation, and how
to link the C ABI by hand. Paths of the form map2check-library: <path> and the scripts/ and
docs/cutover/ files named below are in the source repository, not in the
release archive, which ships only this guide, api-deltas.md and
semantic-deltas.md under docs/. Compatibility details are in
api-deltas.md (symbols and signatures) and
semantic-deltas.md (behaviour). The steps a
maintainer would take to adopt the package are suggested in
map2check-library: docs/cutover/handoff-map2check.md.
What the library is (D6, 2.0.0). The library exports the C runtime ABI of
Map2Check develop: the names its core modules own and the __m2c_rt_*
entry points that the generated IR modules (scripts/runtime-ir/gen_ll.py)
forward to. Together with those IR modules it is the 21 .bc files that
Caller::linkLLVM links into an instrumented program, so Map2Check runs
unchanged with them. It is not a library with its own API next to the
runtime: the 1.x core API (map2check_set_mode, map2check_map_*,
print_all_containers_as_json...) and the libfuzzer and klee features
left the C ABI (CHANGELOG.md). Those functions remain Rust functions of
map2check::ffi for the Rust tests, and the JSON report below belongs to
that Rust API.
A release archive map2check-library-<version>-<target>.tar.gz (see
map2check-library: docs/release.md) unpacks to:
map2check-library-<version>-<target>/
├── include/map2check.h # C header; MAP2CHECK_VERSION == <version>
├── lib/*.bc # the 21 runtime modules (below)
├── lib/libmap2check.a # no features
├── lib/libmap2check_runtime.a # runtime profile, feature wasm
├── MAP2CHECK_VERSION # Map2Check develop commit validated
├── SHA256SUMS # sha256 of every other file
├── docs/integration-guide.md
├── docs/api-deltas.md
├── docs/semantic-deltas.md
├── CHANGELOG.md
└── LICENSE # GPL-3.0-only
The 21 modules are listed in scripts/runtime-modules.txt (Map2CheckFunctions,
TrackBBLog, NonDetLog, PropertyGenerator, ContainerRealloc,
ContainerBTree, BTree, AnalysisModeMemtrack, AnalysisModeMemcleanup,
AnalysisModeOverflow, AnalysisModeAssert, AnalysisModeNone,
AllocationLog, ListLog, HeapLog, NonDetGeneratorNone,
NonDetGeneratorKlee, NonDetGeneratorAFL, WasmRuntimeStubs,
WitnessGeneration, WitnessGenerationNone), each as <Module>.bc. The
layout and the reasons for it are in
map2check-library: docs/cutover/d7-layout.md.
The package was validated against Map2Check develop commit
0bca42eb89d116178e6b52f52acaf73f7c305088, recorded in
vendor/MAP2CHECK_VERSION and shipped as MAP2CHECK_VERSION. The 21 C
runtime modules are byte for byte the same at 0bca42eb and at the commit of
the first gate (3e914528ec57). Use a Map2Check install built from that
commit, or from one whose lib/*.bc are identical to it. Other commits were
not tested.
Verify the archive, then either overlay a copy or patch in place:
tar -xzf map2check-library-<version>-<target>.tar.gz
cd map2check-library-<version>-<target>
sha256sum -c SHA256SUMS # every file must say OK
cd ..
# a copy of an existing install with only lib/*.bc replaced (recommended)
scripts/it/overlay-install.sh <c-install> map2check-library-<version>-<target>/lib <out-install>
# or in place: copy the modules over the C ones
cp map2check-library-<version>-<target>/lib/*.bc "${MAP2CHECK_PATH}/lib/"<c-install> is a Map2Check install whose lib/ holds the 21 C modules
(for example the output of scripts/it/build-map2check.sh). The overlay
script is in this repository, not in the archive. It leaves <c-install>
untouched, replaces only lib/*.bc (bin/, the pass plugins and every
symlink stay as they are), refuses a <bc-dir> that is not exactly the 21
module names (the .a files of the archive's lib/ are ignored), and writes
the marker .map2check-overlay with the sha256 of the 21 modules. The
in-place copy has no such checks: do not mix modules of two builds, and
back up the C ones first if you want to go back.
The frontend then links the program with the Rust runtime and nothing else
changes. Whether a run writes the witness files depends only on the witness
module it links (WitnessGeneration.bc or WitnessGenerationNone.bc), as in
the C runtime.
Two differential gates compared the C runtime and the drop-in on a
41-pair corpus (afl, symex and hybrid generators, 5 repetitions, 615
runs per side), with scripts/it/compare_baselines.py. Both finished with
gate: PASS:
| Gate | Map2Check develop |
Report | Baseline |
|---|---|---|---|
| 1 (2026-10-27, accepted 2026-10-28) | 3e914528ec57 |
map2check-library: docs/cutover/gate-2026-10-27.md |
docs/cutover/baselines/c-3e914528ec57.tsv |
| 2 (2026-10-28) | 0bca42eb89d1 |
map2check-library: docs/cutover/gate-2026-10-28.md |
docs/cutover/baselines/c-0bca42eb89d1.tsv |
The reports hold the exact comparator commands and outputs; this is the second gate's:
python3 scripts/it/compare_baselines.py docs/cutover/baselines/c-0bca42eb89d1.tsv docs/cutover/baselines/rust-fa279fe2af8c.tsv --deltas docs/cutover/deltas-aceitos.tsvTo produce new measurements, from the repository root (Docker needed, and
a clone of Map2Check develop in vendor/map2check/, which
scripts/fetch-map2check.sh makes and .gitignore keeps out of git):
scripts/it/build-map2check.sh # image and the C install: target/map2check-it/c
scripts/build-runtime-bc.sh target/map2check-it/bc # exactly the 21 drop-in modules
scripts/it/overlay-install.sh target/map2check-it/c target/map2check-it/bc target/map2check-it/ov
GENERATORS="afl symex hybrid" TIMEOUT_S=45 KILL_MARGIN_S=30 REPS=5 \
scripts/it/run-corpus.sh target/map2check-it/c c.tsv
GENERATORS="afl symex hybrid" TIMEOUT_S=45 KILL_MARGIN_S=30 REPS=5 \
scripts/it/run-corpus.sh target/map2check-it/ov rust.tsv
python3 scripts/it/compare_baselines.py c.tsv rust.tsv --deltas docs/cutover/deltas-aceitos.tsvThe gate reports interleave C and drop-in repetitions on the same day; do the same, because wall time depends on the host load.
What passing means: no verdict, failed-detail or witness difference that is
not an approved deviation (docs/cutover/deltas-aceitos.tsv, each row tied to
an SD-NN of semantic-deltas.md), the median wall
time of the drop-in at most 1.10 times the C's, the median peak memory
at most 1.36 times, and no new timeouts. For afl and hybrid the wall time
discounts up to 2.0 s of fixed compile time per run (the approved allowance
in map2check-library: docs/cutover/decisoes.md; it is not an SD): the drop-in
core is compiled with the program, and the median stays below the limit with
it (0.905 on the second gate; 1.192 without it). The deviations that the
gates accepted are SD-20, SD-24, SD-25 and SD-26.
Without a release archive, run this from the repository root:
cargo build --release # no features
scripts/build-runtime-bc.sh <out-dir> # the 21 lib/*.bc, deterministicbuild-runtime-bc.sh builds the Rust core from klee/Cargo.toml with
rustc 1.72.1 and the LLVM 16 tools, plus the IR modules, and fails when the
core exceeds 9 500 instruction lines or 13 600 IR lines (the frontend compiles
the program with the core three times with afl-clang-fast, so its size is
compile time). scripts/package-release.sh makes the archive from them.
The runtime profile (release optimisations with panic = "unwind", so the
FFI guard can recover panics) is what libmap2check_runtime.a is:
cargo build --profile runtime --features wasm # target/runtime/libmap2check.abuild.rs regenerates include/map2check.h on every build. The header is
not committed (.gitignore), so always take it from the same build as the
library.
scripts/build-c-abi-program.sh -o my_program -m Map2CheckFunctions-glue -m NonDetGeneratorAFL my_program.cThis is what tests/ffi_harness.c uses, and what Map2Check does with its
passes. Without the script, the pieces are:
cc -I include my_program.c lib/libmap2check.a <the .ll/.bc of the modules> -ldl -lpthread -lm -o my_program-
-ldl -lpthread -lmare the system libraries that the Rust standard library needs. -
The C names that an IR module defines (
map2check_malloc,map2check_assert,analysis_init,map2check_non_det_<t>,mainof a generator...) are not inlibmap2check.a: link the module's.ll(or.bc) too, asscripts/build-c-abi-program.sh -m <Module>does. -
The header and the library must come from the same build. The header carries the version as a string,
MAP2CHECK_VERSION(for example"2.0.0-rc.1"), so you can log it or check it in your build:#include "map2check.h" #ifndef MAP2CHECK_VERSION #error "map2check.h is older than 2.0.0-rc.1" #endif
The call order is the C runtime's, and the Map2Check passes and the IR modules make the calls; a program that links the runtime by hand follows it:
-
map2check_init(0)once, before any other call (the argument is the C runtime'sisSvComp, ignored).1b. Then
analysis_init()of the linked analysis mode module andnondet_init()of the linked generator module, in that order. TheMap2CheckFunctions.bccore of a Map2Check install makes these two calls insidemap2check_init, as the C runtime does; the staticliblibmap2check.adoes not, because it cannot reference names that only the IR modules define. Without them the staticlib runs with the mode unset: it writes nomap2check_property*file, never aborts on a violation, and prints the JSON report at exit.tests/ffi_harness.ccalls the mode's init for this reason. -
map2check_track_bband themap2check_nondet_<t>calls as the program runs, and the check calls of the analysis mode module linked (the names of the C runtime, defined by the IR modules;api-deltas.mdlists them). -
map2check_success()at the end of a program that held its property,map2check_error()ormap2check_exit()when it did not. With an analysis mode selected the verdict goes to themap2check_property*files of the C runtime; with the none modemap2check_success()prints the JSON report below.
The first violation is final. semantic-deltas.md says where the behaviour
differs from the C runtime and from 1.x.
This is the report of the Rust API (map2check::output), kept for the Rust tests
and for map2check_success() with no analysis mode. print_all_containers_as_json()
and the calls below are no longer C exports (D6).
print_all_containers_as_json() prints one pretty-printed JSON object on
stdout when no analysis mode is set (with a mode set, a Map2Check run, it
prints nothing; see below). Whitespace is not part of the contract; parse it. The examples
below are checked against the real serializer by
tests/compat_docs.rs::integration_guide_json_examples_match_the_serializer.
Calls map2check_save_basic_block_log(10, "main") and then
map2check_save_nondet_log_int(11, 0, INT_ID, &seven, "main") print:
{
"map2check_log": {
"result": "TRUE",
"step": 2,
"Container_TrackBBLog": [
{ "step": 1, "line": 10, "function_name": "main" }
],
"Container_NonDetLog": [
{
"step": 2,
"line": 11,
"scope": 0,
"function_name": "main",
"value": { "type": "INT", "value": 7 }
}
]
}
}The same calls followed by set_false_result(5 /* Reachability */, 12, "main")
print:
{
"map2check_log": {
"result": "FALSE",
"step": 2,
"property": "REACHABILITY",
"line_number": 12,
"function_name": "main",
"Container_TrackBBLog": [
{ "step": 1, "line": 10, "function_name": "main" }
],
"Container_NonDetLog": [
{
"step": 2,
"line": 11,
"scope": 0,
"function_name": "main",
"value": { "type": "INT", "value": 7 }
}
]
}
}Container_AllocationLog appears only for memory-safety violations (SD-02).
map2check_map_malloc((void *)0x1000, 16) followed by
map2check_check_free("p", (void *)0x10, (void *)0x2000, 0, 20, "main")
(free of p, at 0x10, holding 0x2000) prints:
{
"map2check_log": {
"result": "FALSE",
"step": 1,
"property": "MEMSAFETY_FREE",
"line_number": 20,
"function_name": "main",
"Container_TrackBBLog": [],
"Container_NonDetLog": [],
"Container_AllocationLog": [
{
"step": 1,
"line": 0,
"scope": 0,
"var_mem_address": 4096,
"mem_address_points_to": 4096,
"is_dynamic": true,
"is_free": false,
"ptr_name": "",
"function_name": "",
"size_destiny": 16,
"size_primitive": 1,
"is_null_valid": false
}
]
}
}property takes one of OVERFLOW, MEMSAFETY_FREE, MEMSAFETY_DEREF,
MEMSAFETY_MEMTRACK, MEMSAFETY_MEMCLEANUP, REACHABILITY,
CONCURRENCY, ASSERT and UNKNOWN. Nondet type takes one of INT, UINT, LONG, CHAR,
FLOAT and DOUBLE. Map2Check does not read this JSON: it reads the
result files described below. The JSON is an additional channel with its
own schema, pinned by a snapshot test (#14, SD-01).
The analysis mode and generator modules linked into the program select what
the runtime writes, as in the C runtime of Map2Check develop. In a
Map2Check run the library writes into the current directory the files the
frontend reads, byte for byte as the C runtime does (map2check_property*,
and with the witness module WitnessGeneration.bc linked track_bb_log.st,
heap_log.csv, allocation_log.csv, list_log.csv, klee_log.csv and
map2check_checked_error). The first violation ends the process, and
map2check_success() returns when the end-of-program verdict holds. With the
none mode nothing is written. The map2check::ffi functions that the
paragraph on the JSON report names set a mode directly, for the Rust tests.
See “Result files” in api-deltas.md and SD-20 for how the files are written.
- Validated commit only: the gates ran on Map2Check
develop0bca42eb(and3e914528ec57, same C runtime modules). A laterdevelopthat changes the runtime modules or the frontend needs a new gate. - KLEE and AFL++ differences: the accepted deviations SD-20, SD-24, SD-25 and SD-26 change verdicts on a few corpus tasks (the gate reports list the exact keys).
- Thread safety: the state sits behind one global mutex. The calls are safe from any thread but serialise, and a callback must not re-enter the library while a call is running.