/src/serenity/Userland/Libraries/LibJS/Runtime/VM.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org> |
4 | | * Copyright (c) 2021-2022, David Tuin <davidot@serenityos.org> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <AK/Array.h> |
10 | | #include <AK/Debug.h> |
11 | | #include <AK/LexicalPath.h> |
12 | | #include <AK/ScopeGuard.h> |
13 | | #include <AK/String.h> |
14 | | #include <AK/StringBuilder.h> |
15 | | #include <LibFileSystem/FileSystem.h> |
16 | | #include <LibJS/AST.h> |
17 | | #include <LibJS/Bytecode/Interpreter.h> |
18 | | #include <LibJS/Runtime/AbstractOperations.h> |
19 | | #include <LibJS/Runtime/Array.h> |
20 | | #include <LibJS/Runtime/ArrayBuffer.h> |
21 | | #include <LibJS/Runtime/BoundFunction.h> |
22 | | #include <LibJS/Runtime/Completion.h> |
23 | | #include <LibJS/Runtime/ECMAScriptFunctionObject.h> |
24 | | #include <LibJS/Runtime/Error.h> |
25 | | #include <LibJS/Runtime/FinalizationRegistry.h> |
26 | | #include <LibJS/Runtime/FunctionEnvironment.h> |
27 | | #include <LibJS/Runtime/Iterator.h> |
28 | | #include <LibJS/Runtime/NativeFunction.h> |
29 | | #include <LibJS/Runtime/PromiseCapability.h> |
30 | | #include <LibJS/Runtime/Reference.h> |
31 | | #include <LibJS/Runtime/Symbol.h> |
32 | | #include <LibJS/Runtime/VM.h> |
33 | | #include <LibJS/SourceTextModule.h> |
34 | | #include <LibJS/SyntheticModule.h> |
35 | | |
36 | | namespace JS { |
37 | | |
38 | | ErrorOr<NonnullRefPtr<VM>> VM::create(OwnPtr<CustomData> custom_data) |
39 | 0 | { |
40 | 0 | ErrorMessages error_messages {}; |
41 | 0 | error_messages[to_underlying(ErrorMessage::OutOfMemory)] = TRY(String::from_utf8(ErrorType::OutOfMemory.message())); |
42 | |
|
43 | 0 | auto vm = adopt_ref(*new VM(move(custom_data), move(error_messages))); |
44 | |
|
45 | 0 | WellKnownSymbols well_known_symbols { |
46 | 0 | #define __JS_ENUMERATE(SymbolName, snake_name) \ |
47 | 0 | Symbol::create(*vm, "Symbol." #SymbolName##_string, false), |
48 | 0 | JS_ENUMERATE_WELL_KNOWN_SYMBOLS |
49 | 0 | #undef __JS_ENUMERATE |
50 | 0 | }; |
51 | |
|
52 | 0 | vm->set_well_known_symbols(move(well_known_symbols)); |
53 | 0 | return vm; |
54 | 0 | } |
55 | | |
56 | | template<size_t... code_points> |
57 | | static constexpr auto make_single_ascii_character_strings(IndexSequence<code_points...>) |
58 | 0 | { |
59 | 0 | return AK::Array { (String::from_code_point(static_cast<u32>(code_points)))... }; |
60 | 0 | } |
61 | | |
62 | | static constexpr auto single_ascii_character_strings = make_single_ascii_character_strings(MakeIndexSequence<128>()); |
63 | | |
64 | | VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages) |
65 | 0 | : m_heap(*this) |
66 | 0 | , m_error_messages(move(error_messages)) |
67 | 0 | , m_custom_data(move(custom_data)) |
68 | 0 | { |
69 | 0 | m_bytecode_interpreter = make<Bytecode::Interpreter>(*this); |
70 | |
|
71 | 0 | m_empty_string = m_heap.allocate_without_realm<PrimitiveString>(String {}); |
72 | |
|
73 | 0 | typeof_strings = { |
74 | 0 | .number = m_heap.allocate_without_realm<PrimitiveString>("number"), |
75 | 0 | .undefined = m_heap.allocate_without_realm<PrimitiveString>("undefined"), |
76 | 0 | .object = m_heap.allocate_without_realm<PrimitiveString>("object"), |
77 | 0 | .string = m_heap.allocate_without_realm<PrimitiveString>("string"), |
78 | 0 | .symbol = m_heap.allocate_without_realm<PrimitiveString>("symbol"), |
79 | 0 | .boolean = m_heap.allocate_without_realm<PrimitiveString>("boolean"), |
80 | 0 | .bigint = m_heap.allocate_without_realm<PrimitiveString>("bigint"), |
81 | 0 | .function = m_heap.allocate_without_realm<PrimitiveString>("function"), |
82 | 0 | }; |
83 | |
|
84 | 0 | for (size_t i = 0; i < single_ascii_character_strings.size(); ++i) |
85 | 0 | m_single_ascii_character_strings[i] = m_heap.allocate_without_realm<PrimitiveString>(single_ascii_character_strings[i]); |
86 | | |
87 | | // Default hook implementations. These can be overridden by the host, for example, LibWeb overrides the default hooks to place promise jobs on the microtask queue. |
88 | 0 | host_promise_rejection_tracker = [this](Promise& promise, Promise::RejectionOperation operation) { |
89 | 0 | promise_rejection_tracker(promise, operation); |
90 | 0 | }; |
91 | |
|
92 | 0 | host_call_job_callback = [this](JobCallback& job_callback, Value this_value, ReadonlySpan<Value> arguments) { |
93 | 0 | return call_job_callback(*this, job_callback, this_value, arguments); |
94 | 0 | }; |
95 | |
|
96 | 0 | host_enqueue_finalization_registry_cleanup_job = [this](FinalizationRegistry& finalization_registry) { |
97 | 0 | enqueue_finalization_registry_cleanup_job(finalization_registry); |
98 | 0 | }; |
99 | |
|
100 | 0 | host_enqueue_promise_job = [this](NonnullGCPtr<HeapFunction<ThrowCompletionOr<Value>()>> job, Realm* realm) { |
101 | 0 | enqueue_promise_job(job, realm); |
102 | 0 | }; |
103 | |
|
104 | 0 | host_make_job_callback = [](FunctionObject& function_object) { |
105 | 0 | return make_job_callback(function_object); |
106 | 0 | }; |
107 | |
|
108 | 0 | host_load_imported_module = [this](ImportedModuleReferrer referrer, ModuleRequest const& module_request, GCPtr<GraphLoadingState::HostDefined> load_state, ImportedModulePayload payload) -> void { |
109 | 0 | return load_imported_module(referrer, module_request, load_state, move(payload)); |
110 | 0 | }; |
111 | |
|
112 | 0 | host_get_import_meta_properties = [&](SourceTextModule const&) -> HashMap<PropertyKey, Value> { |
113 | 0 | return {}; |
114 | 0 | }; |
115 | |
|
116 | 0 | host_finalize_import_meta = [&](Object*, SourceTextModule const&) { |
117 | 0 | }; |
118 | |
|
119 | 0 | host_get_supported_import_attributes = [&] { |
120 | 0 | return Vector<ByteString> { "type" }; |
121 | 0 | }; |
122 | | |
123 | | // 19.2.1.2 HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, direct ), https://tc39.es/ecma262/#sec-hostensurecancompilestrings |
124 | 0 | host_ensure_can_compile_strings = [](Realm&, ReadonlySpan<String>, StringView, EvalMode) -> ThrowCompletionOr<void> { |
125 | | // The host-defined abstract operation HostEnsureCanCompileStrings takes arguments calleeRealm (a Realm Record), |
126 | | // parameterStrings (a List of Strings), bodyString (a String), and direct (a Boolean) and returns either a normal |
127 | | // completion containing unused or a throw completion. |
128 | | // |
129 | | // It allows host environments to block certain ECMAScript functions which allow developers to compile strings into ECMAScript code. |
130 | | // An implementation of HostEnsureCanCompileStrings must conform to the following requirements: |
131 | | // - If the returned Completion Record is a normal completion, it must be a normal completion containing unused. |
132 | | // The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused). |
133 | 0 | return {}; |
134 | 0 | }; |
135 | |
|
136 | 0 | host_ensure_can_add_private_element = [](Object&) -> ThrowCompletionOr<void> { |
137 | | // The host-defined abstract operation HostEnsureCanAddPrivateElement takes argument O (an Object) |
138 | | // and returns either a normal completion containing unused or a throw completion. |
139 | | // It allows host environments to prevent the addition of private elements to particular host-defined exotic objects. |
140 | | // An implementation of HostEnsureCanAddPrivateElement must conform to the following requirements: |
141 | | // - If O is not a host-defined exotic object, this abstract operation must return NormalCompletion(unused) and perform no other steps. |
142 | | // - Any two calls of this abstract operation with the same argument must return the same kind of Completion Record. |
143 | | // The default implementation of HostEnsureCanAddPrivateElement is to return NormalCompletion(unused). |
144 | 0 | return {}; |
145 | | |
146 | | // This abstract operation is only invoked by ECMAScript hosts that are web browsers. |
147 | | // NOTE: Since LibJS has no way of knowing whether the current environment is a browser we always |
148 | | // call HostEnsureCanAddPrivateElement when needed. |
149 | 0 | }; |
150 | | |
151 | | // 25.1.3.8 HostResizeArrayBuffer ( buffer, newByteLength ), https://tc39.es/ecma262/#sec-hostresizearraybuffer |
152 | 0 | host_resize_array_buffer = [this](ArrayBuffer& buffer, size_t new_byte_length) -> ThrowCompletionOr<HandledByHost> { |
153 | | // The host-defined abstract operation HostResizeArrayBuffer takes arguments buffer (an ArrayBuffer) and |
154 | | // newByteLength (a non-negative integer) and returns either a normal completion containing either handled or |
155 | | // unhandled, or a throw completion. It gives the host an opportunity to perform implementation-defined resizing |
156 | | // of buffer. If the host chooses not to handle resizing of buffer, it may return unhandled for the default behaviour. |
157 | | |
158 | | // The implementation of HostResizeArrayBuffer must conform to the following requirements: |
159 | | // - The abstract operation does not detach buffer. |
160 | | // - If the abstract operation completes normally with handled, buffer.[[ArrayBufferByteLength]] is newByteLength. |
161 | | |
162 | | // The default implementation of HostResizeArrayBuffer is to return NormalCompletion(unhandled). |
163 | |
|
164 | 0 | if (auto result = buffer.buffer().try_resize(new_byte_length, ByteBuffer::ZeroFillNewElements::Yes); result.is_error()) |
165 | 0 | return throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, new_byte_length); |
166 | | |
167 | 0 | return HandledByHost::Handled; |
168 | 0 | }; |
169 | | |
170 | | // 3.6.1 HostInitializeShadowRealm ( realm ), https://tc39.es/proposal-shadowrealm/#sec-hostinitializeshadowrealm |
171 | | // https://github.com/tc39/proposal-shadowrealm/pull/410 |
172 | 0 | host_initialize_shadow_realm = [](Realm&, NonnullOwnPtr<ExecutionContext>, ShadowRealm&) -> ThrowCompletionOr<void> { |
173 | | // The host-defined abstract operation HostInitializeShadowRealm takes argument realm (a Realm Record) and returns |
174 | | // either a normal completion containing unused or a throw completion. It is used to inform the host of any newly |
175 | | // created realms from the ShadowRealm constructor. The idea of this hook is to initialize host data structures |
176 | | // related to the ShadowRealm, e.g., for module loading. |
177 | | // |
178 | | // The host may use this hook to add properties to the ShadowRealm's global object. Those properties must be configurable. |
179 | 0 | return {}; |
180 | 0 | }; |
181 | | |
182 | | // AD-HOC: Inform the host that we received a date string we were unable to parse. |
183 | 0 | host_unrecognized_date_string = [](StringView) { |
184 | 0 | }; |
185 | 0 | } |
186 | | |
187 | 0 | VM::~VM() = default; |
188 | | |
189 | | String const& VM::error_message(ErrorMessage type) const |
190 | 0 | { |
191 | 0 | VERIFY(type < ErrorMessage::__Count); |
192 | | |
193 | 0 | auto const& message = m_error_messages[to_underlying(type)]; |
194 | 0 | VERIFY(!message.is_empty()); |
195 | | |
196 | 0 | return message; |
197 | 0 | } |
198 | | |
199 | | Bytecode::Interpreter& VM::bytecode_interpreter() |
200 | 0 | { |
201 | 0 | return *m_bytecode_interpreter; |
202 | 0 | } |
203 | | |
204 | | struct ExecutionContextRootsCollector : public Cell::Visitor { |
205 | | virtual void visit_impl(Cell& cell) override |
206 | 0 | { |
207 | 0 | roots.set(&cell); |
208 | 0 | } |
209 | | |
210 | | virtual void visit_possible_values(ReadonlyBytes) override |
211 | 0 | { |
212 | 0 | VERIFY_NOT_REACHED(); |
213 | 0 | } |
214 | | |
215 | | HashTable<GCPtr<Cell>> roots; |
216 | | }; |
217 | | |
218 | | void VM::gather_roots(HashMap<Cell*, HeapRoot>& roots) |
219 | 0 | { |
220 | 0 | roots.set(m_empty_string, HeapRoot { .type = HeapRoot::Type::VM }); |
221 | 0 | for (auto string : m_single_ascii_character_strings) |
222 | 0 | roots.set(string, HeapRoot { .type = HeapRoot::Type::VM }); |
223 | |
|
224 | 0 | roots.set(typeof_strings.number, HeapRoot { .type = HeapRoot::Type::VM }); |
225 | 0 | roots.set(typeof_strings.undefined, HeapRoot { .type = HeapRoot::Type::VM }); |
226 | 0 | roots.set(typeof_strings.object, HeapRoot { .type = HeapRoot::Type::VM }); |
227 | 0 | roots.set(typeof_strings.string, HeapRoot { .type = HeapRoot::Type::VM }); |
228 | 0 | roots.set(typeof_strings.symbol, HeapRoot { .type = HeapRoot::Type::VM }); |
229 | 0 | roots.set(typeof_strings.boolean, HeapRoot { .type = HeapRoot::Type::VM }); |
230 | 0 | roots.set(typeof_strings.bigint, HeapRoot { .type = HeapRoot::Type::VM }); |
231 | 0 | roots.set(typeof_strings.function, HeapRoot { .type = HeapRoot::Type::VM }); |
232 | |
|
233 | 0 | #define __JS_ENUMERATE(SymbolName, snake_name) \ |
234 | 0 | roots.set(m_well_known_symbols.snake_name, HeapRoot { .type = HeapRoot::Type::VM }); |
235 | 0 | JS_ENUMERATE_WELL_KNOWN_SYMBOLS |
236 | 0 | #undef __JS_ENUMERATE |
237 | |
|
238 | 0 | for (auto& symbol : m_global_symbol_registry) |
239 | 0 | roots.set(symbol.value, HeapRoot { .type = HeapRoot::Type::VM }); |
240 | |
|
241 | 0 | for (auto finalization_registry : m_finalization_registry_cleanup_jobs) |
242 | 0 | roots.set(finalization_registry, HeapRoot { .type = HeapRoot::Type::VM }); |
243 | |
|
244 | 0 | auto gather_roots_from_execution_context_stack = [&roots](Vector<ExecutionContext*> const& stack) { |
245 | 0 | for (auto const& execution_context : stack) { |
246 | 0 | ExecutionContextRootsCollector visitor; |
247 | 0 | execution_context->visit_edges(visitor); |
248 | 0 | for (auto cell : visitor.roots) |
249 | 0 | roots.set(cell, HeapRoot { .type = HeapRoot::Type::VM }); |
250 | 0 | } |
251 | 0 | }; |
252 | 0 | gather_roots_from_execution_context_stack(m_execution_context_stack); |
253 | 0 | for (auto& saved_stack : m_saved_execution_context_stacks) |
254 | 0 | gather_roots_from_execution_context_stack(saved_stack); |
255 | |
|
256 | 0 | for (auto& job : m_promise_jobs) |
257 | 0 | roots.set(job, HeapRoot { .type = HeapRoot::Type::VM }); |
258 | 0 | } |
259 | | |
260 | | // 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference |
261 | | ThrowCompletionOr<Reference> VM::get_identifier_reference(Environment* environment, DeprecatedFlyString name, bool strict, size_t hops) |
262 | 0 | { |
263 | | // 1. If env is the value null, then |
264 | 0 | if (!environment) { |
265 | | // a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }. |
266 | 0 | return Reference { Reference::BaseType::Unresolvable, move(name), strict }; |
267 | 0 | } |
268 | | |
269 | | // 2. Let exists be ? env.HasBinding(name). |
270 | 0 | Optional<size_t> index; |
271 | 0 | auto exists = TRY(environment->has_binding(name, &index)); |
272 | | |
273 | | // Note: This is an optimization for looking up the same reference. |
274 | 0 | Optional<EnvironmentCoordinate> environment_coordinate; |
275 | 0 | if (index.has_value()) { |
276 | 0 | VERIFY(hops <= NumericLimits<u32>::max()); |
277 | 0 | VERIFY(index.value() <= NumericLimits<u32>::max()); |
278 | 0 | environment_coordinate = EnvironmentCoordinate { .hops = static_cast<u32>(hops), .index = static_cast<u32>(index.value()) }; |
279 | 0 | } |
280 | | |
281 | | // 3. If exists is true, then |
282 | 0 | if (exists) { |
283 | | // a. Return the Reference Record { [[Base]]: env, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }. |
284 | 0 | return Reference { *environment, move(name), strict, environment_coordinate }; |
285 | 0 | } |
286 | | // 4. Else, |
287 | 0 | else { |
288 | | // a. Let outer be env.[[OuterEnv]]. |
289 | | // b. Return ? GetIdentifierReference(outer, name, strict). |
290 | 0 | return get_identifier_reference(environment->outer_environment(), move(name), strict, hops + 1); |
291 | 0 | } |
292 | 0 | } |
293 | | |
294 | | // 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding |
295 | | ThrowCompletionOr<Reference> VM::resolve_binding(DeprecatedFlyString const& name, Environment* environment) |
296 | 0 | { |
297 | | // 1. If env is not present or if env is undefined, then |
298 | 0 | if (!environment) { |
299 | | // a. Set env to the running execution context's LexicalEnvironment. |
300 | 0 | environment = running_execution_context().lexical_environment; |
301 | 0 | } |
302 | | |
303 | | // 2. Assert: env is an Environment Record. |
304 | 0 | VERIFY(environment); |
305 | | |
306 | | // 3. If the source text matched by the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false. |
307 | 0 | bool strict = in_strict_mode(); |
308 | | |
309 | | // 4. Return ? GetIdentifierReference(env, name, strict). |
310 | 0 | return get_identifier_reference(environment, name, strict); |
311 | | |
312 | | // NOTE: The spec says: |
313 | | // Note: The result of ResolveBinding is always a Reference Record whose [[ReferencedName]] field is name. |
314 | | // But this is not actually correct as GetIdentifierReference (or really the methods it calls) can throw. |
315 | 0 | } |
316 | | |
317 | | // 9.4.4 ResolveThisBinding ( ), https://tc39.es/ecma262/#sec-resolvethisbinding |
318 | | ThrowCompletionOr<Value> VM::resolve_this_binding() |
319 | 0 | { |
320 | 0 | auto& vm = *this; |
321 | | |
322 | | // 1. Let envRec be GetThisEnvironment(). |
323 | 0 | auto environment = get_this_environment(vm); |
324 | | |
325 | | // 2. Return ? envRec.GetThisBinding(). |
326 | 0 | return TRY(environment->get_this_binding(vm)); |
327 | 0 | } |
328 | | |
329 | | // 9.4.5 GetNewTarget ( ), https://tc39.es/ecma262/#sec-getnewtarget |
330 | | Value VM::get_new_target() |
331 | 0 | { |
332 | | // 1. Let envRec be GetThisEnvironment(). |
333 | 0 | auto env = get_this_environment(*this); |
334 | | |
335 | | // 2. Assert: envRec has a [[NewTarget]] field. |
336 | | // 3. Return envRec.[[NewTarget]]. |
337 | 0 | return verify_cast<FunctionEnvironment>(*env).new_target(); |
338 | 0 | } |
339 | | |
340 | | // 13.3.12.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-meta-properties-runtime-semantics-evaluation |
341 | | // ImportMeta branch only |
342 | | Object* VM::get_import_meta() |
343 | 0 | { |
344 | | // 1. Let module be GetActiveScriptOrModule(). |
345 | 0 | auto script_or_module = get_active_script_or_module(); |
346 | | |
347 | | // 2. Assert: module is a Source Text Module Record. |
348 | 0 | auto& module = verify_cast<SourceTextModule>(*script_or_module.get<NonnullGCPtr<Module>>()); |
349 | | |
350 | | // 3. Let importMeta be module.[[ImportMeta]]. |
351 | 0 | auto* import_meta = module.import_meta(); |
352 | | |
353 | | // 4. If importMeta is empty, then |
354 | 0 | if (import_meta == nullptr) { |
355 | | // a. Set importMeta to OrdinaryObjectCreate(null). |
356 | 0 | import_meta = Object::create(*current_realm(), nullptr); |
357 | | |
358 | | // b. Let importMetaValues be HostGetImportMetaProperties(module). |
359 | 0 | auto import_meta_values = host_get_import_meta_properties(module); |
360 | | |
361 | | // c. For each Record { [[Key]], [[Value]] } p of importMetaValues, do |
362 | 0 | for (auto& entry : import_meta_values) { |
363 | | // i. Perform ! CreateDataPropertyOrThrow(importMeta, p.[[Key]], p.[[Value]]). |
364 | 0 | MUST(import_meta->create_data_property_or_throw(entry.key, entry.value)); |
365 | 0 | } |
366 | | |
367 | | // d. Perform HostFinalizeImportMeta(importMeta, module). |
368 | 0 | host_finalize_import_meta(import_meta, module); |
369 | | |
370 | | // e. Set module.[[ImportMeta]] to importMeta. |
371 | 0 | module.set_import_meta({}, import_meta); |
372 | | |
373 | | // f. Return importMeta. |
374 | 0 | return import_meta; |
375 | 0 | } |
376 | | // 5. Else, |
377 | 0 | else { |
378 | | // a. Assert: Type(importMeta) is Object. |
379 | | // Note: This is always true by the type. |
380 | | |
381 | | // b. Return importMeta. |
382 | 0 | return import_meta; |
383 | 0 | } |
384 | 0 | } |
385 | | |
386 | | // 9.4.5 GetGlobalObject ( ), https://tc39.es/ecma262/#sec-getglobalobject |
387 | | Object& VM::get_global_object() |
388 | 0 | { |
389 | | // 1. Let currentRealm be the current Realm Record. |
390 | 0 | auto& current_realm = *this->current_realm(); |
391 | | |
392 | | // 2. Return currentRealm.[[GlobalObject]]. |
393 | 0 | return current_realm.global_object(); |
394 | 0 | } |
395 | | |
396 | | bool VM::in_strict_mode() const |
397 | 0 | { |
398 | 0 | if (execution_context_stack().is_empty()) |
399 | 0 | return false; |
400 | 0 | return running_execution_context().is_strict_mode; |
401 | 0 | } |
402 | | |
403 | | void VM::run_queued_promise_jobs() |
404 | 0 | { |
405 | 0 | dbgln_if(PROMISE_DEBUG, "Running queued promise jobs"); |
406 | |
|
407 | 0 | while (!m_promise_jobs.is_empty()) { |
408 | 0 | auto job = m_promise_jobs.take_first(); |
409 | 0 | dbgln_if(PROMISE_DEBUG, "Calling promise job function"); |
410 | |
|
411 | 0 | [[maybe_unused]] auto result = job->function()(); |
412 | 0 | } |
413 | 0 | } |
414 | | |
415 | | // 9.5.4 HostEnqueuePromiseJob ( job, realm ), https://tc39.es/ecma262/#sec-hostenqueuepromisejob |
416 | | void VM::enqueue_promise_job(NonnullGCPtr<HeapFunction<ThrowCompletionOr<Value>()>> job, Realm*) |
417 | 0 | { |
418 | | // An implementation of HostEnqueuePromiseJob must conform to the requirements in 9.5 as well as the following: |
419 | | // - FIXME: If realm is not null, each time job is invoked the implementation must perform implementation-defined steps such that execution is prepared to evaluate ECMAScript code at the time of job's invocation. |
420 | | // - FIXME: Let scriptOrModule be GetActiveScriptOrModule() at the time HostEnqueuePromiseJob is invoked. If realm is not null, each time job is invoked the implementation must perform implementation-defined steps |
421 | | // such that scriptOrModule is the active script or module at the time of job's invocation. |
422 | | // - Jobs must run in the same order as the HostEnqueuePromiseJob invocations that scheduled them. |
423 | 0 | m_promise_jobs.append(job); |
424 | 0 | } |
425 | | |
426 | | void VM::run_queued_finalization_registry_cleanup_jobs() |
427 | 0 | { |
428 | 0 | while (!m_finalization_registry_cleanup_jobs.is_empty()) { |
429 | 0 | auto registry = m_finalization_registry_cleanup_jobs.take_first(); |
430 | | // FIXME: Handle any uncatched exceptions here. |
431 | 0 | (void)registry->cleanup(); |
432 | 0 | } |
433 | 0 | } |
434 | | |
435 | | // 9.10.4.1 HostEnqueueFinalizationRegistryCleanupJob ( finalizationRegistry ), https://tc39.es/ecma262/#sec-host-cleanup-finalization-registry |
436 | | void VM::enqueue_finalization_registry_cleanup_job(FinalizationRegistry& registry) |
437 | 0 | { |
438 | 0 | m_finalization_registry_cleanup_jobs.append(®istry); |
439 | 0 | } |
440 | | |
441 | | // 27.2.1.9 HostPromiseRejectionTracker ( promise, operation ), https://tc39.es/ecma262/#sec-host-promise-rejection-tracker |
442 | | void VM::promise_rejection_tracker(Promise& promise, Promise::RejectionOperation operation) const |
443 | 0 | { |
444 | 0 | switch (operation) { |
445 | 0 | case Promise::RejectionOperation::Reject: |
446 | | // A promise was rejected without any handlers |
447 | 0 | if (on_promise_unhandled_rejection) |
448 | 0 | on_promise_unhandled_rejection(promise); |
449 | 0 | break; |
450 | 0 | case Promise::RejectionOperation::Handle: |
451 | | // A handler was added to an already rejected promise |
452 | 0 | if (on_promise_rejection_handled) |
453 | 0 | on_promise_rejection_handled(promise); |
454 | 0 | break; |
455 | 0 | default: |
456 | 0 | VERIFY_NOT_REACHED(); |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | void VM::dump_backtrace() const |
461 | 0 | { |
462 | 0 | for (ssize_t i = m_execution_context_stack.size() - 1; i >= 0; --i) { |
463 | 0 | auto& frame = m_execution_context_stack[i]; |
464 | 0 | if (frame->executable && frame->program_counter.has_value()) { |
465 | 0 | auto source_range = frame->executable->source_range_at(frame->program_counter.value()).realize(); |
466 | 0 | dbgln("-> {} @ {}:{},{}", frame->function_name ? frame->function_name->utf8_string() : ""_string, source_range.filename(), source_range.start.line, source_range.start.column); |
467 | 0 | } else { |
468 | 0 | dbgln("-> {}", frame->function_name ? frame->function_name->utf8_string() : ""_string); |
469 | 0 | } |
470 | 0 | } |
471 | 0 | } |
472 | | |
473 | | void VM::save_execution_context_stack() |
474 | 0 | { |
475 | 0 | m_saved_execution_context_stacks.append(move(m_execution_context_stack)); |
476 | 0 | } |
477 | | |
478 | | void VM::clear_execution_context_stack() |
479 | 0 | { |
480 | 0 | m_execution_context_stack.clear_with_capacity(); |
481 | 0 | } |
482 | | |
483 | | void VM::restore_execution_context_stack() |
484 | 0 | { |
485 | 0 | m_execution_context_stack = m_saved_execution_context_stacks.take_last(); |
486 | 0 | } |
487 | | |
488 | | // 9.4.1 GetActiveScriptOrModule ( ), https://tc39.es/ecma262/#sec-getactivescriptormodule |
489 | | ScriptOrModule VM::get_active_script_or_module() const |
490 | 0 | { |
491 | | // 1. If the execution context stack is empty, return null. |
492 | 0 | if (m_execution_context_stack.is_empty()) |
493 | 0 | return Empty {}; |
494 | | |
495 | | // 2. Let ec be the topmost execution context on the execution context stack whose ScriptOrModule component is not null. |
496 | 0 | for (auto i = m_execution_context_stack.size() - 1; i > 0; i--) { |
497 | 0 | if (!m_execution_context_stack[i]->script_or_module.has<Empty>()) |
498 | 0 | return m_execution_context_stack[i]->script_or_module; |
499 | 0 | } |
500 | | |
501 | | // 3. If no such execution context exists, return null. Otherwise, return ec's ScriptOrModule. |
502 | | // Note: Since it is not empty we have 0 and since we got here all the |
503 | | // above contexts don't have a non-null ScriptOrModule |
504 | 0 | return m_execution_context_stack[0]->script_or_module; |
505 | 0 | } |
506 | | |
507 | | VM::StoredModule* VM::get_stored_module(ImportedModuleReferrer const&, ByteString const& filename, ByteString const&) |
508 | 0 | { |
509 | | // Note the spec says: |
510 | | // If this operation is called multiple times with the same (referrer, specifier) pair and it performs |
511 | | // FinishLoadingImportedModule(referrer, specifier, payload, result) where result is a normal completion, |
512 | | // then it must perform FinishLoadingImportedModule(referrer, specifier, payload, result) with the same result each time. |
513 | | |
514 | | // Editor's Note from https://tc39.es/proposal-json-modules/#sec-hostresolveimportedmodule |
515 | | // The above text implies that is recommended but not required that hosts do not use moduleRequest.[[Assertions]] |
516 | | // as part of the module cache key. In either case, an exception thrown from an import with a given assertion list |
517 | | // does not rule out success of another import with the same specifier but a different assertion list. |
518 | | |
519 | | // FIXME: This should probably check referrer as well. |
520 | 0 | auto end_or_module = m_loaded_modules.find_if([&](StoredModule const& stored_module) { |
521 | 0 | return stored_module.filename == filename; |
522 | 0 | }); |
523 | 0 | if (end_or_module.is_end()) |
524 | 0 | return nullptr; |
525 | 0 | return &(*end_or_module); |
526 | 0 | } |
527 | | |
528 | | ThrowCompletionOr<void> VM::link_and_eval_module(Badge<Bytecode::Interpreter>, SourceTextModule& module) |
529 | 0 | { |
530 | 0 | return link_and_eval_module(module); |
531 | 0 | } |
532 | | |
533 | | ThrowCompletionOr<void> VM::link_and_eval_module(CyclicModule& module) |
534 | 0 | { |
535 | 0 | auto filename = module.filename(); |
536 | 0 | module.load_requested_modules(nullptr); |
537 | |
|
538 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] Linking module {}", filename); |
539 | 0 | auto linked_or_error = module.link(*this); |
540 | 0 | if (linked_or_error.is_error()) |
541 | 0 | return linked_or_error.throw_completion(); |
542 | | |
543 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] Linking passed, now evaluating module {}", filename); |
544 | 0 | auto evaluated_or_error = module.evaluate(*this); |
545 | |
|
546 | 0 | if (evaluated_or_error.is_error()) |
547 | 0 | return evaluated_or_error.throw_completion(); |
548 | | |
549 | 0 | auto* evaluated_value = evaluated_or_error.value(); |
550 | |
|
551 | 0 | run_queued_promise_jobs(); |
552 | 0 | VERIFY(m_promise_jobs.is_empty()); |
553 | | |
554 | | // FIXME: This will break if we start doing promises actually asynchronously. |
555 | 0 | VERIFY(evaluated_value->state() != Promise::State::Pending); |
556 | | |
557 | 0 | if (evaluated_value->state() == Promise::State::Rejected) |
558 | 0 | return JS::throw_completion(evaluated_value->result()); |
559 | | |
560 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] Evaluating passed for module {}", module.filename()); |
561 | 0 | return {}; |
562 | 0 | } |
563 | | |
564 | | static ByteString resolve_module_filename(StringView filename, StringView module_type) |
565 | 0 | { |
566 | 0 | auto extensions = Vector<StringView, 2> { "js"sv, "mjs"sv }; |
567 | 0 | if (module_type == "json"sv) |
568 | 0 | extensions = { "json"sv }; |
569 | 0 | if (!FileSystem::exists(filename)) { |
570 | 0 | for (auto extension : extensions) { |
571 | | // import "./foo" -> import "./foo.ext" |
572 | 0 | auto resolved_filepath = ByteString::formatted("{}.{}", filename, extension); |
573 | 0 | if (FileSystem::exists(resolved_filepath)) |
574 | 0 | return resolved_filepath; |
575 | 0 | } |
576 | 0 | } else if (FileSystem::is_directory(filename)) { |
577 | 0 | for (auto extension : extensions) { |
578 | | // import "./foo" -> import "./foo/index.ext" |
579 | 0 | auto resolved_filepath = LexicalPath::join(filename, ByteString::formatted("index.{}", extension)).string(); |
580 | 0 | if (FileSystem::exists(resolved_filepath)) |
581 | 0 | return resolved_filepath; |
582 | 0 | } |
583 | 0 | } |
584 | 0 | return filename; |
585 | 0 | } |
586 | | |
587 | | // 16.2.1.8 HostLoadImportedModule ( referrer, specifier, hostDefined, payload ), https://tc39.es/ecma262/#sec-HostLoadImportedModule |
588 | | void VM::load_imported_module(ImportedModuleReferrer referrer, ModuleRequest const& module_request, GCPtr<GraphLoadingState::HostDefined>, ImportedModulePayload payload) |
589 | 0 | { |
590 | | // An implementation of HostLoadImportedModule must conform to the following requirements: |
591 | | // |
592 | | // - The host environment must perform FinishLoadingImportedModule(referrer, specifier, payload, result), |
593 | | // where result is either a normal completion containing the loaded Module Record or a throw completion, |
594 | | // either synchronously or asynchronously. |
595 | | // - If this operation is called multiple times with the same (referrer, specifier) pair and it performs |
596 | | // FinishLoadingImportedModule(referrer, specifier, payload, result) where result is a normal completion, |
597 | | // then it must perform FinishLoadingImportedModule(referrer, specifier, payload, result) with the same result each time. |
598 | | // - The operation must treat payload as an opaque value to be passed through to FinishLoadingImportedModule. |
599 | | // |
600 | | // The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to |
601 | | // load the appropriate Module Record. Multiple different (referrer, specifier) pairs may map to the same Module Record instance. |
602 | | // The actual mapping semantics is host-defined but typically a normalization process is applied to specifier as part of the |
603 | | // mapping process. A typical normalization process would include actions such as expansion of relative and abbreviated path specifiers. |
604 | | |
605 | | // Here we check, against the spec, if payload is a promise capability, meaning that this was called for a dynamic import |
606 | 0 | if (payload.has<NonnullGCPtr<PromiseCapability>>() && !m_dynamic_imports_allowed) { |
607 | | // If you are here because you want to enable dynamic module importing make sure it won't be a security problem |
608 | | // by checking the default implementation of HostImportModuleDynamically and creating your own hook or calling |
609 | | // vm.allow_dynamic_imports(). |
610 | 0 | finish_loading_imported_module(referrer, module_request, payload, throw_completion<InternalError>(ErrorType::DynamicImportNotAllowed, module_request.module_specifier)); |
611 | 0 | return; |
612 | 0 | } |
613 | | |
614 | 0 | ByteString module_type; |
615 | 0 | for (auto& attribute : module_request.attributes) { |
616 | 0 | if (attribute.key == "type"sv) { |
617 | 0 | module_type = attribute.value; |
618 | 0 | break; |
619 | 0 | } |
620 | 0 | } |
621 | |
|
622 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module at {} has type {}", module_request.module_specifier, module_type); |
623 | |
|
624 | 0 | StringView const base_filename = referrer.visit( |
625 | 0 | [&](NonnullGCPtr<Realm> const&) { |
626 | | // Generally within ECMA262 we always get a referencing_script_or_module. However, ShadowRealm gives an explicit null. |
627 | | // To get around this is we attempt to get the active script_or_module otherwise we might start loading "random" files from the working directory. |
628 | 0 | return get_active_script_or_module().visit( |
629 | 0 | [](Empty) { |
630 | 0 | return "."sv; |
631 | 0 | }, |
632 | 0 | [](auto const& script_or_module) { |
633 | 0 | return script_or_module->filename(); |
634 | 0 | }); Unexecuted instantiation: VM.cpp:auto JS::VM::load_imported_module(AK::Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>, JS::NonnullGCPtr<JS::Realm> >, JS::ModuleRequest const&, JS::GCPtr<JS::GraphLoadingState::HostDefined>, AK::Variant<JS::NonnullGCPtr<JS::GraphLoadingState>, JS::NonnullGCPtr<JS::PromiseCapability> >)::$_0::operator()(JS::NonnullGCPtr<JS::Realm> const&) const::{lambda(auto:1 const&)#1}::operator()<JS::NonnullGCPtr<JS::Script> >(JS::NonnullGCPtr<JS::Script> const&) constUnexecuted instantiation: VM.cpp:auto JS::VM::load_imported_module(AK::Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>, JS::NonnullGCPtr<JS::Realm> >, JS::ModuleRequest const&, JS::GCPtr<JS::GraphLoadingState::HostDefined>, AK::Variant<JS::NonnullGCPtr<JS::GraphLoadingState>, JS::NonnullGCPtr<JS::PromiseCapability> >)::$_0::operator()(JS::NonnullGCPtr<JS::Realm> const&) const::{lambda(auto:1 const&)#1}::operator()<JS::NonnullGCPtr<JS::Module> >(JS::NonnullGCPtr<JS::Module> const&) const |
635 | 0 | }, |
636 | 0 | [&](auto const& script_or_module) { |
637 | 0 | return script_or_module->filename(); |
638 | 0 | }); Unexecuted instantiation: VM.cpp:auto JS::VM::load_imported_module(AK::Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>, JS::NonnullGCPtr<JS::Realm> >, JS::ModuleRequest const&, JS::GCPtr<JS::GraphLoadingState::HostDefined>, AK::Variant<JS::NonnullGCPtr<JS::GraphLoadingState>, JS::NonnullGCPtr<JS::PromiseCapability> >)::$_1::operator()<JS::NonnullGCPtr<JS::Script> >(JS::NonnullGCPtr<JS::Script> const&) const Unexecuted instantiation: VM.cpp:auto JS::VM::load_imported_module(AK::Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>, JS::NonnullGCPtr<JS::Realm> >, JS::ModuleRequest const&, JS::GCPtr<JS::GraphLoadingState::HostDefined>, AK::Variant<JS::NonnullGCPtr<JS::GraphLoadingState>, JS::NonnullGCPtr<JS::PromiseCapability> >)::$_1::operator()<JS::NonnullGCPtr<JS::CyclicModule> >(JS::NonnullGCPtr<JS::CyclicModule> const&) const |
639 | |
|
640 | 0 | LexicalPath base_path { base_filename }; |
641 | 0 | auto filename = LexicalPath::absolute_path(base_path.dirname(), module_request.module_specifier); |
642 | |
|
643 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] base path: '{}'", base_path); |
644 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] initial filename: '{}'", filename); |
645 | |
|
646 | 0 | filename = resolve_module_filename(filename, module_type); |
647 | |
|
648 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved filename: '{}'", filename); |
649 | |
|
650 | | #if JS_MODULE_DEBUG |
651 | | ByteString referencing_module_string = referrer.visit( |
652 | | [&](Empty) -> ByteString { |
653 | | return "."; |
654 | | }, |
655 | | [&](auto& script_or_module) { |
656 | | if constexpr (IsSame<Script*, decltype(script_or_module)>) { |
657 | | return ByteString::formatted("Script @ {}", script_or_module.ptr()); |
658 | | } |
659 | | return ByteString::formatted("Module @ {}", script_or_module.ptr()); |
660 | | }); |
661 | | |
662 | | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] load_imported_module({}, {})", referencing_module_string, filename); |
663 | | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved {} + {} -> {}", base_path, module_request.module_specifier, filename); |
664 | | #endif |
665 | |
|
666 | 0 | auto* loaded_module_or_end = get_stored_module(referrer, filename, module_type); |
667 | 0 | if (loaded_module_or_end != nullptr) { |
668 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] load_imported_module({}) already loaded at {}", filename, loaded_module_or_end->module.ptr()); |
669 | 0 | finish_loading_imported_module(referrer, module_request, payload, *loaded_module_or_end->module); |
670 | 0 | return; |
671 | 0 | } |
672 | | |
673 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] reading and parsing module {}", filename); |
674 | |
|
675 | 0 | auto file_or_error = Core::File::open(filename, Core::File::OpenMode::Read); |
676 | |
|
677 | 0 | if (file_or_error.is_error()) { |
678 | 0 | finish_loading_imported_module(referrer, module_request, payload, throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier)); |
679 | 0 | return; |
680 | 0 | } |
681 | | |
682 | | // FIXME: Don't read the file in one go. |
683 | 0 | auto file_content_or_error = file_or_error.value()->read_until_eof(); |
684 | |
|
685 | 0 | if (file_content_or_error.is_error()) { |
686 | 0 | if (file_content_or_error.error().code() == ENOMEM) { |
687 | 0 | finish_loading_imported_module(referrer, module_request, payload, throw_completion<JS::InternalError>(error_message(::JS::VM::ErrorMessage::OutOfMemory))); |
688 | 0 | return; |
689 | 0 | } |
690 | 0 | finish_loading_imported_module(referrer, module_request, payload, throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier)); |
691 | 0 | return; |
692 | 0 | } |
693 | | |
694 | 0 | StringView const content_view { file_content_or_error.value().bytes() }; |
695 | |
|
696 | 0 | auto module = [&]() -> ThrowCompletionOr<NonnullGCPtr<Module>> { |
697 | | // If assertions has an entry entry such that entry.[[Key]] is "type", let type be entry.[[Value]]. The following requirements apply: |
698 | | // If type is "json", then this algorithm must either invoke ParseJSONModule and return the resulting Completion Record, or throw an exception. |
699 | 0 | if (module_type == "json"sv) { |
700 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] reading and parsing JSON module {}", filename); |
701 | 0 | return parse_json_module(content_view, *current_realm(), filename); |
702 | 0 | } |
703 | | |
704 | 0 | dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] reading and parsing as SourceTextModule module {}", filename); |
705 | | // Note: We treat all files as module, so if a script does not have exports it just runs it. |
706 | 0 | auto module_or_errors = SourceTextModule::parse(content_view, *current_realm(), filename); |
707 | |
|
708 | 0 | if (module_or_errors.is_error()) { |
709 | 0 | VERIFY(module_or_errors.error().size() > 0); |
710 | 0 | return throw_completion<SyntaxError>(module_or_errors.error().first().to_byte_string()); |
711 | 0 | } |
712 | | |
713 | 0 | auto module = module_or_errors.release_value(); |
714 | 0 | m_loaded_modules.empend( |
715 | 0 | referrer, |
716 | 0 | module->filename(), |
717 | 0 | ByteString {}, // Null type |
718 | 0 | make_handle<Module>(*module), |
719 | 0 | true); |
720 | |
|
721 | 0 | return module; |
722 | 0 | }(); |
723 | |
|
724 | 0 | finish_loading_imported_module(referrer, module_request, payload, module); |
725 | 0 | } |
726 | | |
727 | | void VM::push_execution_context(ExecutionContext& context) |
728 | 0 | { |
729 | 0 | if (!m_execution_context_stack.is_empty()) |
730 | 0 | m_execution_context_stack.last()->program_counter = bytecode_interpreter().program_counter(); |
731 | 0 | m_execution_context_stack.append(&context); |
732 | 0 | } |
733 | | |
734 | | void VM::pop_execution_context() |
735 | 0 | { |
736 | 0 | m_execution_context_stack.take_last(); |
737 | 0 | if (m_execution_context_stack.is_empty() && on_call_stack_emptied) |
738 | 0 | on_call_stack_emptied(); |
739 | 0 | } |
740 | | |
741 | | #if ARCH(X86_64) |
742 | | struct [[gnu::packed]] NativeStackFrame { |
743 | | NativeStackFrame* prev; |
744 | | FlatPtr return_address; |
745 | | }; |
746 | | #endif |
747 | | |
748 | | static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const* context) |
749 | 0 | { |
750 | | // native function |
751 | 0 | if (!context->executable) |
752 | 0 | return {}; |
753 | | |
754 | 0 | if (!context->program_counter.has_value()) |
755 | 0 | return {}; |
756 | | |
757 | 0 | return context->executable->source_range_at(context->program_counter.value()); |
758 | 0 | } |
759 | | |
760 | | Vector<StackTraceElement> VM::stack_trace() const |
761 | 0 | { |
762 | 0 | Vector<StackTraceElement> stack_trace; |
763 | 0 | for (ssize_t i = m_execution_context_stack.size() - 1; i >= 0; i--) { |
764 | 0 | auto* context = m_execution_context_stack[i]; |
765 | 0 | stack_trace.append({ |
766 | 0 | .execution_context = context, |
767 | 0 | .source_range = get_source_range(context).value_or({}), |
768 | 0 | }); |
769 | 0 | } |
770 | |
|
771 | 0 | return stack_trace; |
772 | 0 | } |
773 | | |
774 | | } |