@@ -2156,6 +2156,44 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {
2156
2156
ignoredHandlers.insert (" unhandledrejection" _kj);
2157
2157
ignoredHandlers.insert (" rejectionhandled" _kj);
2158
2158
2159
+ // Helper function to collect methods from a prototype chain
2160
+ auto collectMethodsFromPrototypeChain = [&](jsg::JsValue startProto,
2161
+ kj::HashSet<kj::String>& seenNames) {
2162
+ // Find the prototype for `Object` by creating one.
2163
+ auto obj = js.obj ();
2164
+ jsg::JsValue prototypeOfObject = obj.getPrototype (js);
2165
+
2166
+ // Walk the prototype chain.
2167
+ jsg::JsValue proto = startProto;
2168
+ for (;;) {
2169
+ auto protoObj = JSG_REQUIRE_NONNULL (proto.tryCast <jsg::JsObject>(), TypeError,
2170
+ " Exported value's prototype chain does not end in Object." );
2171
+ if (protoObj == prototypeOfObject) {
2172
+ // Reached the prototype for `Object`. Stop here.
2173
+ break ;
2174
+ }
2175
+
2176
+ // Awkwardly, the prototype's members are not typically enumerable, so we have to
2177
+ // enumerate them rather directly.
2178
+ jsg::JsArray properties = protoObj.getPropertyNames (js, jsg::KeyCollectionFilter::OWN_ONLY,
2179
+ jsg::PropertyFilter::SKIP_SYMBOLS, jsg::IndexFilter::SKIP_INDICES);
2180
+ for (auto i: kj::zeroTo (properties.size ())) {
2181
+ auto name = properties.get (js, i).toString (js);
2182
+ if (name == " constructor" _kj) {
2183
+ // Don't treat special method `constructor` as an exported handler.
2184
+ continue ;
2185
+ }
2186
+
2187
+ if (!ignoredHandlers.contains (name)) {
2188
+ // Only report each method name once, even if it overrides a method in a superclass.
2189
+ seenNames.upsert (kj::mv (name), [&](auto &, auto &&) {});
2190
+ }
2191
+ }
2192
+
2193
+ proto = protoObj.getPrototype (js);
2194
+ }
2195
+ };
2196
+
2159
2197
KJ_IF_SOME (c, worker.impl ->context ) {
2160
2198
// Service workers syntax.
2161
2199
auto handlerNames = c->getHandlerNames ();
@@ -2172,7 +2210,6 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {
2172
2210
errorReporter.addEntrypoint (kj::none, handlers.releaseAsArray ());
2173
2211
} else {
2174
2212
auto report = [&](kj::Maybe<kj::StringPtr > name, api::ExportedHandler& exported) {
2175
- kj::Vector<kj::String> methods;
2176
2213
auto handle = exported.self .getHandle (js);
2177
2214
if (handle->IsArray ()) {
2178
2215
// HACK: toDict() will throw a TypeError if given an array, because jsg::DictWrapper is
@@ -2182,15 +2219,28 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {
2182
2219
// hence we will see it here. Rather than try to correct this inconsistency between
2183
2220
// struct and dict handling (which could have unintended consequences), let's just
2184
2221
// work around by ignoring arrays here.
2222
+ errorReporter.addEntrypoint (name, kj::Array<kj::String>());
2185
2223
} else {
2224
+ // Use a HashSet to avoid duplicates when methods exist both as own properties
2225
+ // and in the prototype chain
2226
+ kj::HashSet<kj::String> methodSet;
2227
+
2228
+ // First, check for own properties (like a plain object literal)
2186
2229
auto dict = js.toDict (handle);
2187
2230
for (auto & field: dict.fields ) {
2188
2231
if (!ignoredHandlers.contains (field.name )) {
2189
- methods. add (kj::mv (field.name ));
2232
+ methodSet. upsert (kj::mv (field.name ), [&]( auto &, auto &&) {} );
2190
2233
}
2191
2234
}
2235
+
2236
+ // Then, check for methods in the prototype chain (like a class instance)
2237
+ js.withinHandleScope ([&]() {
2238
+ collectMethodsFromPrototypeChain (jsg::JsObject (handle).getPrototype (js), methodSet);
2239
+ });
2240
+
2241
+ // Convert HashSet to Array for reporting
2242
+ errorReporter.addEntrypoint (name, KJ_MAP (n, methodSet) { return kj::mv (n); });
2192
2243
}
2193
- errorReporter.addEntrypoint (name, methods.releaseAsArray ());
2194
2244
};
2195
2245
2196
2246
auto getEntrypointName = [&](kj::StringPtr key) -> kj::Maybe<kj::StringPtr > {
@@ -2224,44 +2274,16 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {
2224
2274
// prototype, and its prototype's prototype, and so on, until we get to Object's
2225
2275
// prototype, which we ignore.
2226
2276
auto entrypointName = getEntrypointName (entry.key );
2227
- js.withinHandleScope ([&]() {
2228
- // Find the prototype for `Object` by creating one.
2229
- auto obj = js.obj ();
2230
- jsg::JsValue prototypeOfObject = obj.getPrototype (js);
2277
+ kj::HashSet<kj::String> seenNames;
2231
2278
2232
- // Walk the prototype chain.
2279
+ js.withinHandleScope ([&]() {
2280
+ // For stateless classes, we need to get the class's prototype property
2233
2281
jsg::JsObject ctor (KJ_ASSERT_NONNULL (entry.value .tryGetHandle (js.v8Isolate )));
2234
2282
jsg::JsValue proto = ctor.get (js, " prototype" );
2235
- kj::HashSet<kj::String> seenNames;
2236
- for (;;) {
2237
- auto protoObj = JSG_REQUIRE_NONNULL (proto.tryCast <jsg::JsObject>(), TypeError,
2238
- " Exported entrypoint class's prototype chain does not end in Object." );
2239
- if (protoObj == prototypeOfObject) {
2240
- // Reached the prototype for `Object`. Stop here.
2241
- break ;
2242
- }
2243
-
2244
- // Awkwardly, the prototype's members are not typically enumerable, so we have to
2245
- // enumerate them rather directly.
2246
- jsg::JsArray properties =
2247
- protoObj.getPropertyNames (js, jsg::KeyCollectionFilter::OWN_ONLY,
2248
- jsg::PropertyFilter::SKIP_SYMBOLS, jsg::IndexFilter::SKIP_INDICES);
2249
- for (auto i: kj::zeroTo (properties.size ())) {
2250
- auto name = properties.get (js, i).toString (js);
2251
- if (name == " constructor" _kj) {
2252
- // Don't treat special method `constructor` as an exported handler.
2253
- continue ;
2254
- }
2255
-
2256
- // Only report each method name once, even if it overrides a method in a superclass.
2257
- seenNames.upsert (kj::mv (name), [&](auto &, auto &&) {});
2258
- }
2259
-
2260
- proto = protoObj.getPrototype (js);
2261
- }
2262
-
2263
- errorReporter.addEntrypoint (entrypointName, KJ_MAP (n, seenNames) { return kj::mv (n); });
2283
+ collectMethodsFromPrototypeChain (proto, seenNames);
2264
2284
});
2285
+
2286
+ errorReporter.addEntrypoint (entrypointName, KJ_MAP (n, seenNames) { return kj::mv (n); });
2265
2287
}
2266
2288
}
2267
2289
});
0 commit comments