Skip to content

SEGV in v8::internal::wasm::StackMemory::jslimit() during exception unwind (NULL active_continuation) #415

@ursm

Description

@ursm

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

  1. Is this a known V8 issue / fixed in a later libv8-node?
  2. Would mini_racer be able to force-initialise the active_continuation slot during Context setup so the unwinder always sees a valid pointer?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions