LibJS: Fall back to intrinsic generator prototypes

Generator and async generator instances should use
OrdinaryCreateFromConstructor with %GeneratorPrototype% and
%AsyncGeneratorPrototype%. When `.prototype` was nullish, we created a
null-prototype object, and when it was a primitive, we boxed it with
ToObject.

Use GetPrototypeFromConstructor for the generating function and cover
fallbacks for nullish and primitive prototype values.
This commit is contained in:
Andreas Kling 2026-05-21 13:20:18 +02:00 committed by Andreas Kling
parent 77bf420654
commit 8fb6fa785a
4 changed files with 32 additions and 18 deletions

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/AsyncGenerator.h>
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
#include <LibJS/Runtime/AsyncGeneratorRequest.h>
@ -20,14 +21,11 @@ GC_DEFINE_ALLOCATOR(AsyncGenerator);
GC::Ref<AsyncGenerator> AsyncGenerator::create(Realm& realm, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
{
auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
auto generating_function_prototype = MUST(generating_function.visit([&vm](auto function) {
static Bytecode::StaticPropertyLookupCache cache;
return function->get(vm.names.prototype, cache);
// 2. Let _generator_ be ? OrdinaryCreateFromConstructor(_functionObject_, *"%AsyncGeneratorPrototype%"*,
// « [[AsyncGeneratorState]], [[AsyncGeneratorContext]], [[AsyncGeneratorQueue]], [[GeneratorBrand]] »).
auto* generating_function_prototype_object = MUST(generating_function.visit([&vm](auto function) -> ThrowCompletionOr<Object*> {
return get_prototype_from_constructor(vm, *function, &Intrinsics::async_generator_prototype);
}));
GC::Ptr<Object> generating_function_prototype_object = nullptr;
if (!generating_function_prototype.is_nullish())
generating_function_prototype_object = MUST(generating_function_prototype.to_object(vm));
auto generating_executable = generating_function.visit(
[](GC::Ref<ECMAScriptFunctionObject> function) -> GC::Ref<Bytecode::Executable> {

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/GeneratorPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
@ -18,31 +19,26 @@ GC_DEFINE_ALLOCATOR(GeneratorObject);
GC::Ref<GeneratorObject> GeneratorObject::create(Realm& realm, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
{
auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
Value generating_function_prototype;
auto kind = generating_function.visit(
[](auto function) {
return function->kind();
});
GC::Ptr<Object> generating_function_prototype_object = nullptr;
if (kind == FunctionKind::Async) {
// We implement async functions by transforming them to generator function in the bytecode
// interpreter. However an async function does not have a prototype and should not be
// changed thus we hardcode the prototype.
generating_function_prototype = realm.intrinsics().generator_prototype();
generating_function_prototype_object = realm.intrinsics().generator_prototype();
} else {
static Bytecode::StaticPropertyLookupCache cache;
generating_function_prototype = MUST(generating_function.visit([&vm](auto function) {
static Bytecode::StaticPropertyLookupCache cache;
return function->get(vm.names.prototype, cache);
// 1. Let _generator_ be ? OrdinaryCreateFromConstructor(_functionObject_, *"%GeneratorPrototype%"*,
// « [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]] »).
generating_function_prototype_object = MUST(generating_function.visit([&vm](auto function) -> ThrowCompletionOr<Object*> {
return get_prototype_from_constructor(vm, *function, &Intrinsics::generator_prototype);
}));
}
GC::Ptr<Object> generating_function_prototype_object = nullptr;
if (!generating_function_prototype.is_nullish())
generating_function_prototype_object = MUST(generating_function_prototype.to_object(vm));
auto generating_executable = generating_function.visit(
[](GC::Ref<ECMAScriptFunctionObject> function) -> GC::Ref<Bytecode::Executable> {
return function->bytecode_executable().as_nonnull();

View file

@ -56,3 +56,13 @@ describe("parsing classes with generator methods", () => {
expect(`class Foo { async *constructor() { yield 42; } }`).not.toEval();
});
});
test("async generator instances use the intrinsic prototype when function prototype is not an object", () => {
async function* generator() {}
const AsyncGeneratorPrototype = Object.getPrototypeOf(generator.prototype);
for (const prototype of [undefined, null, false, "", Symbol(), 1]) {
generator.prototype = prototype;
expect(Object.getPrototypeOf(generator())).toBe(AsyncGeneratorPrototype);
}
});

View file

@ -63,3 +63,13 @@ test("function expression names equal to 'yield'", () => {
expect(`function *foo() { (function yield() {}); }`).toEval();
expect(`function *foo() { function yield() {} }`).not.toEval();
});
test("generator instances use the intrinsic prototype when function prototype is not an object", () => {
function* generator() {}
const GeneratorPrototype = Object.getPrototypeOf(generator).prototype;
for (const prototype of [undefined, null, false, "", Symbol(), 1]) {
generator.prototype = prototype;
expect(Object.getPrototypeOf(generator())).toBe(GeneratorPrototype);
}
});