1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 21:57:43 +00:00

LibJS: Add JS::SafeFunction, like Function but protects captures from GC

SafeFunction automatically registers its closure memory area in a place
where the JS garbage collector can find it.

This means that you can capture JS::Value and arbitrary pointers into
the GC heap in closures, as long as you're using a SafeFunction, and the
GC will not zap those values!

There's probably some performance impact from this, and there's a lot of
things that could be nicer/smarter about it, but let's build something
that ensures safety first, and we can worry about performance later. :^)
This commit is contained in:
Andreas Kling 2022-09-24 11:56:43 +02:00
parent 585072fce3
commit 131c3f50de
3 changed files with 282 additions and 0 deletions

View file

@ -17,6 +17,7 @@
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/WeakContainer.h>
#include <LibJS/SafeFunction.h>
#include <setjmp.h>
#ifdef __serenity__
@ -29,6 +30,9 @@ namespace JS {
static int gc_perf_string_id;
#endif
// NOTE: We keep a per-thread list of custom ranges. This hinges on the assumption that there is one JS VM per thread.
static __thread HashMap<FlatPtr*, size_t>* s_custom_ranges_for_conservative_scan = nullptr;
Heap::Heap(VM& vm)
: m_vm(vm)
{
@ -164,6 +168,16 @@ __attribute__((no_sanitize("address"))) void Heap::gather_conservative_roots(Has
add_possible_value(data);
}
// NOTE: If we have any custom ranges registered, scan those as well.
// This is where JS::SafeFunction closures get marked.
if (s_custom_ranges_for_conservative_scan) {
for (auto& custom_range : *s_custom_ranges_for_conservative_scan) {
for (size_t i = 0; i < (custom_range.value / sizeof(FlatPtr)); ++i) {
add_possible_value(custom_range.key[i]);
}
}
}
HashTable<HeapBlock*> all_live_heap_blocks;
for_each_block([&](auto& block) {
all_live_heap_blocks.set(&block);
@ -349,4 +363,21 @@ void Heap::uproot_cell(Cell* cell)
m_uprooted_cells.append(cell);
}
void register_safe_function_closure(void* base, size_t size)
{
if (!s_custom_ranges_for_conservative_scan) {
// FIXME: This per-thread HashMap is currently leaked on thread exit.
s_custom_ranges_for_conservative_scan = new HashMap<FlatPtr*, size_t>;
}
auto result = s_custom_ranges_for_conservative_scan->set(reinterpret_cast<FlatPtr*>(base), size);
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
}
void unregister_safe_function_closure(void* base, size_t)
{
VERIFY(s_custom_ranges_for_conservative_scan);
bool did_remove = s_custom_ranges_for_conservative_scan->remove(reinterpret_cast<FlatPtr*>(base));
VERIFY(did_remove);
}
}