Skip to content

Node-API (Chakra): fix heap corruption when an ObjectWrap constructor throws#199

Merged
bkaradzic-microsoft merged 2 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/chakra-objectwrap-finalizer-throw
Jun 17, 2026
Merged

Node-API (Chakra): fix heap corruption when an ObjectWrap constructor throws#199
bkaradzic-microsoft merged 2 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/chakra-objectwrap-finalizer-throw

Conversation

@bkaradzic-microsoft

@bkaradzic-microsoft bkaradzic-microsoft commented Jun 16, 2026

Copy link
Copy Markdown
Member

What

Fixes a heap corruption in the Chakra Node-API implementation when an ObjectWrap-based constructor throws.

Split out of #198 as an independent fix, per review.

Problem

When a wrapped (napi_wrap) constructor throws, the C++ ObjectWrap instance is destroyed by stack unwinding inside the callback, but the wrap finalizer registered by napi_wrap() stays attached to this. ~ObjectWrap() cannot detach it, because napi_get_reference_value() returns null for the wrap's weak (refcount 0) reference. A later GC then runs the finalizer on the freed native instance — a use-after-free that surfaces as non-deterministic STATUS_HEAP_CORRUPTION (0xC0000374).

Fix

Core/Node-API/Source/js_native_api_chakra.cc:

  • In ExternalCallback, after a construct call that left a pending JS exception, detach the wrap (napi_remove_wrap) before returning, then re-set the exception so it is preserved across the cleanup.
  • Guard napi_remove_wrap against a nullptr result out-param (the finalizer-cleanup call above passes nullptr), so it no longer dereferences a null pointer.

Test

Adds a regression test (Tests/UnitTests/Scripts/tests.ts) that throws from a wrapped constructor 100× to create many dangling wraps, then allocates/decodes to exercise the heap and surface any corruption within the run. It uses TextDecoder as a convenient throwing wrapped constructor; the test passes on main's existing TextDecoder behavior and is independent of the TextDecoder label change in #198.

Verification

Reliably reproduced as STATUS_HEAP_CORRUPTION on Chakra (Win32 x64/x86) before the fix; passes after.

Copilot AI review requested due to automatic review settings June 16, 2026 16:06

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.

Pull request overview

Fixes a ChakraCore Node-API (N-API) use-after-free that can occur when an ObjectWrap-based constructor throws, by proactively detaching any remaining wrap finalizer so it can’t run later during GC and corrupt the heap.

Changes:

  • In Chakra’s N-API callback trampoline, detect pending exceptions after a construct call and napi_remove_wrap() on this while preserving the exception.
  • Make napi_remove_wrap() tolerate a nullptr out-param so it can be called for cleanup-only scenarios.
  • Add a regression test that repeatedly throws from TextDecoder’s wrapped constructor and then performs allocations/decoding to help surface corruption.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Core/Node-API/Source/js_native_api_chakra.cc Preserves pending exceptions across a wrap-detach cleanup on constructor-throw; makes napi_remove_wrap accept nullptr out-param.
Tests/UnitTests/Scripts/tests.ts Adds a regression test that repeatedly triggers the constructor-throw path for wrapped constructors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Core/Node-API/Source/js_native_api_chakra.cc
… throws

When a napi ObjectWrap subclass constructor throws (e.g. TextDecoder given an
unsupported encoding), the C++ instance is destroyed during stack unwinding,
but the wrap finalizer registered by napi_wrap() stays attached to the JS
`this` object. A later GC then runs the finalizer on the freed native
instance -> use-after-free / double free, surfacing as non-deterministic heap
corruption (STATUS_HEAP_CORRUPTION 0xC0000374 / access violation) crashes far
from the throw site.

Root cause: the addon-api ~ObjectWrap() is supposed to detach the wrap on
construction failure, but it calls napi_get_reference_value() on the wrap's
weak (refcount 0) reference to obtain `this`, and the Chakra backend returns
null for every refcount-0 reference (it cannot track weak-reference liveness
with the in-box jsrt API). So ~ObjectWrap() sees an empty object and skips
napi_remove_wrap(), leaving the dangling finalizer attached.

Fix: in the Chakra construct-call trampoline, if the callback leaves a pending
JS exception, detach any wrap left on `this` via napi_remove_wrap() (preserving
the pending exception) so the finalizer cannot run on the freed instance. Also
guard napi_remove_wrap() against a null `result` out-param (it is now called
with nullptr, matching upstream which guards this).

Adds a regression test that throws from a wrapped constructor many times and
then exercises the heap; without the fix this reliably corrupts the heap on
Chakra (Win32 x64/x86).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft bkaradzic-microsoft force-pushed the fix/chakra-objectwrap-finalizer-throw branch from a6f8390 to 34e8302 Compare June 16, 2026 16:21
@bkaradzic-microsoft bkaradzic-microsoft enabled auto-merge (squash) June 16, 2026 16:32

@bghgary bghgary 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.

[Reviewed by Copilot on behalf of @bghgary]

LGTM. The PR description's opening line also says "ChakraCore" — this is the in-box Chakra engine (chakra.dll), not the archived fork; should be Chakra.

Comment thread Tests/UnitTests/Scripts/tests.ts Outdated
Co-authored-by: Gary Hsu <bghgary@users.noreply.github.com>
@bkaradzic-microsoft bkaradzic-microsoft merged commit 2ff325e into BabylonJS:main Jun 17, 2026
23 checks passed
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.

3 participants