diff --git a/src/bun.js/bindings/v8/V8Array.cpp b/src/bun.js/bindings/v8/V8Array.cpp index fce7592e14f4d4..97dd9cf31925d1 100644 --- a/src/bun.js/bindings/v8/V8Array.cpp +++ b/src/bun.js/bindings/v8/V8Array.cpp @@ -17,7 +17,7 @@ Local Array::New(Isolate* isolate, Local* elements, size_t length) static_cast(nullptr), reinterpret_cast(elements), (unsigned int)length); - return isolate->currentHandleScope()->createLocal(array); + return isolate->currentHandleScope()->createLocal(isolate->vm(), array); } } diff --git a/src/bun.js/bindings/v8/V8Boolean.cpp b/src/bun.js/bindings/v8/V8Boolean.cpp index f746584c4d8929..3bc95e043c9786 100644 --- a/src/bun.js/bindings/v8/V8Boolean.cpp +++ b/src/bun.js/bindings/v8/V8Boolean.cpp @@ -10,7 +10,7 @@ bool Boolean::Value() const Local Boolean::New(Isolate* isolate, bool value) { - return isolate->currentHandleScope()->createLocal(JSC::jsBoolean(value)); + return isolate->currentHandleScope()->createLocal(isolate->vm(), JSC::jsBoolean(value)); } } diff --git a/src/bun.js/bindings/v8/V8Context.cpp b/src/bun.js/bindings/v8/V8Context.cpp new file mode 100644 index 00000000000000..1fe001f2370483 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Context.cpp @@ -0,0 +1,10 @@ +#include "V8Context.h" + +namespace v8 { + +Isolate* Context::GetIsolate() +{ + return reinterpret_cast(localToPointer()); +} + +} diff --git a/src/bun.js/bindings/v8/V8Context.h b/src/bun.js/bindings/v8/V8Context.h index a842b9dc8b7d86..2293a22516ff2d 100644 --- a/src/bun.js/bindings/v8/V8Context.h +++ b/src/bun.js/bindings/v8/V8Context.h @@ -6,10 +6,14 @@ namespace v8 { +class Isolate; + // Context is always a reinterpret pointer to V8::Roots, so that inlined V8 functions can find // values they expect to find at fixed offsets class Context : public Data { public: + BUN_EXPORT Isolate* GetIsolate(); + JSC::VM& vm() const { return globalObject()->vm(); diff --git a/src/bun.js/bindings/v8/V8Data.h b/src/bun.js/bindings/v8/V8Data.h index f6d55fab747170..aded85f5c9bbac 100644 --- a/src/bun.js/bindings/v8/V8Data.h +++ b/src/bun.js/bindings/v8/V8Data.h @@ -56,10 +56,10 @@ class Data { } ObjectLayout* v8_object = reinterpret_cast(raw_ptr); - if (v8_object->map.getPtr()->instance_type == InstanceType::HeapNumber) { - return JSC::jsDoubleNumber(*reinterpret_cast(&v8_object->ptr)); + if (v8_object->map()->instance_type == InstanceType::HeapNumber) { + return JSC::jsDoubleNumber(v8_object->asDouble()); } else { - return JSC::JSValue(reinterpret_cast(v8_object->ptr)); + return JSC::JSValue(v8_object->asCell()); } } } @@ -97,8 +97,8 @@ class Data { // root points to the V8 object. The first field of the V8 object is the map, and the // second is a pointer to some object we have stored. So we ignore the map and recover // the object pointer. - JSC::JSCell** v8_object = reinterpret_cast(root.getPtr()); - return TaggedPointer(v8_object[1]); + ObjectLayout* v8_object = root.getPtr(); + return TaggedPointer(v8_object->asRaw()); } } }; diff --git a/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp index 7563b6c6844739..9bc31b58b50402 100644 --- a/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp +++ b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp @@ -7,9 +7,7 @@ EscapableHandleScopeBase::EscapableHandleScopeBase(Isolate* isolate) { // at this point isolate->currentHandleScope() would just be this, so instead we have to get the // previous one - auto& handle = prev->buffer->createUninitializedHandle(); - memset(&handle, 0xaa, sizeof(handle)); - handle.to_v8_object = TaggedPointer(0); + auto& handle = prev->buffer->createEmptyHandle(); escape_slot = &handle; } @@ -18,7 +16,7 @@ EscapableHandleScopeBase::EscapableHandleScopeBase(Isolate* isolate) uintptr_t* EscapableHandleScopeBase::EscapeSlot(uintptr_t* escape_value) { *escape_slot = *reinterpret_cast(escape_value); - return reinterpret_cast(escape_slot); + return &escape_slot->to_v8_object.value; } } diff --git a/src/bun.js/bindings/v8/V8External.cpp b/src/bun.js/bindings/v8/V8External.cpp index b46a0119f7f3d1..64a437490a0801 100644 --- a/src/bun.js/bindings/v8/V8External.cpp +++ b/src/bun.js/bindings/v8/V8External.cpp @@ -11,7 +11,7 @@ Local External::New(Isolate* isolate, void* value) auto& vm = globalObject->vm(); auto structure = globalObject->NapiExternalStructure(); Bun::NapiExternal* val = Bun::NapiExternal::create(vm, structure, value, nullptr, nullptr); - return isolate->currentHandleScope()->createLocal(val); + return isolate->currentHandleScope()->createLocal(vm, val); } void* External::Value() const diff --git a/src/bun.js/bindings/v8/V8FunctionTemplate.cpp b/src/bun.js/bindings/v8/V8FunctionTemplate.cpp index 1cc65d17076a50..b83d5c6fbf6c42 100644 --- a/src/bun.js/bindings/v8/V8FunctionTemplate.cpp +++ b/src/bun.js/bindings/v8/V8FunctionTemplate.cpp @@ -47,23 +47,25 @@ Local FunctionTemplate::New( auto globalObject = isolate->globalObject(); auto& vm = globalObject->vm(); - JSValue jsc_data = data.IsEmpty() ? JSC::jsUndefined() : data->localToJSValue(globalObject->V8GlobalInternals()); + auto* globalInternals = globalObject->V8GlobalInternals(); + JSValue jsc_data = data.IsEmpty() ? JSC::jsUndefined() : data->localToJSValue(globalInternals); - Structure* structure = globalObject->V8GlobalInternals()->functionTemplateStructure(globalObject); + Structure* structure = globalInternals->functionTemplateStructure(globalObject); auto* functionTemplate = new (NotNull, JSC::allocateCell(vm)) FunctionTemplate( vm, structure, callback, jsc_data); functionTemplate->finishCreation(vm); - return isolate->currentHandleScope()->createLocal(functionTemplate); + return globalInternals->currentHandleScope()->createLocal(vm, functionTemplate); } MaybeLocal FunctionTemplate::GetFunction(Local context) { auto& vm = context->vm(); auto* globalObject = context->globalObject(); - auto* f = Function::create(vm, globalObject->V8GlobalInternals()->v8FunctionStructure(globalObject), localToObjectPointer()); + auto* globalInternals = globalObject->V8GlobalInternals(); + auto* f = Function::create(vm, globalInternals->v8FunctionStructure(globalObject), localToObjectPointer()); - return context->currentHandleScope()->createLocal(f); + return globalInternals->currentHandleScope()->createLocal(vm, f); } Structure* FunctionTemplate::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) @@ -83,9 +85,7 @@ void FunctionTemplate::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(fn, info()); Base::visitChildren(fn, visitor); - if (fn->__internals.data.isCell()) { - JSC::JSCell::visitChildren(fn->__internals.data.asCell(), visitor); - } + visitor.append(fn->__internals.data); } DEFINE_VISIT_CHILDREN(FunctionTemplate); @@ -95,19 +95,20 @@ JSC::EncodedJSValue FunctionTemplate::functionCall(JSC::JSGlobalObject* globalOb auto* callee = JSC::jsDynamicCast(callFrame->jsCallee()); auto* functionTemplate = callee->functionTemplate(); auto* isolate = Isolate::fromGlobalObject(JSC::jsDynamicCast(globalObject)); + auto& vm = globalObject->vm(); WTF::Vector args(callFrame->argumentCount() + 1); HandleScope hs(isolate); - Local thisValue = hs.createLocal(callFrame->thisValue()); + Local thisValue = hs.createLocal(vm, callFrame->thisValue()); args[0] = thisValue.tagged(); for (size_t i = 0; i < callFrame->argumentCount(); i++) { - Local argValue = hs.createLocal(callFrame->argument(i)); + Local argValue = hs.createLocal(vm, callFrame->argument(i)); args[i + 1] = argValue.tagged(); } - Local data = hs.createLocal(functionTemplate->__internals.data); + Local data = hs.createLocal(vm, functionTemplate->__internals.data.get()); ImplicitArgs implicit_args = { .holder = nullptr, diff --git a/src/bun.js/bindings/v8/V8FunctionTemplate.h b/src/bun.js/bindings/v8/V8FunctionTemplate.h index 09dcfcea1d9ea8..3d3fe261c188ca 100644 --- a/src/bun.js/bindings/v8/V8FunctionTemplate.h +++ b/src/bun.js/bindings/v8/V8FunctionTemplate.h @@ -111,11 +111,11 @@ class FunctionTemplate : public JSC::InternalFunction { class Internals { private: FunctionCallback callback; - JSC::JSValue data; + JSC::WriteBarrier data; - Internals(FunctionCallback callback_, JSC::JSValue data_) + Internals(FunctionCallback callback_, JSC::VM& vm, FunctionTemplate* owner, JSC::JSValue data_) : callback(callback_) - , data(data_) + , data(vm, owner, data_) { } @@ -144,7 +144,7 @@ class FunctionTemplate : public JSC::InternalFunction { static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES functionCall(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); FunctionTemplate(JSC::VM& vm, JSC::Structure* structure, FunctionCallback callback, JSC::JSValue data) - : __internals(callback, data) + : __internals(callback, vm, this, data) , Base(vm, structure, functionCall, JSC::callHostFunctionAsConstructor) { } diff --git a/src/bun.js/bindings/v8/V8GlobalInternals.cpp b/src/bun.js/bindings/v8/V8GlobalInternals.cpp index a13bad38e41e8d..6a5a36fa872933 100644 --- a/src/bun.js/bindings/v8/V8GlobalInternals.cpp +++ b/src/bun.js/bindings/v8/V8GlobalInternals.cpp @@ -12,6 +12,7 @@ using JSC::ClassInfo; using JSC::LazyClassStructure; +using JSC::LazyProperty; using JSC::Structure; using JSC::VM; @@ -44,6 +45,10 @@ void GlobalInternals::finishCreation(VM& vm) m_V8FunctionStructure.initLater([](LazyClassStructure::Initializer& init) { init.setStructure(Function::createStructure(init.vm, init.global)); }); + m_GlobalHandles.initLater([](const LazyProperty::Initializer& init) { + init.set(HandleScopeBuffer::create(init.vm, + init.owner->handleScopeBufferStructure(init.owner->globalObject))); + }); } template @@ -57,6 +62,7 @@ void GlobalInternals::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_HandleScopeBufferStructure.visit(visitor); thisObject->m_FunctionTemplateStructure.visit(visitor); thisObject->m_V8FunctionStructure.visit(visitor); + thisObject->m_GlobalHandles.visit(visitor); } DEFINE_VISIT_CHILDREN_WITH_MODIFIER(JS_EXPORT_PRIVATE, GlobalInternals); diff --git a/src/bun.js/bindings/v8/V8GlobalInternals.h b/src/bun.js/bindings/v8/V8GlobalInternals.h index ec9c20b0e9130e..bdb2bbc936ce3b 100644 --- a/src/bun.js/bindings/v8/V8GlobalInternals.h +++ b/src/bun.js/bindings/v8/V8GlobalInternals.h @@ -8,6 +8,7 @@ namespace v8 { class HandleScope; +class HandleScopeBuffer; class GlobalInternals : public JSC::JSCell { public: @@ -53,6 +54,8 @@ class GlobalInternals : public JSC::JSCell { return m_V8FunctionStructure.getInitializedOnMainThread(globalObject); } + HandleScopeBuffer* globalHandles() const { return m_GlobalHandles.getInitializedOnMainThread(this); } + HandleScope* currentHandleScope() const { return m_CurrentHandleScope; } void setCurrentHandleScope(HandleScope* handleScope) { m_CurrentHandleScope = handleScope; } @@ -79,6 +82,8 @@ class GlobalInternals : public JSC::JSCell { JSC::LazyClassStructure m_FunctionTemplateStructure; JSC::LazyClassStructure m_V8FunctionStructure; HandleScope* m_CurrentHandleScope; + JSC::LazyProperty m_GlobalHandles; + Oddball undefinedValue; Oddball nullValue; Oddball trueValue; diff --git a/src/bun.js/bindings/v8/V8Handle.h b/src/bun.js/bindings/v8/V8Handle.h index 941b0e992a072a..709481e95e4bb3 100644 --- a/src/bun.js/bindings/v8/V8Handle.h +++ b/src/bun.js/bindings/v8/V8Handle.h @@ -4,10 +4,53 @@ namespace v8 { -struct ObjectLayout { +class ObjectLayout { +private: // these two fields are laid out so that V8 can find the map - TaggedPointer map; - void* ptr; + TaggedPointer tagged_map; + union { + JSC::WriteBarrier cell; + double number; + void* raw; + } contents; + +public: + ObjectLayout() + // using a smi value for map is most likely to catch bugs as almost every access will expect + // map to be a pointer (and even if the assertion is bypassed, it'll be a null pointer) + : tagged_map(0) + , contents({ .raw = nullptr }) + { + } + + ObjectLayout(const Map* map_ptr, JSC::JSCell* cell, JSC::VM& vm, const JSC::JSCell* owner) + : tagged_map(const_cast(map_ptr)) + , contents({ .cell = JSC::WriteBarrier(vm, owner, cell) }) + { + } + + ObjectLayout(double number) + : tagged_map(const_cast(&Map::heap_number_map)) + , contents({ .number = number }) + { + } + + ObjectLayout(void* raw) + : tagged_map(const_cast(&Map::raw_ptr_map)) + , contents({ .raw = raw }) + { + } + + const Map* map() const { return tagged_map.getPtr(); } + + double asDouble() const { return contents.number; } + + JSC::JSCell* asCell() const { return contents.cell.get(); } + + void* asRaw() const { return contents.raw; } + + friend class Handle; + friend class HandleScopeBuffer; }; // A handle stored in a HandleScope with layout suitable for V8's inlined functions: @@ -24,14 +67,31 @@ struct ObjectLayout { // the third field to get the actual object (either a JSCell* or a void*, depending on whether map // points to Map::object_map or Map::raw_ptr_map). struct Handle { - Handle(const Map* map_, void* ptr_) + static_assert(offsetof(ObjectLayout, tagged_map) == 0, "ObjectLayout is wrong"); + static_assert(offsetof(ObjectLayout, contents) == 8, "ObjectLayout is wrong"); + static_assert(sizeof(ObjectLayout) == 16, "ObjectLayout is wrong"); + + Handle(const Map* map, JSC::JSCell* cell, JSC::VM& vm, const JSC::JSCell* owner) + : to_v8_object(&this->object) + , object(map, cell, vm, owner) + { + } + + Handle(double number) + : to_v8_object(&this->object) + , object(number) + { + } + + Handle(void* raw) : to_v8_object(&this->object) - , object({ .map = const_cast(map_), .ptr = ptr_ }) + , object(raw) { } Handle(int32_t smi) : to_v8_object(smi) + , object() { } @@ -40,10 +100,15 @@ struct Handle { *this = that; } + Handle(const ObjectLayout* that) + : to_v8_object(&this->object) + { + object = *that; + } + Handle& operator=(const Handle& that) { - object.map = that.object.map; - object.ptr = that.object.ptr; + object = that.object; if (that.to_v8_object.type() == TaggedPointer::Type::Smi) { to_v8_object = that.to_v8_object; } else { @@ -52,14 +117,18 @@ struct Handle { return *this; } - Handle() {} + Handle() + : to_v8_object(0) + , object() + { + } bool isCell() const { if (to_v8_object.type() == TaggedPointer::Type::Smi) { return false; } - const Map* map_ptr = object.map.getPtr(); + const Map* map_ptr = object.map(); // TODO(@190n) exhaustively switch on InstanceType if (map_ptr == &Map::object_map || map_ptr == &Map::string_map) { return true; diff --git a/src/bun.js/bindings/v8/V8HandleScope.cpp b/src/bun.js/bindings/v8/V8HandleScope.cpp index 35cd6876ccdca5..f7d20b76d2cf1e 100644 --- a/src/bun.js/bindings/v8/V8HandleScope.cpp +++ b/src/bun.js/bindings/v8/V8HandleScope.cpp @@ -15,14 +15,13 @@ HandleScope::HandleScope(Isolate* isolate_) HandleScope::~HandleScope() { isolate->globalInternals()->setCurrentHandleScope(prev); + buffer = nullptr; } uintptr_t* HandleScope::CreateHandle(internal::Isolate* isolate, uintptr_t value) { - // TODO figure out if this is actually used directly - V8_UNIMPLEMENTED(); - // return buffer->createHandle(value); - return nullptr; + auto* handleScope = reinterpret_cast(isolate)->globalInternals()->currentHandleScope(); + return &handleScope->buffer->createHandleFromExistingHandle(TaggedPointer::fromRaw(value))->value; } } diff --git a/src/bun.js/bindings/v8/V8HandleScope.h b/src/bun.js/bindings/v8/V8HandleScope.h index f42639f295c366..81f73e08ee1a8b 100644 --- a/src/bun.js/bindings/v8/V8HandleScope.h +++ b/src/bun.js/bindings/v8/V8HandleScope.h @@ -13,21 +13,18 @@ class HandleScope { public: BUN_EXPORT HandleScope(Isolate* isolate); BUN_EXPORT ~HandleScope(); - BUN_EXPORT uintptr_t* CreateHandle(internal::Isolate* isolate, uintptr_t value); - template Local createLocal(JSC::JSValue value) + template Local createLocal(JSC::VM& vm, JSC::JSValue value) { // TODO(@190n) handle more types if (value.isString()) { - return Local(buffer->createHandle(value.asCell(), &Map::string_map)); + return Local(buffer->createHandle(value.asCell(), &Map::string_map, vm)); } else if (value.isCell()) { - return Local(buffer->createHandle(value.asCell(), &Map::object_map)); + return Local(buffer->createHandle(value.asCell(), &Map::object_map, vm)); } else if (value.isInt32()) { return Local(buffer->createSmiHandle(value.asInt32())); } else if (value.isNumber()) { - double numeric_value = value.asNumber(); - void* double_reinterpreted_to_pointer = *reinterpret_cast(&numeric_value); - return Local(buffer->createHandle(double_reinterpreted_to_pointer, &Map::heap_number_map)); + return Local(buffer->createDoubleHandle(value.asNumber())); } else if (value.isUndefined()) { return Local(isolate->globalInternals()->undefinedSlot()); } else if (value.isNull()) { @@ -44,7 +41,7 @@ class HandleScope { template Local createRawLocal(void* ptr) { - TaggedPointer* handle = buffer->createHandle(ptr, &Map::raw_ptr_map); + TaggedPointer* handle = buffer->createRawHandle(ptr); return Local(handle); } @@ -55,6 +52,9 @@ class HandleScope { Isolate* isolate; HandleScope* prev; HandleScopeBuffer* buffer; + + // is protected in v8, which matters on windows + BUN_EXPORT static uintptr_t* CreateHandle(internal::Isolate* isolate, uintptr_t value); }; static_assert(sizeof(HandleScope) == 24, "HandleScope has wrong layout"); diff --git a/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp b/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp index b9445dadd9dec5..8468cc64674d53 100644 --- a/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp +++ b/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp @@ -7,7 +7,7 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers; const JSC::ClassInfo HandleScopeBuffer::s_info = { "HandleScopeBuffer"_s, - &Base::s_info, + nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(HandleScopeBuffer) @@ -27,36 +27,62 @@ void HandleScopeBuffer::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); - for (int i = 0; i < thisObject->size; i++) { - auto& handle = thisObject->storage[i]; + WTF::Locker locker { thisObject->gc_lock }; + + for (auto& handle : thisObject->storage) { if (handle.isCell()) { - JSCell::visitChildren(reinterpret_cast(handle.object.ptr), visitor); + visitor.append(handle.object.contents.cell); } } } DEFINE_VISIT_CHILDREN(HandleScopeBuffer); -Handle& HandleScopeBuffer::createUninitializedHandle() +Handle& HandleScopeBuffer::createEmptyHandle() +{ + WTF::Locker locker { gc_lock }; + storage.append(Handle {}); + return storage.last(); +} + +TaggedPointer* HandleScopeBuffer::createHandle(JSCell* ptr, const Map* map, JSC::VM& vm) { - RELEASE_ASSERT(size < capacity - 1); - int index = size; - size++; - return storage[index]; + auto& handle = createEmptyHandle(); + handle = Handle(map, ptr, vm, this); + return &handle.to_v8_object; } -TaggedPointer* HandleScopeBuffer::createHandle(void* ptr, const Map* map) +TaggedPointer* HandleScopeBuffer::createRawHandle(void* ptr) { - // TODO(@190n) specify the map more correctly - auto& handle = createUninitializedHandle(); - handle = Handle(map, ptr); + auto& handle = createEmptyHandle(); + handle = Handle(ptr); return &handle.to_v8_object; } TaggedPointer* HandleScopeBuffer::createSmiHandle(int32_t smi) { - auto& handle = createUninitializedHandle(); - handle.to_v8_object = TaggedPointer(smi); + auto& handle = createEmptyHandle(); + handle = Handle(smi); + return &handle.to_v8_object; +} + +TaggedPointer* HandleScopeBuffer::createDoubleHandle(double value) +{ + auto& handle = createEmptyHandle(); + handle = Handle(value); + return &handle.to_v8_object; +} + +TaggedPointer* HandleScopeBuffer::createHandleFromExistingHandle(TaggedPointer address) +{ + auto& handle = createEmptyHandle(); + int32_t smi; + if (address.getSmi(smi)) { + handle = Handle(smi); + } else { + auto* v8_object = address.getPtr(); + handle = Handle(v8_object); + } return &handle.to_v8_object; } diff --git a/src/bun.js/bindings/v8/V8HandleScopeBuffer.h b/src/bun.js/bindings/v8/V8HandleScopeBuffer.h index e52ca9bf7509e2..a045951436973e 100644 --- a/src/bun.js/bindings/v8/V8HandleScopeBuffer.h +++ b/src/bun.js/bindings/v8/V8HandleScopeBuffer.h @@ -9,15 +9,15 @@ namespace v8 { // An array used by HandleScope to store the items. Must keep pointer stability when resized, since // v8::Locals point inside this array. -class HandleScopeBuffer : public JSC::JSNonFinalObject { +class HandleScopeBuffer : public JSC::JSCell { public: - using Base = JSC::JSNonFinalObject; + using Base = JSC::JSCell; static HandleScopeBuffer* create(JSC::VM& vm, JSC::Structure* structure); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { - return JSC::Structure::create(vm, globalObject, JSC::jsNull(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), 0, 0); + return JSC::Structure::create(vm, globalObject, JSC::jsNull(), JSC::TypeInfo(JSC::CellType, StructureFlags), info(), 0, 0); } template @@ -33,8 +33,11 @@ class HandleScopeBuffer : public JSC::JSNonFinalObject { [](auto& spaces, auto&& space) { spaces.m_subspaceForHandleScopeBuffer = std::forward(space); }); } - TaggedPointer* createHandle(void* ptr, const Map* map); + TaggedPointer* createHandle(JSC::JSCell* object, const Map* map, JSC::VM& vm); + TaggedPointer* createRawHandle(void* ptr); TaggedPointer* createSmiHandle(int32_t smi); + TaggedPointer* createDoubleHandle(double value); + TaggedPointer* createHandleFromExistingHandle(TaggedPointer address); DECLARE_INFO; DECLARE_VISIT_CHILDREN; @@ -42,13 +45,10 @@ class HandleScopeBuffer : public JSC::JSNonFinalObject { friend class EscapableHandleScopeBase; private: - // TODO make resizable - static constexpr int capacity = 64; + WTF::Lock gc_lock; + WTF::SegmentedVector storage; - Handle storage[capacity]; - int size = 0; - - Handle& createUninitializedHandle(); + Handle& createEmptyHandle(); HandleScopeBuffer(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) diff --git a/src/bun.js/bindings/v8/V8InternalFieldObject.cpp b/src/bun.js/bindings/v8/V8InternalFieldObject.cpp index 0f7bf035712b56..69dd3878da1450 100644 --- a/src/bun.js/bindings/v8/V8InternalFieldObject.cpp +++ b/src/bun.js/bindings/v8/V8InternalFieldObject.cpp @@ -22,4 +22,18 @@ InternalFieldObject* InternalFieldObject::create(JSC::VM& vm, JSC::Structure* st return object; } +template +void InternalFieldObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + InternalFieldObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + for (auto& value : thisObject->fields) { + visitor.append(value); + } +} + +DEFINE_VISIT_CHILDREN(InternalFieldObject); + } diff --git a/src/bun.js/bindings/v8/V8InternalFieldObject.h b/src/bun.js/bindings/v8/V8InternalFieldObject.h index c2cc27064ea7ba..6a118786bf1783 100644 --- a/src/bun.js/bindings/v8/V8InternalFieldObject.h +++ b/src/bun.js/bindings/v8/V8InternalFieldObject.h @@ -23,15 +23,18 @@ class InternalFieldObject : public JSC::JSDestructibleObject { [](auto& spaces, auto&& space) { spaces.m_subspaceForInternalFieldObject = std::forward(space); }); } - using FieldContainer = WTF::Vector; + // never changes size + using FieldContainer = WTF::FixedVector>; FieldContainer* internalFields() { return &fields; } static InternalFieldObject* create(JSC::VM& vm, JSC::Structure* structure, Local objectTemplate); + DECLARE_VISIT_CHILDREN; + protected: InternalFieldObject(JSC::VM& vm, JSC::Structure* structure, int internalFieldCount) : Base(vm, structure) - , fields(internalFieldCount) + , fields(internalFieldCount, JSC::WriteBarrier(vm, this, JSC::jsUndefined())) { } diff --git a/src/bun.js/bindings/v8/V8Number.cpp b/src/bun.js/bindings/v8/V8Number.cpp index bd4a7af630ce17..31c6744f2fc0ae 100644 --- a/src/bun.js/bindings/v8/V8Number.cpp +++ b/src/bun.js/bindings/v8/V8Number.cpp @@ -5,7 +5,7 @@ namespace v8 { Local Number::New(Isolate* isolate, double value) { - return isolate->currentHandleScope()->createLocal(JSC::jsNumber(value)); + return isolate->currentHandleScope()->createLocal(isolate->vm(), JSC::jsNumber(value)); } double Number::Value() const diff --git a/src/bun.js/bindings/v8/V8Object.cpp b/src/bun.js/bindings/v8/V8Object.cpp index 7f7bf364036d3b..cb6d5215eab232 100644 --- a/src/bun.js/bindings/v8/V8Object.cpp +++ b/src/bun.js/bindings/v8/V8Object.cpp @@ -32,7 +32,7 @@ static FieldContainer* getInternalFieldsContainer(Object* object) Local Object::New(Isolate* isolate) { JSFinalObject* object = JSC::constructEmptyObject(isolate->globalObject()); - return isolate->currentHandleScope()->createLocal(object); + return isolate->currentHandleScope()->createLocal(isolate->vm(), object); } Maybe Object::Set(Local context, Local key, Local value) @@ -65,7 +65,9 @@ void Object::SetInternalField(int index, Local data) auto* fields = getInternalFieldsContainer(this); RELEASE_ASSERT(fields, "object has no internal fields"); RELEASE_ASSERT(index >= 0 && index < fields->size(), "internal field index is out of bounds"); - fields->at(index) = data->localToJSValue(Isolate::GetCurrent()->globalInternals()); + JSObject* js_object = localToObjectPointer(); + auto* globalObject = JSC::jsDynamicCast(js_object->globalObject()); + fields->at(index).set(globalObject->vm(), localToCell(), data->localToJSValue(globalObject->V8GlobalInternals())); } Local Object::GetInternalField(int index) @@ -77,12 +79,13 @@ Local Object::SlowGetInternalField(int index) { auto* fields = getInternalFieldsContainer(this); JSObject* js_object = localToObjectPointer(); - HandleScope* handleScope = Isolate::fromGlobalObject(JSC::jsDynamicCast(js_object->globalObject()))->currentHandleScope(); + auto* globalObject = JSC::jsDynamicCast(js_object->globalObject()); + HandleScope* handleScope = Isolate::fromGlobalObject(globalObject)->currentHandleScope(); if (fields && index >= 0 && index < fields->size()) { auto& field = fields->at(index); - return handleScope->createLocal(field); + return handleScope->createLocal(globalObject->vm(), field.get()); } - return handleScope->createLocal(JSC::jsUndefined()); + return handleScope->createLocal(globalObject->vm(), JSC::jsUndefined()); } } diff --git a/src/bun.js/bindings/v8/V8ObjectTemplate.cpp b/src/bun.js/bindings/v8/V8ObjectTemplate.cpp index 1fcfc516cc31c7..07b32031f13fea 100644 --- a/src/bun.js/bindings/v8/V8ObjectTemplate.cpp +++ b/src/bun.js/bindings/v8/V8ObjectTemplate.cpp @@ -41,13 +41,14 @@ const JSC::ClassInfo ObjectTemplate::s_info = { Local ObjectTemplate::New(Isolate* isolate, Local constructor) { RELEASE_ASSERT(constructor.IsEmpty()); - auto globalObject = isolate->globalObject(); + auto* globalObject = isolate->globalObject(); auto& vm = globalObject->vm(); - Structure* structure = globalObject->V8GlobalInternals()->objectTemplateStructure(globalObject); + auto* globalInternals = globalObject->V8GlobalInternals(); + Structure* structure = globalInternals->objectTemplateStructure(globalObject); auto* objectTemplate = new (NotNull, JSC::allocateCell(vm)) ObjectTemplate(vm, structure); // TODO pass constructor objectTemplate->finishCreation(vm); - return isolate->currentHandleScope()->createLocal(objectTemplate); + return globalInternals->currentHandleScope()->createLocal(vm, objectTemplate); } MaybeLocal ObjectTemplate::NewInstance(Local context) @@ -69,7 +70,7 @@ MaybeLocal ObjectTemplate::NewInstance(Local context) // todo: apply properties - return MaybeLocal(context->currentHandleScope()->createLocal(newInstance)); + return MaybeLocal(context->currentHandleScope()->createLocal(vm, newInstance)); } template diff --git a/src/bun.js/bindings/v8/V8String.cpp b/src/bun.js/bindings/v8/V8String.cpp index 4b56f144869265..7e6d9965d91d15 100644 --- a/src/bun.js/bindings/v8/V8String.cpp +++ b/src/bun.js/bindings/v8/V8String.cpp @@ -23,12 +23,34 @@ MaybeLocal String::NewFromUtf8(Isolate* isolate, char const* data, NewSt return MaybeLocal(); } + auto& vm = isolate->vm(); std::span span(reinterpret_cast(data), length); // ReplacingInvalidSequences matches how v8 behaves here auto string = WTF::String::fromUTF8ReplacingInvalidSequences(span); - RELEASE_ASSERT(!string.isNull()); - JSString* jsString = JSC::jsString(isolate->vm(), string); - return MaybeLocal(isolate->globalInternals()->currentHandleScope()->createLocal(jsString)); + JSString* jsString = JSC::jsString(vm, string); + return MaybeLocal(isolate->currentHandleScope()->createLocal(vm, jsString)); +} + +MaybeLocal String::NewFromOneByte(Isolate* isolate, const uint8_t* data, NewStringType type, int signed_length) +{ + (void)type; + size_t length = 0; + if (signed_length < 0) { + length = strlen(reinterpret_cast(data)); + } else { + length = static_cast(signed_length); + } + + if (length > JSString::MaxLength) { + // empty + return MaybeLocal(); + } + + auto& vm = isolate->vm(); + std::span span(data, length); + WTF::String string(span); + JSString* jsString = JSC::jsString(vm, string); + return MaybeLocal(isolate->currentHandleScope()->createLocal(vm, jsString)); } int String::Utf8Length(Isolate* isolate) const diff --git a/src/bun.js/bindings/v8/V8String.h b/src/bun.js/bindings/v8/V8String.h index ac0f21a9d839f2..4ec76762c7614b 100644 --- a/src/bun.js/bindings/v8/V8String.h +++ b/src/bun.js/bindings/v8/V8String.h @@ -23,6 +23,7 @@ class String : Primitive { }; BUN_EXPORT static MaybeLocal NewFromUtf8(Isolate* isolate, char const* data, NewStringType type, int length = -1); + BUN_EXPORT static MaybeLocal NewFromOneByte(Isolate* isolate, const uint8_t* data, NewStringType type, int length); // length: number of bytes in buffer (if negative, assume it is large enough) // nchars_ref: store number of code units written here diff --git a/src/bun.js/bindings/v8/V8TaggedPointer.h b/src/bun.js/bindings/v8/V8TaggedPointer.h index 6cb18feb820017..910adb2845a149 100644 --- a/src/bun.js/bindings/v8/V8TaggedPointer.h +++ b/src/bun.js/bindings/v8/V8TaggedPointer.h @@ -81,13 +81,10 @@ struct TaggedPointer { JSC::JSValue getJSValue() const { - // TODO handle heap doubles int32_t smi; if (getSmi(smi)) { return JSC::jsNumber(smi); } - // TODO(@190n) this needs to be a pointer to a specific class for v8 objects - // maybe not? return getPtr(); } }; diff --git a/src/bun.js/bindings/v8/node.cpp b/src/bun.js/bindings/v8/node.cpp index fa12e1d62af7a2..1cf41766ded623 100644 --- a/src/bun.js/bindings/v8/node.cpp +++ b/src/bun.js/bindings/v8/node.cpp @@ -89,9 +89,9 @@ void node_module_register(void* opaque_mod) HandleScope hs(Isolate::fromGlobalObject(globalObject)); // exports, module - Local exports = hs.createLocal(*strongExportsObject); - Local module = hs.createLocal(object); - Local context = hs.createLocal(globalObject); + Local exports = hs.createLocal(vm, *strongExportsObject); + Local module = hs.createLocal(vm, object); + Local context = Isolate::fromGlobalObject(globalObject)->GetCurrentContext(); if (mod->nm_context_register_func) { mod->nm_context_register_func(exports, module, context, mod->nm_priv); } else if (mod->nm_register_func) { diff --git a/src/bun.js/bindings/v8/v8_api_internal.cpp b/src/bun.js/bindings/v8/v8_api_internal.cpp index 800ac92ece0492..f610051696143c 100644 --- a/src/bun.js/bindings/v8/v8_api_internal.cpp +++ b/src/bun.js/bindings/v8/v8_api_internal.cpp @@ -1,12 +1,25 @@ -#include "v8.h" +#include "v8_api_internal.h" +#include "V8Isolate.h" +#include "V8HandleScopeBuffer.h" namespace v8 { namespace api_internal { -BUN_EXPORT void ToLocalEmpty() +void ToLocalEmpty() { - // TODO(@190n) proper error handling - V8_UNIMPLEMENTED(); + BUN_PANIC("Attempt to unwrap an empty v8::MaybeLocal"); +} + +uintptr_t* GlobalizeReference(v8::internal::Isolate* isolate, uintptr_t address) +{ + auto* globalHandles = reinterpret_cast(isolate)->globalInternals()->globalHandles(); + return &globalHandles->createHandleFromExistingHandle(TaggedPointer::fromRaw(address))->value; +} + +void DisposeGlobal(uintptr_t* location) +{ + // TODO free up a slot in the handle scope + (void)location; } } diff --git a/src/bun.js/bindings/v8/v8_api_internal.h b/src/bun.js/bindings/v8/v8_api_internal.h new file mode 100644 index 00000000000000..ec3fa7347b5f71 --- /dev/null +++ b/src/bun.js/bindings/v8/v8_api_internal.h @@ -0,0 +1,14 @@ +#pragma once + +#include "v8.h" +#include "v8_internal.h" + +namespace v8 { +namespace api_internal { + +BUN_EXPORT void ToLocalEmpty(); +BUN_EXPORT uintptr_t* GlobalizeReference(v8::internal::Isolate* isolate, uintptr_t address); +BUN_EXPORT void DisposeGlobal(uintptr_t* location); + +} +} diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 7b433c281578a1..c6f0d58edef75b 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1744,12 +1744,16 @@ const V8API = if (!bun.Environment.isWindows) struct { pub extern fn _ZNK2v85Value8IsStringEv() *anyopaque; pub extern fn _ZN2v87Boolean3NewEPNS_7IsolateEb() *anyopaque; pub extern fn _ZN2v86Object16GetInternalFieldEi() *anyopaque; + pub extern fn _ZN2v87Context10GetIsolateEv() *anyopaque; + pub extern fn _ZN2v86String14NewFromOneByteEPNS_7IsolateEPKhNS_13NewStringTypeEi() *anyopaque; pub extern fn _ZNK2v86String10Utf8LengthEPNS_7IsolateE() *anyopaque; pub extern fn _ZNK2v86String10IsExternalEv() *anyopaque; pub extern fn _ZNK2v86String17IsExternalOneByteEv() *anyopaque; pub extern fn _ZNK2v86String17IsExternalTwoByteEv() *anyopaque; pub extern fn _ZNK2v86String9IsOneByteEv() *anyopaque; pub extern fn _ZNK2v86String19ContainsOnlyOneByteEv() *anyopaque; + pub extern fn _ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEm() *anyopaque; + pub extern fn _ZN2v812api_internal13DisposeGlobalEPm() *anyopaque; } else struct { // MSVC name mangling is different than it is on unix. // To make this easier to deal with, I have provided a script to generate the list of functions. @@ -1774,7 +1778,7 @@ const V8API = if (!bun.Environment.isWindows) struct { pub extern fn @"?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@1@Z"() *anyopaque; pub extern fn @"?SetInternalField@Object@v8@@QEAAXHV?$Local@VData@v8@@@2@@Z"() *anyopaque; pub extern fn @"?SlowGetInternalField@Object@v8@@AEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque; - pub extern fn @"?CreateHandle@HandleScope@v8@@QEAAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque; + pub extern fn @"?CreateHandle@HandleScope@v8@@KAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque; pub extern fn @"??0HandleScope@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque; pub extern fn @"??1HandleScope@v8@@QEAA@XZ"() *anyopaque; pub extern fn @"?GetFunction@FunctionTemplate@v8@@QEAA?AV?$MaybeLocal@VFunction@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque; @@ -1806,12 +1810,16 @@ const V8API = if (!bun.Environment.isWindows) struct { pub extern fn @"?IsString@Value@v8@@QEBA_NXZ"() *anyopaque; pub extern fn @"?New@Boolean@v8@@SA?AV?$Local@VBoolean@v8@@@2@PEAVIsolate@2@_N@Z"() *anyopaque; pub extern fn @"?GetInternalField@Object@v8@@QEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque; + pub extern fn @"?GetIsolate@Context@v8@@QEAAPEAVIsolate@2@XZ"() *anyopaque; + pub extern fn @"?NewFromOneByte@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBEW4NewStringType@2@H@Z"() *anyopaque; pub extern fn @"?IsExternal@String@v8@@QEBA_NXZ"() *anyopaque; pub extern fn @"?IsExternalOneByte@String@v8@@QEBA_NXZ"() *anyopaque; pub extern fn @"?IsExternalTwoByte@String@v8@@QEBA_NXZ"() *anyopaque; pub extern fn @"?IsOneByte@String@v8@@QEBA_NXZ"() *anyopaque; pub extern fn @"?Utf8Length@String@v8@@QEBAHPEAVIsolate@2@@Z"() *anyopaque; pub extern fn @"?ContainsOnlyOneByte@String@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?GlobalizeReference@api_internal@v8@@YAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque; + pub extern fn @"?DisposeGlobal@api_internal@v8@@YAXPEA_K@Z"() *anyopaque; }; pub fn fixDeadCodeElimination() void { diff --git a/src/symbols.def b/src/symbols.def index 8cfb7cf16deaf5..71f031ae9b34d2 100644 --- a/src/symbols.def +++ b/src/symbols.def @@ -584,7 +584,7 @@ EXPORTS ?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@1@Z ?SetInternalField@Object@v8@@QEAAXHV?$Local@VData@v8@@@2@@Z ?SlowGetInternalField@Object@v8@@AEAA?AV?$Local@VData@v8@@@2@H@Z - ?CreateHandle@HandleScope@v8@@QEAAPEA_KPEAVIsolate@internal@2@_K@Z + ?CreateHandle@HandleScope@v8@@KAPEA_KPEAVIsolate@internal@2@_K@Z ??0HandleScope@v8@@QEAA@PEAVIsolate@1@@Z ??1HandleScope@v8@@QEAA@XZ ?GetFunction@FunctionTemplate@v8@@QEAA?AV?$MaybeLocal@VFunction@v8@@@2@V?$Local@VContext@v8@@@2@@Z @@ -616,9 +616,13 @@ EXPORTS ?IsString@Value@v8@@QEBA_NXZ ?New@Boolean@v8@@SA?AV?$Local@VBoolean@v8@@@2@PEAVIsolate@2@_N@Z ?GetInternalField@Object@v8@@QEAA?AV?$Local@VData@v8@@@2@H@Z + ?GetIsolate@Context@v8@@QEAAPEAVIsolate@2@XZ + ?NewFromOneByte@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBEW4NewStringType@2@H@Z ?IsExternal@String@v8@@QEBA_NXZ ?IsExternalOneByte@String@v8@@QEBA_NXZ ?IsExternalTwoByte@String@v8@@QEBA_NXZ ?IsOneByte@String@v8@@QEBA_NXZ ?Utf8Length@String@v8@@QEBAHPEAVIsolate@2@@Z ?ContainsOnlyOneByte@String@v8@@QEBA_NXZ + ?GlobalizeReference@api_internal@v8@@YAPEA_KPEAVIsolate@internal@2@_K@Z + ?DisposeGlobal@api_internal@v8@@YAXPEA_K@Z diff --git a/src/symbols.dyn b/src/symbols.dyn index b0a08843f9a8ba..e49d1f06eb6069 100644 --- a/src/symbols.dyn +++ b/src/symbols.dyn @@ -203,10 +203,14 @@ __ZNK2v85Value8IsStringEv; __ZN2v87Boolean3NewEPNS_7IsolateEb; __ZN2v86Object16GetInternalFieldEi; + __ZN2v87Context10GetIsolateEv; + __ZN2v86String14NewFromOneByteEPNS_7IsolateEPKhNS_13NewStringTypeEi; __ZNK2v86String10Utf8LengthEPNS_7IsolateE; __ZNK2v86String10IsExternalEv; __ZNK2v86String17IsExternalOneByteEv; __ZNK2v86String17IsExternalTwoByteEv; __ZNK2v86String9IsOneByteEv; __ZNK2v86String19ContainsOnlyOneByteEv; + __ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEm; + __ZN2v812api_internal13DisposeGlobalEPm; }; diff --git a/src/symbols.txt b/src/symbols.txt index bf87b6ffa39840..6d692d24e69447 100644 --- a/src/symbols.txt +++ b/src/symbols.txt @@ -202,9 +202,13 @@ __ZNK2v85Value7IsFalseEv __ZNK2v85Value8IsStringEv __ZN2v87Boolean3NewEPNS_7IsolateEb __ZN2v86Object16GetInternalFieldEi +__ZN2v87Context10GetIsolateEv +__ZN2v86String14NewFromOneByteEPNS_7IsolateEPKhNS_13NewStringTypeEi __ZNK2v86String10Utf8LengthEPNS_7IsolateE __ZNK2v86String10IsExternalEv __ZNK2v86String17IsExternalOneByteEv __ZNK2v86String17IsExternalTwoByteEv __ZNK2v86String9IsOneByteEv -__ZNK2v86String19ContainsOnlyOneByteEv \ No newline at end of file +__ZNK2v86String19ContainsOnlyOneByteEv +__ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEm +__ZN2v812api_internal13DisposeGlobalEPm diff --git a/test/v8/v8-module/main.cpp b/test/v8/v8-module/main.cpp index 04b68c608c04c6..76dd3c0120f2cb 100644 --- a/test/v8/v8-module/main.cpp +++ b/test/v8/v8-module/main.cpp @@ -2,85 +2,57 @@ #include #include +#include +#include using namespace v8; -namespace v8tests { +#define LOG_EXPR(e) std::cout << #e << " = " << (e) << std::endl + +#define LOG_VALUE_KIND(v) \ + do { \ + LOG_EXPR(v->IsUndefined()); \ + LOG_EXPR(v->IsNull()); \ + LOG_EXPR(v->IsNullOrUndefined()); \ + LOG_EXPR(v->IsTrue()); \ + LOG_EXPR(v->IsFalse()); \ + LOG_EXPR(v->IsBoolean()); \ + LOG_EXPR(v->IsString()); \ + LOG_EXPR(v->IsObject()); \ + LOG_EXPR(v->IsNumber()); \ + } while (0) -enum class ValueKind : uint16_t { - Undefined = 1 << 0, - Null = 1 << 1, - NullOrUndefined = 1 << 2, - True = 1 << 3, - False = 1 << 4, - Boolean = 1 << 5, - String = 1 << 6, - Object = 1 << 7, - Number = 1 << 8, -}; +namespace v8tests { -static bool check_value_kind(Local value, ValueKind kind) { - uint16_t matched_kinds = 0; - if (value->IsUndefined()) { - matched_kinds |= static_cast(ValueKind::Undefined); - } - if (value->IsNull()) { - matched_kinds |= static_cast(ValueKind::Null); - } - if (value->IsNullOrUndefined()) { - matched_kinds |= static_cast(ValueKind::NullOrUndefined); - } - if (value->IsTrue()) { - matched_kinds |= static_cast(ValueKind::True); - } - if (value->IsFalse()) { - matched_kinds |= static_cast(ValueKind::False); - } - if (value->IsBoolean()) { - matched_kinds |= static_cast(ValueKind::Boolean); - } - if (value->IsString()) { - matched_kinds |= static_cast(ValueKind::String); - } - if (value->IsObject()) { - matched_kinds |= static_cast(ValueKind::Object); - } - if (value->IsNumber()) { - matched_kinds |= static_cast(ValueKind::Number); +static void log_buffer(const char *buf, int len) { + for (int i = 0; i < len; i++) { + printf("buf[%d] = 0x%02x\n", i, buf[i]); } +} - switch (kind) { - case ValueKind::Undefined: - return matched_kinds == (static_cast(ValueKind::Undefined) | - static_cast(ValueKind::NullOrUndefined)); - case ValueKind::Null: - return matched_kinds == (static_cast(ValueKind::Null) | - static_cast(ValueKind::NullOrUndefined)); - case ValueKind::True: - return matched_kinds == (static_cast(ValueKind::True) | - static_cast(ValueKind::Boolean)); - case ValueKind::False: - return matched_kinds == (static_cast(ValueKind::False) | - static_cast(ValueKind::Boolean)); - case ValueKind::String: - return matched_kinds == static_cast(ValueKind::String); - case ValueKind::Object: - return matched_kinds == static_cast(ValueKind::Object); - case ValueKind::Number: - return matched_kinds == static_cast(ValueKind::Number); - case ValueKind::NullOrUndefined: - return (matched_kinds == - (static_cast(ValueKind::Undefined) | - static_cast(ValueKind::NullOrUndefined))) || - ((static_cast(ValueKind::Null) | - static_cast(ValueKind::NullOrUndefined))); - case ValueKind::Boolean: - return (matched_kinds == (static_cast(ValueKind::True) | - static_cast(ValueKind::Boolean))) || - (matched_kinds == (static_cast(ValueKind::False) | - static_cast(ValueKind::Boolean))); +static std::string describe(Isolate *isolate, Local value) { + if (value->IsUndefined()) { + return "undefined"; + } else if (value->IsNull()) { + return "null"; + } else if (value->IsTrue()) { + return "true"; + } else if (value->IsFalse()) { + return "false"; + } else if (value->IsString()) { + char buf[1024] = {0}; + value.As()->WriteUtf8(isolate, buf, sizeof(buf) - 1); + std::string result = "\""; + result += buf; + result += "\""; + return result; + } else if (value->IsObject()) { + return "[object Object]"; + } else if (value->IsNumber()) { + return std::to_string(value.As()->Value()); + } else { + return "unknown"; } - return false; } void fail(const FunctionCallbackInfo &info, const char *fmt, ...) { @@ -108,24 +80,13 @@ void test_v8_primitives(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local v8_undefined = Undefined(isolate); - if (!check_value_kind(v8_undefined, ValueKind::Undefined)) { - return fail(info, "undefined is not undefined"); - } - + LOG_VALUE_KIND(v8_undefined); Local v8_null = Null(isolate); - if (!check_value_kind(v8_null, ValueKind::Null)) { - return fail(info, "null is not null"); - } - + LOG_VALUE_KIND(v8_null); Local v8_true = Boolean::New(isolate, true); - if (!check_value_kind(v8_true, ValueKind::True)) { - return fail(info, "true is not true"); - } - + LOG_VALUE_KIND(v8_true); Local v8_false = Boolean::New(isolate, false); - if (!check_value_kind(v8_false, ValueKind::False)) { - return fail(info, "false is not false"); - } + LOG_VALUE_KIND(v8_false); return ok(info); } @@ -135,13 +96,8 @@ static void perform_number_test(const FunctionCallbackInfo &info, Isolate *isolate = info.GetIsolate(); Local v8_number = Number::New(isolate, number); - if (v8_number->Value() != number) { - return fail(info, "wrong v8 number value: expected %f got %f", number, - v8_number->Value()); - } - if (!check_value_kind(v8_number, ValueKind::Number)) { - return fail(info, "number is not a number"); - } + LOG_EXPR(v8_number->Value()); + LOG_VALUE_KIND(v8_number); return ok(info); } @@ -159,130 +115,50 @@ void test_v8_number_fraction(const FunctionCallbackInfo &info) { perform_number_test(info, 2.5); } -static bool perform_string_test(const FunctionCallbackInfo &info, - const char *c_string, int utf_16_code_units, - int encoded_utf_8_length, - const char *encoded_utf_8_data) { +static void perform_string_test(const FunctionCallbackInfo &info, + Local v8_string) { Isolate *isolate = info.GetIsolate(); - char buf[256] = {0}; + char buf[256] = {0x7f}; int retval; int nchars; - Local v8_string = - String::NewFromUtf8(isolate, c_string).ToLocalChecked(); + LOG_VALUE_KIND(v8_string); + LOG_EXPR(v8_string->Length()); + LOG_EXPR(v8_string->Utf8Length(isolate)); + LOG_EXPR(v8_string->IsOneByte()); + LOG_EXPR(v8_string->ContainsOnlyOneByte()); + LOG_EXPR(v8_string->IsExternal()); + LOG_EXPR(v8_string->IsExternalTwoByte()); + LOG_EXPR(v8_string->IsExternalOneByte()); - if (!check_value_kind(v8_string, ValueKind::String)) { - fail(info, "string is not a string"); - return false; - } + // check string has the right contents + LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, &nchars)); + LOG_EXPR(nchars); + log_buffer(buf, retval + 1); - if (v8_string->Length() != utf_16_code_units) { - fail(info, "String::Length return: expected %d got %d", utf_16_code_units, - v8_string->Length()); - return false; - } - - if ((retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, &nchars)) != - encoded_utf_8_length + 1) { - fail(info, "String::WriteUtf8 return: expected %d got %d", - encoded_utf_8_length + 1, retval); - return false; - } - if (nchars != utf_16_code_units) { - fail(info, - "String::WriteUtf8 set nchars to wrong value: expected %d got %d", - utf_16_code_units, nchars); - return false; - } - // cmp including terminator - if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { - fail(info, - "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", - c_string, buf); - return false; - } + memset(buf, 0x7f, sizeof buf); // try with assuming the buffer is large enough - if ((retval = v8_string->WriteUtf8(isolate, buf, -1, &nchars)) != - encoded_utf_8_length + 1) { - fail(info, "String::WriteUtf8 return: expected %d got %d", - encoded_utf_8_length + 1, retval); - return false; - } - if (nchars != utf_16_code_units) { - fail(info, - "String::WriteUtf8 set nchars to wrong value: expected %d got %d", - utf_16_code_units, nchars); - return false; - } - // cmp including terminator - if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { - fail(info, - "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", - c_string, buf); - return false; - } + LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, -1, &nchars)); + LOG_EXPR(nchars); + log_buffer(buf, retval + 1); - // try with ignoring nchars (it should not try to store anything in a nullptr) - if ((retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, nullptr)) != - encoded_utf_8_length + 1) { - fail(info, "String::WriteUtf8 return: expected %d got %d", - encoded_utf_8_length + 1, retval); - return false; - } - // cmp including terminator - if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { - fail(info, - "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", - c_string, buf); - return false; - } + memset(buf, 0x7f, sizeof buf); - if (v8_string->Utf8Length(isolate) != encoded_utf_8_length) { - fail(info, "String::Utf8Length returned wrong length: expected %d got %d", - encoded_utf_8_length, v8_string->Utf8Length(isolate)); - return false; - } + // try with ignoring nchars (it should not try to store anything in a + // nullptr) + LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, nullptr)); + log_buffer(buf, retval + 1); - if (v8_string->IsExternal()) { - fail(info, "String::IsExternal returned true"); - return false; - } + memset(buf, 0x7f, sizeof buf); - ok(info); - return true; + return ok(info); } void test_v8_string_ascii(const FunctionCallbackInfo &info) { - if (!perform_string_test(info, "hello world", 11, 11, "hello world")) { - // if perform_string_test failed, don't replace the return value with - // success in the below truncated test - return; - } - - // try with a length shorter than the string - Isolate *isolate = info.GetIsolate(); - Local v8_string = + auto string = String::NewFromUtf8(info.GetIsolate(), "hello world").ToLocalChecked(); - char buf[256]; - memset(buf, 0xaa, sizeof buf); - int retval; - int nchars; - if ((retval = v8_string->WriteUtf8(isolate, buf, 5, &nchars)) != 5) { - return fail(info, "String::WriteUtf8 return: expected 5 got %d", retval); - } - if (nchars != 5) { - return fail( - info, "String::WriteUtf8 set nchars to wrong value: expected 5 got %d", - nchars); - } - // check it did not write a terminator - if (memcmp(buf, "hello\xaa", 6) != 0) { - return fail(info, - "String::WriteUtf8 stored wrong data in buffer: expected " - "hello\\xaa got %s", - buf); - } + perform_string_test(info, string); } void test_v8_string_utf8(const FunctionCallbackInfo &info) { @@ -290,7 +166,9 @@ void test_v8_string_utf8(const FunctionCallbackInfo &info) { 143, 226, 128, 141, 226, 154, 167, 239, 184, 143, 0}; const char *trans_flag = reinterpret_cast(trans_flag_unsigned); - perform_string_test(info, trans_flag, 6, 16, trans_flag); + auto string = + String::NewFromUtf8(info.GetIsolate(), trans_flag).ToLocalChecked(); + perform_string_test(info, string); } void test_v8_string_invalid_utf8(const FunctionCallbackInfo &info) { @@ -298,12 +176,20 @@ void test_v8_string_invalid_utf8(const FunctionCallbackInfo &info) { 'o', 0xc2, '!', 0xf5, 0}; const char *mixed_sequence = reinterpret_cast(mixed_sequence_unsigned); - const unsigned char replaced_sequence_unsigned[] = { - 'o', 'h', ' ', 0xef, 0xbf, 0xbd, 'n', 'o', - 0xef, 0xbf, 0xbd, '!', 0xef, 0xbf, 0xbd, 0}; - const char *replaced_sequence = - reinterpret_cast(replaced_sequence_unsigned); - perform_string_test(info, mixed_sequence, 9, 15, replaced_sequence); + auto string = + String::NewFromUtf8(info.GetIsolate(), mixed_sequence).ToLocalChecked(); + perform_string_test(info, string); +} + +void test_v8_string_latin1(const FunctionCallbackInfo &info) { + const unsigned char latin1[] = {0xa1, 'b', 'u', 'n', '!', 0}; + auto string = + String::NewFromOneByte(info.GetIsolate(), latin1).ToLocalChecked(); + perform_string_test(info, string); + string = String::NewFromOneByte(info.GetIsolate(), latin1, + NewStringType::kNormal, 1) + .ToLocalChecked(); + perform_string_test(info, string); } void test_v8_string_write_utf8(const FunctionCallbackInfo &info) { @@ -336,6 +222,7 @@ void test_v8_external(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); int x = 5; Local external = External::New(isolate, &x); + LOG_EXPR(*reinterpret_cast(external->Value())); if (external->Value() != &x) { return fail(info, "External::Value() returned wrong pointer: expected %p got %p", @@ -346,43 +233,19 @@ void test_v8_external(const FunctionCallbackInfo &info) { void test_v8_object(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); Local obj = Object::New(isolate); auto key = String::NewFromUtf8(isolate, "key").ToLocalChecked(); auto val = Number::New(isolate, 5.0); - Maybe retval = Nothing(); - if ((retval = obj->Set(isolate->GetCurrentContext(), key, val)) != - Just(true)) { - return fail(info, "Object::Set wrong return: expected Just(true), got %s", - retval.IsNothing() ? "Nothing" : "Just(false)"); - } + Maybe set_status = obj->Set(context, key, val); + LOG_EXPR(set_status.IsJust()); + LOG_EXPR(set_status.FromJust()); - return ok(info); -} + // Local retval = obj->Get(context, key).ToLocalChecked(); + // LOG_EXPR(describe(isolate, retval)); -static std::string describe(Isolate *isolate, Local value) { - if (value->IsUndefined()) { - return "undefined"; - } else if (value->IsNull()) { - return "null"; - } else if (value->IsTrue()) { - return "true"; - } else if (value->IsFalse()) { - return "false"; - } else if (value->IsString()) { - char buf[1024] = {0}; - value.As()->WriteUtf8(isolate, buf, sizeof(buf) - 1); - std::string result = "\""; - result += buf; - result += "\""; - return result; - } else if (value->IsObject()) { - return "[object Object]"; - } else if (value->IsNumber()) { - return std::to_string(value.As()->Value()); - } else { - return "unknown"; - } + return ok(info); } void test_v8_array_new(const FunctionCallbackInfo &info) { @@ -398,18 +261,15 @@ void test_v8_array_new(const FunctionCallbackInfo &info) { Local v8_array = Array::New(isolate, vals, sizeof(vals) / sizeof(Local)); - if (v8_array->Length() != 5) { - return fail(info, "Array::Length wrong return: expected 5, got %" PRIu32, - v8_array->Length()); - } + LOG_EXPR(v8_array->Length()); for (uint32_t i = 0; i < 5; i++) { Local array_value = v8_array->Get(isolate->GetCurrentContext(), i).ToLocalChecked(); if (!array_value->StrictEquals(vals[i])) { - return fail(info, "array has wrong value at index %" PRIu32 ": %s", i, - describe(isolate, array_value).c_str()); + printf("array[%u] does not match\n", i); } + LOG_EXPR(describe(isolate, array_value)); } return ok(info); @@ -421,12 +281,7 @@ void test_v8_object_template(const FunctionCallbackInfo &info) { Local obj_template = ObjectTemplate::New(isolate); obj_template->SetInternalFieldCount(2); - if (obj_template->InternalFieldCount() != 2) { - return fail(info, - "ObjectTemplate did not remember internal field count: " - "expected 2, got %d", - obj_template->InternalFieldCount()); - } + LOG_EXPR(obj_template->InternalFieldCount()); Local obj1 = obj_template->NewInstance(context).ToLocalChecked(); obj1->SetInternalField(0, Number::New(isolate, 3.0)); @@ -436,30 +291,25 @@ void test_v8_object_template(const FunctionCallbackInfo &info) { obj2->SetInternalField(0, Number::New(isolate, 5.0)); obj2->SetInternalField(1, Number::New(isolate, 6.0)); - double value = obj1->GetInternalField(0).As()->Value(); - if (value != 3.0) { - return fail(info, - "obj1 internal field 0 has wrong value: expected 3.0, got %f", - value); - } - value = obj1->GetInternalField(1).As()->Value(); - if (value != 4.0) { - return fail(info, - "obj1 internal field 1 has wrong value: expected 4.0, got %f", - value); - } - value = obj2->GetInternalField(0).As()->Value(); - if (value != 5.0) { - return fail(info, - "obj2 internal field 0 has wrong value: expected 5.0, got %f", - value); - } - value = obj2->GetInternalField(1).As()->Value(); - if (value != 6.0) { - return fail(info, - "obj2 internal field 1 has wrong value: expected 6.0, got %f", - value); - } + LOG_EXPR(obj1->GetInternalField(0).As()->Value()); + LOG_EXPR(obj1->GetInternalField(1).As()->Value()); + LOG_EXPR(obj2->GetInternalField(0).As()->Value()); + LOG_EXPR(obj2->GetInternalField(1).As()->Value()); +} + +void return_data_callback(const FunctionCallbackInfo &info) { + info.GetReturnValue().Set(info.Data()); +} + +void create_function_with_data(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Local s = + String::NewFromUtf8(isolate, "hello world").ToLocalChecked(); + Local tmp = + FunctionTemplate::New(isolate, return_data_callback, s); + Local f = tmp->GetFunction(context).ToLocalChecked(); + info.GetReturnValue().Set(f); } void print_values_from_js(const FunctionCallbackInfo &info) { @@ -472,7 +322,152 @@ void print_values_from_js(const FunctionCallbackInfo &info) { return ok(info); } -void initialize(Local exports) { +class GlobalTestWrapper { +public: + static void set(const FunctionCallbackInfo &info); + static void get(const FunctionCallbackInfo &info); + static void cleanup(void *unused); + +private: + static Global value; +}; + +Global GlobalTestWrapper::value; + +void GlobalTestWrapper::set(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + if (value.IsEmpty()) { + info.GetReturnValue().Set(Undefined(isolate)); + } else { + info.GetReturnValue().Set(value.Get(isolate)); + } + value.Reset(isolate, info[0]); +} + +void GlobalTestWrapper::get(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + if (value.IsEmpty()) { + info.GetReturnValue().Set(Undefined(isolate)); + } else { + info.GetReturnValue().Set(value.Get(isolate)); + } +} + +void GlobalTestWrapper::cleanup(void *unused) { value.Reset(); } + +void test_many_v8_locals(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + Local nums[1000]; + for (int i = 0; i < 1000; i++) { + nums[i] = Number::New(isolate, (double)i + 0.5); + } + // try accessing them all to make sure the pointers are stable + for (int i = 0; i < 1000; i++) { + LOG_EXPR(nums[i]->Value()); + } +} + +static Local setup_object_with_string_field(Isolate *isolate, + Local context, + Local tmp, + int i, + const std::string &str) { + EscapableHandleScope ehs(isolate); + Local o = tmp->NewInstance(context).ToLocalChecked(); + // print_cell_location(o, "objects[%5d] ", i); + Local value = + String::NewFromUtf8(isolate, str.c_str()).ToLocalChecked(); + + o->SetInternalField(0, value); + return ehs.Escape(o); +} + +static void examine_object_fields(Isolate *isolate, Local o) { + char buf[16]; + HandleScope hs(isolate); + o->GetInternalField(0).As()->WriteUtf8(isolate, buf); + + Local field1 = o->GetInternalField(1); + if (field1.As()->IsString()) { + field1.As()->WriteUtf8(isolate, buf); + } +} + +void test_handle_scope_gc(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + // allocate a ton of objects + constexpr size_t num_small_allocs = 10000; + + Local mini_strings[num_small_allocs]; + for (size_t i = 0; i < num_small_allocs; i++) { + std::string cpp_str = std::to_string(i); + mini_strings[i] = + String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked(); + } + + // allocate some objects with internal fields, to check that those are traced + Local tmp = ObjectTemplate::New(isolate); + tmp->SetInternalFieldCount(2); + Local objects[num_small_allocs]; + + for (size_t i = 0; i < num_small_allocs; i++) { + std::string cpp_str = std::to_string(i + num_small_allocs); + // this uses a function so that the strings aren't kept alive by the current + // handle scope + objects[i] = + setup_object_with_string_field(isolate, context, tmp, i, cpp_str); + } + + // allocate some massive strings + // this should cause GC to start looking for objects to free + // after each big string allocation, we try reading all of the strings we + // created above to ensure they are still alive + constexpr size_t num_strings = 100; + constexpr size_t string_size = 20 * 1000 * 1000; + + auto string_data = new char[string_size]; + string_data[string_size - 1] = 0; + + Local huge_strings[num_strings]; + for (size_t i = 0; i < num_strings; i++) { + printf("%zu\n", i); + memset(string_data, i + 1, string_size - 1); + huge_strings[i] = + String::NewFromUtf8(isolate, string_data).ToLocalChecked(); + + // try to use all mini strings + for (size_t j = 0; j < num_small_allocs; j++) { + char buf[16]; + mini_strings[j]->WriteUtf8(isolate, buf); + } + + for (size_t j = 0; j < num_small_allocs; j++) { + examine_object_fields(isolate, objects[j]); + } + + if (i == 1) { + // add more internal fields to the objects a long time after they were + // created, to ensure these can also be traced + // make a new handlescope here so that the new strings we allocate are + // only referenced by the objects + HandleScope inner_hs(isolate); + for (auto &o : objects) { + int i = &o - &objects[0]; + auto cpp_str = std::to_string(i + 2 * num_small_allocs); + Local field = + String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked(); + o->SetInternalField(1, field); + } + } + } + + delete[] string_data; +} + +void initialize(Local exports, Local module, + Local context) { NODE_SET_METHOD(exports, "test_v8_native_call", test_v8_native_call); NODE_SET_METHOD(exports, "test_v8_primitives", test_v8_primitives); NODE_SET_METHOD(exports, "test_v8_number_int", test_v8_number_int); @@ -483,15 +478,26 @@ void initialize(Local exports) { NODE_SET_METHOD(exports, "test_v8_string_utf8", test_v8_string_utf8); NODE_SET_METHOD(exports, "test_v8_string_invalid_utf8", test_v8_string_invalid_utf8); + NODE_SET_METHOD(exports, "test_v8_string_latin1", test_v8_string_latin1); NODE_SET_METHOD(exports, "test_v8_string_write_utf8", test_v8_string_write_utf8); NODE_SET_METHOD(exports, "test_v8_external", test_v8_external); NODE_SET_METHOD(exports, "test_v8_object", test_v8_object); NODE_SET_METHOD(exports, "test_v8_array_new", test_v8_array_new); NODE_SET_METHOD(exports, "test_v8_object_template", test_v8_object_template); + NODE_SET_METHOD(exports, "create_function_with_data", + create_function_with_data); NODE_SET_METHOD(exports, "print_values_from_js", print_values_from_js); + NODE_SET_METHOD(exports, "global_get", GlobalTestWrapper::get); + NODE_SET_METHOD(exports, "global_set", GlobalTestWrapper::set); + NODE_SET_METHOD(exports, "test_many_v8_locals", test_many_v8_locals); + NODE_SET_METHOD(exports, "test_handle_scope_gc", test_handle_scope_gc); + + // without this, node hits a UAF deleting the Global + node::AddEnvironmentCleanupHook(context->GetIsolate(), + GlobalTestWrapper::cleanup, nullptr); } -NODE_MODULE(NODE_GYP_MODULE_NAME, initialize) +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, initialize) } // namespace v8tests diff --git a/test/v8/v8-module/main.js b/test/v8/v8-module/main.js index d7a252d3ff7662..1fdb23342f9b63 100644 --- a/test/v8/v8-module/main.js +++ b/test/v8/v8-module/main.js @@ -2,7 +2,7 @@ const buildMode = process.argv[5]; -const tests = require(`./build/${buildMode === "debug" ? "Debug" : "Release"}/v8tests`); +const tests = require("./module")(buildMode === "debug"); const testName = process.argv[2]; const args = JSON.parse(process.argv[3] ?? "[]"); diff --git a/test/v8/v8-module/module.js b/test/v8/v8-module/module.js new file mode 100644 index 00000000000000..b15152026be439 --- /dev/null +++ b/test/v8/v8-module/module.js @@ -0,0 +1,23 @@ +module.exports = debugMode => { + const nativeModule = require(`./build/${debugMode ? "Debug" : "Release"}/v8tests`); + return { + ...nativeModule, + test_v8_global() { + console.log(nativeModule.global_get()); + nativeModule.global_set(123); + console.log(nativeModule.global_get()); + nativeModule.global_set({ foo: 5, bar: ["one", "two", "three"] }); + if (process.isBun) { + Bun.gc(true); + } + console.log(JSON.stringify(nativeModule.global_get())); + }, + test_v8_function_template() { + const f = nativeModule.create_function_with_data(); + if (process.isBun) { + Bun.gc(true); + } + console.log(f()); + }, + }; +}; diff --git a/test/v8/v8.test.ts b/test/v8/v8.test.ts index 5ff85678ec697c..9d27cdd9528ce2 100644 --- a/test/v8/v8.test.ts +++ b/test/v8/v8.test.ts @@ -119,6 +119,9 @@ describe("String", () => { it("handles replacement correctly in strings with invalid UTF-8 sequences", () => { checkSameOutput("test_v8_string_invalid_utf8", []); }); + it("can create strings from null-terminated Latin-1 data", () => { + checkSameOutput("test_v8_string_latin1", []); + }); describe("WriteUtf8", () => { it("truncates the string correctly", () => { checkSameOutput("test_v8_string_write_utf8", []); @@ -150,6 +153,12 @@ describe("ObjectTemplate", () => { }); }); +describe("FunctionTemplate", () => { + it("keeps the data parameter alive", () => { + checkSameOutput("test_v8_function_template", []); + }); +}); + describe("Function", () => { it("correctly receives all its arguments from JS", () => { checkSameOutput("print_values_from_js", [5.0, true, null, false, "meow", {}], {}); @@ -170,6 +179,21 @@ describe("error handling", () => { }); }); +describe("Global", () => { + it("can create, modify, and read the value from global handles", () => { + checkSameOutput("test_v8_global", []); + }); +}); + +describe("HandleScope", () => { + it("can hold a lot of locals", () => { + checkSameOutput("test_many_v8_locals", []); + }); + it("keeps GC objects alive", () => { + checkSameOutput("test_handle_scope_gc", []); + }, 10000); +}); + afterAll(async () => { await Promise.all([ fs.rm(directories.bunRelease, { recursive: true, force: true }), @@ -228,9 +252,10 @@ function runOn(runtime: Runtime, buildMode: BuildMode, testName: string, jsArgs: env: bunEnv, }); const errs = exec.stderr.toString(); + const crashMsg = `test ${testName} crashed under ${Runtime[runtime]} in ${BuildMode[buildMode]} mode`; if (errs !== "") { - throw new Error(errs); + throw new Error(`${crashMsg}: ${errs}`); } - expect(exec.success, `test ${testName} crashed under ${Runtime[runtime]} in ${BuildMode[buildMode]} mode`).toBeTrue(); + expect(exec.success, crashMsg).toBeTrue(); return exec.stdout.toString(); }