[Filed by Copilot on behalf of @bghgary]
On the JSC backend, constructors returned by napi_define_class / Napi::Function::New(...) have their user-visible .prototype slot pointing at the global Object.prototype instead of a fresh per-class object. Writing to it from C++ via func.Get("prototype").Set(...) therefore mutates Object.prototype globally, breaking for..in and prototype-chain lookups across the runtime.
V8 and ChakraCore return a fresh per-class object, so the bug is JSC-specific.
Repro
Napi::Function ctor = DefineClass(env, "Foo", {});
ctor.Get("prototype").As<Napi::Object>().Set("X", Napi::Number::New(env, 0));
In JS: 'X' in {} → true (should be false), and for (const k in {}) ... enumerates X.
Root cause
JSC's JSObjectMakeConstructor defaults the new function's .prototype to Object.prototype when none is supplied. The napi-jsc shim should allocate a fresh object per class.
Workaround
Use InstanceValue / InstanceMethod / InstanceAccessor inside DefineClass. That path goes through napi's internal prototype slot, distinct from the user-visible .prototype on JSC, and works correctly on every backend.
Discovered while reviewing #169.
[Filed by Copilot on behalf of @bghgary]
On the JSC backend, constructors returned by
napi_define_class/Napi::Function::New(...)have their user-visible.prototypeslot pointing at the globalObject.prototypeinstead of a fresh per-class object. Writing to it from C++ viafunc.Get("prototype").Set(...)therefore mutatesObject.prototypeglobally, breakingfor..inand prototype-chain lookups across the runtime.V8 and ChakraCore return a fresh per-class object, so the bug is JSC-specific.
Repro
In JS:
'X' in {}→true(should befalse), andfor (const k in {}) ...enumeratesX.Root cause
JSC's
JSObjectMakeConstructordefaults the new function's.prototypetoObject.prototypewhen none is supplied. The napi-jsc shim should allocate a fresh object per class.Workaround
Use
InstanceValue/InstanceMethod/InstanceAccessorinsideDefineClass. That path goes through napi's internal prototype slot, distinct from the user-visible.prototypeon JSC, and works correctly on every backend.Discovered while reviewing #169.