Commit 37bd237
Make the Zig FFI compile + memory-safety fixes + CI (W1-6) (#38)
## What & why
The Zig C-ABI FFI in `ffi/zig` did not compile and had no CI, so it
could rot
undetected while every other check stayed green. `main.zig` declared
`Handle` as
`opaque {}` yet gave it fields and `allocator.create(Handle)`d it (both
hard Zig
compile errors), `integration_test.zig` re-declared its own conflicting
`Handle`,
and `build.zig` referenced a missing header and bench file. This makes
the FFI
compile, memory-safe, and CI-checked.
**The FFI is EXPERIMENTAL:** the exported operations validate their
arguments and
enforce handle liveness but are PLACEHOLDERS — they are NOT backed by
the Julia
statistical core, and no Idris2 ABI is wired up. That is deliberate and
called out
in a status comment block at the top of `main.zig`; the documentation
reframe is
tracked separately (W2-4). This PR does not invent a Julia bridge.
## Changes
- **`ffi/zig/src/main.zig`** — `Handle` is now a real internal `struct`
(crossing
the C ABI only as an opaque `*Handle`). Added a `magic` liveness cookie
(`HANDLE_MAGIC`) that is poisoned (`HANDLE_FREED`) before the block is
released,
and checked on every entry point that dereferences a handle. Double-free
and
use-after-free are now safe: a second `statistikles_free` returns
`.invalid_param` instead of a second `destroy()`; operations on a freed
handle
return an error rather than invoking UB. Fixed the
`statistikles_last_error`
ownership leak — error messages are program-lifetime string literals
(static
storage), documented as a borrowed, never-freed pointer. Added
`export fn statistikles_abi_version() callconv(.C) u32` returning a
monotonic
ABI number alongside the existing semantic version string.
- **`ffi/zig/test/integration_test.zig`** — imports the ONE shared
`Handle`/`Result`
from the root module (via the `statistikles` module wired in
`build.zig`) instead
of re-declaring them, so the tests and library can never drift on the
ABI. The
double-free test asserts the safe-error behaviour (`.invalid_param`),
not merely
"should not crash".
- **`ffi/zig/build.zig`** — removed the dead references to the missing
`include/statistikles.h` and `bench/bench.zig`; wires `src/main.zig` in
as the
`statistikles` module for the integration tests; `zig build test` runs
both the
unit and integration test suites.
- **`.github/workflows/zig.yml`** (NEW) — SHA-pinned `actions/checkout`
(`9c091bb2…` = v7.0.0) + SHA-pinned `mlugg/setup-zig` (`d1434d08…` =
v2.2.1)
pinning Zig **0.13.0**, running `zig build test --summary all` in
`ffi/zig`.
Matches the repo's SHA-pinning style; `permissions: read-all`;
step-level
path/branch triggers only.
## Zig version
Built and CI-checked against **Zig 0.13.0**. The setup environment
shipped Zig
0.16.0, whose std Io redesign is incompatible with this code's dialect,
so the
workflow pins 0.13.0 (the version `build.zig` / the source target) via
`mlugg/setup-zig`.
## Verification
Local (WSL Debian, Zig 0.13.0 binary), `zig build test --summary all` in
`ffi/zig`:
```
Build Summary: 5/5 steps succeeded; 24/24 tests passed
```
The 24 tests include the memory-safety cases: double-free returns
`.invalid_param`
(not UB), operations on a freed handle are rejected, `free(null)` is a
safe no-op,
and `last_error` returns the same stable static pointer across calls (no
leak).
CI runs the identical `zig build test` on `ubuntu-latest`.
## Skipped / out of scope
Real Julia-backed FFI semantics, the Idris2 ABI, and the documentation
reframe
(W2-4) are intentionally out of scope per the work order.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 77c999e commit 37bd237
5 files changed
Lines changed: 313 additions & 148 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
11 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
12 | 16 | | |
13 | 17 | | |
14 | 18 | | |
15 | 19 | | |
16 | 20 | | |
| 21 | + | |
17 | 22 | | |
18 | | - | |
19 | | - | |
20 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
21 | 26 | | |
22 | 27 | | |
23 | 28 | | |
| |||
26 | 31 | | |
27 | 32 | | |
28 | 33 | | |
| 34 | + | |
29 | 35 | | |
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
33 | 39 | | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
| 40 | + | |
42 | 41 | | |
43 | 42 | | |
44 | 43 | | |
45 | 44 | | |
46 | 45 | | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
54 | 53 | | |
55 | 54 | | |
56 | 55 | | |
57 | 56 | | |
58 | 57 | | |
59 | | - | |
60 | | - | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
61 | 64 | | |
62 | 65 | | |
63 | 66 | | |
64 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
65 | 74 | | |
66 | 75 | | |
67 | 76 | | |
| |||
70 | 79 | | |
71 | 80 | | |
72 | 81 | | |
| 82 | + | |
73 | 83 | | |
74 | 84 | | |
75 | 85 | | |
76 | 86 | | |
77 | 87 | | |
78 | 88 | | |
79 | 89 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | 90 | | |
0 commit comments