mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
21 Commits
claude/fix
...
ben/v8-nod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a9cadb306 | ||
|
|
eac91e79bb | ||
|
|
b0c59dce13 | ||
|
|
e74804a6e6 | ||
|
|
638d66712e | ||
|
|
b004cc68a7 | ||
|
|
04cafa3f16 | ||
|
|
ed3ae95f51 | ||
|
|
0fef2bb90c | ||
|
|
a312af2da6 | ||
|
|
febaa58c42 | ||
|
|
2dd1e162e8 | ||
|
|
67e83f8e1b | ||
|
|
732cd43d59 | ||
|
|
cb3ed30308 | ||
|
|
647f22d921 | ||
|
|
2c4c1e1888 | ||
|
|
08841b9311 | ||
|
|
6db538d17b | ||
|
|
01c0551bd1 | ||
|
|
5e8074c00d |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
24
src/bun.js/bindings/v8/V8Integer.cpp
Normal file
24
src/bun.js/bindings/v8/V8Integer.cpp
Normal 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
|
||||
11
src/bun.js/bindings/v8/V8Integer.h
Normal file
11
src/bun.js/bindings/v8/V8Integer.h
Normal 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
|
||||
9
src/bun.js/bindings/v8/V8Name.h
Normal file
9
src/bun.js/bindings/v8/V8Name.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "V8Primitive.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
class Name : public Primitive {};
|
||||
|
||||
} // namespace v8
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user