nim-ffi/CHANGELOG.md
Ivan FB 2e30f06757
fix: reject {.ffi.} calls on an unconstructed ref library
When no {.ffiCtor.} has stored a library, the FFI thread points `myLib` at a
default-valued fallback so handlers always have something to bind, and
`myLibOwned` is what distinguishes that fallback from a real one. For an
`object` library the fallback is a usable zero value and callers legitimately
depend on it, but for a `ref` library it is `nil`: the user body received a nil
ref and faulted on its first field access. A failing ctor is the common way to
get there, since the C entry point hands back a live context before the ctor
body has run on the FFI thread.

Guard on `myLibOwned` for `ref` library types only. The check sits in the
generated handler rather than the C entry point, so it runs behind any queued
constructor — a host that fires a call without awaiting the create callback
still succeeds, as before.

Backport of the same fix on master, where the state had to be added rather than
reused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULz7Md52AF6PmqZeCmh8b7
2026-07-28 02:44:09 +02:00

8.1 KiB

Changelog

All notable changes to this project are documented in this file.

[Unreleased]

Fixed

  • A {.ffi.} call against a ref library type whose {.ffiCtor.} never stored a library (it failed, or none ran) no longer crashes. Without a constructed library the FFI thread points myLib at a default-valued fallback; for an object that is a usable zero value, but for a ref it is nil, so the user body faulted on its first field access. Such a request is now rejected with library is not initialized: the constructor failed or has not run yet through the callback. The check is emitted only for ref library types, and it runs on the FFI thread behind any queued constructor, so a host that issues a call without awaiting the create callback is unaffected.

Changed

  • User event callbacks now run on a dedicated event thread fed by a bounded SPSC queue (default capacity 1024), so a slow listener can no longer block the FFI thread or concurrent add_event_listener / remove_event_listener calls (#6).
  • Replaced the dedicated watchdog thread with a heartbeat check that runs on the event thread. The FFI thread advances an atomic heartbeat each loop iteration; if it stalls for more than 1s past the start-up grace window, the event thread emits the not_responding event.
  • declareLibrary no longer emits the shared-library soname / install_name linker flags when building as an executable (--app:lib guard), so FFI code can be unit-tested as a plain binary — fatal on macOS, where -install_name requires -dynamiclib.

Added

  • {.ffiEvent.} now accepts multiple parameters. The macro synthesises and registers an envelope object (<WireNamePascalCase>Payload) whose fields are the parameters and dispatches an instance of it, so multi-field events no longer need a hand-written payload type. A single parameter still rides the wire directly (a scalar, or an existing {.ffi.} object). The foreign bindings gain the envelope as a first-class struct plus a typed handler.
  • Per-interaction ABI-format annotations: declareLibrary now takes an optional defaultABIFormat ("cbor" default, or "c") that every {.ffi.} / {.ffiCtor.} / {.ffiDtor.} / {.ffiRaw.} / {.ffiEvent.} inherits, and each annotation can override it with an "abi = c" / "abi = cbor" spec (e.g. {.ffi: "abi = cbor".}). declareLibrary is now required before any FFI annotation (#78).
  • c (flat C-struct) ABI codec: every {.ffi: "abi = c".} type gets a <T>_CWire companion plus cwirePack / cwireUnpack / cwireFree. This first slice covers the flat path — POD scalars and string (as cstring); composite fields follow. The c proc-dispatch path and the foreign (C++ / Rust) generators are still pending, so c remains rejected on proc/ctor/dtor/event annotations for now.
  • tests/bench/bench_codec.nim (+ nimble bench_codec): a single-process microbenchmark comparing the cbor and c codecs across payload shapes, isolating codec cost from the (identical) thread/callback round-trip.
  • Queue-overflow handling: when the bounded event queue is full, the library sets a sticky "stuck" flag, logs an error, fires not_responding from the event thread, and rejects subsequent sendRequestToFFIThread calls with event queue stuck - library cannot accept new requests.

[0.2.0] - 2026-06-04

Major release introducing the CBOR-based wire format, CBOR-backed FFI events with a multi-listener registry, multi-language binding generation (C++, Rust, CDDL), CI hardening with sanitizers, and several robustness fixes around context lifetime and memory safety.

Added

  • CBOR serialization as the FFI wire format, replacing the previous JSON/string-based serial.nim (#23).
  • CBOR-backed FFI events: event payloads are now serialized with CBOR (#39).
  • Multi-listener event registry (FFIEventRegistry) and its wiring into FFIContext (#45, #49).
  • Event-listener ABI with per-event typed listeners (#50).
  • C++ typed per-event listeners in the generated bindings (#51).
  • Rust per-event typed listeners (add_on_<x>_listener + wildcard add_event_listener) (#52) and Rust event example bindings/clients (#53).
  • C++ binding generator with end-to-end tests driven by CMake/CTest (#27), later expanded with multi-context, cross-library, pipeline, and stress tests (#42).
  • CDDL schema generator for the FFI types (#24).
  • CI pipeline: parallel test execution (#26), AddressSanitizer / UndefinedBehaviorSanitizer / ThreadSanitizer jobs (#34), and a cross-platform OS matrix for the C++ e2e suite (#38).
  • CBOR type-coverage tests (#41).

Changed

  • Removed the redundant ffiType macro; the ffi macro is now the single authoring entry point (#22).
  • Generated C++ avoids move constructors and assignment operators (#36) and no longer throws exceptions across the binding boundary (#46).
  • Removed the wildcard event listener; event dispatch is now strictly per-event (#70).

Fixed

  • Use-after-free in the event/context lifetime path (#47).

[0.1.4] - 2026-05-13

Full changelog

Added

  • Simplified FFI authoring with auto-generated C++ and Rust language bindings, including new ffi/codegen/cpp.nim, ffi/codegen/rust.nim and shared ffi/codegen/meta.nim helpers (#15).
  • Rust example bindings and clients under examples/nim_timer/ (rust_bindings and rust_client, the latter with a Tokio async variant) (#15).
  • JSON/string-based FFI (de)serialization via ffi/serial.nim (ffiSerialize/ffiDeserialize), with tests/test_serial.nim coverage. (CBOR replaced this layer later, in 0.2.0.)
  • FFI context pool (ffi/ffi_context_pool.nim) using a fixed array of contexts.
  • Test suite expansion: test_alloc.nim, test_ctx_validation.nim, test_ffi_context.nim, test_gc_compat.nim.
  • Continuous integration pipeline (#12).

Fixed

  • Context buffer overflow (#21).
  • Use a fixed array of contexts to avoid consuming all file descriptors (#14).
  • Memory leaks (#11).
  • Add install_name for macOS shared libraries (#8).

Changed

  • Run tests with the refc garbage collector (#20).
  • Remove CatchableError usage (#19).
  • Update license files to comply with Logos licensing requirements.

[0.1.3] - 2026-01-23

Fixed

  • Properly import and re-export chronicles so downstream packages get the logging macros transitively.

[0.1.2] - 2026-01-23

Fixed

  • Re-export chronicles and std/tables when the ffi module is imported, so generated code resolves these symbols at the call site.

[0.1.1] - 2026-01-23

Initial tagged release.

Added

  • Core ffi macro for declaring procs exposed across the FFI boundary.
  • FFIContext with a dedicated worker thread, request dispatch, and a watchdog with configurable timeout (#7).
  • License files updated to comply with Logos licensing requirements.