Skip to content

statsig-go: Fix C — JNI-style callback FFI for raw_feature_gate#11

Closed
nsaini-figma wants to merge 1 commit into
mainfrom
nsaini/fix-c-callback
Closed

statsig-go: Fix C — JNI-style callback FFI for raw_feature_gate#11
nsaini-figma wants to merge 1 commit into
mainfrom
nsaini/fix-c-callback

Conversation

@nsaini-figma
Copy link
Copy Markdown
Collaborator

Summary

Prototype of the structural fix for the labmate SIGSEGV in the
Rust→Go FFI string return path. Companion minimal-fix prototype
(Fix A+B — NUL-scan + ptr guard) lives on branch
nsaini/fix-a-nul-scan. Both prototypes are draft for side-by-side
review.

What changed

  • New #[no_mangle] pub extern "C" fn statsig_get_raw_feature_gate_cb
    in statsig-ffi/src/statsig_c.rs. Builds the JSON string inside
    the closure body of use_raw_feature_gate_with_options and passes
    it to the Go callback by borrowed slice. The existing
    statsig_get_raw_feature_gate extern is left untouched.
  • statsig-go/statsig_ffi.go: registers the new callback symbol via
    purego.
  • statsig-go/statsig.go GetFeatureGateWithOptions: rewritten
    to use the callback path. Other gate/experiment/config functions
    are unchanged — they still use the heap-transfer model. Follow-up
    work would port them once Fix C is validated.
  • statsig-cpp/include/libstatsig_ffi.h, statsig-ffi/include/statsig_ffi.h,
    statsig-dotnet/src/Statsig/StatsigFFI.g.cs: auto-regenerated by
    cargo build to reflect the new extern. No hand edits — included
    to keep the checked-in bindings in sync with the Rust ABI.

Why this shape

Mirrors the JNI binding (statsig-ffi/src/jni/statsig_jni.rs:838-861)
and pyo3/napi: the Rust side builds the host-language object inside
the closure while the borrow is alive. No *mut c_char ever crosses
the FFI boundary, no *mut u64 inout writeback, no free_string
round-trip — i.e. every failure surface identified for the original
crash is eliminated for this code path.

Failure modes covered

  • bad len from purego trampoline → not used
  • bad ptr from purego trampoline → buffer is Rust-owned, only crosses
    the boundary for the callback's lifetime
  • premature free → no heap transfer to free
  • internal race → same Rust RwLock as before

Test plan

  • cargo check -p statsig_ffi clean
  • cargo build --release -p statsig_ffi --target x86_64-unknown-linux-gnu clean
  • nm -D libstatsig_ffi.so | grep statsig_get_raw_feature_gate_cb confirms symbol exported
  • go vet ./statsig-go/... clean
  • go build ./statsig-go/... clean
  • Validation in labmate staging once tagged as v0.19.4-figma2

Refs

  • Investigation: ~/nsaini/state/explorations/labmate-statsig-ffi-crash.md
  • Fix design: ~/nsaini/state/explorations/labmate-statsig-ffi-fix.md (Fix C section)
  • Companion PR (minimal): branch nsaini/fix-a-nul-scan

Adds statsig_get_raw_feature_gate_cb, a callback-style variant that
mirrors how JNI/pyo3/napi already cross the FFI boundary in this
codebase. The Go side passes a purego.NewCallback that receives the
JSON bytes via a borrowed *const u8 + length while the Rust-side
FeatureGateRaw is still alive — no heap transfer, no *mut u64 inout,
no free_string round-trip.

Scope-limited to GetFeatureGateWithOptions for prototype review.
Other _raw_ returners would be ported in follow-up work.

JNI parallel: statsig-ffi/src/jni/statsig_jni.rs:838.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant