Compare commits

...

21 Commits

Author SHA1 Message Date
Ben Grant
9a9cadb306 Merge branch 'main' into ben/v8-node-canvas-functions 2025-01-28 10:50:20 -08:00
Ben Grant
eac91e79bb Merge branch 'main' into ben/v8-node-canvas-functions 2024-10-01 12:17:39 -07:00
Ben Grant
b0c59dce13 Add v8::Value::ToString 2024-09-27 17:56:06 -07:00
Ben Grant
e74804a6e6 Add v8::Value::IsArray 2024-09-27 17:18:16 -07:00
Ben Grant
638d66712e Add layout check to v8::Integer 2024-09-27 16:50:39 -07:00
Ben Grant
b004cc68a7 Support v8::Value::NumberValue 2024-09-27 16:42:43 -07:00
Ben Grant
04cafa3f16 Add v8::api_internal::FromJustIsNothing 2024-09-27 16:42:43 -07:00
Ben Grant
ed3ae95f51 Add v8::Value::IsInt32 and v8::Integer 2024-09-27 16:42:43 -07:00
Ben Grant
0fef2bb90c Fix and test v8::Value::{IsUint32,Uint32Value} 2024-09-27 16:42:43 -07:00
Ben Grant
a312af2da6 Simplify control flow in v8::Object::Set 2024-09-27 16:42:43 -07:00
Ben Grant
febaa58c42 Fix error handling in v8::Object::Set 2024-09-27 16:42:43 -07:00
Ben Grant
2dd1e162e8 Add test for exceptions thrown in v8::Object::Set 2024-09-27 16:42:43 -07:00
Ben Grant
67e83f8e1b Implement v8::Function::Call 2024-09-27 16:42:43 -07:00
Ben Grant
732cd43d59 Stub v8::Template::Set 2024-09-27 16:42:43 -07:00
Ben Grant
cb3ed30308 Stub new v8::Object functions 2024-09-27 16:42:43 -07:00
Ben Grant
647f22d921 Stub new v8::FunctionTemplate functions 2024-09-27 16:42:43 -07:00
Ben Grant
2c4c1e1888 Add symbols for v8::Function::Call 2024-09-27 16:42:43 -07:00
Ben Grant
08841b9311 Add v8::Function tests and stub v8::Function::Call implementation 2024-09-27 16:42:43 -07:00
Ben Grant
6db538d17b Don't log the environment variables 2024-09-27 16:42:41 -07:00
Ben Grant
01c0551bd1 Restore environment variables in V8 test 2024-09-27 16:42:41 -07:00
Ben Grant
5e8074c00d Increase GC level when running V8 tests 2024-09-27 16:42:39 -07:00
22 changed files with 807 additions and 104 deletions

View File

@@ -35,4 +35,37 @@ Local<Value> Function::GetName() const
return handleScope->createLocal<Value>(globalObject->vm(), jsString);
}
MaybeLocal<Value> Function::Call(Local<Context> context, Local<Value> recv, int argc, Local<Value> argv[])
{
JSC::JSCell* func = localToCell();
JSC::CallData callData = JSC::getCallData(func);
auto* globalObject = context->globalObject();
auto& vm = globalObject->vm();
JSC::MarkedArgumentBuffer args;
if (argc > 0 && LIKELY(argv != nullptr)) {
auto* end = argv + argc;
for (auto* it = argv; it != end; ++it) {
args.append((*it)->localToJSValue());
}
}
JSC::JSValue thisValue = recv->localToJSValue();
auto scope = DECLARE_THROW_SCOPE(vm);
if (thisValue.isEmpty()) {
thisValue = JSC::jsUndefined();
}
JSC::JSValue result = call(globalObject, func, callData, thisValue, args);
if (result.isEmpty()) {
result = JSC::jsUndefined();
}
RETURN_IF_EXCEPTION(scope, MaybeLocal<Value>());
auto* handleScope = globalObject->V8GlobalInternals()->currentHandleScope();
RELEASE_AND_RETURN(scope, MaybeLocal<Value>(handleScope->createLocal<Value>(vm, result)));
}
} // namespace v8

View File

@@ -12,6 +12,7 @@ class Function : public Object {
public:
BUN_EXPORT void SetName(Local<String> name);
BUN_EXPORT Local<Value> GetName() const;
BUN_EXPORT MaybeLocal<Value> Call(Local<Context> context, Local<Value> recv, int argc, Local<Value> argv[]);
};
} // namespace v8

View File

@@ -72,4 +72,28 @@ MaybeLocal<Function> FunctionTemplate::GetFunction(Local<Context> context)
return globalInternals->currentHandleScope()->createLocal<Function>(vm, f);
}
bool FunctionTemplate::HasInstance(Local<Value> object)
{
V8_UNIMPLEMENTED();
return false;
}
void FunctionTemplate::SetClassName(Local<String> name)
{
V8_UNIMPLEMENTED();
(void)name;
}
Local<ObjectTemplate> FunctionTemplate::InstanceTemplate()
{
V8_UNIMPLEMENTED();
return Local<ObjectTemplate>();
}
Local<ObjectTemplate> FunctionTemplate::PrototypeTemplate()
{
V8_UNIMPLEMENTED();
return Local<ObjectTemplate>();
}
} // namespace v8

View File

@@ -9,6 +9,7 @@
#include "V8Signature.h"
#include "V8Template.h"
#include "V8FunctionCallbackInfo.h"
#include "V8String.h"
#include "shim/FunctionTemplate.h"
namespace v8 {
@@ -38,6 +39,8 @@ private:
const void* type_info;
};
class ObjectTemplate;
class FunctionTemplate : public Template {
public:
BUN_EXPORT static Local<FunctionTemplate> New(
@@ -55,6 +58,18 @@ public:
BUN_EXPORT MaybeLocal<Function> GetFunction(Local<Context> context);
// Check if object is an instance of this function template
BUN_EXPORT bool HasInstance(Local<Value> object);
// Set the name displayed when printing objects created with this FunctionTemplate as the constructor
BUN_EXPORT void SetClassName(Local<String> name);
// Get the template used for instances constructed with this function
BUN_EXPORT Local<ObjectTemplate> InstanceTemplate();
// Get the template used for the prototype object of the function created by this template
BUN_EXPORT Local<ObjectTemplate> PrototypeTemplate();
private:
shim::FunctionTemplate* localToObjectPointer()
{

View File

@@ -0,0 +1,24 @@
#include "V8Integer.h"
#include "V8HandleScope.h"
#include "v8_compatibility_assertions.h"
ASSERT_V8_TYPE_LAYOUT_MATCHES(v8::Integer)
namespace v8 {
int64_t Integer::Value() const
{
return localToJSValue().asAnyInt();
}
Local<Integer> Integer::New(Isolate* isolate, int32_t value)
{
return isolate->currentHandleScope()->createLocal<Integer>(isolate->vm(), JSC::jsNumber(value));
}
Local<Integer> Integer::NewFromUnsigned(Isolate* isolate, uint32_t value)
{
return isolate->currentHandleScope()->createLocal<Integer>(isolate->vm(), JSC::jsNumber(value));
}
} // namespace v8

View File

@@ -0,0 +1,11 @@
#pragma once
#include "V8Number.h"
namespace v8 {
class Integer : public Number {
BUN_EXPORT int64_t Value() const;
BUN_EXPORT static Local<Integer> New(Isolate* isolate, int32_t value);
BUN_EXPORT static Local<Integer> NewFromUnsigned(Isolate* isolate, uint32_t value);
};
} // namespace v8

View File

@@ -0,0 +1,9 @@
#pragma once
#include "V8Primitive.h"
namespace v8 {
class Name : public Primitive {};
} // namespace v8

View File

@@ -45,21 +45,20 @@ Maybe<bool> Object::Set(Local<Context> context, Local<Value> key, Local<Value> v
JSValue v = value->localToJSValue();
auto& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
auto scope = DECLARE_THROW_SCOPE(vm);
PutPropertySlot slot(object, false);
Identifier identifier = k.toPropertyKey(globalObject);
RETURN_IF_EXCEPTION(scope, Nothing<bool>());
if (!object->put(object, globalObject, identifier, v, slot)) {
scope.clearExceptionExceptTermination();
return Nothing<bool>();
if (!object->methodTable()->put(object, globalObject, identifier, v, slot)) {
// ProxyObject::performPut returns false if the JS handler returned a falsy value no matter
// the mode. V8 native functions run as if they are in sloppy mode, so we only consider a
// failure if the handler function actually threw, not if it returned false without
// throwing.
RETURN_IF_EXCEPTION(scope, Nothing<bool>());
}
if (scope.exception()) {
scope.clearException();
return Nothing<bool>();
}
return Just(true);
RELEASE_AND_RETURN(scope, Just(true));
}
void Object::SetInternalField(int index, Local<Data> data)
@@ -90,4 +89,29 @@ Local<Data> Object::SlowGetInternalField(int index)
return handleScope->createLocal<Data>(globalObject->vm(), JSC::jsUndefined());
}
MaybeLocal<Value> Object::Get(Local<Context> context, Local<Value> key)
{
V8_UNIMPLEMENTED();
return MaybeLocal<Value>();
}
MaybeLocal<Value> Object::Get(Local<Context> context, uint32_t index)
{
V8_UNIMPLEMENTED();
return MaybeLocal<Value>();
}
void Object::SetAlignedPointerInInternalField(int index, void* value)
{
V8_UNIMPLEMENTED();
(void)index;
(void)value;
}
void* Object::SlowGetAlignedPointerFromInternalField(int index)
{
V8_UNIMPLEMENTED();
return nullptr;
}
} // namespace v8

View File

@@ -7,6 +7,7 @@
#include "V8Maybe.h"
#include "V8Context.h"
#include "V8Data.h"
#include "v8/V8MaybeLocal.h"
namespace v8 {
@@ -18,8 +19,16 @@ public:
// usually inlined
BUN_EXPORT Local<Data> GetInternalField(int index);
// Set a 2-byte-aligned pointer in an internal field. The field may only be retrieved by
// GetAlignedPointerFromInternalField
BUN_EXPORT void SetAlignedPointerInInternalField(int index, void* value);
BUN_EXPORT MaybeLocal<Value> Get(Local<Context> context, Local<Value> key);
BUN_EXPORT MaybeLocal<Value> Get(Local<Context> context, uint32_t index);
private:
BUN_EXPORT Local<Data> SlowGetInternalField(int index);
BUN_EXPORT void* SlowGetAlignedPointerFromInternalField(int index);
};
} // namespace v8

View File

@@ -1,7 +1,7 @@
#pragma once
#include "v8.h"
#include "V8Primitive.h"
#include "V8Name.h"
#include "V8MaybeLocal.h"
#include "V8Isolate.h"
@@ -12,7 +12,7 @@ enum class NewStringType {
kInternalized,
};
class String : Primitive {
class String : public Name {
public:
enum WriteOptions {
NO_OPTIONS = 0,

View File

@@ -11,4 +11,9 @@ JSC::EncodedJSValue Template::DummyCallback(JSC::JSGlobalObject* globalObject, J
return JSC::JSValue::encode(JSC::jsUndefined());
}
void Template::Set(Local<Name> name, Local<Data> value, PropertyAttribute attribute)
{
V8_UNIMPLEMENTED();
}
} // namespace v8

View File

@@ -1,13 +1,27 @@
#pragma once
#include "V8Data.h"
#include "V8Name.h"
namespace v8 {
enum class PropertyAttribute {
None = 0,
// not writable
ReadOnly = 1 << 0,
// not enumerable
DontEnum = 1 << 1,
// not configurable
DontDelete = 1 << 2,
};
// matches V8 class hierarchy
class Template : public Data {
public:
static JSC_HOST_CALL_ATTRIBUTES JSC::EncodedJSValue DummyCallback(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
// Set a property on objects created by this template
BUN_EXPORT void Set(Local<Name> name, Local<Data> value, PropertyAttribute attribute = PropertyAttribute::None);
};
} // namespace v8

View File

@@ -1,5 +1,6 @@
#include "V8Value.h"
#include "V8Isolate.h"
#include "V8HandleScope.h"
#include "v8_compatibility_assertions.h"
ASSERT_V8_TYPE_LAYOUT_MATCHES(v8::Value)
@@ -23,7 +24,12 @@ bool Value::IsNumber() const
bool Value::IsUint32() const
{
return localToJSValue().isUInt32();
return localToJSValue().isUInt32AsAnyInt();
}
bool Value::IsInt32() const
{
return localToJSValue().isInt32AsAnyInt();
}
bool Value::IsUndefined() const
@@ -61,14 +67,39 @@ bool Value::IsFunction() const
return JSC::jsTypeofIsFunction(defaultGlobalObject(), localToJSValue());
}
bool Value::IsArray() const
{
JSC::JSValue val = localToJSValue();
return val.isCell() && val.asCell()->type() == JSC::ArrayType;
}
Maybe<uint32_t> Value::Uint32Value(Local<Context> context) const
{
auto js_value = localToJSValue();
uint32_t value;
if (js_value.getUInt32(value)) {
return Just(value);
}
return Nothing<uint32_t>();
auto scope = DECLARE_THROW_SCOPE(context->vm());
uint32_t num = js_value.toUInt32(context->globalObject());
RETURN_IF_EXCEPTION(scope, Nothing<uint32_t>());
RELEASE_AND_RETURN(scope, Just(num));
}
Maybe<double> Value::NumberValue(Local<Context> context) const
{
auto js_value = localToJSValue();
auto scope = DECLARE_THROW_SCOPE(context->vm());
double num = js_value.toNumber(context->globalObject());
RETURN_IF_EXCEPTION(scope, Nothing<double>());
RELEASE_AND_RETURN(scope, Just(num));
}
MaybeLocal<String> Value::ToString(Local<Context> context) const
{
auto js_value = localToJSValue();
auto& vm = context->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::JSString* string = js_value.toStringOrNull(context->globalObject());
RETURN_IF_EXCEPTION(scope, MaybeLocal<String>());
RELEASE_AND_RETURN(scope,
MaybeLocal<String>(context->currentHandleScope()->createLocal<String>(vm, string)));
}
bool Value::FullIsTrue() const

View File

@@ -4,6 +4,7 @@
#include "V8Maybe.h"
#include "V8Local.h"
#include "V8Context.h"
#include "V8MaybeLocal.h"
namespace v8 {
@@ -13,8 +14,12 @@ public:
BUN_EXPORT bool IsObject() const;
BUN_EXPORT bool IsNumber() const;
BUN_EXPORT bool IsUint32() const;
BUN_EXPORT bool IsInt32() const;
BUN_EXPORT bool IsFunction() const;
BUN_EXPORT bool IsArray() const;
BUN_EXPORT Maybe<uint32_t> Uint32Value(Local<Context> context) const;
BUN_EXPORT Maybe<double> NumberValue(Local<Context> context) const;
BUN_EXPORT MaybeLocal<String> ToString(Local<Context> context) const;
// usually inlined:
BUN_EXPORT bool IsUndefined() const;

View File

@@ -66,15 +66,11 @@ JSC::EncodedJSValue FunctionTemplate::functionCall(JSC::JSGlobalObject* globalOb
HandleScope hs(isolate);
// V8 function calls always run in "sloppy mode," even if the JS side is in strict mode. So if
// `this` is null or undefined, we use globalThis instead; otherwise, we convert `this` to an
// object.
JSC::JSObject* jscThis = globalObject->globalThis();
if (!callFrame->thisValue().isUndefinedOrNull()) {
jscThis = callFrame->thisValue().toObject(globalObject);
}
Local<Object> thisObject = hs.createLocal<Object>(vm, jscThis);
args[0] = thisObject.tagged();
// Functions created in the V8 API act like they were declared in a sloppy scope, even if the JS
// side is in strict mode, so we should always convert `this` according to the sloppy rules
JSC::JSValue jscThis = callFrame->thisValue().toThis(globalObject, JSC::ECMAMode::sloppy());
Local<Value> thisValue = hs.createLocal<Value>(vm, jscThis);
args[0] = thisValue.tagged();
for (size_t i = 0; i < callFrame->argumentCount(); i++) {
Local<Value> argValue = hs.createLocal<Value>(vm, callFrame->argument(i));

View File

@@ -1849,6 +1849,7 @@ const V8API = if (!bun.Environment.isWindows) struct {
pub extern fn _ZNK2v85Value8IsObjectEv() *anyopaque;
pub extern fn _ZNK2v85Value8IsNumberEv() *anyopaque;
pub extern fn _ZNK2v85Value8IsUint32Ev() *anyopaque;
pub extern fn _ZNK2v85Value7IsInt32Ev() *anyopaque;
pub extern fn _ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE() *anyopaque;
pub extern fn _ZNK2v85Value11IsUndefinedEv() *anyopaque;
pub extern fn _ZNK2v85Value6IsNullEv() *anyopaque;
@@ -1870,7 +1871,23 @@ const V8API = if (!bun.Environment.isWindows) struct {
pub extern fn _ZN2v812api_internal13DisposeGlobalEPm() *anyopaque;
pub extern fn _ZNK2v88Function7GetNameEv() *anyopaque;
pub extern fn _ZNK2v85Value10IsFunctionEv() *anyopaque;
pub extern fn _ZN2v88Function4CallENS_5LocalINS_7ContextEEENS1_INS_5ValueEEEiPS5_() *anyopaque;
pub extern fn _ZN2v816FunctionTemplate11HasInstanceENS_5LocalINS_5ValueEEE() *anyopaque;
pub extern fn _ZN2v816FunctionTemplate12SetClassNameENS_5LocalINS_6StringEEE() *anyopaque;
pub extern fn _ZN2v816FunctionTemplate16InstanceTemplateEv() *anyopaque;
pub extern fn _ZN2v816FunctionTemplate17PrototypeTemplateEv() *anyopaque;
pub extern fn _ZN2v86Object3GetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEE() *anyopaque;
pub extern fn _ZN2v86Object3GetENS_5LocalINS_7ContextEEEj() *anyopaque;
pub extern fn _ZN2v86Object38SlowGetAlignedPointerFromInternalFieldEi() *anyopaque;
pub extern fn _ZN2v86Object32SetAlignedPointerInInternalFieldEiPv() *anyopaque;
pub extern fn _ZN2v88Template3SetENS_5LocalINS_4NameEEENS1_INS_4DataEEENS_17PropertyAttributeE() *anyopaque;
pub extern fn _ZN2v87Integer15NewFromUnsignedEPNS_7IsolateEj() *anyopaque;
pub extern fn _ZN2v87Integer3NewEPNS_7IsolateEi() *anyopaque;
pub extern fn _ZNK2v87Integer5ValueEv() *anyopaque;
pub extern fn _ZN2v812api_internal17FromJustIsNothingEv() *anyopaque;
pub extern fn _ZNK2v85Value11NumberValueENS_5LocalINS_7ContextEEE() *anyopaque;
pub extern fn _ZNK2v85Value7IsArrayEv() *anyopaque;
pub extern fn _ZNK2v85Value8ToStringENS_5LocalINS_7ContextEEE() *anyopaque;
pub extern fn uv_os_getpid() *anyopaque;
pub extern fn uv_os_getppid() *anyopaque;
} else struct {
@@ -1920,6 +1937,7 @@ const V8API = if (!bun.Environment.isWindows) struct {
pub extern fn @"?IsObject@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?IsNumber@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?IsUint32@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?IsInt32@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?Uint32Value@Value@v8@@QEBA?AV?$Maybe@I@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
pub extern fn @"?IsUndefined@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?IsNull@Value@v8@@QEBA_NXZ"() *anyopaque;
@@ -1941,7 +1959,23 @@ const V8API = if (!bun.Environment.isWindows) struct {
pub extern fn @"?DisposeGlobal@api_internal@v8@@YAXPEA_K@Z"() *anyopaque;
pub extern fn @"?GetName@Function@v8@@QEBA?AV?$Local@VValue@v8@@@2@XZ"() *anyopaque;
pub extern fn @"?IsFunction@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?Call@Function@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@HQEAV52@@Z"() *anyopaque;
pub extern fn @"?HasInstance@FunctionTemplate@v8@@QEAA_NV?$Local@VValue@v8@@@2@@Z"() *anyopaque;
pub extern fn @"?SetClassName@FunctionTemplate@v8@@QEAAXV?$Local@VString@v8@@@2@@Z"() *anyopaque;
pub extern fn @"?InstanceTemplate@FunctionTemplate@v8@@QEAA?AV?$Local@VObjectTemplate@v8@@@2@XZ"() *anyopaque;
pub extern fn @"?PrototypeTemplate@FunctionTemplate@v8@@QEAA?AV?$Local@VObjectTemplate@v8@@@2@XZ"() *anyopaque;
pub extern fn @"?SetAlignedPointerInInternalField@Object@v8@@QEAAXHPEAX@Z"() *anyopaque;
pub extern fn @"?SlowGetAlignedPointerFromInternalField@Object@v8@@AEAAPEAXH@Z"() *anyopaque;
pub extern fn @"?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@@Z"() *anyopaque;
pub extern fn @"?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@I@Z"() *anyopaque;
pub extern fn @"?Set@Template@v8@@QEAAXV?$Local@VName@v8@@@2@V?$Local@VData@v8@@@2@W4PropertyAttribute@2@@Z"() *anyopaque;
pub extern fn @"?Value@Integer@v8@@QEBA_JXZ"() *anyopaque;
pub extern fn @"?NewFromUnsigned@Integer@v8@@SA?AV?$Local@VInteger@v8@@@2@PEAVIsolate@2@I@Z"() *anyopaque;
pub extern fn @"?New@Integer@v8@@SA?AV?$Local@VInteger@v8@@@2@PEAVIsolate@2@H@Z"() *anyopaque;
pub extern fn @"?FromJustIsNothing@api_internal@v8@@YAXXZ"() *anyopaque;
pub extern fn @"?NumberValue@Value@v8@@QEBA?AV?$Maybe@N@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
pub extern fn @"?IsArray@Value@v8@@QEBA_NXZ"() *anyopaque;
pub extern fn @"?ToString@Value@v8@@QEBA?AV?$MaybeLocal@VString@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque;
};
// To update this list, use find + multi-cursor in your editor.

View File

@@ -607,6 +607,7 @@ EXPORTS
?IsObject@Value@v8@@QEBA_NXZ
?IsNumber@Value@v8@@QEBA_NXZ
?IsUint32@Value@v8@@QEBA_NXZ
?IsInt32@Value@v8@@QEBA_NXZ
?Uint32Value@Value@v8@@QEBA?AV?$Maybe@I@2@V?$Local@VContext@v8@@@2@@Z
?IsUndefined@Value@v8@@QEBA_NXZ
?IsNull@Value@v8@@QEBA_NXZ
@@ -628,4 +629,20 @@ EXPORTS
?DisposeGlobal@api_internal@v8@@YAXPEA_K@Z
?GetName@Function@v8@@QEBA?AV?$Local@VValue@v8@@@2@XZ
?IsFunction@Value@v8@@QEBA_NXZ
?Call@Function@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@HQEAV52@@Z
?HasInstance@FunctionTemplate@v8@@QEAA_NV?$Local@VValue@v8@@@2@@Z
?SetClassName@FunctionTemplate@v8@@QEAAXV?$Local@VString@v8@@@2@@Z
?InstanceTemplate@FunctionTemplate@v8@@QEAA?AV?$Local@VObjectTemplate@v8@@@2@XZ
?PrototypeTemplate@FunctionTemplate@v8@@QEAA?AV?$Local@VObjectTemplate@v8@@@2@XZ
?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@@Z
?Get@Object@v8@@QEAA?AV?$MaybeLocal@VValue@v8@@@2@V?$Local@VContext@v8@@@2@I@Z
?SetAlignedPointerInInternalField@Object@v8@@QEAAXHPEAX@Z
?SlowGetAlignedPointerFromInternalField@Object@v8@@AEAAPEAXH@Z
?Set@Template@v8@@QEAAXV?$Local@VName@v8@@@2@V?$Local@VData@v8@@@2@W4PropertyAttribute@2@@Z
?Value@Integer@v8@@QEBA_JXZ
?NewFromUnsigned@Integer@v8@@SA?AV?$Local@VInteger@v8@@@2@PEAVIsolate@2@I@Z
?New@Integer@v8@@SA?AV?$Local@VInteger@v8@@@2@PEAVIsolate@2@H@Z
?FromJustIsNothing@api_internal@v8@@YAXXZ
?NumberValue@Value@v8@@QEBA?AV?$Maybe@N@2@V?$Local@VContext@v8@@@2@@Z
?IsArray@Value@v8@@QEBA_NXZ
?ToString@Value@v8@@QEBA?AV?$MaybeLocal@VString@v8@@@2@V?$Local@VContext@v8@@@2@@Z

View File

@@ -194,6 +194,7 @@
__ZNK2v85Value8IsObjectEv;
__ZNK2v85Value8IsNumberEv;
__ZNK2v85Value8IsUint32Ev;
__ZNK2v85Value7IsInt32Ev;
__ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE;
__ZNK2v85Value11IsUndefinedEv;
__ZNK2v85Value6IsNullEv;
@@ -215,7 +216,23 @@
__ZN2v812api_internal13DisposeGlobalEPm;
__ZNK2v88Function7GetNameEv;
__ZNK2v85Value10IsFunctionEv;
__ZN2v88Function4CallENS_5LocalINS_7ContextEEENS1_INS_5ValueEEEiPS5_;
__ZN2v816FunctionTemplate11HasInstanceENS_5LocalINS_5ValueEEE;
__ZN2v816FunctionTemplate12SetClassNameENS_5LocalINS_6StringEEE;
__ZN2v816FunctionTemplate16InstanceTemplateEv;
__ZN2v816FunctionTemplate17PrototypeTemplateEv;
__ZN2v86Object3GetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEE;
__ZN2v86Object3GetENS_5LocalINS_7ContextEEEj;
__ZN2v86Object32SetAlignedPointerInInternalFieldEiPv;
__ZN2v86Object38SlowGetAlignedPointerFromInternalFieldEi;
__ZN2v88Template3SetENS_5LocalINS_4NameEEENS1_INS_4DataEEENS_17PropertyAttributeE;
__ZN2v87Integer15NewFromUnsignedEPNS_7IsolateEj;
__ZN2v87Integer3NewEPNS_7IsolateEi;
__ZNK2v87Integer5ValueEv;
__ZN2v812api_internal17FromJustIsNothingEv;
__ZNK2v85Value11NumberValueENS_5LocalINS_7ContextEEE;
__ZNK2v85Value7IsArrayEv;
__ZNK2v85Value8ToStringENS_5LocalINS_7ContextEEE;
_uv_os_getpid;
_uv_os_getppid;
};

View File

@@ -193,6 +193,7 @@ __ZN2v820EscapableHandleScopeD2Ev
__ZNK2v85Value8IsObjectEv
__ZNK2v85Value8IsNumberEv
__ZNK2v85Value8IsUint32Ev
__ZNK2v85Value7IsInt32Ev
__ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE
__ZNK2v85Value11IsUndefinedEv
__ZNK2v85Value6IsNullEv
@@ -214,6 +215,22 @@ __ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEm
__ZN2v812api_internal13DisposeGlobalEPm
__ZNK2v88Function7GetNameEv
__ZNK2v85Value10IsFunctionEv
__ZN2v88Function4CallENS_5LocalINS_7ContextEEENS1_INS_5ValueEEEiPS5_
__ZN2v816FunctionTemplate11HasInstanceENS_5LocalINS_5ValueEEE
__ZN2v816FunctionTemplate12SetClassNameENS_5LocalINS_6StringEEE
__ZN2v816FunctionTemplate16InstanceTemplateEv
__ZN2v816FunctionTemplate17PrototypeTemplateEv
__ZN2v86Object3GetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEE
__ZN2v86Object3GetENS_5LocalINS_7ContextEEEj
__ZN2v86Object32SetAlignedPointerInInternalFieldEiPv
__ZN2v86Object38SlowGetAlignedPointerFromInternalFieldEi
__ZN2v88Template3SetENS_5LocalINS_4NameEEENS1_INS_4DataEEENS_17PropertyAttributeE
__ZN2v87Integer15NewFromUnsignedEPNS_7IsolateEj
__ZN2v87Integer3NewEPNS_7IsolateEi
__ZNK2v87Integer5ValueEv
__ZN2v812api_internal17FromJustIsNothingEv
__ZNK2v85Value11NumberValueENS_5LocalINS_7ContextEEE
__ZNK2v85Value7IsArrayEv
__ZNK2v85Value8ToStringENS_5LocalINS_7ContextEEE
_uv_os_getpid
_uv_os_getppid

View File

@@ -13,6 +13,17 @@ using namespace v8;
#define LOG_EXPR(e) std::cout << #e << " = " << (e) << std::endl
#define LOG_MAYBE(m) \
do { \
auto maybe__ = (m); \
std::cout << #m << " = "; \
if (maybe__.IsJust()) { \
std::cout << "Just(" << maybe__.FromJust() << ")" << std::endl; \
} else { \
std::cout << "Nothing" << std::endl; \
} \
} while (0)
#define LOG_VALUE_KIND(v) \
do { \
LOG_EXPR(v->IsUndefined()); \
@@ -24,6 +35,10 @@ using namespace v8;
LOG_EXPR(v->IsString()); \
LOG_EXPR(v->IsObject()); \
LOG_EXPR(v->IsNumber()); \
LOG_EXPR(v->IsUint32()); \
LOG_EXPR(v->IsInt32()); \
LOG_EXPR(v->IsArray()); \
LOG_EXPR(v->IsFunction()); \
} while (0)
namespace v8tests {
@@ -65,7 +80,9 @@ static std::string describe(Isolate *isolate, Local<Value> value) {
result += "()";
return result;
} else if (value->IsObject()) {
return "[object Object]";
return "{object}";
} else if (value->IsArray()) {
return "[array]";
} else if (value->IsNumber()) {
return std::to_string(value.As<Number>()->Value());
} else {
@@ -109,28 +126,81 @@ void test_v8_primitives(const FunctionCallbackInfo<Value> &info) {
return ok(info);
}
static void perform_number_test(const FunctionCallbackInfo<Value> &info,
static void
perform_number_and_integer_test(const FunctionCallbackInfo<Value> &info,
double number) {
Isolate *isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Number> v8_number = Number::New(isolate, number);
LOG_EXPR(v8_number->Value());
LOG_MAYBE(v8_number->Uint32Value(context));
LOG_VALUE_KIND(v8_number);
// we need to check if number can be a uint32 or a int32 before running
// these tests. first, check if it has a fractional part
double _int_part;
if (modf(number, &_int_part) == 0.0) {
if (number >= 0 && number <= UINT32_MAX) {
Local<Integer> v8_uint = Integer::NewFromUnsigned(isolate, number);
LOG_EXPR(v8_uint->Value());
LOG_VALUE_KIND(v8_uint);
}
if (number >= INT32_MIN && number <= INT32_MAX) {
Local<Integer> v8_int = Integer::New(isolate, number);
LOG_EXPR(v8_int->Value());
LOG_VALUE_KIND(v8_int);
}
}
return ok(info);
}
void test_v8_number_int(const FunctionCallbackInfo<Value> &info) {
perform_number_test(info, 123.0);
perform_number_and_integer_test(info, 123.0);
}
void test_v8_number_large_int(const FunctionCallbackInfo<Value> &info) {
// 2^33
perform_number_test(info, 8589934592.0);
// 2^31 (should fit as uint32 but not as int32)
perform_number_and_integer_test(info, 2147483648.0);
// 2^33 (should not fit as any 32-bit integer)
perform_number_and_integer_test(info, 8589934592.0);
}
void test_v8_number_fraction(const FunctionCallbackInfo<Value> &info) {
perform_number_test(info, 2.5);
perform_number_and_integer_test(info, 2.5);
}
void test_v8_value_uint32value_and_numbervalue(
const FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Value> vals[] = {
String::NewFromUtf8(isolate, "53").ToLocalChecked(),
Boolean::New(isolate, true),
Number::New(isolate, -1.5),
Number::New(isolate, 8589934593.9),
};
for (int i = 0; i < 4; i++) {
// should not throw for any of the values we check here
Maybe<uint32_t> maybe_u32 = vals[i]->Uint32Value(context);
LOG_MAYBE(maybe_u32);
Maybe<double> maybe_double = vals[i]->NumberValue(context);
LOG_MAYBE(maybe_double);
}
}
void call_uint32value_on_arg_from_js(const FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
LOG_MAYBE(info[0]->Uint32Value(context));
}
void call_numbervalue_on_arg_from_js(const FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
LOG_MAYBE(info[0]->NumberValue(context));
}
static void perform_string_test(const FunctionCallbackInfo<Value> &info,
@@ -271,12 +341,19 @@ void test_v8_object(const FunctionCallbackInfo<Value> &info) {
Local<Object> obj = Object::New(isolate);
auto key = String::NewFromUtf8(isolate, "key").ToLocalChecked();
auto val = Number::New(isolate, 5.0);
Maybe<bool> set_status = obj->Set(context, key, val);
LOG_EXPR(set_status.IsJust());
LOG_EXPR(set_status.FromJust());
LOG_MAYBE(obj->Set(context, key, val));
// Local<Value> retval = obj->Get(context, key).ToLocalChecked();
// LOG_EXPR(describe(isolate, retval));
return ok(info);
}
void set_field_from_js(const FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
LOG_EXPR(info[0]->IsObject());
Local<Object> obj = info[0].As<Object>();
Local<Value> key = info[1];
Local<Number> value = Number::New(isolate, 321.0);
LOG_MAYBE(obj->Set(context, key, value));
return ok(info);
}
@@ -348,6 +425,70 @@ void create_function_with_data(const FunctionCallbackInfo<Value> &info) {
info.GetReturnValue().Set(f);
}
void proto_method_callback(const FunctionCallbackInfo<Value> &info) {
printf("proto_method()\n");
info.GetReturnValue().Set(Number::New(info.GetIsolate(), 42.0));
}
void instance_accessor_getter(Local<Name> property,
const PropertyCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
printf("get %s()\n", describe(isolate, property).c_str());
printf("data = %s\n", describe(isolate, info.Data()).c_str());
info.GetReturnValue().Set(Number::New(info.GetIsolate(), 43.0));
}
void instance_accessor_setter(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<void> &info) {
Isolate *isolate = info.GetIsolate();
printf("set %s() to %s\n", describe(isolate, property).c_str(),
describe(isolate, value).c_str());
printf("data = %s\n", describe(isolate, info.Data()).c_str());
}
void create_object_from_template(const FunctionCallbackInfo<Value> &info) {
// https://v8.github.io/api/v12.4/classv8_1_1FunctionTemplate.html#details
Isolate *isolate = info.GetIsolate();
Local<FunctionTemplate> t = FunctionTemplate::New(isolate);
Local<String> func_property =
String::NewFromUtf8(isolate, "func_property").ToLocalChecked();
t->Set(func_property, Number::New(isolate, 1.0));
Local<ObjectTemplate> proto_t = t->PrototypeTemplate();
Local<String> proto_method =
String::NewFromUtf8(isolate, "proto_method").ToLocalChecked();
proto_t->Set(proto_method,
FunctionTemplate::New(isolate, proto_method_callback));
Local<String> proto_const =
String::NewFromUtf8(isolate, "proto_const").ToLocalChecked();
proto_t->Set(proto_const, Number::New(isolate, 2.0));
Local<ObjectTemplate> instance_t = t->InstanceTemplate();
// pass as Local<Name> instead of Local<String> to ensure we use the right
// overload
Local<Name> instance_accessor =
String::NewFromUtf8(isolate, "instance_accessor").ToLocalChecked();
instance_t->SetAccessor(instance_accessor, instance_accessor_getter,
instance_accessor_setter,
Number::New(isolate, 123.0));
// not trying to support handlers yet
// instance_t->SetHandler(
// NamedPropertyHandlerConfiguration(PropertyHandlerCallback));
Local<String> instance_property =
String::NewFromUtf8(isolate, "instance_property").ToLocalChecked();
instance_t->Set(instance_property, Number::New(isolate, 3.0));
// actually construct the object
Local<Context> context = isolate->GetCurrentContext();
Local<Function> function = t->GetFunction(context).ToLocalChecked();
Local<Object> instance = function->NewInstance(context).ToLocalChecked();
info.GetReturnValue().Set(instance);
}
void print_values_from_js(const FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
printf("%d arguments\n", info.Length());
@@ -362,6 +503,34 @@ void return_this(const FunctionCallbackInfo<Value> &info) {
info.GetReturnValue().Set(info.This());
}
void run_function_from_js(const FunctionCallbackInfo<Value> &info) {
// extract function, this value, and arguments
Local<Context> context = info.GetIsolate()->GetCurrentContext();
Local<Value> function_generic = info[0];
LOG_VALUE_KIND(function_generic);
Local<Function> function = function_generic.As<Function>();
Local<Value> jsThis = info[1];
int num_args = info.Length() - 2;
std::vector<Local<Value>> args(num_args);
for (int i = 0; i < num_args; i++) {
args[i] = info[i + 2];
}
char buf[1024] = {0};
function->GetName().As<String>()->WriteUtf8(info.GetIsolate(), buf,
sizeof(buf) - 1);
printf("function name seen from native = %s\n", buf);
MaybeLocal<Value> result =
function->Call(context, jsThis, num_args, args.data());
if (result.IsEmpty()) {
printf("callback threw an exception\n");
} else {
info.GetReturnValue().Set(result.ToLocalChecked());
}
}
class GlobalTestWrapper {
public:
static void set(const FunctionCallbackInfo<Value> &info);
@@ -490,60 +659,42 @@ void test_handle_scope_gc(const FunctionCallbackInfo<Value> &info) {
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 = 50;
constexpr size_t string_size = 20 * 1000 * 1000;
// force GC
run_gc(info);
auto string_data = new char[string_size];
string_data[string_size - 1] = 0;
// 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);
assert(atoi(buf) == (int)j);
}
Local<String> 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();
for (size_t j = 0; j < num_small_allocs; j++) {
examine_object_fields(isolate, objects[j], j + num_small_allocs,
j + 2 * num_small_allocs);
}
// 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);
assert(atoi(buf) == (int)j);
}
for (size_t j = 0; j < num_small_allocs; j++) {
examine_object_fields(isolate, objects[j], j + num_small_allocs,
j + 2 * num_small_allocs);
}
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<String> field =
String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked();
o->SetInternalField(1, field);
}
// add more internal fields to the objects after the first collection, to
// ensure these can also be traced. we 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<String> field =
String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked();
o->SetInternalField(1, field);
}
}
memset(string_data, 0, string_size);
for (size_t i = 0; i < num_strings; i++) {
huge_strings[i]->WriteUtf8(isolate, string_data);
for (size_t j = 0; j < string_size - 1; j++) {
assert(string_data[j] == (char)(i + 1));
}
}
run_gc(info);
delete[] string_data;
// make sure the new internal fields didn't get deleted
for (size_t j = 0; j < num_small_allocs; j++) {
examine_object_fields(isolate, objects[j], j + num_small_allocs,
j + 2 * num_small_allocs);
}
}
Local<String> escape_object(Isolate *isolate) {
@@ -574,6 +725,10 @@ void test_v8_escapable_handle_scope(const FunctionCallbackInfo<Value> &info) {
Local<Number> n = escape_smi(isolate);
Local<Boolean> t = escape_true(isolate);
// we don't trigger GC here because Bun's handle scope eagerly overwrites
// all handles once it goes out of scope, so the original handles created in
// those functions are already invalidated.
LOG_VALUE_KIND(s);
LOG_VALUE_KIND(n);
LOG_VALUE_KIND(t);
@@ -602,6 +757,16 @@ void test_uv_os_getppid(const FunctionCallbackInfo<Value> &info) {
return ok(info);
}
void call_value_to_string(const FunctionCallbackInfo<Value> &info) {
Local<Context> context = info.GetIsolate()->GetCurrentContext();
Local<Value> value = info[0];
MaybeLocal<String> str = value->ToString(context);
LOG_EXPR(str.IsEmpty());
if (!str.IsEmpty()) {
info.GetReturnValue().Set(str.ToLocalChecked());
}
}
void initialize(Local<Object> exports, Local<Value> module,
Local<Context> context) {
NODE_SET_METHOD(exports, "test_v8_native_call", test_v8_native_call);
@@ -610,6 +775,12 @@ void initialize(Local<Object> exports, Local<Value> module,
NODE_SET_METHOD(exports, "test_v8_number_large_int",
test_v8_number_large_int);
NODE_SET_METHOD(exports, "test_v8_number_fraction", test_v8_number_fraction);
NODE_SET_METHOD(exports, "test_v8_value_uint32value_and_numbervalue",
test_v8_value_uint32value_and_numbervalue);
NODE_SET_METHOD(exports, "call_uint32value_on_arg_from_js",
call_uint32value_on_arg_from_js);
NODE_SET_METHOD(exports, "call_numbervalue_on_arg_from_js",
call_numbervalue_on_arg_from_js);
NODE_SET_METHOD(exports, "test_v8_string_ascii", test_v8_string_ascii);
NODE_SET_METHOD(exports, "test_v8_string_utf8", test_v8_string_utf8);
NODE_SET_METHOD(exports, "test_v8_string_invalid_utf8",
@@ -619,12 +790,16 @@ void initialize(Local<Object> exports, Local<Value> module,
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, "set_field_from_js", set_field_from_js);
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, "create_object_from_template",
create_object_from_template);
NODE_SET_METHOD(exports, "print_values_from_js", print_values_from_js);
NODE_SET_METHOD(exports, "return_this", return_this);
NODE_SET_METHOD(exports, "run_function_from_js", run_function_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);
@@ -633,6 +808,7 @@ void initialize(Local<Object> exports, Local<Value> module,
test_v8_escapable_handle_scope);
NODE_SET_METHOD(exports, "test_uv_os_getpid", test_uv_os_getpid);
NODE_SET_METHOD(exports, "test_uv_os_getppid", test_uv_os_getppid);
NODE_SET_METHOD(exports, "call_value_to_string", call_value_to_string);
// without this, node hits a UAF deleting the Global
node::AddEnvironmentCleanupHook(context->GetIsolate(),

View File

@@ -1,8 +1,59 @@
// usually returns x, but overridden if x is a boxed String or equal to globalThis
// to overcome differences in bun vs. node's logging
function describeValue(x) {
if (x == globalThis) {
return "globalThis";
} else if (x instanceof String) {
return `boxed String: ${x.toString()}`;
} else if (x instanceof Object) {
return JSON.stringify(x);
} else {
return x;
}
}
function printValues() {
console.log(`this = ${typeof this}`, describeValue(this));
console.log(`${arguments.length} arguments`);
for (let i = 0; i < arguments.length; i++) {
console.log(`argument ${i} = ${typeof arguments[i]}`, describeValue(arguments[i]));
}
return "hello";
}
module.exports = debugMode => {
const nativeModule = require(`./build/${debugMode ? "Debug" : "Release"}/v8tests`);
return {
...nativeModule,
test_v8_value_uint32value_and_numbervalue_throw() {
// TODO(@190n) once Symbol and BigInt are supported in the V8 API, do this test in C++
const values = [
Symbol("20"),
190n,
{
[Symbol.toPrimitive]() {
throw new RangeError("oops");
},
},
];
for (const value of values) {
try {
nativeModule.call_uint32value_on_arg_from_js(value);
console.log(`Uint32Value() on ${typeof value} did not throw`);
} catch (e) {
console.log("threw", e.name);
}
try {
nativeModule.call_numbervalue_on_arg_from_js(value);
console.log(`NumberValue() on ${typeof value} did not throw`);
} catch (e) {
console.log("threw", e.name);
}
}
},
test_v8_global() {
console.log("global initial value =", nativeModule.global_get());
@@ -27,6 +78,27 @@ module.exports = debugMode => {
console.log(f());
},
test_v8_function_template_instance() {
const instance = nativeModule.create_object_from_template();
const constructor = instance.constructor;
console.log("instanceof =", instance instanceof constructor);
console.log("func_property =", constructor.func_property);
console.log("proto_method() on prototype =", constructor.prototype.proto_method());
console.log("proto_method() on instance =", instance.proto_method());
console.log("proto_const on prototype =", constructor.prototype.proto_const);
console.log("proto_const on instance =", instance.proto_const);
console.log("hasOwnProperty('proto_const') =", instance.hasOwnProperty("proto_const"));
console.log("instance_accessor on prototype =", constructor.prototype.instance_accessor);
console.log("instance_accessor on instance =", instance.instance_accessor);
console.log("hasOwnProperty('instance_accessor') =", instance.hasOwnProperty("instance_accessor"));
instance.instance_accessor = "hello";
console.log("instance_accessor on instance after assignment =", instance.instance_accessor);
console.log("instance_property on prototype =", constructor.prototype.instance_property);
console.log("instance_property on instance =", instance.instance_property);
console.log("hasOwnProperty('instance_property') =", instance.hasOwnProperty("instance_property"));
},
print_native_function() {
nativeModule.print_values_from_js(nativeModule.create_function_with_data());
},
@@ -35,15 +107,150 @@ module.exports = debugMode => {
for (const thisValue of [null, undefined, 5, "abc"]) {
const ret = nativeModule.return_this.call(thisValue);
console.log("typeof =", typeof ret);
if (ret == globalThis) {
console.log("returned globalThis");
} else if (ret instanceof String) {
console.log("returned boxed String:", ret.toString());
} else {
console.log("returned", ret);
}
console.log("returned", describeValue(ret));
console.log("constructor is", ret.constructor.name);
}
},
call_js_functions_from_native() {
console.log(
"nativeModule.run_function_from_js returned",
nativeModule.run_function_from_js(printValues, 1, 2, 3, { foo: "bar" }),
);
try {
nativeModule.run_function_from_js(function () {
printValues.apply(this, arguments);
throw new Error("oh no");
}, null);
console.log("nativeModule.run_function_from_js did not throw");
} catch (e) {
console.log("nativeModule.run_function_from_js threw:", e.toString());
}
},
call_native_function_from_native() {
console.log(
"nativeModule.run_function_from_js returned",
nativeModule.run_function_from_js(nativeModule.create_function_with_data(), null),
);
},
test_v8_object_set_proxy() {
"use strict";
const object = {};
const proxy = new Proxy(object, {
set(obj, prop, value) {
obj[prop] = 2 * value;
// should NOT throw because the native function behaves as if it is in sloppy mode
return false;
},
});
nativeModule.set_field_from_js(proxy, "foo");
console.log("proxy.foo =", proxy.foo);
console.log("object.foo =", object.foo);
},
test_v8_object_set_failure() {
const object = {};
const key = {
toString() {
throw new Error("thrown by key.toString()");
},
};
console.log("=== key with a toString() that throws ===");
try {
nativeModule.set_field_from_js(object, key);
console.log("no exception while setting with key that throws in toString()");
} catch (e) {
console.log(e.toString());
}
const setterThrows = new Proxy(object, {
set(obj, prop, value) {
throw new Error(`proxy setting ${prop} to ${value}`);
},
});
console.log("=== proxy that throws in set() ===");
try {
nativeModule.set_field_from_js(setterThrows, "xyz");
console.log("no exception while setting on Proxy that throws");
} catch (e) {
console.log(e.toString());
}
console.log("after setting, object.xyz is", object.xyz);
const onlyGetter = {
get foo() {
return 5;
},
};
console.log("=== object with only a getter for the key ===");
try {
nativeModule.set_field_from_js(onlyGetter, "foo");
// apparently this is expected in node
console.log("no exception while setting a key that only has a getter");
} catch (e) {
console.log(e.toString());
}
console.log("after setting, onlyGetter.foo is", onlyGetter.foo);
},
test_v8_value_to_string() {
for (const value of [
null,
undefined,
true,
false,
5.0,
190n,
"foo bar",
{},
[],
{
toString() {
return "abc";
},
[Symbol.toPrimitive]() {
return "123";
},
},
]) {
console.log(nativeModule.call_value_to_string(value));
}
},
test_v8_value_to_string_exceptions() {
for (const value of [
Symbol("abc"),
{
toString() {
throw new TypeError("oops");
},
},
{
[Symbol.toPrimitive]() {
throw new RangeError("oops");
},
},
]) {
try {
const string = nativeModule.call_value_to_string(value);
console.log(`returned '${string}' instead of throwing`);
} catch (e) {
console.log("threw", e.name);
}
}
},
};
};

View File

@@ -88,7 +88,7 @@ async function build(
};
}
describe.todoIf(isBroken && isMusl)("node:v8", () => {
describe.todoIf(isBroken && isMusl)("V8 C++ API", () => {
beforeAll(async () => {
// set up clean directories for our 4 builds
directories.bunRelease = tmpdirSync();
@@ -127,18 +127,36 @@ describe.todoIf(isBroken && isMusl)("node:v8", () => {
});
});
describe("Number", () => {
it("can create small integer", async () => {
await checkSameOutput("test_v8_number_int", []);
describe("Number & Integer", () => {
it("can create small integer", async () => {
await checkSameOutput("test_v8_number_int", []);
});
it("can create large integer", async () => {
await checkSameOutput("test_v8_number_large_int", []);
});
it("can create fraction", async () => {
await checkSameOutput("test_v8_number_fraction", []);
});
});
describe("Value", () => {
describe("Uint32Value & NumberValue", () => {
it("coerces correctly", async () => {
await checkSameOutput("test_v8_value_uint32value_and_numbervalue", []);
});
// non-i32 v8::Number is not implemented yet
it("can create large integer", async () => {
await checkSameOutput("test_v8_number_large_int", []);
});
it("can create fraction", async () => {
await checkSameOutput("test_v8_number_fraction", []);
it("throws in the right cases", async () => {
await checkSameOutput("test_v8_value_uint32value_and_numbervalue_throw", []);
});
});
describe("ToString", () => {
it("returns the right result", async () => {
await checkSameOutput("test_v8_value_to_string", []);
});
it("handles exceptions", async () => {
await checkSameOutput("test_v8_value_to_string_exceptions", []);
});
});
});
describe("String", () => {
it("can create and read back strings with only ASCII characters", async () => {
@@ -171,8 +189,14 @@ describe.todoIf(isBroken && isMusl)("node:v8", () => {
it("can create an object and set properties", async () => {
await checkSameOutput("test_v8_object", []);
});
it("uses proxies properly", async () => {
await checkSameOutput("test_v8_object_set_proxy", []);
});
it("can handle failure in Set()", async () => {
await checkSameOutput("test_v8_object_set_failure", []);
});
});
describe("Array", () => {
describe("Array", () => {
// v8::Array::New is broken as it still tries to reinterpret locals as JSValues
it.skip("can create an array from a C array of Locals", async () => {
await checkSameOutput("test_v8_array_new", []);
@@ -189,17 +213,27 @@ describe.todoIf(isBroken && isMusl)("node:v8", () => {
it("keeps the data parameter alive", async () => {
await checkSameOutput("test_v8_function_template", []);
});
// calls tons of functions we don't implement yet
it.skip("can create an object with prototype properties, instance properties, and an accessor", async () => {
await checkSameOutput("test_v8_function_template_instance", []);
});
});
describe("Function", () => {
// TODO call native from native and napi from native
it("correctly receives all its arguments from JS", async () => {
await checkSameOutput("print_values_from_js", [5.0, true, null, false, "async meow", {}]);
// this will print out all the values we pass it
await checkSameOutput("print_values_from_js", [5.0, true, null, false, "meow", {}, [5, 6]]);
await checkSameOutput("print_native_function", []);
});
it("correctly receives the this value from JS", async () => {
await checkSameOutput("call_function_with_weird_this_values", []);
});
it("can call a JS function from native code", async () => {
await checkSameOutput("call_js_functions_from_native", []);
});
});
describe("error handling", () => {