Coverage Report

Created: 2025-11-02 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/TemporaryChange.h>
8
#include <LibJS/Bytecode/Generator.h>
9
#include <LibJS/Bytecode/Interpreter.h>
10
#include <LibJS/Runtime/GeneratorObject.h>
11
#include <LibJS/Runtime/GeneratorPrototype.h>
12
#include <LibJS/Runtime/GlobalObject.h>
13
#include <LibJS/Runtime/Iterator.h>
14
15
namespace JS {
16
17
JS_DEFINE_ALLOCATOR(GeneratorObject);
18
19
ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
20
0
{
21
0
    auto& vm = realm.vm();
22
    // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
23
0
    Value generating_function_prototype;
24
0
    if (generating_function->kind() == FunctionKind::Async) {
25
        // We implement async functions by transforming them to generator function in the bytecode
26
        // interpreter. However an async function does not have a prototype and should not be
27
        // changed thus we hardcode the prototype.
28
0
        generating_function_prototype = realm.intrinsics().generator_prototype();
29
0
    } else {
30
0
        generating_function_prototype = TRY(generating_function->get(vm.names.prototype));
31
0
    }
32
0
    auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
33
0
    auto object = realm.heap().allocate<GeneratorObject>(realm, realm, generating_function_prototype_object, move(execution_context));
34
0
    object->m_generating_function = generating_function;
35
0
    object->m_previous_value = initial_value;
36
0
    return object;
37
0
}
38
39
GeneratorObject::GeneratorObject(Realm&, Object& prototype, NonnullOwnPtr<ExecutionContext> context, Optional<StringView> generator_brand)
40
0
    : Object(ConstructWithPrototypeTag::Tag, prototype)
41
0
    , m_execution_context(move(context))
42
0
    , m_generator_brand(move(generator_brand))
43
0
{
44
0
}
45
46
void GeneratorObject::visit_edges(Cell::Visitor& visitor)
47
0
{
48
0
    Base::visit_edges(visitor);
49
0
    visitor.visit(m_generating_function);
50
0
    visitor.visit(m_previous_value);
51
0
    m_execution_context->visit_edges(visitor);
52
0
}
53
54
// 27.5.3.2 GeneratorValidate ( generator, generatorBrand ), https://tc39.es/ecma262/#sec-generatorvalidate
55
ThrowCompletionOr<GeneratorObject::GeneratorState> GeneratorObject::validate(VM& vm, Optional<StringView> const& generator_brand)
56
0
{
57
    // 1. Perform ? RequireInternalSlot(generator, [[GeneratorState]]).
58
    // 2. Perform ? RequireInternalSlot(generator, [[GeneratorBrand]]).
59
    // NOTE: Already done by the caller of resume or resume_abrupt, as they wouldn't have a GeneratorObject otherwise.
60
61
    // 3. If generator.[[GeneratorBrand]] is not the same value as generatorBrand, throw a TypeError exception.
62
0
    if (m_generator_brand != generator_brand)
63
0
        return vm.throw_completion<TypeError>(ErrorType::GeneratorBrandMismatch, m_generator_brand.value_or("<empty>"sv), generator_brand.value_or("<empty>"sv));
64
65
    // 4. Assert: generator also has a [[GeneratorContext]] internal slot.
66
    // NOTE: Done by already being a GeneratorObject.
67
68
    // 5. Let state be generator.[[GeneratorState]].
69
0
    auto state = m_generator_state;
70
71
    // 6. If state is executing, throw a TypeError exception.
72
0
    if (state == GeneratorState::Executing)
73
0
        return vm.throw_completion<TypeError>(ErrorType::GeneratorAlreadyExecuting);
74
75
    // 7. Return state.
76
0
    return state;
77
0
}
78
79
ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& completion)
80
0
{
81
    // Loosely based on step 4 of https://tc39.es/ecma262/#sec-generatorstart mixed with https://tc39.es/ecma262/#sec-generatoryield at the end.
82
83
0
    VERIFY(completion.value().has_value());
84
85
0
    auto generated_value = [](Value value) -> Value {
86
0
        if (value.is_object())
87
0
            return value.as_object().get_without_side_effects("result");
88
0
        return value.is_empty() ? js_undefined() : value;
89
0
    };
90
91
0
    auto generated_continuation = [&](Value value) -> Optional<size_t> {
92
0
        if (value.is_object()) {
93
0
            auto number_value = value.as_object().get_without_side_effects("continuation");
94
0
            if (number_value.is_null())
95
0
                return {};
96
0
            return static_cast<u64>(number_value.as_double());
97
0
        }
98
0
        return {};
99
0
    };
100
101
0
    auto& realm = *vm.current_realm();
102
0
    auto completion_object = Object::create(realm, nullptr);
103
0
    completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes);
104
0
    completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes);
105
106
0
    auto& bytecode_interpreter = vm.bytecode_interpreter();
107
108
0
    auto const next_block = generated_continuation(m_previous_value);
109
110
    // We should never enter `execute` again after the generator is complete.
111
0
    VERIFY(next_block.has_value());
112
113
0
    auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block, completion_object);
114
115
0
    vm.pop_execution_context();
116
117
0
    auto result_value = move(next_result.value);
118
0
    if (result_value.is_throw_completion()) {
119
        // Uncaught exceptions disable the generator.
120
0
        m_generator_state = GeneratorState::Completed;
121
0
        return result_value;
122
0
    }
123
0
    m_previous_value = result_value.release_value();
124
0
    bool done = !generated_continuation(m_previous_value).has_value();
125
126
0
    m_generator_state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield;
127
0
    return create_iterator_result_object(vm, generated_value(m_previous_value), done);
128
0
}
129
130
// 27.5.3.3 GeneratorResume ( generator, value, generatorBrand ), https://tc39.es/ecma262/#sec-generatorresume
131
ThrowCompletionOr<Value> GeneratorObject::resume(VM& vm, Value value, Optional<StringView> const& generator_brand)
132
0
{
133
    // 1. Let state be ? GeneratorValidate(generator, generatorBrand).
134
0
    auto state = TRY(validate(vm, generator_brand));
135
136
    // 2. If state is completed, return CreateIterResultObject(undefined, true).
137
0
    if (state == GeneratorState::Completed)
138
0
        return create_iterator_result_object(vm, js_undefined(), true);
139
140
    // 3. Assert: state is either suspendedStart or suspendedYield.
141
0
    VERIFY(state == GeneratorState::SuspendedStart || state == GeneratorState::SuspendedYield);
142
143
    // 4. Let genContext be generator.[[GeneratorContext]].
144
0
    auto& generator_context = m_execution_context;
145
146
    // 5. Let methodContext be the running execution context.
147
0
    auto const& method_context = vm.running_execution_context();
148
149
    // FIXME: 6. Suspend methodContext.
150
151
    // 8. Push genContext onto the execution context stack; genContext is now the running execution context.
152
    // NOTE: This is done out of order as to not permanently disable the generator if push_execution_context throws,
153
    //       as `resume` will immediately throw when [[GeneratorState]] is "executing", never allowing the state to change.
154
0
    TRY(vm.push_execution_context(*generator_context, {}));
155
156
    // 7. Set generator.[[GeneratorState]] to executing.
157
0
    m_generator_state = GeneratorState::Executing;
158
159
    // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
160
0
    auto result = execute(vm, normal_completion(value));
161
162
    // 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
163
0
    VERIFY(&vm.running_execution_context() == &method_context);
164
165
    // 11. Return ? result.
166
0
    return result;
167
0
}
168
169
// 27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ), https://tc39.es/ecma262/#sec-generatorresumeabrupt
170
ThrowCompletionOr<Value> GeneratorObject::resume_abrupt(JS::VM& vm, JS::Completion abrupt_completion, Optional<StringView> const& generator_brand)
171
0
{
172
    // Not part of the spec, but the spec assumes abruptCompletion.[[Value]] is not empty.
173
0
    VERIFY(abrupt_completion.value().has_value());
174
175
    // 1. Let state be ? GeneratorValidate(generator, generatorBrand).
176
0
    auto state = TRY(validate(vm, generator_brand));
177
178
    // 2. If state is suspendedStart, then
179
0
    if (state == GeneratorState::SuspendedStart) {
180
        // a. Set generator.[[GeneratorState]] to completed.
181
0
        m_generator_state = GeneratorState::Completed;
182
183
        // b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point.
184
        // We don't currently discard anything.
185
186
        // c. Set state to completed.
187
0
        state = GeneratorState::Completed;
188
0
    }
189
190
    // 3. If state is completed, then
191
0
    if (state == GeneratorState::Completed) {
192
        // a. If abruptCompletion.[[Type]] is return, then
193
0
        if (abrupt_completion.type() == Completion::Type::Return) {
194
            // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true).
195
0
            return create_iterator_result_object(vm, abrupt_completion.value().value(), true);
196
0
        }
197
198
        // b. Return ? abruptCompletion.
199
0
        return abrupt_completion;
200
0
    }
201
202
    // 4. Assert: state is suspendedYield.
203
0
    VERIFY(state == GeneratorState::SuspendedYield);
204
205
    // 5. Let genContext be generator.[[GeneratorContext]].
206
0
    auto& generator_context = m_execution_context;
207
208
    // 6. Let methodContext be the running execution context.
209
0
    auto const& method_context = vm.running_execution_context();
210
211
    // FIXME: 7. Suspend methodContext.
212
213
    // 9. Push genContext onto the execution context stack; genContext is now the running execution context.
214
    // NOTE: This is done out of order as to not permanently disable the generator if push_execution_context throws,
215
    //       as `resume_abrupt` will immediately throw when [[GeneratorState]] is "executing", never allowing the state to change.
216
0
    TRY(vm.push_execution_context(*generator_context, {}));
217
218
    // 8. Set generator.[[GeneratorState]] to executing.
219
0
    m_generator_state = GeneratorState::Executing;
220
221
    // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
222
0
    auto result = execute(vm, abrupt_completion);
223
224
    // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
225
0
    VERIFY(&vm.running_execution_context() == &method_context);
226
227
    // 12. Return ? result.
228
0
    return result;
229
0
}
230
231
}