LibWeb: Prevent GC from running during intrinsics allocation

Due to the way we lazily construct prototypes and constructors for web
platform interfaces, it's possible for nested GC allocation to occur
while GC objects have been allocated but not fully constructed.

If the garbage collector ends up running in this state, it may attempt
to call JS::Cell::visit_edges() on an object whose vtable pointer hasn't
been set up yet.

This patch works around the issue by deferring GC while intrinsics are
being brought up. Furthermore, we also create a dummy global object for
the internal realm, and populate it with intrinsics. This works around
the same issue happening when allocating something (like the default UA
stylesheets) in the internal realm.

These solutions are pretty hacky and sad, so I've left FIXMEs about
finding a nicer way.
This commit is contained in:
Andreas Kling 2022-10-17 10:38:21 +02:00
parent 8412206cb4
commit 68452c749a
Notes: sideshowbarker 2024-07-17 05:17:27 +09:00
2 changed files with 17 additions and 0 deletions

View file

@ -53,6 +53,7 @@ static ErrorOr<void> generate_exposed_interface_implementation(StringView class_
generator.set("global_object_snake_name", String(class_name).to_snakecase());
generator.append(R"~~~(
#include <LibJS/Heap/DeferGC.h>
#include <LibJS/Runtime/Object.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/@global_object_name@ExposedInterfaces.h>
@ -85,6 +86,11 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global, JS::R
{
auto& vm = global.vm();
// FIXME: Should we use vm.current_realm() here?
// NOTE: Temporarily disable garbage collection to prevent GC from triggering while a not-fully-constructed
// prototype or constructor object has been allocated. This is a hack.
// FIXME: Find a nicer way to solve this.
JS::DeferGC defer_gc(vm.heap());
)~~~");
auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class) {

View file

@ -6,6 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Heap/DeferGC.h>
#include <LibJS/Module.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Environment.h>
@ -16,6 +17,7 @@
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/LocationObject.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/WindowExposedInterfaces.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/PromiseRejectionEvent.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
@ -374,6 +376,15 @@ JS::VM& main_thread_vm()
auto host_defined = make<HostDefined>(nullptr, *intrinsics);
root_realm->set_host_defined(move(host_defined));
// NOTE: We make sure the internal realm has all the Window intrinsics initialized.
// The DeferGC is a hack to avoid nested GC allocations due to lazy ensure_web_prototype()
// and ensure_web_constructor() invocations.
// FIXME: Find a nicer way to do this.
JS::DeferGC defer_gc(root_realm->heap());
auto* object = JS::Object::create(*root_realm, nullptr);
root_realm->set_global_object(object, object);
add_window_exposed_interfaces(*object, *root_realm);
vm->push_execution_context(*custom_data.root_execution_context);
}
return *vm;