Skip to content

test(ci): Miri + valgrind on the unsafe FFI boundary (complements #66) #82

@membphis

Description

@membphis

Motivation

#66 proposes ASan + cargo-audit. ASan catches runtime memory errors (use-after-free, OOB), but it does not catch the bug classes that qjson's documented invariants are most exposed to:

  • get_str pointer invalidationqjson_get_str / qjson_cursor_get_str hand back a (ptr,len) into the input buffer or doc.scratch; any later *_get_str on the same doc may invalidate it (scratch reuse). This is an aliasing / use-after-reuse contract, not necessarily a heap UAF ASan would flag.
  • 'a'static transmute in qjson_parse — lifetime erasure that relies on the caller pinning the buffer. Provenance/lifetime misuse here is UB that Miri models directly.
  • RefCell aliasing for scratch / skip, and the unsafe extern "C" surface in src/ffi.rs.

These are exactly the things Miri (Stacked/Tree Borrows, pointer provenance, UB in unsafe) is built to detect, and valgrind over the end-to-end Lua path (dlopen → FFI → interleaved get_str) catches at the integration level the way lua-cjson's prove (valgrind) job does.

This issue is complementary to #66 (ASan), not a duplicate — different tools, different bug classes.

Proposed work

1. Miri job (nightly, non-blocking)

- run: rustup +nightly component add miri
- run: cargo +nightly miri test --test ffi_smoke --test ffi_cursor --test ffi_strings
  • Prioritize the FFI integration tests and especially fuzz/fuzz_targets/fuzz_ffi_ops.rs-style interleaved op sequences (a small deterministic harness driving alternating get_str calls to assert the pointer-invalidation contract).
  • Some intrinsics (AVX2/NEON) are unsupported under Miri → run with --no-default-features (scalar scanner) so Miri exercises the safe-vs-unsafe logic without SIMD intrinsics.

2. valgrind over the Lua busted suite (Linux)

- run: |
    LD_LIBRARY_PATH="$PWD/target/release" \
      valgrind --error-exitcode=1 --leak-check=full \
      luajit -e 'require("busted.runner")({...})' ... tests/lua

Mirrors lua-cjson's valgrind gate but over qjson's real deployment path (LuaJIT FFI + dlopen). Catches leaks/invalid reads that only manifest across the FFI boundary.

Tradeoffs

  • Miri is slow and can't run SIMD intrinsics → scalar-only, subset of tests, nightly, non-blocking.
  • valgrind under LuaJIT needs --suppressions for LuaJIT's own allocator quirks; expect to commit a suppression file.

Affected files

  • .github/workflows/ci.yml
  • possibly valgrind.supp (new), a small deterministic FFI-ops test harness

Relates to #66.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions