Summary
ctx.call reliably SEGVs at v8::internal::wasm::StackMemory::jslimit() (NULL deref at 0x68) when running a large Ember + webpack app (Discourse 3.6) that does not use WebAssembly. The crash happens inside V8's exception unwinder.
The same workload runs fine under quickjs.rb, so the trigger is V8-specific.
Versions
mini_racer 0.21.0
libv8-node 24.12.0.1
- Ruby 3.4.6
- Linux x86_64 (Gentoo, glibc 2.x)
Crash (gdb, Thread 1, signal SIGSEGV)
#0 0x...43f0 v8::internal::wasm::StackMemory::jslimit() const
#1 0x...2470 v8::internal::Isolate::UnwindAndFindHandler()
#2 0x...48dc v8::internal::Runtime_UnwindAndFindExceptionHandler(int, unsigned long*, v8::internal::Isolate*)
#3 0x...067d v8_Default_embedded_blob_code_
…
#9 0x...f901 v8::internal::Object::TransitionAndWriteDataProperty(
v8::internal::LookupIterator*,
v8::internal::DirectHandle<v8::internal::Object>,
v8::internal::PropertyAttributes,
v8::Maybe<v8::internal::ShouldThrow>,
v8::internal::StoreOrigin)
Ruby crash report:
[BUG] Segmentation fault at 0x0000000000000068
Control frame:
CFUNC :call (ctx.call)
METHOD .../v8_runtime.rb:138 in #call
METHOD .../browser.rb:472 in #check_stale (calls __csimAlive)
METHOD .../node.rb:159 in #visible?
…
Capybara::Node::Element#visible? polling loop
Analysis
Isolate::UnwindAndFindHandler calls wasm::StackMemory::jslimit() while walking up the frame stack to find the next catch handler. jslimit() reads this->jslimit_ at offset 0x68; the this pointer is NULL because the Isolate's active_continuation slot has never been initialised — the workload never executed wasm.
Trigger conditions in this report:
- Large JS bundle loaded (Discourse's main webpack chunk is ~5.7 MB).
- A JS-side exception is thrown during a property write (
Object::TransitionAndWriteDataProperty).
- The Isolate has never run wasm code.
Things ruled out (don't change the crash)
- Disabling V8 optimisers (
--no-opt)
- Disabling snapshots (no
MiniRacer::Snapshot warmup)
- Disposing the Context synchronously vs in a background thread
MiniRacer::Platform.set_flags!(:single_threaded)
- 8 MB V8 stack
verbose_exceptions: true
- Running a single test in isolation (no cross-test contamination)
Reproduction
I unfortunately couldn't reduce this to a self-contained JS string — extracting Ember + webpack into a standalone harness boots part-way but never reaches the code path that throws. The repro therefore requires the application's running Rails backend.
git clone https://github.com/ursm/capybara-simulated-vs-world
cd capybara-simulated-vs-world
git submodule update --init apps/discourse
# pgvector PostgreSQL + asset precompile prerequisites — see README
CSIM_JS_ENGINE=v8 bin/run-discourse spec/system/about_page_spec.rb -e 'months old'
Crashes 3/3 runs. CSIM_JS_ENGINE=quickjs passes 33/33.
The driver under test is capybara-simulated — a Capybara driver that hosts a JS DOM inside mini_racer. The failing call is a trivial JS handle lookup; instrumenting ctx.call to skip __csimAlive only pushes the crash to the next bridge call (__csimVisible …), so the trigger is the Isolate state, not the specific function.
Asks
- Is this a known V8 issue / fixed in a later
libv8-node?
- Would mini_racer be able to force-initialise the
active_continuation slot during Context setup so the unwinder always sees a valid pointer?
Summary
ctx.callreliably SEGVs atv8::internal::wasm::StackMemory::jslimit()(NULL deref at0x68) when running a large Ember + webpack app (Discourse 3.6) that does not use WebAssembly. The crash happens inside V8's exception unwinder.The same workload runs fine under
quickjs.rb, so the trigger is V8-specific.Versions
mini_racer0.21.0libv8-node24.12.0.1Crash (gdb, Thread 1, signal SIGSEGV)
Ruby crash report:
Analysis
Isolate::UnwindAndFindHandlercallswasm::StackMemory::jslimit()while walking up the frame stack to find the next catch handler.jslimit()readsthis->jslimit_at offset0x68; thethispointer is NULL because the Isolate'sactive_continuationslot has never been initialised — the workload never executed wasm.Trigger conditions in this report:
Object::TransitionAndWriteDataProperty).Things ruled out (don't change the crash)
--no-opt)MiniRacer::Snapshotwarmup)MiniRacer::Platform.set_flags!(:single_threaded)verbose_exceptions: trueReproduction
I unfortunately couldn't reduce this to a self-contained JS string — extracting Ember + webpack into a standalone harness boots part-way but never reaches the code path that throws. The repro therefore requires the application's running Rails backend.
Crashes 3/3 runs.
CSIM_JS_ENGINE=quickjspasses 33/33.The driver under test is
capybara-simulated— a Capybara driver that hosts a JS DOM insidemini_racer. The failing call is a trivial JS handle lookup; instrumentingctx.callto skip__csimAliveonly pushes the crash to the next bridge call (__csimVisible…), so the trigger is the Isolate state, not the specific function.Asks
libv8-node?active_continuationslot duringContextsetup so the unwinder always sees a valid pointer?