Skip to content

Fix napi_get_property_names conformance on JSC, Chakra and QuickJS - #218

Merged
bkaradzic-microsoft merged 23 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/get-property-names-conformance
Sep 29, 2026
Merged

bkaradzic-microsoft merged 23 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/get-property-names-conformance

Conversation

@bkaradzic-microsoft

@bkaradzic-microsoft bkaradzic-microsoft commented Jul 30, 2026 •

Copy link
Copy Markdown
Member

[Updated by Copilot on behalf of @bghgary]

Fixes #216.

Context

napi_get_property_names should return enumerable string-keyed properties across the prototype chain, matching for...in. Before this PR, JavaScriptCore threw, Chakra and QuickJS returned only own properties, and JSI had an unimplemented stub. The three Node-API backends now share a property-name walk; JSI uses its own implementation.

The walk must account for non-enumerable own properties hiding inherited names, terminate when a Proxy returns a repeated prototype, and keep the returned status consistent with napi_get_last_error_info. JavaScriptCore’s napi_get_prototype also needed correction for primitives and the null end of a prototype chain.

Review follow-up

A retained JavaScriptCore context could collect reference sentinels after their environment was deleted, causing a use-after-free. This branch addresses that lifetime defect as well as the conformance changes.

Coverage and limits

The feature-named unit tests cover own and inherited names, shadowing, symbols, arrays, prototype termination, error reporting, and reference lifetime. Proxy-dependent cases are skipped on JavaScriptCore because its prototype lookup does not invoke the Proxy trap. Hermes’ for...in shadowing discrepancy remains tracked in #219; napi_get_all_property_names remains V8-only.

Copilot AI review requested due to automatic review settings July 30, 2026 04:02

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

This PR aligns napi_get_property_names behavior across the JavaScriptCore, ChakraCore, and QuickJS backends to match the Node-API/V8 semantics: enumerable, string-keyed property names including the prototype chain (i.e., matching for...in, including the “non-enumerable own property shadows inherited enumerable” rule).

Changes:

  • Added an engine-agnostic implementation (napi_shared::GetEnumerablePropertyNames) that walks the prototype chain using Object.keys plus Object.getOwnPropertyNames to enforce for...in-equivalent semantics.
  • Updated JavaScriptCore, ChakraCore, and QuickJS napi_get_property_names implementations to delegate to the shared helper (and added missing CHECK_ARG(env, object) on JSC/Chakra).
  • Added script-level regression tests and a test harness global (napiGetPropertyNames) to validate behavior against for...in.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/UnitTests/Shared/Shared.cpp Exposes napiGetPropertyNames to script tests via the C++ harness.
Tests/UnitTests/Scripts/tests.ts Adds regression tests for napi_get_property_names semantics (prototype chain, enumerability, symbols, shadowing, arrays).
Core/Node-API/Source/js_native_api_shared.h Declares shared helper for napi_get_property_names semantics.
Core/Node-API/Source/js_native_api_shared.cc Implements the shared prototype-chain walk using only public napi_* APIs.
Core/Node-API/Source/js_native_api_quickjs.cc Replaces own-only QuickJS implementation with shared helper.
Core/Node-API/Source/js_native_api_javascriptcore.cc Fixes JSC implementation (previously throwing) by delegating to shared helper and validating object.
Core/Node-API/Source/js_native_api_chakra.cc Replaces own-only / non-enumerable-including Chakra implementation with shared helper and validates object.
Core/Node-API/CMakeLists.txt Adds shared helper sources to non-V8 backend builds.

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

Comment thread Tests/UnitTests/Scripts/tests.ts Outdated
Comment thread Tests/UnitTests/Shared/Shared.cpp Outdated

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

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

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

  • napi_shared::GetEnumerablePropertyNames can return napi_object_expected (for null/undefined) without setting env->last_error. Since CHECK_NAPI assumes last_error is already set, callers may observe stale napi_get_last_error_info data. Set last_error explicitly for this status before returning it.
  // `JsGetOwnPropertyNames` is own-only and includes non-enumerable properties,
  // so use the shared prototype-chain walk instead.
  CHECK_NAPI(napi_shared::GetEnumerablePropertyNames(env, object, result));

Core/Node-API/Source/js_native_api_javascriptcore.cc:976

  • napi_shared::GetEnumerablePropertyNames can return napi_object_expected (for null/undefined) without setting env->last_error. Because CHECK_NAPI assumes last_error was already set, this can leave stale last-error info visible via napi_get_last_error_info when an error is returned. Set last_error explicitly for this status before returning.
  // JavaScriptCore's `JSObjectCopyPropertyNames` walks the prototype chain but
  // silently drops properties shadowed by a non-enumerable own property, so use
  // the shared prototype-chain walk instead.
  CHECK_NAPI(napi_shared::GetEnumerablePropertyNames(env, object, result));

  return napi_ok;

Tests/UnitTests/Shared/Shared.cpp:142

  • When napi_get_property_names fails with a pending JavaScript exception, the current code clears and discards the original exception and instead throws a generic status-based error. This makes debugging failures harder and can hide the real engine error. If an exception is pending, rethrow that exception value instead of discarding it.
                    // A failed call may or may not have left a JavaScript
                    // exception pending; surface either as a thrown error so
                    // that the script tests can assert on it uniformly.
                    bool isExceptionPending{};
                    if (napi_is_exception_pending(rawEnv, &isExceptionPending) == napi_ok && isExceptionPending)
                    {
                        napi_value error{};
                        napi_get_and_clear_last_exception(rawEnv, &error);
                    }

Core/Node-API/Source/js_native_api_quickjs.cc:1403

  • napi_shared::GetEnumerablePropertyNames can return napi_object_expected (for null/undefined) without setting env->last_error. Because CHECK_NAPI assumes the callee already set last_error, this can leave stale last-error info visible via napi_get_last_error_info even though the API returned an error. Handle this status explicitly and set last_error before returning it.
  // `JS_GetOwnPropertyNames` is own-only, so use the shared prototype-chain
  // walk instead.
  CHECK_NAPI(napi_shared::GetEnumerablePropertyNames(env, object, result));

  napi_clear_last_error(env);

@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. Comments inline.

Comment thread Core/Node-API/Source/js_native_api_shared.h
Comment thread Tests/UnitTests/Scripts/tests.ts Outdated
Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
@bkaradzic-microsoft bkaradzic-microsoft changed the title Fix napi_get_property_names conformance on JSC, ChakraCore and QuickJS Fix napi_get_property_names conformance on JSC, Chakra and QuickJS Jul 31, 2026

@matthargett matthargett left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

thanks for coming at N-API! hopefully this means you can review my existing work in this area :D a few review notes:

  • This does not duplicate the implementation work in #189, #217, rebeckerspecialties#8, or #10 but it does overlap with PR #116’s already-vendored test_object conformance case, although that directory is not currently enabled.

  • Please use PR #189’s JSR_NAPI_ENGINE_* definition family rather than adding a parallel engine-name macro. The current merge of this PR branch against my N-API stack has one Android CMake conflict for exactly this reason.

  • Make a focused v1-compatible NodeApi/CTS addon durable ABI test, retaining this PR’s additional shadowing, coercion, poisoned-intrinsic, proxy-cycle, and JSI cases.

  • Please design the shared collector so it can support v6 napi_get_all_property_names, or link a required follow-up; the combined v7 stack (#189 ) otherwise still exposes a non-V8 symbol gap.

a couple of other security things to guard against:

  • rethrow the pending value in the raw test bridge instead of clearing and replacing it with a generic error
  • avoid UTF-8 strings as property-key identity because distinct lone-surrogate keys may collapse

Comment thread Core/Node-API/Source/js_native_api_quickjs.cc Outdated
Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
Comment thread Core/Node-API/Source/js_native_api_quickjs.cc Outdated

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

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

Suppressed comments (1)

Core/Node-API/Source/js_native_api_shared.cc:23

  • GetUtf8Value converts property names via napi_get_value_string_utf8 and then uses the UTF-8 bytes for shadowing/deduping. In this repo’s V8 implementation, napi_get_value_string_utf8 uses REPLACE_INVALID_UTF8, which can make distinct JS strings (e.g., ones containing unpaired surrogates) collapse to the same UTF-8 sequence. That can break the shadowing rule and cause incorrect results for exotic-but-valid property names. Prefer tracking keys using UTF-16 code units (napi_get_value_string_utf16 + std::u16string) or otherwise comparing actual JS strings rather than their UTF-8 encoding.
    napi_status GetUtf8Value(napi_env env, napi_value value, std::string& result) {
      size_t length{};
      RETURN_IF_NOT_OK(napi_get_value_string_utf8(env, value, nullptr, 0, &length));

      std::vector<char> buffer(length + 1);

Comment thread Core/Node-API/Source/js_native_api_javascriptcore.cc
@bkaradzic-microsoft
bkaradzic-microsoft force-pushed the fix/get-property-names-conformance branch from 0d4fc08 to d9383af Compare September 14, 2026 20:00
bkaradzic and others added 11 commits September 22, 2026 13:06
napi_get_property_names is specified to return the enumerable string-keyed
properties of an object *and of its prototype chain* -- the same set a
`for...in` loop visits. Only the V8 backend did that, via GetPropertyNames
configured with kIncludePrototypes | ONLY_ENUMERABLE | SKIP_SYMBOLS. The
other three each diverged:

| backend        | enumerable-only | includes prototypes | throws |
|----------------|-----------------|---------------------|--------|
| V8             | yes             | yes                 | no     |
| JavaScriptCore | n/a             | n/a                 | yes    |
| ChakraCore     | no              | no                  | no     |
| QuickJS        | yes             | no                  | no     |

JavaScriptCore was outright broken: it called Object.getOwnPropertyNames
with argc 0, so the `object` argument was never used and the call always
threw "TypeError: undefined is not an object". ChakraCore used
JsGetOwnPropertyNames, which is own-only and also reports non-enumerable
properties. QuickJS used JS_GetOwnPropertyNames with JS_GPN_ENUM_ONLY,
which is enumerable-only but still own-only.

None of the three engines exposes a native equivalent of V8's key
collection, so add a single shared implementation that walks the prototype
chain explicitly, written purely against the public napi_* surface, and
have all three backends delegate to it. Per level it takes Object.keys for
the properties `for...in` reports, and Object.getOwnPropertyNames for the
shadowing set: a non-enumerable own property is not reported itself, but it
does hide a same-named enumerable property further up the chain.

JavaScriptCore's JSObjectCopyPropertyNames was considered for that backend
since it walks the chain natively, but it drops the shadowing rule, so the
shared walk is used there too and all backends stay consistent.

Adds nine script tests, exercised through a new napiGetPropertyNames global
that calls Napi::Object::GetPropertyNames. Verified locally on ChakraCore
and QuickJS (225 passing, 0 failing on both). As a negative control, five of
the nine fail when the old ChakraCore implementation is restored.

Fixes BabylonJS#216

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
…s broken

Two follow-ups from CI on the previous commit.

napi_get_prototype on JavaScriptCore ran the result of JSObjectGetPrototype
through JSValueToObject. At the top of a prototype chain that value is
`null`, so the conversion threw "TypeError: null is not an object" instead
of reporting the end of the chain, which made the chain impossible to walk
and failed all nine new tests. Return the raw prototype value, as V8 does.
It has no other callers in this repository: Blob.cpp deliberately uses
Object.getPrototypeOf instead.

The "matches for...in" assertion also failed on Hermes, but in the opposite
direction: napi_get_property_names returned the correct ['own', 'middle']
while Hermes' own `for...in` returned ['own', 'middle', 'deep'], i.e. Hermes
does not implement the rule that a non-enumerable own property shadows an
inherited enumerable one. Probe for that behaviour at runtime rather than
naming engines, and only use `for...in` as an oracle where it holds. The
explicit expected-value assertion still runs everywhere.

Re-verified on ChakraCore and QuickJS: 225 passing, 0 failing on both.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
The JSI adapter provides the Napi C++ surface directly on top of JSI rather
than over the C Node-API, and Object::GetPropertyNames was still a stub that
threw std::runtime_error{"TODO"}, surfacing in script as
"Error: Exception in HostFunction: TODO".

jsi::Object::getPropertyNames returns the enumerable string-keyed properties
of an object and of its prototype chain, which is exactly the specified
behaviour, so forward to it.

Verified locally against the ReactNative.V8Jsi runtime: all nine
napi_get_property_names tests pass, 225 passing, 0 failing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
Review feedback: the implementation coerces its argument with ToObject, but
nothing covered that. `Napi::Object::GetPropertyNames` can only be called on an
already-constructed `Napi::Object`, so the C++ harness structurally could not
reach the coercion path.

Expose the C entry point directly as `napiGetPropertyNamesRaw` and pass the raw
value through. The Node-API-JSI backend implements the `Napi::` C++ surface
straight on top of JSI and has no C Node-API at all, so the global is left
undefined there (it already has a `JSRUNTIMEHOST_NAPI_ENGINE_JSI` define) and
the six new tests skip themselves.

Testing that also exposed a divergence for `null` and `undefined`, which have no
object wrapper: V8 reports `napi_object_expected`, QuickJS's `napi_coerce_to_object`
uses `Object(value)` and happily returns an empty object, and JavaScriptCore's
throws. Check `napi_typeof` explicitly in the shared implementation so all three
match V8 instead.

Verified locally: ChakraCore 231 passing / 0 failing, QuickJS 231 / 0, JSI
225 / 0 with 6 pending. Negative control: with the null/undefined check removed,
QuickJS fails exactly the two tests that cover it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
Hermes' Node-API is not implemented in this repository -- it comes from the
Hermes dependency itself -- and it rejects primitives outright instead of
applying ToObject, so the three wrapping tests failed there. It does reject
null and undefined like everyone else, so those two still run.

Hermes is documented as an experimental engine here and its napi is not ours to
fix, so skip those cases explicitly rather than weakening the assertions for
every backend. That needs an engine identifier in script, so plumb
NAPI_JAVASCRIPT_ENGINE through as a `napiEngine` global alongside the existing
`hostPlatform` one.

Verified locally: ChakraCore 231 passing / 0 failing, QuickJS 231 / 0, JSI
225 / 0 with 6 pending.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae
Use Chakra terminology, keep a Hermes coercion tripwire linked to BabylonJS#219, and avoid building the final unused shadowing set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 79ad319b-72f9-4b93-9c81-57858177c0a7
…ently

A `getPrototypeOf` Proxy trap may return an object that is already on the
chain. Nothing in the specification forbids it -- the invariants on that trap
constrain only the non-extensible case -- so `Object.getPrototypeOf(p) === p`
is reachable from script. V8 walks the chain recursively and so terminates
with a `RangeError`; the shared walk introduced here is iterative and spun
forever instead. Confirmed against a real build: the test process burned 56
seconds of CPU and never returned, and no JavaScript-level timeout can preempt
it, because control never re-enters the engine.

Stopping at the first repeated level is exact rather than a bail-out. Every
level adds its full own-property-name set to `shadowed` before the walk
advances, so a level reached a second time can only re-encounter names that
are already shadowed; breaking there yields precisely the fixed point the
non-terminating walk converges on.

Separately, `napi_get_property_names` disagreed with
`napi_get_last_error_info`. The shared walk is written against the public
`napi_*` surface and so cannot reach `napi_set_last_error`, `CHECK_NAPI` only
propagates the status, and the `napi_typeof` performed just before the
rejection clears the last error on success. A caller therefore saw
`napi_object_expected` returned while the recorded error code was still
`napi_ok`. The success path had the mirror-image problem: it left whatever
error a previous call had recorded in place. Set and clear the error
explicitly at all three call sites.

Covered by four script tests for the cyclic cases and a native regression test
for the error reporting; reverting either fix fails them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: af4ea82e-5fb0-4e56-b71a-f8ff3932d033
CI turned up two places where the tests asserted behaviour that belongs to an
engine rather than to this change.

JavaScriptCore's `napi_get_prototype` calls `JSObjectGetPrototype`, which reads
the internal [[Prototype]] slot and never runs a proxy `getPrototypeOf` trap.
Since a cycle can only be built with that trap, a trapped chain there reports
the target's real prototype instead: the cycle is invisible, the throwing trap
never fires, and the walk was never at risk on that backend to begin with. That
is a pre-existing limitation of `napi_get_prototype`, not of the walk, and
fixing it means giving JavaScriptCore `Reflect.getPrototypeOf` semantics, which
is a separate change. Skip the three proxy-dependent cases there and say why.
The acyclic-chain case needs no trap and still runs everywhere.

The native error-reporting test asserted a contract V8 does not keep, and I was
wrong to claim otherwise: `napi_get_property_names` there is vendored upstream
Node code whose rejection leaves a pending exception, so the next call reports
`napi_pending_exception` rather than `napi_object_expected`, and whose success
path returns bare `napi_ok` through `GET_RETURN_STATUS` without clearing.
Both are upstream's to define. Scope the test to the three backends that share
the walk -- the ones this change actually touches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: af4ea82e-5fb0-4e56-b71a-f8ff3932d033
The conversion this function already performed was on the wrong operand. It
belongs on the argument, which is where V8 puts it, and moving it there fixes a
latent defect on the same line.

The argument was handed straight to `ToJSObject`, which only asserts that its
input is an object. A primitive therefore tripped the assert in debug builds
and, in release, reinterpreted a non-object `JSValueRef` as a `JSObjectRef`
before handing it to `JSObjectGetPrototype` -- undefined behaviour rather than
a status. Coercing instead matches V8's `CHECK_TO_OBJECT`: a primitive yields
its wrapper's prototype, and only `null` and `undefined` are rejected, with
`napi_object_expected`.

Nothing in the repository could reach this. The shared prototype walk is the
only caller, and it recurses only into values it has already established are
object-like, so the behaviour it depends on is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: af4ea82e-5fb0-4e56-b71a-f8ff3932d033
The comment had the failure mode backwards. JavaScriptCore does not drop the
shadowed inherited property; it reports it. `JSObject::getPropertyNames` walks
the chain calling `getOwnPropertyNames` per level with
`DontEnumPropertiesMode::Exclude`, so a non-enumerable own property is never
added to the array and therefore cannot suppress a same-named enumerable
property further up -- the inherited name survives where `for...in` correctly
omits it.

The conclusion is unchanged, and so is the code: it is still not a conforming
replacement for the shared walk. Thanks to @matthargett for catching it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: af4ea82e-5fb0-4e56-b71a-f8ff3932d033
The refreshed base already exposes NAPI_JAVASCRIPT_ENGINE as hostEngine.
Use it throughout the property-name tests instead of retaining a parallel
napiEngine global and Android compile definition.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54aaa1e1-b4b3-46e7-9de4-5a56add4ac42
@bkaradzic-microsoft
bkaradzic-microsoft force-pushed the fix/get-property-names-conformance branch from d9383af to 6ee4f42 Compare September 22, 2026 20:07

@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]

Concerns inline.

Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
Comment thread Core/Node-API/Source/js_native_api_shared.cc
Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
Comment thread Tests/UnitTests/Shared/Shared.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305

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.

Copilot review overview

🟡 Changes recommended

The shared walk depends on mutable JavaScript intrinsics and uses quadratic name deduplication.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread Core/Node-API/Source/js_native_api_shared.cc Outdated
Branimir Karadzic and others added 6 commits September 24, 2026 08:45
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d35d0a8b-b073-4f2a-bbd3-a0b1d3584305
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc6f260a-f58d-49ed-9bed-d69248708138
Carry the ownership fix and engine-specific regressions from a5a86a9 on the reorganized test layout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc6f260a-f58d-49ed-9bed-d69248708138
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc6f260a-f58d-49ed-9bed-d69248708138
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc6f260a-f58d-49ed-9bed-d69248708138
Discard native cached-reference records after runtime disposal, without touching invalid JavaScript handles; cover direct embedding and finalizer lifetime.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc6f260a-f58d-49ed-9bed-d69248708138

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

@bkaradzic-microsoft
bkaradzic-microsoft merged commit b2b51ca into BabylonJS:main Sep 29, 2026
25 checks passed
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
BabylonJS#218 rewrote Chakra's Napi::Attach around a try block that retains the hasOwnProperty function and
the wrap symbol through references; the globalThis definition moves inside that block, right after
the global object is fetched and before the Object/hasOwnProperty lookups reuse the property id.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
BabylonJS#218 moved the JavaScriptCore reference tracking into a shared state that the sentinel finalizer
holds weakly, so a finalizer that runs after the environment is gone simply finds no state. That
covers the case this branch guarded with the shutting_down check inside the finalizer; the upstream
lambda is kept and the flag stays for the destructor's own use.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
Only the include block overlapped: BabylonJS#218's shared property-name helper include joins this branch's
BigInt C API availability guard at the top of the JavaScriptCore backend.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
BabylonJS#218 overlaps this branch in three places of the JavaScriptCore backend:

- napi_ref__::value keeps this branch's primitive-aware body; IsObjectAlive reads the tracking map
  from the shared reference-tracking state BabylonJS#218 introduced.
- napi_get_prototype coerces the argument with ToJSObjectCoerced (a primitive yields its wrapper,
  null/undefined report napi_object_expected with the TypeError pending, as Node does) and returns
  the raw prototype value so the top of a chain is reportable, as BabylonJS#218 needs for its property-name walk.
- napi_create_reference takes BabylonJS#218's unique_ptr form; it propagates init's status the same way.

Tests.NodeApi.cpp keeps BabylonJS#218's new tests ahead of this branch's three.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
The Chakra globalThis definition this branch carries from BabylonJS#237 moves inside the try block BabylonJS#218 gave
Napi::Attach, the same resolution as on BabylonJS#237.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
The Chakra globalThis definition this branch carries from BabylonJS#237 moves inside the try block BabylonJS#218 gave
Napi::Attach, the same resolution as on BabylonJS#237.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
…he Worker stack

Brings BabylonJS#218 in through jsc-napi-primitive-values; the include block keeps the BigInt availability
guard after the shared helper include, the sentinel finalizer takes upstream's weak-pointer form, and
Chakra's Attach keeps the globalThis definition inside the new try block.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
…gh teardown) into the Worker stack

A worker terminated right after construction dispatches its startup work into a runtime that has
already been torn down; without BabylonJS#254 that lock lands on a destroyed mutex (mutex lock failed:
Invalid argument) as soon as environment start-up takes a little longer, which BabylonJS#218's intrinsic
capture made deterministic in Worker.UndefinedTypeMeansClassic.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 29, 2026
The worker thread posts to the parent realm after terminate() and after close(): the deferred
release of the parent-side Worker object, and message/error delivery. It did so through the parent's
JsRuntime pointer, which is only valid while the parent runtime lives. A worker terminated as it is
created, or a host tearing down while a worker closes, then dispatched into a destroyed runtime
(mutex lock failed: Invalid argument, or a null dispatch state). BabylonJS#218's intrinsic capture made the
race deterministic in Worker.UndefinedTypeMeansClassic.

A JsRuntimeScheduler copy (BabylonJS#254) keeps the parent's dispatch state alive and discards work once the
runtime has shut down, so the parent-realm dispatches are routed through one held in the worker state.
bkaradzic-microsoft added a commit to BabylonJS/BabylonNative that referenced this pull request Sep 29, 2026
Bumps `JsRuntimeHost` from `839b3521c8a5143be6588fc8325dad5e5157a541` to
[`b2b51ca86737cf8145235226b7591c191a94355a`](BabylonJS/JsRuntimeHost@b2b51ca),
the latest upstream `main`.

Includes:
- BabylonJS/JsRuntimeHost#218: property-name enumeration conformance on
JavaScriptCore, Chakra, and QuickJS, JSI `GetPropertyNames` support, and
JavaScriptCore reference-lifetime fixes.
- BabylonJS/JsRuntimeHost#257: organize runtime tests by feature.
- BabylonJS/JsRuntimeHost#205: remove redundant Linux compiler
environment settings.

This PR changes only the JRH pin in `CMakeLists.txt`, based on latest
BabylonNative master after #1900. No BN compatibility changes or other
dependency updates are included.

Local validation: Windows x64 D3D11 / Chakra / RelWithDebInfo builds of
UnitTests, Playground, and ModuleLoadTest succeeded. All 69 native tests
passed, ModuleLoadTest passed, and the Native Canvas Playground
validation passed (1 run, 1 pass, 0 failures).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6c3c123-c355-4790-a3d4-94337ed6e052
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.

napi_get_property_names: throws on JavaScriptCore; inconsistent enumerability/prototype semantics on Chakra and QuickJS

6 participants