mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-05 22:52:22 +02:00
When allocating ExecutionContext, we were skipping allocating constants, because they are filled in shortly after. However, there is still some code executing between allocating the ExecutionContext and assigning constants. This code may allocate GC-aware objects before the assignment. Allocating any GC object may cause a garbage collection to be triggered. And running garbage collection on uninitialized objects might have all kinds of unintended effects. To avoid that, simply initialize constants right away. To be safe, also initialize arguments, but I haven't checked more closely if it is needed for them or not. The specific case where this bug manifested was JetStream3's raytrace-private-class-fields.js, which was triggering a segmentation fault when trying to visit a functions constant during GC marking phase. The offending allocation triggering the garbage collection is the corresponding FunctionEnvironment being created. This crash was exposed as a combination of multiple things coming together. In particular, both of1179e40d3fand61e6dbe4e7combined were exposing the problem, but is seems neither commit is at fault. Most likely the crash happening or not is sensitive to the exact amount of GC pressure being present or size of individual execution contexts. And so any change that affects those might make it appear or go away. To put in an additional wrinkle, this could only be observed using the ASM JS interpreter. The CPP interpreter was using a fast path for function calls that has a different allocation pattern and did not run into the crash. No explicit regression test for this change because: * The problem is very sensitive to implementation details and a reproduction that stays valid with code changes in the interpreter is probably impossible to come by. * The bug was exposed by the JS benchmarks, which already are as good as a regression test as we are going to get here realistically.
144 lines
5.0 KiB
C++
144 lines
5.0 KiB
C++
/*
|
|
* Copyright (c) 2020-2026, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
|
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
|
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Checked.h>
|
|
#include <LibJS/Bytecode/BasicBlock.h>
|
|
#include <LibJS/Export.h>
|
|
#include <LibJS/Forward.h>
|
|
#include <LibJS/Module.h>
|
|
#include <LibJS/Runtime/PrivateEnvironment.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
#include <LibJS/SourceRange.h>
|
|
|
|
namespace JS {
|
|
|
|
using ScriptOrModule = Variant<Empty, GC::Ref<Script>, GC::Ref<Module>>;
|
|
|
|
// 9.4 Execution Contexts, https://tc39.es/ecma262/#sec-execution-contexts
|
|
struct JS_API ExecutionContext {
|
|
static NonnullOwnPtr<ExecutionContext> create(u32 registers_and_locals_count, u32 constants_count, u32 arguments_count);
|
|
[[nodiscard]] NonnullOwnPtr<ExecutionContext> copy() const;
|
|
|
|
~ExecutionContext() = default;
|
|
|
|
void visit_edges(Cell::Visitor&);
|
|
|
|
private:
|
|
friend class ExecutionContextAllocator;
|
|
|
|
public:
|
|
// NB: The layout is: [registers | locals | constants | arguments]
|
|
ALWAYS_INLINE ExecutionContext(u32 registers_and_locals_count, u32 constants_count, u32 arguments_count_)
|
|
{
|
|
VERIFY(!Checked<u32>::addition_would_overflow(registers_and_locals_count, constants_count, arguments_count_));
|
|
registers_and_constants_and_locals_and_arguments_count = registers_and_locals_count + constants_count + arguments_count_;
|
|
argument_count = arguments_count_;
|
|
auto* values = registers_and_constants_and_locals_and_arguments();
|
|
for (size_t i = 0; i < registers_and_constants_and_locals_and_arguments_count; ++i)
|
|
values[i] = js_special_empty_value();
|
|
}
|
|
|
|
void operator delete(void* ptr);
|
|
|
|
GC::Ptr<FunctionObject> function; // [[Function]]
|
|
GC::Ptr<Realm> realm; // [[Realm]]
|
|
ScriptOrModule script_or_module; // [[ScriptOrModule]]
|
|
GC::Ptr<Environment> lexical_environment; // [[LexicalEnvironment]]
|
|
GC::Ptr<Environment> variable_environment; // [[VariableEnvironment]]
|
|
GC::Ptr<PrivateEnvironment> private_environment; // [[PrivateEnvironment]]
|
|
|
|
u32 program_counter { 0 };
|
|
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#skip-when-determining-incumbent-counter
|
|
// FIXME: Move this out of LibJS (e.g. by using the CustomData concept), as it's used exclusively by LibWeb.
|
|
u32 skip_when_determining_incumbent_counter { 0 };
|
|
|
|
// Non-standard: Used by generators/async generators to communicate yield/await
|
|
// state back to the caller without allocating a GC cell.
|
|
// UINT32_MAX means "no continuation" (generator is done).
|
|
static constexpr u32 no_yield_continuation = UINT32_MAX;
|
|
u32 yield_continuation { no_yield_continuation };
|
|
|
|
bool yield_is_await { false };
|
|
bool caller_is_construct { false };
|
|
|
|
Optional<Value> this_value;
|
|
|
|
GC::Ptr<Bytecode::Executable> executable;
|
|
|
|
Span<Value> registers_and_constants_and_locals_and_arguments_span()
|
|
{
|
|
return { registers_and_constants_and_locals_and_arguments(), registers_and_constants_and_locals_and_arguments_count };
|
|
}
|
|
|
|
Value const* registers_and_constants_and_locals_and_arguments() const
|
|
{
|
|
return reinterpret_cast<Value*>(reinterpret_cast<uintptr_t>(this) + sizeof(ExecutionContext));
|
|
}
|
|
|
|
Value argument(size_t index) const
|
|
{
|
|
if (index >= argument_count) [[unlikely]]
|
|
return js_undefined();
|
|
return arguments_data()[index];
|
|
}
|
|
|
|
Span<Value> arguments_span()
|
|
{
|
|
return { arguments_data(), argument_count };
|
|
}
|
|
|
|
ReadonlySpan<Value> arguments_span() const
|
|
{
|
|
return { arguments_data(), argument_count };
|
|
}
|
|
|
|
Value* arguments_data()
|
|
{
|
|
return registers_and_constants_and_locals_and_arguments() + (registers_and_constants_and_locals_and_arguments_count - argument_count);
|
|
}
|
|
|
|
Value const* arguments_data() const
|
|
{
|
|
return registers_and_constants_and_locals_and_arguments() + (registers_and_constants_and_locals_and_arguments_count - argument_count);
|
|
}
|
|
|
|
// Non-standard: Inline frame linkage for the bytecode interpreter.
|
|
// When a JS-to-JS call is inlined in the dispatch loop, these fields
|
|
// allow the Return handler to restore the caller's frame.
|
|
ExecutionContext* caller_frame { nullptr };
|
|
u32 passed_argument_count { 0 };
|
|
u32 caller_return_pc { 0 };
|
|
u32 caller_dst_raw { 0 };
|
|
|
|
private:
|
|
friend class Bytecode::Interpreter;
|
|
|
|
Value* registers_and_constants_and_locals_and_arguments()
|
|
{
|
|
return reinterpret_cast<Value*>(reinterpret_cast<uintptr_t>(this) + sizeof(ExecutionContext));
|
|
}
|
|
|
|
u32 registers_and_constants_and_locals_and_arguments_count { 0 };
|
|
|
|
public:
|
|
u32 argument_count { 0 };
|
|
};
|
|
|
|
static_assert(IsTriviallyDestructible<ExecutionContext>);
|
|
|
|
struct StackTraceElement {
|
|
ExecutionContext* execution_context { nullptr };
|
|
Optional<SourceRange> source_range;
|
|
};
|
|
|
|
}
|