This document describes the comprehensive testing infrastructure for hexray, including ground truth benchmarks, multi-language test fixtures, regression tests, and fuzz testing.
Hexray uses a multi-layered testing approach:
| Layer | Purpose | Location |
|---|---|---|
| Unit Tests | Component correctness | crates/*/src/**/*.rs |
| Integration Tests | Cross-crate functionality | crates/hexray/tests/ |
| Differential Tests | Compare with system tools | tests/differential_tests.rs |
| Snapshot Tests | CLI output stability | tests/snapshot_tests.rs |
| Regression Tests | Decompiler stability | tests/decompiler_regression.rs |
| Benchmark Suite | Ground truth validation | hexray-analysis/src/decompiler/benchmark.rs |
| Fuzz Tests | Crash resistance | fuzz/fuzz_targets/ |
| Miri Gate | Targeted UB-focused interpreter checks | scripts/check-memory-safety |
| CUDA differential | SASS decode vs nvdisasm -json |
tests/differential/sass_compare.rs |
| CUDA corpus regression | Real-cubin invariants (kernel, params, exits) | crates/hexray-formats/tests/cuda_corpus.rs |
| CUDA fault injection | Truncation / bit-flip / framing chaos | crates/hexray-formats/tests/cuda_fault_injection.rs |
A handwritten CUDA corpus (tests/corpus/cuda/sources/, BSD-3-Clause)
is compiled against sm_80, sm_86, and sm_89 via
scripts/build-cuda-corpus.sh to produce the differential ground
truth (nvdisasm -json). The corpus is gitignored — CI without a
CUDA toolkit no-ops the dependent tests silently.
CI gates enforced when the corpus is present:
| Gate | Threshold |
|---|---|
| sm_80 base mnemonic match | ≥ 70.0% (current: 100.0%) |
| every SM predicate guard match | ≥ 95.0% (current: 100.0%) |
| every SM full mnemonic match | ≥ 92.0% (current: 95.8%) |
Five new fuzz targets target the CUDA parsers: sass_decoder,
cubin_view, nv_info_parser, ptx_parser, fatbin_parser. They
register through the standard cargo fuzz workflow and are validated
on every push by scripts/check-fuzz-targets. See
docs/CUDA.md for the architecture-level picture.
Hexray uses a local-first tiered workflow instead of GitHub Actions:
| Tier | Typical Hook | Purpose |
|---|---|---|
fast |
pre-commit | Keep commits lightweight (fmt + workspace check) |
medium |
pre-push | Block regressions before push (adds clippy + tests) |
full |
manual/ci-local | Exhaustive validation for release-quality checks |
Run tiers directly:
scripts/ci-local --tier fast
scripts/ci-local --tier medium
scripts/ci-local --tier medium --no-strict-callback
scripts/ci-local --tier full
scripts/ci-local --tier full --no-coverage
scripts/ci-local --tier full --perf
scripts/check-feature-matrix
scripts/check-fuzz-targets
scripts/check-security
HEXRAY_COVERAGE_LINES=70 scripts/check-coverage
scripts/check-tla
scripts/check-memory-safety
scripts/quality-smoke
scripts/quality-smoke --with-callback
scripts/quality-smoke --strict-callbackFor stable benchmark comparisons and parallelism tuning guidance, see PERFORMANCE.md.
scripts/quality-smoke— fixture-backed control-flow quality cases (switch/goto recovery gates) for a cheap fail-fast signal.--with-callbackadds callback CLI smoke checks;--strict-callbackenforces strict callback snapshots.scripts/ci-local --tier medium— routes strict callback checks throughscripts/quality-smoke --strict-callbackby default; use--no-strict-callbackto disable during exploratory work.scripts/check-feature-matrix— compiles the non-defaulthexray-disasmarchitecture feature combinations plushexray-core/serde, so feature flags stay honest instead of only working through the default workspace path.scripts/check-fuzz-targets— compiles every cargo-fuzz target from the stable toolchain path used by local CI, so newly added fuzz entrypoints cannot silently rot.scripts/check-security— runscargo audit.scripts/check-coverage— runscargo llvm-covwith a configurable line threshold viaHEXRAY_COVERAGE_LINES.scripts/check-tla— model-checks the incremental invalidation TLA+ spec with TLC, then runs the matching Rust conformance test so the formal model and implementation stay in lockstep.scripts/check-memory-safety— targeted nightlycargo mirigate against the hardened Mach-O parser, the incremental invalidation model-conformance path, and the CUDA fault-injection suite. Requiresrustup, a nightly toolchain, and themiricomponent.
- Standard decompiler benchmarks include strict callback API
quality / index gates for
qsort,qsort_r,bsd_qsort_r,bsearch,signal,on_exit,pthread_create, andpthread_atfork. This includes stack-spill forwarding callback cases forqsortandpthread_create; callback-index precision and recall are enforced where index stability is expected. - Callback benchmark cases enforce a callback provenance quality
gate: shape-fallback provenance ratio must remain zero
(
max_callback_shape_fallback_ratio = 0.0) for allcallback_*cases. - Callback CLI fixture regressions exercise shim-backed callback
APIs (
hexray_qsort_r,hexray_bsd_qsort_r,hexray_on_exit,hexray_pthread_atfork) to keep end-to-end typed callback output portable across toolchains. - Signature recovery unit tests include callback index stability regressions for ambiguous lifted-alias reuse and slot-0 callback fallback behavior.
- Strict callback header snapshots pin canonical callback-parameter
positions for wrapper APIs (
register_on_exit,register_atfork) and verify slot-0 fallback diagnostics.
The benchmark suite provides automated quality assessment of decompiled output against expected patterns.
crates/hexray-analysis/src/decompiler/benchmark.rs
| Pattern | Description | Expected Output |
|---|---|---|
simple_for_loop |
Basic counting loop | for or while with increment |
while_loop |
Condition-controlled loop | while keyword |
do_while_loop |
Post-test loop | do { } while |
nested_loops |
Multi-level iteration | Nested loop constructs |
loop_with_break |
Early exit pattern | break statement |
loop_with_continue |
Skip iteration | continue statement |
| Pattern | Description | Expected Output |
|---|---|---|
simple_if_else |
Basic conditional | if / else |
nested_conditionals |
Multi-level branching | Nested if statements |
short_circuit_and |
Lazy AND evaluation | && operator |
short_circuit_or |
Lazy OR evaluation | ` |
ternary_operator |
Conditional expression | ? : operator |
| Pattern | Description | Expected Output |
|---|---|---|
bit_test |
Test specific bit | & (1 << or bitfield |
bit_set |
Set specific bit | ` |
bit_clear |
Clear specific bit | &= ~(1 << |
is_power_of_two |
Power-of-2 check | (n & (n-1)) == 0 |
compound_add |
In-place addition | += operator |
increment |
Counter increment | ++ or += 1 |
clamp_pattern |
Value clamping | min / max or conditional |
| Pattern | Description | Expected Output |
|---|---|---|
bubble_sort |
Sorting algorithm | Nested loops with swap |
factorial_iterative |
Factorial computation | Loop with multiplication |
The benchmark suite evaluates decompiled output against:
- Expected Patterns: Keywords/constructs that MUST appear
- Forbidden Patterns: Constructs that MUST NOT appear (e.g.,
goto) - Structural Metrics:
- Nesting depth (target: ≤3 levels)
- Switch recovery thresholds (per-case minimum switch count)
- Goto reduction thresholds (per-case maximum goto count)
- Label count (target: 0 for structured code)
- Variable naming quality
use hexray_analysis::decompiler::benchmark::{BenchmarkSuite, create_standard_suite};
let suite = create_standard_suite();
let results = suite.run_all(&decompiler);
for result in &results {
println!("{}: {:.1}% ({})",
result.name,
result.score * 100.0,
if result.passed { "PASS" } else { "FAIL" }
);
}Different compilers generate different code patterns. The test fixtures cover multiple source languages to ensure the decompiler handles various compilation strategies.
tests/fixtures/
| File | Patterns Covered |
|---|---|
test_loops.c |
for, while, do-while, nested, break, continue |
test_conditionals.c |
if-else, switch, ternary, short-circuit |
test_structs.c |
Field access, nested structs, arrays of structs |
test_arithmetic.c |
Bit manipulation, overflow, division |
| File | Patterns Covered |
|---|---|
test_cpp_classes.cpp |
Classes, constructors, destructors, virtual functions |
| Multiple inheritance, vtables, RTTI | |
| IntStack (template-like expanded), dynamic allocation | |
test_cpp_exceptions.cpp |
try-catch, multiple catch blocks, nested handlers |
| RAII pattern, noexcept, function try blocks |
| File | Patterns Covered |
|---|---|
test_rust_patterns.rs |
Enums with variants, Option, Result<T, E> |
| Trait implementations, closures, iterators | |
| Box allocation, Vec operations, generics | |
| Slice patterns, panic/assert |
| File | Patterns Covered |
|---|---|
test_d_patterns.d |
Structs with methods, classes with inheritance |
| Templates (generic functions), contracts (in/out/invariant) | |
| Scope guards (scope(exit/success/failure)) | |
| Compile-time function evaluation (CTFE), mixins | |
| Nullable types, associative arrays |
| File | Patterns Covered |
|---|---|
test_go_patterns.go |
Structs with methods, interfaces |
| Multiple return values, defer pattern | |
| Panic/recover, goroutines, channels | |
| Slices vs arrays, maps, type assertions | |
| Closures, variadic functions |
| File | Patterns Covered |
|---|---|
test_swift_patterns.swift |
Structs (value types), classes (reference types) |
| Protocols, extensions, optionals | |
| Enums with associated values, closures | |
| Guard statements, switch with pattern matching | |
| Property observers, computed properties |
# C
gcc -O2 -o test_loops tests/fixtures/test_loops.c
# C++
clang++ -O2 -fexceptions -o test_cpp_exceptions tests/fixtures/test_cpp_exceptions.cpp
# Rust
rustc -O -o test_rust_patterns tests/fixtures/test_rust_patterns.rs
# D (requires dmd or ldc2)
dmd -O -of=test_d_patterns tests/fixtures/test_d_patterns.d
# Go
go build -o test_go_patterns tests/fixtures/test_go_patterns.go
# Swift (macOS)
swiftc -O -o test_swift_patterns tests/fixtures/test_swift_patterns.swiftRegression tests ensure the decompiler produces consistent, valid output across versions.
crates/hexray/tests/decompiler_regression.rs
test_macos_binary_decompilation_structureDecompiles system binaries (/bin/ls, /bin/cat, /bin/echo) and validates:
- Function header present (parentheses)
- Braces present
- Return statement present
- Variables assigned
- No goto statements (quality indicator)
test_decompiler_no_crash_on_various_patternsTests edge cases that should not crash:
- Empty function (just
ret) - Simple function with push/pop
- Function with conditional branches
test_decompiler_stability_on_repeated_runsVerifies that decompiling the same code multiple times produces identical output.
test_decompiler_handles_loop_patternsValidates that loop constructs are detected in decompiled output.
cargo test --package hexray --test decompiler_regressionFuzz testing ensures robustness against malformed or adversarial inputs.
fuzz/fuzz_targets/
| Target | Description |
|---|---|
x86_64_decoder |
Random byte sequences to x86-64 decoder |
arm64_decoder |
Random byte sequences to ARM64 decoder |
riscv_decoder |
Random byte sequences to RISC-V decoder |
| Target | Description |
|---|---|
elf_parser |
Malformed ELF files |
macho_parser |
Malformed Mach-O files |
elf_structured |
Structure-aware ELF fuzzing |
macho_structured |
Structure-aware Mach-O fuzzing |
| Target | Description |
|---|---|
cfg_builder |
CFG construction from instruction sequences |
decompiler |
Full decompilation pipeline |
cd fuzz
# Run specific target
cargo +nightly fuzz run x86_64_decoder
# Run with time limit
cargo +nightly fuzz run cfg_builder -- -max_total_time=300
# Run all targets via Docker
./run-fuzzers.sh --hours 2Compares hexray output against system tools to ensure accuracy.
crates/hexray/tests/differential_tests.rs
| Component | System Tool | What's Compared |
|---|---|---|
| Disassembly | objdump -d |
Instruction mnemonics, operands |
| Symbols | nm |
Symbol names, addresses, types |
| Strings | strings |
Detected string content |
cargo test --package hexray --test differential_testsAs of v1.3.0 (with the cuda feature enabled):
| Crate | Test Count |
|---|---|
| hexray-analysis | 1088+ |
| hexray-types | 177+ |
| hexray (CLI) | 171+ |
| hexray-core | 171+ |
| hexray-disasm | 167+ |
| hexray-signatures | 144+ |
| hexray-formats | 138+ |
| hexray-emulate | 45+ |
| hexray-demangle | 32+ |
| Total | 2100+ |
This project uses local and self-hosted CI workflows (no GitHub Actions required):
- Fast tier:
scripts/ci-local --tier fast - Medium tier:
scripts/ci-local --tier medium - Full tier:
scripts/ci-local --tier full - Cross-arch Docker matrix:
scripts/ci-cross-docker(orscripts/ci-local --tier full --cross)
// In benchmark.rs
BenchmarkCase::new("my_pattern", my_bytes.to_vec(), 0x1000)
.expect_pattern("while")
.expect_pattern("break")
.forbid_pattern("goto")- Create the source file in
tests/fixtures/ - Document the patterns it covers
- Add compilation instructions in comments
- Create
fuzz/fuzz_targets/my_target.rs - Add entry to
fuzz/Cargo.toml - Document in
fuzz/README.md