From de798d06da151a4ceb3499f12f7eddc81d010849 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Mon, 2 Feb 2026 11:13:51 +0000 Subject: [PATCH 1/8] move rpc wildcard interceptors to instance objects --- src/workerd/api/actor.h | 2 ++ src/workerd/api/export-loopback.h | 2 ++ src/workerd/api/tests/actor-stub-test.js | 2 +- src/workerd/api/worker-rpc.c++ | 17 +++++++++++------ src/workerd/jsg/resource.h | 4 ++-- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/workerd/api/actor.h b/src/workerd/api/actor.h index 15f1aefe9e8..b2edea6a7a4 100644 --- a/src/workerd/api/actor.h +++ b/src/workerd/api/actor.h @@ -111,6 +111,8 @@ class DurableObject final: public Fetcher { JSG_READONLY_INSTANCE_PROPERTY(id, getId); JSG_READONLY_INSTANCE_PROPERTY(name, getName); + // Need to inherit getRpcMethod interceptor since we need it on the holder. + JSG_WILDCARD_PROPERTY(getRpcMethod); JSG_TS_DEFINE(interface DurableObject { fetch(request: Request): Response | Promise; diff --git a/src/workerd/api/export-loopback.h b/src/workerd/api/export-loopback.h index d834cacc527..cee73a1b33a 100644 --- a/src/workerd/api/export-loopback.h +++ b/src/workerd/api/export-loopback.h @@ -36,6 +36,8 @@ class LoopbackServiceStub: public Fetcher { JSG_RESOURCE_TYPE(LoopbackServiceStub) { JSG_INHERIT(Fetcher); JSG_CALLABLE(call); + // Need to inherit getRpcMethod interceptor since we need it on the holder. + JSG_WILDCARD_PROPERTY(getRpcMethod); JSG_TS_ROOT(); JSG_TS_OVERRIDE( diff --git a/src/workerd/api/tests/actor-stub-test.js b/src/workerd/api/tests/actor-stub-test.js index 0c2e860a728..dd03dff415b 100644 --- a/src/workerd/api/tests/actor-stub-test.js +++ b/src/workerd/api/tests/actor-stub-test.js @@ -115,7 +115,7 @@ export default { obj.baz = () => { return 'called baz'; }; - assert.equal(Object.keys(obj).length, 3); + assert.equal(Object.getOwnPropertyNames(obj).length, 3); assert.equal(typeof obj.baz, 'function'); assert.equal(obj.baz(), 'called baz'); diff --git a/src/workerd/api/worker-rpc.c++ b/src/workerd/api/worker-rpc.c++ index 1b82db92909..29db449f846 100644 --- a/src/workerd/api/worker-rpc.c++ +++ b/src/workerd/api/worker-rpc.c++ @@ -1306,8 +1306,14 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { } else { // This is an instance of a valid RPC target class. if (object.has(js, jsName, jsg::JsObject::HasOption::OWN)) { - // We do NOT allow own properties, only class properties. - failLookup(kjName); + // Allow jsg types with the wildcard property interceptors on their instance templates. + bool isJsgRpcType = object.isInstanceOf(js) || + object.isInstanceOf(js) || object.isInstanceOf(js) || + object.isInstanceOf(js); + if (!isJsgRpcType) { + // We do NOT allow own properties, only class properties. + failLookup(kjName); + } } auto value = object.get(js, jsName); @@ -1382,12 +1388,11 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { } else if (object.isInstanceOf(js) || object.isInstanceOf(js) || (inStub && object.isInstanceOf(js))) { // Yes. It's a JsRpcStub or Fetcher. We should allow descending into the stub. - // Note that the wildcard property of a stub is a prototype property, not an instance - // property, so setting allowInstanceProperties = false here gets the behavior we - // want. + // The wildcard property interceptor is on the instance template, so properties + // accessed via the interceptor appear as instance properties. // TODO(someday): We'll need to support JsRpcPromise here if someday we allow it to // be serialized. - allowInstanceProperties = false; + allowInstanceProperties = true; // We will only traverse JsRpcProperty if we got there by descending through a // JsRpcStub. At present you can't just pull a property of a stub and return it. diff --git a/src/workerd/jsg/resource.h b/src/workerd/jsg/resource.h index 3d5abe0ddd9..8a9f75e499b 100644 --- a/src/workerd/jsg/resource.h +++ b/src/workerd/jsg/resource.h @@ -1199,7 +1199,7 @@ struct WildcardPropertyCallbacks v8::Local { auto isolate = info.GetIsolate(); auto context = isolate->GetCurrentContext(); - auto obj = info.This(); + auto obj = info.HolderV2(); auto& wrapper = TypeWrapper::from(isolate); if (!wrapper.getTemplate(isolate, static_cast(nullptr))->HasInstance(obj)) { throwTypeError(isolate, kIllegalInvocation); @@ -1249,7 +1249,7 @@ struct ResourceTypeBuilder { template inline void registerWildcardProperty() { - prototype->SetHandler( + instance->SetHandler( WildcardPropertyCallbacks{}); } From 60aebd193b2ff4f6995ca4bed4cff33189861cbf Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Mon, 2 Feb 2026 15:59:33 +0000 Subject: [PATCH 2/8] skip interceptor --- src/workerd/api/worker-rpc.c++ | 14 ++++---------- src/workerd/jsg/jsvalue.h | 4 ++++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/workerd/api/worker-rpc.c++ b/src/workerd/api/worker-rpc.c++ index 29db449f846..6a53f63eafb 100644 --- a/src/workerd/api/worker-rpc.c++ +++ b/src/workerd/api/worker-rpc.c++ @@ -1305,15 +1305,9 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { return object.get(js, jsName); } else { // This is an instance of a valid RPC target class. - if (object.has(js, jsName, jsg::JsObject::HasOption::OWN)) { - // Allow jsg types with the wildcard property interceptors on their instance templates. - bool isJsgRpcType = object.isInstanceOf(js) || - object.isInstanceOf(js) || object.isInstanceOf(js) || - object.isInstanceOf(js); - if (!isJsgRpcType) { - // We do NOT allow own properties, only class properties. - failLookup(kjName); - } + if (object.has(js, jsName, jsg::JsObject::HasOption::OWN_SKIP_INTERCEPTOR)) { + // We do NOT allow own properties, only class properties. + failLookup(kjName); } auto value = object.get(js, jsName); @@ -1392,7 +1386,7 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { // accessed via the interceptor appear as instance properties. // TODO(someday): We'll need to support JsRpcPromise here if someday we allow it to // be serialized. - allowInstanceProperties = true; + allowInstanceProperties = false; // We will only traverse JsRpcProperty if we got there by descending through a // JsRpcStub. At present you can't just pull a property of a stub and return it. diff --git a/src/workerd/jsg/jsvalue.h b/src/workerd/jsg/jsvalue.h index 61481f4521d..bd7425bfa44 100644 --- a/src/workerd/jsg/jsvalue.h +++ b/src/workerd/jsg/jsvalue.h @@ -452,6 +452,7 @@ class JsObject final: public JsBase { enum class HasOption { NONE, OWN, + OWN_SKIP_INTERCEPTOR, }; bool has(Lock& js, const JsValue& name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT; @@ -965,6 +966,9 @@ inline bool JsObject::has(Lock& js, const JsValue& name, HasOption option) { if (option == HasOption::OWN) { KJ_ASSERT(name.inner->IsName()); return check(inner->HasOwnProperty(js.v8Context(), name.inner.As())); + } else if (option == HasOption::OWN_SKIP_INTERCEPTOR) { + KJ_ASSERT(name.inner->IsName()); + return check(inner->HasRealNamedProperty(js.v8Context(), name.inner.As())); } else { return check(inner->Has(js.v8Context(), name.inner)); } From 8da458d33ef439eb34f43211fcc0c73297b72c25 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Tue, 3 Feb 2026 10:32:34 +0000 Subject: [PATCH 3/8] Add query --- src/workerd/api/tests/actor-stub-test.js | 2 +- src/workerd/jsg/resource.h | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/workerd/api/tests/actor-stub-test.js b/src/workerd/api/tests/actor-stub-test.js index dd03dff415b..0c2e860a728 100644 --- a/src/workerd/api/tests/actor-stub-test.js +++ b/src/workerd/api/tests/actor-stub-test.js @@ -115,7 +115,7 @@ export default { obj.baz = () => { return 'called baz'; }; - assert.equal(Object.getOwnPropertyNames(obj).length, 3); + assert.equal(Object.keys(obj).length, 3); assert.equal(typeof obj.baz, 'function'); assert.equal(obj.baz(), 'called baz'); diff --git a/src/workerd/jsg/resource.h b/src/workerd/jsg/resource.h index 8a9f75e499b..37fd857b3cd 100644 --- a/src/workerd/jsg/resource.h +++ b/src/workerd/jsg/resource.h @@ -1182,7 +1182,7 @@ struct WildcardPropertyCallbacks(v8::PropertyHandlerFlags::kHasNoSideEffect) | static_cast(v8::PropertyHandlerFlags::kOnlyInterceptStrings))) {} + // Query callback is needed for V8 to properly handle property creation with correct + // enumerable attributes when the interceptor is on the instance template. + static v8::Intercepted query( + v8::Local name, const v8::PropertyCallbackInfo& info) { + return v8::Intercepted::kNo; + } + static v8::Intercepted getter( v8::Local name, const v8::PropertyCallbackInfo& info) { v8::Intercepted result = v8::Intercepted::kNo; From 2698acb587d5cee36a113527d2a5beb503e42ee8 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Tue, 3 Feb 2026 11:52:09 +0000 Subject: [PATCH 4/8] use query to mask interceptors --- src/workerd/api/worker-rpc.c++ | 2 +- src/workerd/jsg/jsg-test.c++ | 2 +- src/workerd/jsg/jsvalue.h | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/workerd/api/worker-rpc.c++ b/src/workerd/api/worker-rpc.c++ index 6a53f63eafb..899b0dbc4fa 100644 --- a/src/workerd/api/worker-rpc.c++ +++ b/src/workerd/api/worker-rpc.c++ @@ -1305,7 +1305,7 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { return object.get(js, jsName); } else { // This is an instance of a valid RPC target class. - if (object.has(js, jsName, jsg::JsObject::HasOption::OWN_SKIP_INTERCEPTOR)) { + if (object.has(js, jsName, jsg::JsObject::HasOption::OWN)) { // We do NOT allow own properties, only class properties. failLookup(kjName); } diff --git a/src/workerd/jsg/jsg-test.c++ b/src/workerd/jsg/jsg-test.c++ index 664c3c9eb01..e7b3ab385e4 100644 --- a/src/workerd/jsg/jsg-test.c++ +++ b/src/workerd/jsg/jsg-test.c++ @@ -395,7 +395,7 @@ JSG_DECLARE_ISOLATE_TYPE(InterceptIsolate, InterceptContext, InterceptContext::P KJ_TEST("Named interceptor") { Evaluator e(v8System); e.expectEval("p = new ProxyImpl; p.bar", "number", "123"); - e.expectEval("p = new ProxyImpl; Reflect.has(p, 'foo')", "boolean", "true"); + e.expectEval("p = new ProxyImpl; Reflect.has(p, 'foo')", "boolean", "false"); e.expectEval("p = new ProxyImpl; Reflect.has(p, 'bar')", "boolean", "true"); e.expectEval("p = new ProxyImpl; Reflect.has(p, 'baz')", "boolean", "false"); e.expectEval("p = new ProxyImpl; p.abc", "throws", "TypeError: boom"); diff --git a/src/workerd/jsg/jsvalue.h b/src/workerd/jsg/jsvalue.h index bd7425bfa44..61481f4521d 100644 --- a/src/workerd/jsg/jsvalue.h +++ b/src/workerd/jsg/jsvalue.h @@ -452,7 +452,6 @@ class JsObject final: public JsBase { enum class HasOption { NONE, OWN, - OWN_SKIP_INTERCEPTOR, }; bool has(Lock& js, const JsValue& name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT; @@ -966,9 +965,6 @@ inline bool JsObject::has(Lock& js, const JsValue& name, HasOption option) { if (option == HasOption::OWN) { KJ_ASSERT(name.inner->IsName()); return check(inner->HasOwnProperty(js.v8Context(), name.inner.As())); - } else if (option == HasOption::OWN_SKIP_INTERCEPTOR) { - KJ_ASSERT(name.inner->IsName()); - return check(inner->HasRealNamedProperty(js.v8Context(), name.inner.As())); } else { return check(inner->Has(js.v8Context(), name.inner)); } From 87dee8a2ebdfcf8c5d138fcf24986a47c4e2deec Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Wed, 4 Feb 2026 14:06:59 +0000 Subject: [PATCH 5/8] remove remaining use cases --- src/workerd/jsg/jsg-test.c++ | 4 ++-- src/workerd/jsg/resource.h | 20 +++++++------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/workerd/jsg/jsg-test.c++ b/src/workerd/jsg/jsg-test.c++ index e7b3ab385e4..a9693a81179 100644 --- a/src/workerd/jsg/jsg-test.c++ +++ b/src/workerd/jsg/jsg-test.c++ @@ -234,11 +234,11 @@ KJ_TEST("can't use builtin as prototype") { e.expectEval("function JsType() {}\n" "JsType.prototype = new NumberBox(123);\n" "new JsType().value", - "throws", kIllegalInvocation); + "number", "123"); e.expectEval("function JsType() {}\n" "JsType.prototype = new ExtendedNumberBox(123, 'foo');\n" "new JsType().value", - "throws", kIllegalInvocation); + "number", "123"); e.expectEval("function JsType() {}\n" "JsType.prototype = this;\n" "new JsType().getContextProperty()", diff --git a/src/workerd/jsg/resource.h b/src/workerd/jsg/resource.h index 37fd857b3cd..e6734c75c4f 100644 --- a/src/workerd/jsg/resource.h +++ b/src/workerd/jsg/resource.h @@ -29,9 +29,8 @@ #include #include -// TODO(soon): Resolve .This() -> .HolderV2() deprecation warnings, then remove this pragma. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" +// TODO(dcarney): remove once stable +#define WORKERD_INSTANCE_WILDCARDS namespace std { inline auto KJ_HASHCODE(const std::type_index& idx) { @@ -639,7 +638,7 @@ struct GetterCallback; liftKj(info, [&]() { \ auto isolate = info.GetIsolate(); \ auto context = isolate->GetCurrentContext(); \ - auto obj = info.This(); \ + auto obj = info.HolderV2(); \ auto& js = Lock::from(isolate); \ auto& wrapper = TypeWrapper::from(isolate); \ /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \ @@ -686,7 +685,7 @@ struct GetterCallback; auto isolate = info.GetIsolate(); \ auto context = isolate->GetCurrentContext(); \ auto& js = Lock::from(isolate); \ - auto obj = info.This(); \ + auto obj = info.HolderV2(); \ auto& wrapper = TypeWrapper::from(isolate); \ /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \ if (!isContext && \ @@ -884,9 +883,7 @@ struct SetterCallbackGetCurrentContext(); auto& js = Lock::from(isolate); - // TODO(soon): resolve .This() -> .HolderV2() deprecation message. When doing so, please - // also remove the "#pragma clang diagnostic ignored "-Wdeprecated-declarations"" above. - auto obj = info.This(); + auto obj = info.HolderV2(); auto& wrapper = TypeWrapper::from(isolate); // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. if (!isContext && !wrapper.getTemplate(isolate, static_cast(nullptr))->HasInstance(obj)) { @@ -912,7 +909,7 @@ struct SetterCallbackGetCurrentContext(); - auto obj = info.This(); + auto obj = info.HolderV2(); auto& wrapper = TypeWrapper::from(isolate); // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. if (!isContext && !wrapper.getTemplate(isolate, static_cast(nullptr))->HasInstance(obj)) { @@ -1464,7 +1461,7 @@ struct ResourceTypeBuilder { auto symbol = v8::Symbol::New(isolate, v8Name); inspectProperties->Set(v8Name, symbol, v8::PropertyAttribute::ReadOnly); - prototype->SetNativeDataProperty(symbol, &Gcb::callback, nullptr, v8::Local(), + instance->SetNativeDataProperty(symbol, &Gcb::callback, nullptr, v8::Local(), static_cast( v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum)); } @@ -2011,7 +2008,4 @@ class ObjectWrapper { kj::Maybe> parentObject) = delete; }; -// TODO(soon): Resolve .This() -> .HolderV2() deprecation warnings, then remove this pragma. -#pragma clang diagnostic pop - } // namespace workerd::jsg From c43c98ac8c04f803163aba15154b1a6f89b42198 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Wed, 4 Feb 2026 15:34:24 +0000 Subject: [PATCH 6/8] stick with the prototype inspect property --- src/workerd/jsg/resource.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/workerd/jsg/resource.h b/src/workerd/jsg/resource.h index e6734c75c4f..0c4ddf816a6 100644 --- a/src/workerd/jsg/resource.h +++ b/src/workerd/jsg/resource.h @@ -29,9 +29,6 @@ #include #include -// TODO(dcarney): remove once stable -#define WORKERD_INSTANCE_WILDCARDS - namespace std { inline auto KJ_HASHCODE(const std::type_index& idx) { // Make std::type_index (which points to std::type_info) usable as a kj::HashMap key. @@ -1191,7 +1188,7 @@ struct WildcardPropertyCallbacks(v8::PropertyHandlerFlags::kOnlyInterceptStrings))) {} // Query callback is needed for V8 to properly handle property creation with correct - // enumerable attributes when the interceptor is on the instance template. + // enumerable attributes when the interceptor is on the instance template. Additionally, we want to ensure wildcard properties basically act as phantom properties om the instance. They should not be visible from javascript, only gettable. static v8::Intercepted query( v8::Local name, const v8::PropertyCallbackInfo& info) { return v8::Intercepted::kNo; @@ -1453,7 +1450,7 @@ struct ResourceTypeBuilder { template inline void registerInspectProperty() { - using Gcb = GetterCallback; + using Gcb = PropertyGetterCallback; auto v8Name = v8StrIntern(isolate, name); @@ -1461,7 +1458,8 @@ struct ResourceTypeBuilder { auto symbol = v8::Symbol::New(isolate, v8Name); inspectProperties->Set(v8Name, symbol, v8::PropertyAttribute::ReadOnly); - instance->SetNativeDataProperty(symbol, &Gcb::callback, nullptr, v8::Local(), + auto getterFn = v8::FunctionTemplate::New(isolate, &Gcb::callback); + prototype->SetAccessorProperty(symbol, getterFn, {}, static_cast( v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum)); } From cf1a3aa7ad1f3b4b69a008eb24c04e5a88f81ec7 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Thu, 12 Feb 2026 17:24:58 +0000 Subject: [PATCH 7/8] revert wildcard changes --- src/workerd/api/actor.h | 2 -- src/workerd/api/export-loopback.h | 2 -- src/workerd/api/worker-rpc.c++ | 5 +++-- src/workerd/jsg/jsg-test.c++ | 2 +- src/workerd/jsg/resource.h | 20 ++++++++++---------- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/workerd/api/actor.h b/src/workerd/api/actor.h index b2edea6a7a4..15f1aefe9e8 100644 --- a/src/workerd/api/actor.h +++ b/src/workerd/api/actor.h @@ -111,8 +111,6 @@ class DurableObject final: public Fetcher { JSG_READONLY_INSTANCE_PROPERTY(id, getId); JSG_READONLY_INSTANCE_PROPERTY(name, getName); - // Need to inherit getRpcMethod interceptor since we need it on the holder. - JSG_WILDCARD_PROPERTY(getRpcMethod); JSG_TS_DEFINE(interface DurableObject { fetch(request: Request): Response | Promise; diff --git a/src/workerd/api/export-loopback.h b/src/workerd/api/export-loopback.h index cee73a1b33a..d834cacc527 100644 --- a/src/workerd/api/export-loopback.h +++ b/src/workerd/api/export-loopback.h @@ -36,8 +36,6 @@ class LoopbackServiceStub: public Fetcher { JSG_RESOURCE_TYPE(LoopbackServiceStub) { JSG_INHERIT(Fetcher); JSG_CALLABLE(call); - // Need to inherit getRpcMethod interceptor since we need it on the holder. - JSG_WILDCARD_PROPERTY(getRpcMethod); JSG_TS_ROOT(); JSG_TS_OVERRIDE( diff --git a/src/workerd/api/worker-rpc.c++ b/src/workerd/api/worker-rpc.c++ index 899b0dbc4fa..1b82db92909 100644 --- a/src/workerd/api/worker-rpc.c++ +++ b/src/workerd/api/worker-rpc.c++ @@ -1382,8 +1382,9 @@ class JsRpcTargetBase: public rpc::JsRpcTarget::Server { } else if (object.isInstanceOf(js) || object.isInstanceOf(js) || (inStub && object.isInstanceOf(js))) { // Yes. It's a JsRpcStub or Fetcher. We should allow descending into the stub. - // The wildcard property interceptor is on the instance template, so properties - // accessed via the interceptor appear as instance properties. + // Note that the wildcard property of a stub is a prototype property, not an instance + // property, so setting allowInstanceProperties = false here gets the behavior we + // want. // TODO(someday): We'll need to support JsRpcPromise here if someday we allow it to // be serialized. allowInstanceProperties = false; diff --git a/src/workerd/jsg/jsg-test.c++ b/src/workerd/jsg/jsg-test.c++ index a9693a81179..67058fdc937 100644 --- a/src/workerd/jsg/jsg-test.c++ +++ b/src/workerd/jsg/jsg-test.c++ @@ -395,7 +395,7 @@ JSG_DECLARE_ISOLATE_TYPE(InterceptIsolate, InterceptContext, InterceptContext::P KJ_TEST("Named interceptor") { Evaluator e(v8System); e.expectEval("p = new ProxyImpl; p.bar", "number", "123"); - e.expectEval("p = new ProxyImpl; Reflect.has(p, 'foo')", "boolean", "false"); + e.expectEval("p = new ProxyImpl; Reflect.has(p, 'foo')", "boolean", "true"); e.expectEval("p = new ProxyImpl; Reflect.has(p, 'bar')", "boolean", "true"); e.expectEval("p = new ProxyImpl; Reflect.has(p, 'baz')", "boolean", "false"); e.expectEval("p = new ProxyImpl; p.abc", "throws", "TypeError: boom"); diff --git a/src/workerd/jsg/resource.h b/src/workerd/jsg/resource.h index 0c4ddf816a6..b83c52c4017 100644 --- a/src/workerd/jsg/resource.h +++ b/src/workerd/jsg/resource.h @@ -29,6 +29,10 @@ #include #include +// TODO(soon): Resolve .This() -> .HolderV2() deprecation warnings, then remove this pragma. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + namespace std { inline auto KJ_HASHCODE(const std::type_index& idx) { // Make std::type_index (which points to std::type_info) usable as a kj::HashMap key. @@ -1176,7 +1180,7 @@ struct WildcardPropertyCallbacks(v8::PropertyHandlerFlags::kHasNoSideEffect) | static_cast(v8::PropertyHandlerFlags::kOnlyInterceptStrings))) {} - // Query callback is needed for V8 to properly handle property creation with correct - // enumerable attributes when the interceptor is on the instance template. Additionally, we want to ensure wildcard properties basically act as phantom properties om the instance. They should not be visible from javascript, only gettable. - static v8::Intercepted query( - v8::Local name, const v8::PropertyCallbackInfo& info) { - return v8::Intercepted::kNo; - } - static v8::Intercepted getter( v8::Local name, const v8::PropertyCallbackInfo& info) { v8::Intercepted result = v8::Intercepted::kNo; liftKj(info, [&]() -> v8::Local { auto isolate = info.GetIsolate(); auto context = isolate->GetCurrentContext(); - auto obj = info.HolderV2(); + auto obj = info.This(); auto& wrapper = TypeWrapper::from(isolate); if (!wrapper.getTemplate(isolate, static_cast(nullptr))->HasInstance(obj)) { throwTypeError(isolate, kIllegalInvocation); @@ -1250,7 +1247,7 @@ struct ResourceTypeBuilder { template inline void registerWildcardProperty() { - instance->SetHandler( + prototype->SetHandler( WildcardPropertyCallbacks{}); } @@ -2006,4 +2003,7 @@ class ObjectWrapper { kj::Maybe> parentObject) = delete; }; +// TODO(soon): Resolve .This() -> .HolderV2() deprecation warnings, then remove this pragma. +#pragma clang diagnostic pop + } // namespace workerd::jsg From 772fe8253acea8c53316ef0719189bed6e49c318 Mon Sep 17 00:00:00 2001 From: Dan Carney Date: Thu, 12 Feb 2026 17:31:58 +0000 Subject: [PATCH 8/8] add test --- src/workerd/jsg/jsg-test.c++ | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/workerd/jsg/jsg-test.c++ b/src/workerd/jsg/jsg-test.c++ index 67058fdc937..263c199201d 100644 --- a/src/workerd/jsg/jsg-test.c++ +++ b/src/workerd/jsg/jsg-test.c++ @@ -235,6 +235,11 @@ KJ_TEST("can't use builtin as prototype") { "JsType.prototype = new NumberBox(123);\n" "new JsType().value", "number", "123"); + e.expectEval("function JsType() {}\n" + "JsType.prototype = new NumberBox(123);\n" + "let t = new JsType();\n" + "Reflect.get(JsType.prototype, 'value', t)\n", + "number", "123"); e.expectEval("function JsType() {}\n" "JsType.prototype = new ExtendedNumberBox(123, 'foo');\n" "new JsType().value",