You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(api): Stash Notecard "err" message into allocator-backed storage
The Api singleton thunk path captures Notecard error strings into a caller-stack `detail::NcErrorCapture` whose `view()` returned a pointer into its own `buf[64]`. Generated `execute()` then constructed `ApiResult<...>(ErrorInfo{..., nc_err_.view()})` whose `ErrorMessage` held that pointer — undefined behavior the moment execute() returned and the stack frame was reused. Reading `result.error().message` after a Notecard error returned silently truncated bytes or random heap contents. The non-singleton path doesn't have the bug because it routes through `streaming_attempt`'s `pool.intern(err)` (allocator-backed, heap-leaked, durable).
Adds `Notecard::stash_nc_err(string_view)` and the matching `StaticNotecard::stash_nc_err(string_view)` that copy the bytes into the configured allocator (one allocation per Notecard error, never freed — same lifetime model as `StringPool::intern`). Adds an optional `durable_ptr` field to `NcErrorCapture` whose `view()` prefers the durable pointer when set. The api.hpp.j2 thunks call `stash_nc_err` after `execute_void`/`execute_generic_with_body` returns and route the durable pointer back through `set_durable`, so the generated code needs no change — `nc_err_.view()` just starts returning durable bytes automatically.
Avoids fixed-size buffers entirely: arena allocators pay only for the messages they actually receive, and zero-allocator builds get `nullptr` and fall back to the original (still-dangling) buf-pointer path — no worse than before, no extra RAM consumed.
NOTE_MINIMAL gates the entire stash mechanism out — the durable_ptr field, set_durable(), the view() branch, stash_nc_err() itself, and the thunk's call to it all collapse to the original code. AVR-class targets (which set NOTE_MINIMAL) typically branch on `result.error().code` and ignore the textual message; the +250 B of flash for the durable-copy logic isn't worth it there. AVR sizes verified at baseline post-change (avr-notecpp / direct / raw / multistack all unchanged).
Test impact: the streaming-error tests that previously failed under NOTE_SINGLETON=1 (e.g. `streaming: void endpoint with Notecard error`, `streaming: Notecard error response propagates`) now pass because `rsp.error().message == "<text>"` reads stable bytes. Failure count in the singleton-only build drops from 73 to 65; the remaining 65 are unrelated pre-existing limitations of singleton mode (GenericResponseSink can't dispatch array fields; thunk path doesn't inject request IDs).
Also documents the rationale for the existing `binary_ctrl_buf_[256]` fixed-size buffer in Notecard, since it's the same pattern this fix deliberately avoided.
0 commit comments