Skip to content
File

Blob: src/workerd/jsg/iterator.h

cpp1150 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include <workerd/jsg/jsg.h>
8#include <workerd/jsg/memory.h>
9#include <workerd/jsg/struct.h>
10#include <workerd/util/weak-refs.h>
11 
12#include <list>
13 
14namespace workerd::jsg {
15 
16// -----------------------------------------------------------------------------
17// Generators
18 
19template <typename TypeWrapper>
20class GeneratorWrapper;
21 
22template <typename T>
23struct GeneratorNext {
24 bool done;
25 
26 // Value should only be nullptr if done is true. It does not
27 // *have* to be nullptr if done is true, however.
28 kj::Maybe<T> value;
29};
30 
31template <typename Signature, typename TypeWrapper>
32static kj::Maybe<Signature> tryGetGeneratorFunction(
33 Lock& js, JsObject& object, kj::StringPtr name) {
34 auto value = object.get(js, name);
35 return TypeWrapper::from(js.v8Isolate)
36 .tryUnwrap(js, js.v8Context(), value, static_cast<Signature*>(nullptr),
37 kj::Maybe<v8::Local<v8::Object>>(object));
38}
39 
40template <typename T>
41class Generator final {
42 // See the documentation in jsg.h
43 public:
44 template <typename TypeWrapper>
45 Generator(Lock& js, JsObject object, TypeWrapper*)
46 : maybeActive(Active(js, object, static_cast<TypeWrapper*>(nullptr))) {}
47 Generator(Generator&&) = default;
48 Generator& operator=(Generator&&) = default;
49 KJ_DISALLOW_COPY(Generator);
50 
51 // If nothing is returned, the generator is complete.
52 kj::Maybe<T> next(Lock& js) {
53 KJ_IF_SOME(active, maybeActive) {
54 KJ_IF_SOME(nextfn, active.maybeNext) {
55 return js.tryCatch([&] {
56 auto result = nextfn(js);
57 if (result.done || result.value == kj::none) {
58 maybeActive = kj::none;
59 }
60 return kj::mv(result.value);
61 }, [&](Value exception) { return throw_(js, kj::mv(exception)); });
62 }
63 maybeActive = kj::none;
64 }
65 return kj::none;
66 }
67 
68 // If nothing is returned, the generator is complete.
69 // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the 'return'
70 // property exists but is not callable, we throw a TypeError.
71 kj::Maybe<T> return_(Lock& js, kj::Maybe<T> maybeValue = kj::none) {
72 KJ_IF_SOME(active, maybeActive) {
73 // Per GetMethod spec: if property exists but is not callable, throw TypeError
74 if (active.returnExistsButNotCallable) {
75 maybeActive = kj::none;
76 JSG_FAIL_REQUIRE(TypeError, "Property 'return' is not a function");
77 }
78 
79 KJ_IF_SOME(returnFn, active.maybeReturn) {
80 return js.tryCatch([&] {
81 auto result = returnFn(js, kj::mv(maybeValue));
82 if (result.done || result.value == kj::none) {
83 maybeActive = kj::none;
84 }
85 return kj::mv(result.value);
86 }, [&](Value exception) { return throw_(js, kj::mv(exception)); });
87 }
88 maybeActive = kj::none;
89 }
90 return kj::none;
91 }
92 
93 // If nothing is returned, the generator is complete. If there
94 // is no throw handler in the generator, the method will throw.
95 // It's also possible (and even likely) that the throw handler
96 // will just re-throw the exception.
97 kj::Maybe<T> throw_(Lock& js, Value exception) {
98 KJ_IF_SOME(active, maybeActive) {
99 KJ_IF_SOME(throwFn, active.maybeThrow) {
100 return js.tryCatch([&] -> kj::Maybe<T> {
101 auto result = throwFn(js, kj::mv(exception));
102 if (result.done || result.value == kj::none) {
103 maybeActive = kj::none;
104 }
105 return kj::mv(result.value);
106 }, [&](Value exception) -> kj::Maybe<T> {
107 maybeActive = kj::none;
108 js.throwException(kj::mv(exception));
109 });
110 }
111 }
112 js.throwException(kj::mv(exception));
113 }
114 
115 void visitForGc(GcVisitor& visitor) {
116 visitForGc(maybeActive);
117 }
118 
119 private:
120 using Next = GeneratorNext<T>;
121 using NextSignature = Function<Next()>;
122 using ReturnSignature = Function<Next(Optional<T>)>;
123 using ThrowSignature = Function<Next(Optional<Value>)>;
124 
125 struct Active final {
126 kj::Maybe<NextSignature> maybeNext;
127 kj::Maybe<ReturnSignature> maybeReturn;
128 kj::Maybe<ThrowSignature> maybeThrow;
129 // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the
130 // 'return' property exists but is not callable, we should throw a TypeError.
131 // We track this state to defer the error to when return_() is actually called.
132 bool returnExistsButNotCallable = false;
133 
134 template <typename TypeWrapper>
135 Active(Lock& js, JsObject object, TypeWrapper*)
136 : maybeNext(tryGetGeneratorFunction<NextSignature, TypeWrapper>(js, object, "next"_kj)),
137 // maybeReturn is initialized in the constructor body — see below.
138 maybeThrow(tryGetGeneratorFunction<ThrowSignature, TypeWrapper>(js, object, "throw"_kj)) {
139 // Per the GetMethod spec (https://262.ecma-international.org/#sec-getmethod):
140 // 1. If the property value is undefined or null, the method is absent — not an error.
141 // 2. If the property value exists but is not callable, throw a TypeError.
142 // 3. If the property value is callable, use it.
143 //
144 // tryGetGeneratorFunction() calls object.get() then tryUnwrap(), which returns
145 // kj::none for both cases (1) and (2) since tryUnwrap just checks IsFunction().
146 // To distinguish them we need access to the raw property value, so we inline the
147 // lookup here rather than calling tryGetGeneratorFunction(). This also avoids a
148 // second property lookup that could trigger observable side effects from getters
149 // or return a different value on each access.
150 auto returnVal = object.get(js, "return"_kj);
151 if (!returnVal.isNullOrUndefined()) {
152 maybeReturn =
153 TypeWrapper::from(js.v8Isolate)
154 .tryUnwrap(js, js.v8Context(), returnVal, static_cast<ReturnSignature*>(nullptr),
155 kj::Maybe<v8::Local<v8::Object>>(object));
156 returnExistsButNotCallable = (maybeReturn == kj::none);
157 }
158 }
159 Active(Active&&) = default;
160 Active& operator=(Active&&) = default;
161 KJ_DISALLOW_COPY(Active);
162 
163 void visitForGc(GcVisitor& visitor) {
164 visitor.visit(maybeNext, maybeReturn, maybeThrow);
165 }
166 };
167 kj::Maybe<Active> maybeActive;
168};
169 
170template <typename T>
171class AsyncGenerator final {
172 // See the documentation in jsg.h
173 public:
174 template <typename TypeWrapper>
175 AsyncGenerator(Lock& js, JsObject object, TypeWrapper*)
176 : maybeActive(Active(js, object, static_cast<TypeWrapper*>(nullptr))),
177 maybeSelfRef(kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this)) {}
178 AsyncGenerator(AsyncGenerator&& other) noexcept
179 : maybeActive(kj::mv(other.maybeActive)),
180 maybeSelfRef(kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this)) {
181 // Invalidate the old WeakRef since it's being moved.
182 KJ_IF_SOME(selfRef, other.maybeSelfRef) {
183 selfRef->invalidate();
184 }
185 }
186 AsyncGenerator& operator=(AsyncGenerator&& other) {
187 if (this != &other) {
188 KJ_IF_SOME(selfRef, maybeSelfRef) {
189 selfRef->invalidate();
190 }
191 KJ_IF_SOME(selfRef, other.maybeSelfRef) {
192 selfRef->invalidate();
193 }
194 maybeActive = kj::mv(other.maybeActive);
195 maybeSelfRef = kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this);
196 }
197 return *this;
198 }
199 KJ_DISALLOW_COPY(AsyncGenerator);
200 ~AsyncGenerator() noexcept(false) {
201 KJ_IF_SOME(selfRef, maybeSelfRef) {
202 selfRef->invalidate();
203 }
204 }
205 
206 // If nothing is returned, the generator is complete.
207 Promise<kj::Maybe<T>> next(Lock& js) {
208 KJ_IF_SOME(active, maybeActive) {
209 KJ_IF_SOME(next, active.maybeNext) {
210 auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);
211 return js.tryCatch([&] {
212 return next(js).then(js, [ref = selfRef.addRef()](Lock& js, auto result) {
213 if (result.done || result.value == kj::none) {
214 ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
215 }
216 return js.resolvedPromise<kj::Maybe<T>>(kj::mv(result.value));
217 }, [ref = selfRef.addRef()](Lock& js, Value exception) {
218 Promise<kj::Maybe<T>> retPromise = nullptr;
219 if (ref->runIfAlive([&](AsyncGenerator& self) {
220 retPromise = self.throw_(js, kj::mv(exception));
221 })) {
222 return kj::mv(retPromise);
223 }
224 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
225 });
226 }, [&](Value exception) {
227 maybeActive = kj::none;
228 return throw_(js, kj::mv(exception));
229 });
230 }
231 maybeActive = kj::none;
232 }
233 
234 return js.resolvedPromise(kj::Maybe<T>(kj::none));
235 }
236 
237 // If nothing is returned, the generator is complete.
238 // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the 'return'
239 // property exists but is not callable, we throw a TypeError.
240 Promise<kj::Maybe<T>> return_(Lock& js, kj::Maybe<T> maybeValue = kj::none) {
241 KJ_IF_SOME(active, maybeActive) {
242 // Per GetMethod spec: if property exists but is not callable, throw TypeError
243 if (active.returnExistsButNotCallable) {
244 maybeActive = kj::none;
245 return js.rejectedPromise<kj::Maybe<T>>(
246 js.typeError("Property 'return' is not a function"_kj));
247 }
248 
249 KJ_IF_SOME(return_, active.maybeReturn) {
250 auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);
251 return js.tryCatch([&] {
252 return return_(js, kj::mv(maybeValue))
253 .then(js, [ref = selfRef.addRef()](Lock& js, auto result) {
254 if (result.done || result.value == kj::none) {
255 ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
256 }
257 return js.resolvedPromise(kj::mv(result.value));
258 }, [ref = selfRef.addRef()](Lock& js, Value exception) {
259 // Per spec, rejections from return() should be propagated directly
260 ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
261 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
262 });
263 }, [&](Value exception) {
264 maybeActive = kj::none;
265 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
266 });
267 }
268 maybeActive = kj::none;
269 }
270 return js.resolvedPromise(kj::Maybe<T>(kj::none));
271 }
272 
273 // If nothing is returned, the generator is complete. If there
274 // is no throw handler in the generator, the method will throw.
275 // It's also possible (and even likely) that the throw handler
276 // will just re-throw the exception.
277 Promise<kj::Maybe<T>> throw_(Lock& js, Value exception) {
278 KJ_IF_SOME(active, maybeActive) {
279 KJ_IF_SOME(throw_, active.maybeThrow) {
280 auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);
281 return js.tryCatch([&] {
282 return throw_(js, kj::mv(exception))
283 .then(js, [ref = selfRef.addRef()](Lock& js, auto result) {
284 if (result.done || result.value == kj::none) {
285 ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
286 }
287 // In this case, the exception was handled and we might have a value to return.
288 // The generator might still be active.
289 return js.resolvedPromise(kj::mv(result.value));
290 }, [ref = selfRef.addRef()](Lock& js, Value exception) {
291 ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
292 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
293 });
294 }, [&](Value exception) {
295 maybeActive = kj::none;
296 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
297 });
298 }
299 maybeActive = kj::none;
300 }
301 return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));
302 }
303 
304 private:
305 using Next = GeneratorNext<T>;
306 using NextSignature = Function<Promise<Next>()>;
307 using ReturnSignature = Function<Promise<Next>(Optional<T>)>;
308 using ThrowSignature = Function<Promise<Next>(Optional<Value>)>;
309 
310 struct Active final {
311 kj::Maybe<NextSignature> maybeNext;
312 kj::Maybe<ReturnSignature> maybeReturn;
313 kj::Maybe<ThrowSignature> maybeThrow;
314 // Per GetMethod spec, if property exists but is not callable, we should throw TypeError.
315 // We track this state to defer the error to when return_() is actually called.
316 bool returnExistsButNotCallable = false;
317 
318 template <typename TypeWrapper>
319 Active(Lock& js, JsObject object, TypeWrapper*)
320 : maybeNext(tryGetGeneratorFunction<NextSignature, TypeWrapper>(js, object, "next"_kj)),
321 // maybeReturn is initialized in the constructor body — see below.
322 maybeThrow(tryGetGeneratorFunction<ThrowSignature, TypeWrapper>(js, object, "throw"_kj)) {
323 // Per the GetMethod spec (https://262.ecma-international.org/#sec-getmethod):
324 // 1. If the property value is undefined or null, the method is absent — not an error.
325 // 2. If the property value exists but is not callable, throw a TypeError.
326 // 3. If the property value is callable, use it.
327 //
328 // tryGetGeneratorFunction() calls object.get() then tryUnwrap(), which returns
329 // kj::none for both cases (1) and (2) since tryUnwrap just checks IsFunction().
330 // To distinguish them we need access to the raw property value, so we inline the
331 // lookup here rather than calling tryGetGeneratorFunction(). This also avoids a
332 // second property lookup that could trigger observable side effects from getters
333 // or return a different value on each access.
334 auto returnVal = object.get(js, "return"_kj);
335 if (!returnVal.isNullOrUndefined()) {
336 maybeReturn =
337 TypeWrapper::from(js.v8Isolate)
338 .tryUnwrap(js, js.v8Context(), returnVal, static_cast<ReturnSignature*>(nullptr),
339 kj::Maybe<v8::Local<v8::Object>>(object));
340 returnExistsButNotCallable = (maybeReturn == kj::none);
341 }
342 }
343 Active(Active&&) = default;
344 Active& operator=(Active&&) = default;
345 KJ_DISALLOW_COPY(Active);
346 
347 void visitForGc(GcVisitor& visitor) {
348 visitor.visit(maybeNext, maybeReturn, maybeThrow);
349 }
350 };
351 kj::Maybe<Active> maybeActive;
352 kj::Maybe<kj::Rc<WeakRef<AsyncGenerator>>> maybeSelfRef;
353};
354 
355template <typename T>
356class AsyncGeneratorIgnoringStrings final {
357 public:
358 template <typename TypeWrapper>
359 AsyncGeneratorIgnoringStrings(Lock& js, JsObject object, TypeWrapper* ptr)
360 : inner(AsyncGenerator<T>(js, object, ptr)) {}
361 
362 AsyncGenerator<T> release() {
363 return kj::mv(inner);
364 }
365 
366 private:
367 AsyncGenerator<T> inner;
368};
369 
370template <typename TypeWrapper>
371class GeneratorWrapper {
372 public:
373 GeneratorWrapper(const auto& config): config(getConfig(config)) {}
374 
375 template <typename T>
376 static constexpr const char* getName(Generator<T>*) {
377 return "Generator";
378 }
379 
380 template <typename T>
381 static constexpr const char* getName(AsyncGenerator<T>*) {
382 return "AsyncGenerator";
383 }
384 
385 template <typename T>
386 static constexpr const char* getName(AsyncGeneratorIgnoringStrings<T>*) {
387 return "AsyncGenerator";
388 }
389 
390 template <typename T>
391 static constexpr const char* getName(GeneratorNext<T>*) {
392 return "GeneratorNext";
393 }
394 
395 template <typename T>
396 v8::Local<v8::Object> wrap(
397 Lock& js, v8::Local<v8::Context>, kj::Maybe<v8::Local<v8::Object>>, Generator<T>&&) {
398 KJ_FAIL_ASSERT("Generator instances do not support wrap");
399 }
400 
401 template <typename T>
402 v8::Local<v8::Object> wrap(
403 Lock& js, v8::Local<v8::Context>, kj::Maybe<v8::Local<v8::Object>>, AsyncGenerator<T>&&) {
404 KJ_FAIL_ASSERT("AsyncGenerator instances do not support wrap");
405 }
406 
407 template <typename T>
408 v8::Local<v8::Object> wrap(Lock& js,
409 v8::Local<v8::Context>,
410 kj::Maybe<v8::Local<v8::Object>>,
411 AsyncGeneratorIgnoringStrings<T>&&) {
412 KJ_FAIL_ASSERT("AsyncGenerator instances do not support wrap");
413 }
414 
415 template <typename T>
416 v8::Local<v8::Object> wrap(Lock& js,
417 v8::Local<v8::Context> context,
418 kj::Maybe<v8::Local<v8::Object>>,
419 GeneratorNext<T>&& next) {
420 KJ_FAIL_ASSERT("GeneratorNext instances do not support wrap");
421 }
422 // Generator, AsyncGenerator, and GeneratorNext instances should never be
423 // passed back out into JavaScript. Use Iterators for that.
424 
425 template <typename T>
426 kj::Maybe<GeneratorNext<T>> tryUnwrap(Lock& js,
427 v8::Local<v8::Context> context,
428 v8::Local<v8::Value> handle,
429 GeneratorNext<T>*,
430 kj::Maybe<v8::Local<v8::Object>> parentObject) {
431 if (handle->IsObject()) {
432 auto isolate = js.v8Isolate;
433 auto& typeWrapper = TypeWrapper::from(isolate);
434 auto object = handle.template As<v8::Object>();
435 
436 bool done = typeWrapper.template unwrap<bool>(js, context,
437 check(object->Get(context, v8StrIntern(isolate, "done"_kj))), TypeErrorContext::other());
438 
439 auto value = check(object->Get(context, v8StrIntern(isolate, "value"_kj)));
440 
441 if (done) {
442 // If done is true, then it is OK if the value does not map to anything.
443 // Why are we doing it this way? Currently in the Generator pattern, there
444 // is no way of distinguishing between the generator not having any return
445 // value or the generator having undefined as a return value. Because we
446 // cannot differentiate the two, we treat undefined specially and always
447 // return nullptr in this case rather than trying to map it to anything --
448 // even if the thing we'd be mapping to can safely handle undefined as
449 // a value.
450 if (value->IsUndefined()) {
451 return GeneratorNext<T>{
452 .done = true,
453 .value = kj::none,
454 };
455 } else {
456 return GeneratorNext<T>{
457 .done = true,
458 .value =
459 typeWrapper.tryUnwrap(js, context, value, static_cast<T*>(nullptr), parentObject),
460 };
461 }
462 }
463 
464 KJ_IF_SOME(v, typeWrapper.tryUnwrap(js, context, value, (T*)nullptr, parentObject)) {
465 return GeneratorNext<T>{
466 .done = false,
467 .value = kj::mv(v),
468 };
469 } else {
470 throwTypeError(js.v8Isolate, TypeErrorContext::other(),
471 TypeWrapper::getName(static_cast<T*>(nullptr)));
472 }
473 }
474 
475 return kj::none;
476 }
477 
478 template <typename T>
479 kj::Maybe<Generator<T>> tryUnwrap(Lock& js,
480 v8::Local<v8::Context> context,
481 v8::Local<v8::Value> handle,
482 Generator<T>*,
483 kj::Maybe<v8::Local<v8::Object>> parentObject) {
484 if (handle->IsString()) {
485 // In order to be able to treat a string as a generator, we need to first
486 // convert it to a String object. Yes, this means that each call to next
487 // will yield a single character from the string, which is terrible but
488 // that's the spec.
489 handle = check(handle->ToObject(context));
490 }
491 if (handle->IsObject()) {
492 auto isolate = js.v8Isolate;
493 auto object = handle.As<v8::Object>();
494 auto iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));
495 if (iter->IsFunction()) {
496 auto func = iter.As<v8::Function>();
497 auto iterObj = check(func->Call(context, object, 0, nullptr));
498 if (iterObj->IsObject()) {
499 return Generator<T>(
500 js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));
501 }
502 }
503 }
504 return kj::none;
505 }
506 
507 template <typename T>
508 kj::Maybe<AsyncGenerator<T>> tryUnwrap(Lock& js,
509 v8::Local<v8::Context> context,
510 v8::Local<v8::Value> handle,
511 AsyncGenerator<T>*,
512 kj::Maybe<v8::Local<v8::Object>> parentObject) {
513 if (handle->IsString()) {
514 // In order to be able to treat a string as a generator, we need to first
515 // convert it to a String object. Yes, this means that each call to next
516 // will yield a single character from the string, which is terrible but
517 // that's the spec.
518 handle = check(handle->ToObject(context));
519 }
520 if (handle->IsObject()) {
521 auto isolate = js.v8Isolate;
522 auto object = handle.As<v8::Object>();
523 auto iter = check(object->Get(context, v8::Symbol::GetAsyncIterator(isolate)));
524 // If there is no async iterator, let's try a sync iterator.
525 if (iter->IsNullOrUndefined()) {
526 iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));
527 }
528 if (iter->IsFunction()) {
529 auto func = iter.As<v8::Function>();
530 auto iterObj = check(func->Call(context, object, 0, nullptr));
531 if (iterObj->IsObject()) {
532 return AsyncGenerator<T>(
533 js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));
534 }
535 }
536 }
537 return kj::none;
538 }
539 
540 template <typename T>
541 kj::Maybe<AsyncGeneratorIgnoringStrings<T>> tryUnwrap(Lock& js,
542 v8::Local<v8::Context> context,
543 v8::Local<v8::Value> handle,
544 AsyncGeneratorIgnoringStrings<T>*,
545 kj::Maybe<v8::Local<v8::Object>> parentObject) {
546 // This variation of the wrapper is used in cases where Strings should not be treated
547 // as iterators. Specifically, for cases like `kj::OneOf<kj::String,AsyncGenerator<T>>`
548 // where we want to allow strings to be passed through as strings but also want to allow
549 // sync and async generators to be handled as well. Without this, the strings would be
550 // treated as sync iterables.
551 if (config.fetchIterableTypeSupport && handle->IsObject() && !handle->IsStringObject()) {
552 auto isolate = js.v8Isolate;
553 auto object = handle.As<v8::Object>();
554 
555 auto iter = check(object->Get(context, v8::Symbol::GetAsyncIterator(isolate)));
556 // If there is no async iterator, let's try a sync iterator.
557 if (iter->IsNullOrUndefined()) {
558 // Before checking for the sync iterator, let's also check to see if the object
559 // implements a custom toString to Symbol.toPrimitive method that is not the default
560 // Object.prototype.toString. If it does, then we won't treat it as
561 // an iterator either. If the object is an Array, then we skip this check since
562 // it's exceedingly uncommon for arrays to be subclassed with a custom toString method,
563 // so much that it's not worth handling the extreme edge case.
564 // This is to deal with edge cases around objects with customized stringify methods,
565 // which are likely more common than those with customized iterator methods. While
566 // these are both rare cases, it's better to err on the side of custom stringification
567 // rather than custom iteration.
568 if (config.fetchIterableTypeSupportOverrideAdjustment && !object->IsArray()) {
569 if (protoToString == kj::none) {
570 // TODO(cleanup): In several places in the codebase we have this pattern of
571 // lazily grabbing the object prototype. We should probably centralize this
572 // an cache it in the IsolateBase or something.
573 auto obj = js.obj();
574 auto proto = obj.getPrototype(js);
575 protoToString = jsg::JsRef(
576 js, KJ_ASSERT_NONNULL(proto.tryCast<jsg::JsObject>()).get(js, "toString"_kj));
577 toPrimitiveString = jsg::JsRef(js,
578 KJ_ASSERT_NONNULL(proto.tryCast<jsg::JsObject>()).get(js, js.symbolToPrimitive()));
579 }
580 
581 // We only check that the toString/Symbol.toPrimitive is the same value as
582 // Object.prototype.toString/Symbol.toPrimitive. This does not guarantee every
583 // possible edge case but should be sufficient for our purposes.
584 auto jsobj = JsObject(object);
585 if (jsobj.get(js, "toString"_kj) != KJ_ASSERT_NONNULL(protoToString).getHandle(js) ||
586 jsobj.get(js, js.symbolToPrimitive()) !=
587 KJ_ASSERT_NONNULL(toPrimitiveString).getHandle(js)) {
588 return kj::none;
589 }
590 }
591 
592 iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));
593 }
594 if (iter->IsFunction()) {
595 auto func = iter.As<v8::Function>();
596 auto iterObj = check(func->Call(context, object, 0, nullptr));
597 if (iterObj->IsObject()) {
598 return AsyncGeneratorIgnoringStrings<T>(
599 js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));
600 }
601 }
602 }
603 return kj::none;
604 }
605 
606 private:
607 const JsgConfig config;
608 kj::Maybe<jsg::JsRef<jsg::JsValue>> protoToString;
609 kj::Maybe<jsg::JsRef<jsg::JsValue>> toPrimitiveString;
610};
611 
612// -----------------------------------------------------------------------------
613// Sequences
614 
615template <typename T>
616struct Sequence: public kj::Array<T> {
617 // See the documentation in jsg.h
618 Sequence() = default;
619 Sequence(kj::Array<T> items): kj::Array<T>(kj::mv(items)) {}
620};
621 
622template <typename TypeWrapper>
623class SequenceWrapper {
624 // TypeWrapper mixin for Sequences.
625 
626 public:
627 template <typename U>
628 static constexpr const char* getName(Sequence<U>*) {
629 // TODO(later): It would be nicer if the name included the demangled name of U
630 // e.g. Sequence<Foo>
631 return "Sequence";
632 }
633 
634 template <typename U>
635 v8::Local<v8::Value> wrap(Lock& js,
636 v8::Local<v8::Context> context,
637 kj::Maybe<v8::Local<v8::Object>> creator,
638 Sequence<U> sequence) {
639 v8::Isolate* isolate = js.v8Isolate;
640 v8::EscapableHandleScope handleScope(isolate);
641 v8::LocalVector<v8::Value> items(isolate, sequence.size());
642 for (auto i: kj::indices(sequence)) {
643 items[i] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(sequence[i]));
644 }
645 return handleScope.Escape(v8::Array::New(isolate, items.data(), items.size()));
646 }
647 
648 template <typename U>
649 v8::Local<v8::Value> wrap(Lock& js,
650 v8::Local<v8::Context> context,
651 kj::Maybe<v8::Local<v8::Object>> creator,
652 Sequence<U>& sequence) {
653 v8::Isolate* isolate = js.v8Isolate;
654 v8::EscapableHandleScope handleScope(isolate);
655 v8::LocalVector<v8::Value> items(isolate, sequence.size());
656 for (auto i: kj::indices(sequence)) {
657 items[i] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(sequence[i]));
658 }
659 return handleScope.Escape(v8::Array::New(isolate, items.data(), items.size()));
660 }
661 
662 template <typename U>
663 kj::Maybe<Sequence<U>> tryUnwrap(Lock& js,
664 v8::Local<v8::Context> context,
665 v8::Local<v8::Value> handle,
666 Sequence<U>*,
667 kj::Maybe<v8::Local<v8::Object>> parentObject) {
668 auto isolate = js.v8Isolate;
669 auto& typeWrapper = TypeWrapper::from(isolate);
670 // In this case, if handle is a string, we likely do not want to treat it as
671 // a sequence of characters, which the Generator case would do. If someone
672 // really wants to treat a string as a sequence of characters, then they
673 // should use the Generator interface directly.
674 if (handle->IsString()) return kj::none;
675 KJ_IF_SOME(gen,
676 typeWrapper.tryUnwrap(js, context, handle, (Generator<U>*)nullptr, parentObject)) {
677 // The generator gives us no indication of how many items there might be, so we
678 // have to just keep pulling them until it says it's done.
679 kj::Vector<U> items;
680 while (true) {
681 KJ_IF_SOME(item, gen.next(js)) {
682 items.add(kj::mv(item));
683 } else {
684 gen.return_(js, kj::none);
685 break;
686 }
687 }
688 return Sequence<U>(items.releaseAsArray());
689 }
690 return kj::none;
691 }
692};
693 
694// -----------------------------------------------------------------------------
695 
696template <typename SelfType, typename Type, typename State>
697class IteratorBase: public Object {
698 // Provides the base implementation of JSG_ITERATOR types. See the documentation
699 // for JSG_ITERATOR for details.
700 public:
701 using NextSignature = kj::Maybe<Type>(Lock&, State&);
702 explicit IteratorBase(State state): state(kj::mv(state)) {}
703 struct Next {
704 bool done;
705 Optional<Type> value;
706 JSG_STRUCT(done, value);
707 };
708 
709 v8::Local<v8::Object> self(const v8::FunctionCallbackInfo<v8::Value>& info) {
710 return info.This();
711 }
712 
713 void visitForGc(GcVisitor& visitor) {
714 if constexpr (hasPublicVisitForGc<State>()) {
715 visitor.visit(state);
716 }
717 }
718 
719 JSG_MEMORY_INFO(IteratorBase) {
720 if constexpr (MemoryRetainer<State>) {
721 tracker.trackField("state", state);
722 } else {
723 tracker.trackFieldWithSize("state", sizeof(State));
724 }
725 }
726 
727 private:
728 State state;
729 
730 Next nextImpl(Lock& js, NextSignature nextFunc) {
731 KJ_IF_SOME(value, nextFunc(js, state)) {
732 return Next{.done = false, .value = kj::mv(value)};
733 }
734 return Next{
735 .done = true,
736 .value = kj::none,
737 };
738 }
739 
740 friend SelfType;
741};
742 
743class AsyncIteratorImpl {
744 public:
745 struct Finished {};
746 
747 kj::Maybe<Promise<void>&> maybeCurrent();
748 
749 void pushCurrent(Promise<void> promise);
750 
751 void popCurrent();
752 
753 bool returning = false;
754 
755 void visitForGc(GcVisitor& visitor);
756 
757 template <typename Type>
758 struct Next {
759 bool done;
760 Optional<Type> value;
761 JSG_STRUCT(done, value);
762 };
763 
764 JSG_MEMORY_INFO(AsyncIteratorImpl) {
765 // TODO(soon): Implement memory tracking
766 }
767 
768 private:
769 std::list<Promise<void>> pendingStack;
770};
771 
772// Provides the base implementation of JSG_ASYNC_ITERATOR types. See the documentation
773// for JSG_ASYNC_ITERATOR for details.
774//
775// Objects that use AsyncIteratorBase will be usable with the for await syntax in
776// JavaScript, e.g.:
777//
778// const obj = new MyNewAsyncIterableObject();
779// for await (const chunk of obj) {
780// console.log(chunk);
781// }
782//
783// The for await syntax is just sugar for using an async generator object.
784// All async iterable objects will have a method that will return an instance
785// of the AsyncIteratorBase. This is typically a method named values() or
786// entries().
787//
788// const obj = new MyNewAsyncIterableObject();
789// const gen = obj.values();
790//
791// The async generator object has two methods: next() and return()
792// next() is called to fetch the next item from the iterator, and should
793// be called until there is no more data to return. The return() method
794// is called to signal early termination of the iterator. Both methods
795// return a JavaScript promise that resolves to an IteratorResult object
796// (an ordinary JavaScript object with a done and value property).
797//
798// const result = await gen.next();
799// console.log(result.done); // true or false
800// console.log(result.value); // the value yielded in this iteration.
801//
802// const result = await gen.return("foo");
803// console.log(result.done); // true
804// console.log(result.value); // "foo" ... whatever value was passed in.
805//
806// It is important for the generator to queue and properly sequence concurrent
807// next() and return() calls. Specifically, the following pattern should read
808// five elements off the iterator before terminating it early:
809//
810// await Promise.all([
811// gen.next(), // must resolve to the first item
812// gen.next(), // must resolve to the second item
813// gen.next(), // must resolve to the third item
814// gen.next(), // must resolve to the fourth item
815// gen.next(), // must resolve to the fifth item
816// gen.return("boom"), // must not be processed until after the fifth next()
817// ]);
818//
819// Once return() is called, all subsequent next() and return() calls must just
820// return an immediately resolved promise indicating that the iterator is done.
821template <typename SelfType, typename Type, typename State>
822class AsyncIteratorBase: public Object {
823 public:
824 using NextSignature = Promise<kj::Maybe<Type>>(Lock&, State&);
825 using ReturnSignature = Promise<void>(Lock&, State&, Optional<Type>&);
826 using Next = AsyncIteratorImpl::Next<Type>;
827 using Finished = AsyncIteratorImpl::Finished;
828 
829 explicit AsyncIteratorBase(State state): state(InnerState{.state = kj::mv(state)}) {}
830 
831 v8::Local<v8::Object> self(const v8::FunctionCallbackInfo<v8::Value>& info) {
832 return info.This();
833 }
834 
835 void visitForGc(GcVisitor& visitor) {
836 KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {
837 if constexpr (hasPublicVisitForGc<State>()) {
838 visitor.visit(inner.state);
839 }
840 visitor.visit(inner.impl);
841 }
842 }
843 
844 JSG_MEMORY_INFO(AsyncIteratorBase) {
845 KJ_SWITCH_ONEOF(state) {
846 KJ_CASE_ONEOF(fin, Finished) {
847 tracker.trackFieldWithSize("state", sizeof(Finished));
848 }
849 KJ_CASE_ONEOF(state, InnerState) {
850 tracker.trackField("state", state);
851 }
852 }
853 }
854 
855 private:
856 struct InnerState {
857 State state;
858 AsyncIteratorImpl impl;
859 
860 JSG_MEMORY_INFO(InnerState) {
861 if constexpr (MemoryRetainer<State>) {
862 tracker.trackField("state", state);
863 } else {
864 tracker.trackFieldWithSize("state", sizeof(State));
865 }
866 tracker.trackField("impl", impl);
867 }
868 };
869 
870 kj::OneOf<Finished, InnerState> state;
871 
872 void pushCurrent(Lock& js, Promise<void> promise) {
873 auto& inner = state.template get<InnerState>();
874 auto result = promise.whenResolved(js).then(js, [this, self = JSG_THIS](Lock& js) {
875 // If state is Finished, then there's nothing we need to do here.
876 KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {
877 inner.impl.popCurrent();
878 }
879 return js.resolvedPromise();
880 }, [this, self = JSG_THIS](Lock& js, Value value) {
881 KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {
882 inner.impl.popCurrent();
883 }
884 return js.rejectedPromise<void>(kj::mv(value));
885 });
886 // The error is already propagated through the promise returned by nextImpl/returnImpl.
887 // Mark as handled so the internally-held promise does not trigger unhandledrejection.
888 result.markAsHandled(js);
889 inner.impl.pushCurrent(kj::mv(result));
890 }
891 
892 Promise<Next> nextImpl(Lock& js, NextSignature nextFunc) {
893 KJ_SWITCH_ONEOF(state) {
894 KJ_CASE_ONEOF(finished, Finished) {
895 return js.resolvedPromise(Next{.done = true});
896 }
897 KJ_CASE_ONEOF(inner, InnerState) {
898 // If return_() has already been called on the async iterator, we just return an immediately
899 // resolved promise indicating done, regardless of whether there are still other outstanding
900 // next promises or not.
901 if (inner.impl.returning) {
902 return js.resolvedPromise(Next{.done = true});
903 }
904 
905 auto callNext = [this, self = JSG_THIS, nextFunc = kj::mv(nextFunc)](Lock& js) mutable {
906 KJ_SWITCH_ONEOF(state) {
907 KJ_CASE_ONEOF(finished, Finished) {
908 return js.resolvedPromise(Next{.done = true});
909 }
910 KJ_CASE_ONEOF(inner, InnerState) {
911 auto promise = nextFunc(js, inner.state);
912 pushCurrent(js, promise.whenResolved(js));
913 return promise.then(
914 js, [this, self = kj::mv(self)](Lock& js, kj::Maybe<Type> maybeResult) mutable {
915 KJ_IF_SOME(result, maybeResult) {
916 return js.resolvedPromise(Next{.done = false, .value = kj::mv(result)});
917 } else {
918 state.template init<Finished>();
919 return js.resolvedPromise(Next{.done = true});
920 }
921 });
922 }
923 }
924 KJ_UNREACHABLE;
925 };
926 
927 KJ_IF_SOME(current, inner.impl.maybeCurrent()) {
928 auto promise = current.whenResolved(js).then(js, kj::mv(callNext));
929 pushCurrent(js, promise.whenResolved(js));
930 return kj::mv(promise);
931 }
932 
933 // Otherwise, call the next function and handle the result.
934 return callNext(js);
935 }
936 }
937 KJ_UNREACHABLE;
938 }
939 
940 Promise<Next> returnImpl(Lock& js, Optional<Type> value, ReturnSignature returnFunc) {
941 KJ_SWITCH_ONEOF(state) {
942 KJ_CASE_ONEOF(finished, Finished) {
943 return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});
944 }
945 KJ_CASE_ONEOF(inner, InnerState) {
946 
947 // When inner.returning is true, return_() has already been called on the iterator.
948 // Any further calls to either next() or return_() will result in immediately resolved
949 // promises indicating a done status being returned, regardless of any other promises
950 // that may be pending.
951 if (inner.impl.returning) {
952 return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});
953 }
954 
955 inner.impl.returning = true;
956 
957 auto callReturn = [this, self = JSG_THIS, value = kj::mv(value),
958 returnFunc = kj::mv(returnFunc)](Lock& js) mutable {
959 KJ_SWITCH_ONEOF(state) {
960 KJ_CASE_ONEOF(finished, Finished) {
961 return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});
962 }
963 KJ_CASE_ONEOF(inner, InnerState) {
964 return returnFunc(js, inner.state, value)
965 .then(js, [this, self = kj::mv(self), value = kj::mv(value)](Lock& js) mutable {
966 state.template init<Finished>();
967 return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});
968 });
969 }
970 }
971 KJ_UNREACHABLE;
972 };
973 
974 // If there is something on the pending stack, we are going to wait for that promise
975 // to resolve then call callReturn.
976 KJ_IF_SOME(current, inner.impl.maybeCurrent()) {
977 return current.whenResolved(js).then(js, kj::mv(callReturn));
978 }
979 
980 // Otherwise, we call callReturn immediately.
981 return callReturn(js);
982 }
983 }
984 KJ_UNREACHABLE;
985 }
986 
987 friend SelfType;
988};
989 
990// The JSG_ITERATOR macro provides a mechanism for easily implementing JavaScript-style iterators
991// for JSG_RESOURCE_TYPES.
992//
993// Example usage:
994//
995// class MyApiType: public jsg::Object {
996// private:
997// struct IteratorState {
998// // The iterator's internal state.
999// // Implement visitForGc here if the state stores any visitable references.
1000// };
1001//
1002// static kj::Maybe<kj::String> nextFunction(jsg::Lock& js, IteratorState& state) {
1003// // Return nullptr to indicate we've reached the end of the iterator.
1004// // Otherwise, return the next iterator value.
1005// }
1006// public:
1007// JSG_ITERATOR(MyApiTypeIterator,
1008// entries,
1009// kj::String,
1010// IteratorState,
1011// nextFunction);
1012//
1013// JSG_RESOURCE_TYPE(MyApiType) {
1014// JSG_METHOD(entries);
1015// JSG_ITERABLE(entries);
1016// }
1017//
1018// jsg::Ref<MyApiTypeIterator> entries(jsg::Lock& js) {
1019// return js.alloc<MyApiTypeIterator>(IteratorState { /* any necessary state init */ });
1020// }
1021// };
1022//
1023// In this example, instances of MyApiType will support the JavaScript synchronous iterator
1024// pattern (e.g. for (const item of myApiType) {}).
1025//
1026// The actual iterator instance is defined by the type MyApiType::MyApiTypeIterator, which
1027// will use the IteratorState struct to store internal state and the nextFunction to yield
1028// the next value for the iterator.
1029//
1030// A member function named entries(Lock&) will be added to MyApiType that returns a
1031// jsg::Ref<MyApiTypeIterator>() instance. It will be necessary for uses to provide the
1032// implementation of the entries(Lock&) member function.
1033#define JSG_ITERATOR(Name, Label, Type, State, NextFunc) \
1034 class Name final: public jsg::IteratorBase<Name, Type, State> { \
1035 public: \
1036 using jsg::IteratorBase<Name, Type, State>::IteratorBase; \
1037 inline Next next(jsg::Lock& js) { \
1038 return nextImpl(js, NextFunc); \
1039 } \
1040 JSG_RESOURCE_TYPE(Name) { \
1041 JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype); \
1042 JSG_METHOD(next); \
1043 JSG_ITERABLE(self); \
1044 } \
1045 }; \
1046 jsg::Ref<Name> Label(jsg::Lock&);
1047 
1048// Like JSG_ITERATOR but don't declare the method name automatically.
1049//
1050// TODO(cleanup): Change all JSG_ITERATOR usages to this. It's confusing for the macro to declare
1051// the method.
1052#define JSG_ITERATOR_TYPE(Name, Type, State, NextFunc) \
1053 class Name final: public jsg::IteratorBase<Name, Type, State> { \
1054 public: \
1055 using jsg::IteratorBase<Name, Type, State>::IteratorBase; \
1056 inline Next next(jsg::Lock& js) { \
1057 return nextImpl(js, NextFunc); \
1058 } \
1059 JSG_RESOURCE_TYPE(Name) { \
1060 JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype); \
1061 JSG_METHOD(next); \
1062 JSG_ITERABLE(self); \
1063 } \
1064 };
1065 
1066#define JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc) \
1067 class Name final: public jsg::AsyncIteratorBase<Name, Type, State> { \
1068 public: \
1069 using jsg::AsyncIteratorBase<Name, Type, State>::AsyncIteratorBase; \
1070 inline jsg::Promise<Next> next(jsg::Lock& js) { \
1071 return nextImpl(js, NextFunc); \
1072 } \
1073 inline jsg::Promise<Next> return_(jsg::Lock& js, jsg::Optional<Type> value) { \
1074 return returnImpl(js, kj::mv(value), ReturnFunc); \
1075 } \
1076 JSG_RESOURCE_TYPE(Name) { \
1077 JSG_INHERIT_INTRINSIC(v8::kAsyncIteratorPrototype); \
1078 JSG_METHOD(next); \
1079 JSG_METHOD_NAMED(return, return_); \
1080 JSG_ASYNC_ITERABLE(self); \
1081 } \
1082 };
1083 
1084// The JSG_ASYNC_ITERATOR and JSG_ASYNC_ITERATOR_WITH_OPTIONS macros provide a mechanism for
1085// easily implementing JavaScript-style asynchronous iterators for JSG_RESOURCE_TYPES.
1086//
1087// Example usage:
1088//
1089// class MyApiType: public jsg::Object {
1090// private:
1091// struct IteratorState {
1092// // The iterator's internal state.
1093// // Implement visitForGc here if the state stores any visitable references.
1094// };
1095//
1096// static jsg::Promise<kj::Maybe<kj::String>> nextFunction(
1097// jsg::Lock& js,
1098// IteratorState& state) {
1099// // Called to asynchronous get the next item for the iterator.
1100// // Return nullptr to indicate we've reached the end of the iterator.
1101// // Otherwise, return the next iterator value.
1102// }
1103//
1104// static jsg::Promise<void> returnFunction(
1105// jsg::Lock& js,
1106// IteratorState& state,
1107// jsg::Optional<jsg::Value> value) {
1108// // Called when the iterator is abruptly terminated or when the
1109// // iterator generator's return() method is called. On success,
1110// // an immediately resolved promise should be returned.
1111// }
1112//
1113// public:
1114// JSG_ASYNC_ITERATOR(MyApiTypeIterator,
1115// entries,
1116// kj::String,
1117// IteratorState,
1118// nextFunction,
1119// returnFunction);
1120//
1121// JSG_RESOURCE_TYPE(MyApiType) {
1122// JSG_METHOD(entries);
1123// JSG_ASYNC_ITERABLE(entries);
1124// }
1125//
1126// jsg::Ref<MyApiTypeIterator> entries(jsg::Lock& js) {
1127// return js.alloc<MyApiTypeIterator>(IteratorState { /* any necessary state init */ });
1128// }
1129// };
1130//
1131// In this example, instances of MyApiType will support the JavaScript asynchronous iterator
1132// pattern (e.g. for await (const item of myApiType) {}).
1133//
1134// The actual iterator instance is defined by the type MyApiType::MyApiTypeIterator, which
1135// will use the IteratorState struct to store internal state and the nextFunction to yield
1136// the next value for the iterator.
1137//
1138// A member function named entries(Lock&) will be added to MyApiType that returns a
1139// jsg::Ref<MyApiTypeIterator>() instance. It will be necessary for uses to provide the
1140// implementation of the entries(Lock&) member function.
1141#define JSG_ASYNC_ITERATOR(Name, Label, Type, State, NextFunc, ReturnFunc) \
1142 JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc) \
1143 jsg::Ref<Name> Label(jsg::Lock&);
1144 
1145#define JSG_ASYNC_ITERATOR_WITH_OPTIONS(Name, Label, Type, State, NextFunc, ReturnFunc, Options) \
1146 JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc) \
1147 jsg::Ref<Name> Label(jsg::Lock&, jsg::Optional<Options>);
1148 
1149} // namespace workerd::jsg