data: collect-safe movable-pointer FFI knowledge

ober

edef9ab2f51df7076e2727ddf51465456ab0753f

diff --git a/data/anti-patterns.sexp b/data/anti-patterns.sexp
index 80ec136..3780026 100644
--- a/data/anti-patterns.sexp
+++ b/data/anti-patterns.sexp
@@ -5964,4 +5964,25 @@
    ("tools"
      "jerboa_check_balance"
      "jerboa_verify"
-     "jerboa_compile_check")))
+     "jerboa_compile_check"))
+ (("advice"
+    .
+    "Declare the binding with void* and bounce through a scoped native buffer: foreign-alloc + foreign-set!/foreign-ref copy + foreign-free inside dynamic-wind (see call-with-rustls-io-buffer in (std net tls-rustls)). Keep the public bytevector signature unchanged so call sites don't change. Audit with: rg \"__collect_safe\" -A1 | rg \"u8\\*|string\". Blocking calls WITHOUT __collect_safe are safe from this (GC cannot run) but pin the scheduler; either make them fast/nonblocking or use the void* bounce pattern.")
+   ("avoid"
+     .
+     "Never pass a movable Scheme bytevector/string as u8*/string to a __collect_safe foreign-procedure. The call releases the Chez TC mutex while parked in a syscall; another thread triggers GC; the moving collector relocates the bytevector; native code then reads/writes the stale address. Result: heap corruption and later \"Exception: invalid memory reference\" segfaults in unrelated-looking places (observed as SEGFAULT ctx=tui.agent-worker in jcode's rustls HTTPS streaming).")
+   ("id" . "collect-safe-u8-star-movable-buffer")
+   ("kinds" "ffi" "bug-fix" "codegen")
+   ("pattern"
+     .
+     "(foreign-procedure __collect_safe \"...\" (... u8* ...) ...)")
+   ("severity" . "high")
+   ("tags" "ffi" "collect-safe" "u8*" "gc" "segfault"
+     "invalid-memory-reference" "foreign-alloc" "void*")
+   ("title"
+     .
+     "__collect_safe FFI with u8* (movable Scheme bytevector) segfaults after GC")
+   ("tools"
+     "jerboa_ffi_null_safety"
+     "jerboa_security_scan"
+     "jerboa_ffi_type_check")))
diff --git a/data/error-fixes.sexp b/data/error-fixes.sexp
index a4a1783..3f25ac9 100644
--- a/data/error-fixes.sexp
+++ b/data/error-fixes.sexp
@@ -3374,4 +3374,16 @@
      "Do not pass (make-time ...) to Chez `sleep` when (jerboa prelude) is imported: the prelude shadows make-time with the (std datetime) constructor, which produces a dt-raw record, not a Chez time record. Use the prelude's own (sleep-ms ms) instead (it internally uses a private %chez-make-time). If you must build a duration by hand, re-import Chez's make-time under a rename: (rename (only (chezscheme) make-time) (make-time %chez-make-time)).")
    ("id" . "sleep-prelude-make-time-type-mismatch")
    ("pattern" . "is not a time record of type time-duration")
+   ("type" . "runtime"))
+ (("code_example"
+    .
+    ";; BAD (heap corruption -> segfault after GC while blocked):\n;; (foreign-procedure __collect_safe \"jerboa_tls_read\" (unsigned-64 u8* unsigned-64) int)\n;; GOOD: void* + scoped foreign buffer\n(def c-read (foreign-procedure __collect_safe \"jerboa_tls_read\" (unsigned-64 void* unsigned-64) int))\n;; copy through foreign-alloc'd memory, freed via dynamic-wind")
+   ("explanation"
+     .
+     "A __collect_safe foreign call releases the Chez TC mutex while parked in the kernel, so the moving collector can relocate a bytevector/string passed as u8*/u16*/u32*/string; native code then reads/writes the stale address, corrupting the heap. The crash often surfaces later in an unrelated-looking thread (seen in production as SEGFAULT ctx=tui.agent-worker in jcode's crash.log during rustls HTTPS streaming).")
+   ("fix"
+     .
+     "Never pass movable Scheme pointers to __collect_safe bindings. Redeclare with void* and bounce through a scoped foreign-alloc buffer (copy in/out, foreign-free via dynamic-wind) -- the call-with-rustls-io-buffer pattern in (std net tls-rustls). Audit with: rg \"__collect_safe\" -A1 | rg \"u8\\*|string\". Blocking calls without __collect_safe are GC-safe but pin the scheduler.")
+   ("id" . "collect-safe-movable-pointer-segfault")
+   ("pattern" . "invalid memory reference|Some debugging context lost")
    ("type" . "runtime")))