Skip to content

QuickJS: bound the JS stack to the thread's real stack; Chakra: define globalThis - #237

Open
matthargett wants to merge 6 commits into
BabylonJS:mainfrom
rebeckerspecialties:engine-fixes
Open

matthargett wants to merge 6 commits into
BabylonJS:mainfrom
rebeckerspecialties:engine-fixes

Conversation

@matthargett

Copy link
Copy Markdown

Two small engine-backend fixes surfaced by running the Fetch/Streams polyfill PRs (#208#212) through the full CI matrix on the fork.

QuickJS (Android crash). RunEnvironmentTier never calls JS_SetMaxStackSize, so QuickJS guards recursion against stack_top - JS_DEFAULT_STACK_SIZE — 1 MiB in quickjs-ng, which is also the default size of a non-main thread on Android and Windows. On those threads the check sits below the real guard page: deep recursion faults (SIGSEGV, one frame repeated 150+ deep in the tombstone) before QuickJS can raise InternalError: stack overflow. The 8 MiB-stacked desktop QuickJS jobs were unaffected. The limit is now derived from the running thread (pthread_getattr_np / pthread_get_stacksize_np / GetCurrentThreadStackLimits) minus a margin for native frames.

Chakra (globalThis). The Windows 10 Chakra predates ES2020 and has no globalThis; scripts written against browsers reference it (ReferenceError: 'globalThis' is not defined). It's now defined on the global object at env attach as a plain writable, configurable property, per spec.

Fork CI twin with the full 24-job matrix: rebeckerspecialties/JsRuntimeHost (linked below).

…e globalThis

QuickJS guards JS recursion against stack_top - JS_DEFAULT_STACK_SIZE, which is
1 MiB in quickjs-ng -- also the default size of a non-main thread on Android and
Windows. On those threads the check sits below the guard page, so deep recursion
faults before QuickJS can raise "InternalError: stack overflow": Android_QuickJS
died with SIGSEGV (one frame repeated 150+ deep) in the Fetch polyfill's 40k-chunk
Response test, while the 8 MiB-stacked desktop QuickJS jobs were fine. Derive the
limit from the running thread (pthread_getattr_np / GetCurrentThreadStackLimits)
minus a margin for native frames.

The Windows 10 Chakra predates ES2020 and has no `globalThis`; every Fetch test
failed there with "ReferenceError: 'globalThis' is not defined". Define it on the
global object at env attach, as a plain writable configurable property.
Copilot AI lite review requested due to automatic review settings September 13, 2026 18:35
@matthargett

Copy link
Copy Markdown
Author

Fork CI twin (full 24-job matrix): rebeckerspecialties#21.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical build/linkage issues and correctness issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR improves QuickJS stack safety and adds Chakra globalThis compatibility.

Changes:

  • Derives QuickJS stack limits from the current thread.
  • Defines Chakra’s globalThis property during environment setup.
File summaries
File Changes Review findings
Core/Node-API/Source/env_chakra.cc Initializes Chakra’s globalThis. Moderate (3 votes): The property is enumerable; it should use a non-enumerable descriptor via JsDefineProperty.
Core/AppRuntime/Source/AppRuntime_QuickJS.cpp Configures platform-specific, thread-aware QuickJS stack limits. Critical (2 votes): pthread_getattr_np requires GNU feature declarations on glibc. Moderate (1 vote): Known small stacks can receive an unsafe 512 KiB limit. Critical (1 vote): Android API 21 lacks pthread_getattr_np, causing linkage failure.
Review details

Suppressed comments (1)

Core/AppRuntime/Source/AppRuntime_QuickJS.cpp:73

  • When the stack query succeeds for a thread whose real stack is at most Margin (for example, a host-created 128/256 KiB thread), this branch returns a 512 KiB limit. QuickJS then permits recursion beyond that thread's guard page, which is the crash this change is intended to prevent. Distinguish an unknown size (threadStack == 0) from a known small stack and use a conservative limit derived from the latter (for example, half of threadStack).
            if (threadStack <= Margin)
            {
                return Fallback;
            }
            return std::min(threadStack - Margin, static_cast<size_t>(JS_DEFAULT_STACK_SIZE) * 8);
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Core/AppRuntime/Source/AppRuntime_QuickJS.cpp Outdated
Comment thread Core/AppRuntime/Source/AppRuntime_QuickJS.cpp Outdated
Comment thread Core/Node-API/Source/env_chakra.cc
…allback stack limit by the thread's own size, define globalThis non-enumerable via JsDefineProperty
…o the thread's base (the nominal size under-budgeted the Android runtime thread: depth-128 recursion hit the limit)
…nstead of clamping the recursion limit

The prior fix measured the worker thread's stack and lowered JS_SetMaxStackSize below
it to convert the guard-page SIGSEGV into a catchable error -- but bionic's ~1 MiB
worker stack leaves no budget that both avoids the guard page and admits the call
depths every other engine accepts (macOS QuickJS clears depth-128 on a 512 KiB
secondary-thread stack), so depth-128 recursion started failing with 'Maximum call
stack size exceeded'. Instead, run the QuickJS environment on a nested thread with an
8 MiB stack (parity with the desktop threads) and keep QuickJS's own default 1 MiB
limit, which now sits safely below the guard page while leaving ordinary recursion
room. Confined to the QuickJS backend; other engines and platforms are unchanged.
…ursion (Debug frames)

The nested 8 MiB thread removed the guard-page SIGSEGV but QuickJS's default 1 MiB
limit still rejected depth-128 recursion in unoptimized Debug builds (large
JS_CallInternal frames). Set JS_SetMaxStackSize to 6 MiB on the guaranteed-8 MiB
nested thread; default elsewhere. Emulator-validated: Android QuickJS 30/30.
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.

2 participants