QuickJS: bound the JS stack to the thread's real stack; Chakra: define globalThis - #237
Open
matthargett wants to merge 6 commits into
Open
matthargett wants to merge 6 commits into
matthargett wants to merge 6 commits into
Conversation
…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.
Author
|
Fork CI twin (full 24-job matrix): rebeckerspecialties#21. |
Contributor
There was a problem hiding this comment.
🟡 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
globalThisproperty 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 ofthreadStack).
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.
…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)
matthargett
force-pushed
the
engine-fixes
branch
from
September 13, 2026 19:12
3cf11ba to
d1ca32e
Compare
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).
RunEnvironmentTiernever callsJS_SetMaxStackSize, so QuickJS guards recursion againststack_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 raiseInternalError: 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 noglobalThis; 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).