mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 16:10:02 +01:00
47a9eea8c8
Since the API we expose through the `v8` module is Buffer-based, we cannot transfer WASM modules directly. Instead, we enable the V8-provided inline WASM (de)serialization for WASM modules. PR-URL: https://github.com/nodejs/node/pull/25313 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Gus Caplan <me@gus.host>
503 lines
17 KiB
C++
503 lines
17 KiB
C++
#include "node_internals.h"
|
|
#include "node_buffer.h"
|
|
#include "node_errors.h"
|
|
#include "base_object-inl.h"
|
|
|
|
namespace node {
|
|
|
|
using v8::Array;
|
|
using v8::ArrayBuffer;
|
|
using v8::Context;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Just;
|
|
using v8::Local;
|
|
using v8::Maybe;
|
|
using v8::MaybeLocal;
|
|
using v8::Nothing;
|
|
using v8::Object;
|
|
using v8::SharedArrayBuffer;
|
|
using v8::String;
|
|
using v8::Value;
|
|
using v8::ValueDeserializer;
|
|
using v8::ValueSerializer;
|
|
|
|
namespace {
|
|
|
|
class SerializerContext : public BaseObject,
|
|
public ValueSerializer::Delegate {
|
|
public:
|
|
SerializerContext(Environment* env,
|
|
Local<Object> wrap);
|
|
|
|
~SerializerContext() {}
|
|
|
|
void ThrowDataCloneError(Local<String> message) override;
|
|
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override;
|
|
Maybe<uint32_t> GetSharedArrayBufferId(
|
|
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override;
|
|
|
|
static void SetTreatArrayBufferViewsAsHostObjects(
|
|
const FunctionCallbackInfo<Value>& args);
|
|
|
|
static void New(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteHeader(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteValue(const FunctionCallbackInfo<Value>& args);
|
|
static void ReleaseBuffer(const FunctionCallbackInfo<Value>& args);
|
|
static void TransferArrayBuffer(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteUint32(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteUint64(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteDouble(const FunctionCallbackInfo<Value>& args);
|
|
static void WriteRawBytes(const FunctionCallbackInfo<Value>& args);
|
|
|
|
SET_NO_MEMORY_INFO()
|
|
SET_MEMORY_INFO_NAME(SerializerContext)
|
|
SET_SELF_SIZE(SerializerContext)
|
|
|
|
private:
|
|
ValueSerializer serializer_;
|
|
};
|
|
|
|
class DeserializerContext : public BaseObject,
|
|
public ValueDeserializer::Delegate {
|
|
public:
|
|
DeserializerContext(Environment* env,
|
|
Local<Object> wrap,
|
|
Local<Value> buffer);
|
|
|
|
~DeserializerContext() {}
|
|
|
|
MaybeLocal<Object> ReadHostObject(Isolate* isolate) override;
|
|
|
|
static void New(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadHeader(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadValue(const FunctionCallbackInfo<Value>& args);
|
|
static void TransferArrayBuffer(const FunctionCallbackInfo<Value>& args);
|
|
static void GetWireFormatVersion(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadUint32(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadUint64(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadDouble(const FunctionCallbackInfo<Value>& args);
|
|
static void ReadRawBytes(const FunctionCallbackInfo<Value>& args);
|
|
|
|
SET_NO_MEMORY_INFO()
|
|
SET_MEMORY_INFO_NAME(DeserializerContext)
|
|
SET_SELF_SIZE(DeserializerContext)
|
|
|
|
private:
|
|
const uint8_t* data_;
|
|
const size_t length_;
|
|
|
|
ValueDeserializer deserializer_;
|
|
};
|
|
|
|
SerializerContext::SerializerContext(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap),
|
|
serializer_(env->isolate(), this) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void SerializerContext::ThrowDataCloneError(Local<String> message) {
|
|
Local<Value> args[1] = { message };
|
|
Local<Value> get_data_clone_error =
|
|
object()->Get(env()->context(),
|
|
env()->get_data_clone_error_string())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(get_data_clone_error->IsFunction());
|
|
MaybeLocal<Value> error =
|
|
get_data_clone_error.As<Function>()->Call(env()->context(),
|
|
object(),
|
|
arraysize(args),
|
|
args);
|
|
|
|
if (error.IsEmpty()) return;
|
|
|
|
env()->isolate()->ThrowException(error.ToLocalChecked());
|
|
}
|
|
|
|
Maybe<uint32_t> SerializerContext::GetSharedArrayBufferId(
|
|
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) {
|
|
Local<Value> args[1] = { shared_array_buffer };
|
|
Local<Value> get_shared_array_buffer_id =
|
|
object()->Get(env()->context(),
|
|
env()->get_shared_array_buffer_id_string())
|
|
.ToLocalChecked();
|
|
|
|
if (!get_shared_array_buffer_id->IsFunction()) {
|
|
return ValueSerializer::Delegate::GetSharedArrayBufferId(
|
|
isolate, shared_array_buffer);
|
|
}
|
|
|
|
MaybeLocal<Value> id =
|
|
get_shared_array_buffer_id.As<Function>()->Call(env()->context(),
|
|
object(),
|
|
arraysize(args),
|
|
args);
|
|
|
|
if (id.IsEmpty()) return Nothing<uint32_t>();
|
|
|
|
return id.ToLocalChecked()->Uint32Value(env()->context());
|
|
}
|
|
|
|
Maybe<bool> SerializerContext::WriteHostObject(Isolate* isolate,
|
|
Local<Object> input) {
|
|
MaybeLocal<Value> ret;
|
|
Local<Value> args[1] = { input };
|
|
|
|
Local<Value> write_host_object =
|
|
object()->Get(env()->context(),
|
|
env()->write_host_object_string()).ToLocalChecked();
|
|
|
|
if (!write_host_object->IsFunction()) {
|
|
return ValueSerializer::Delegate::WriteHostObject(isolate, input);
|
|
}
|
|
|
|
ret = write_host_object.As<Function>()->Call(env()->context(),
|
|
object(),
|
|
arraysize(args),
|
|
args);
|
|
|
|
if (ret.IsEmpty())
|
|
return Nothing<bool>();
|
|
|
|
return Just(true);
|
|
}
|
|
|
|
void SerializerContext::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new SerializerContext(env, args.This());
|
|
}
|
|
|
|
void SerializerContext::WriteHeader(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
ctx->serializer_.WriteHeader();
|
|
}
|
|
|
|
void SerializerContext::WriteValue(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
Maybe<bool> ret =
|
|
ctx->serializer_.WriteValue(ctx->env()->context(), args[0]);
|
|
|
|
if (ret.IsJust()) args.GetReturnValue().Set(ret.FromJust());
|
|
}
|
|
|
|
void SerializerContext::SetTreatArrayBufferViewsAsHostObjects(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
bool value = args[0]->BooleanValue(ctx->env()->isolate());
|
|
ctx->serializer_.SetTreatArrayBufferViewsAsHostObjects(value);
|
|
}
|
|
|
|
void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
std::pair<uint8_t*, size_t> ret = ctx->serializer_.Release();
|
|
auto buf = Buffer::New(ctx->env(),
|
|
reinterpret_cast<char*>(ret.first),
|
|
ret.second);
|
|
|
|
if (!buf.IsEmpty()) {
|
|
args.GetReturnValue().Set(buf.ToLocalChecked());
|
|
}
|
|
}
|
|
|
|
void SerializerContext::TransferArrayBuffer(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<uint32_t> id = args[0]->Uint32Value(ctx->env()->context());
|
|
if (id.IsNothing()) return;
|
|
|
|
if (!args[1]->IsArrayBuffer())
|
|
return node::THROW_ERR_INVALID_ARG_TYPE(
|
|
ctx->env(), "arrayBuffer must be an ArrayBuffer");
|
|
|
|
Local<ArrayBuffer> ab = args[1].As<ArrayBuffer>();
|
|
ctx->serializer_.TransferArrayBuffer(id.FromJust(), ab);
|
|
return;
|
|
}
|
|
|
|
void SerializerContext::WriteUint32(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<uint32_t> value = args[0]->Uint32Value(ctx->env()->context());
|
|
if (value.IsNothing()) return;
|
|
|
|
ctx->serializer_.WriteUint32(value.FromJust());
|
|
}
|
|
|
|
void SerializerContext::WriteUint64(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<uint32_t> arg0 = args[0]->Uint32Value(ctx->env()->context());
|
|
Maybe<uint32_t> arg1 = args[1]->Uint32Value(ctx->env()->context());
|
|
if (arg0.IsNothing() || arg1.IsNothing())
|
|
return;
|
|
|
|
uint64_t hi = arg0.FromJust();
|
|
uint64_t lo = arg1.FromJust();
|
|
ctx->serializer_.WriteUint64((hi << 32) | lo);
|
|
}
|
|
|
|
void SerializerContext::WriteDouble(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<double> value = args[0]->NumberValue(ctx->env()->context());
|
|
if (value.IsNothing()) return;
|
|
|
|
ctx->serializer_.WriteDouble(value.FromJust());
|
|
}
|
|
|
|
void SerializerContext::WriteRawBytes(const FunctionCallbackInfo<Value>& args) {
|
|
SerializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
if (!args[0]->IsArrayBufferView()) {
|
|
return node::THROW_ERR_INVALID_ARG_TYPE(
|
|
ctx->env(), "source must be a TypedArray or a DataView");
|
|
}
|
|
|
|
ctx->serializer_.WriteRawBytes(Buffer::Data(args[0]),
|
|
Buffer::Length(args[0]));
|
|
}
|
|
|
|
DeserializerContext::DeserializerContext(Environment* env,
|
|
Local<Object> wrap,
|
|
Local<Value> buffer)
|
|
: BaseObject(env, wrap),
|
|
data_(reinterpret_cast<const uint8_t*>(Buffer::Data(buffer))),
|
|
length_(Buffer::Length(buffer)),
|
|
deserializer_(env->isolate(), data_, length_, this) {
|
|
object()->Set(env->context(), env->buffer_string(), buffer).FromJust();
|
|
deserializer_.SetExpectInlineWasm(true);
|
|
|
|
MakeWeak();
|
|
}
|
|
|
|
MaybeLocal<Object> DeserializerContext::ReadHostObject(Isolate* isolate) {
|
|
Local<Value> read_host_object =
|
|
object()->Get(env()->context(),
|
|
env()->read_host_object_string()).ToLocalChecked();
|
|
|
|
if (!read_host_object->IsFunction()) {
|
|
return ValueDeserializer::Delegate::ReadHostObject(isolate);
|
|
}
|
|
|
|
Isolate::AllowJavascriptExecutionScope allow_js(isolate);
|
|
MaybeLocal<Value> ret =
|
|
read_host_object.As<Function>()->Call(env()->context(),
|
|
object(),
|
|
0,
|
|
nullptr);
|
|
|
|
if (ret.IsEmpty())
|
|
return MaybeLocal<Object>();
|
|
|
|
Local<Value> return_value = ret.ToLocalChecked();
|
|
if (!return_value->IsObject()) {
|
|
env()->ThrowTypeError("readHostObject must return an object");
|
|
return MaybeLocal<Object>();
|
|
}
|
|
|
|
return return_value.As<Object>();
|
|
}
|
|
|
|
void DeserializerContext::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (!args[0]->IsArrayBufferView()) {
|
|
return node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "buffer must be a TypedArray or a DataView");
|
|
}
|
|
|
|
new DeserializerContext(env, args.This(), args[0]);
|
|
}
|
|
|
|
void DeserializerContext::ReadHeader(const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<bool> ret = ctx->deserializer_.ReadHeader(ctx->env()->context());
|
|
|
|
if (ret.IsJust()) args.GetReturnValue().Set(ret.FromJust());
|
|
}
|
|
|
|
void DeserializerContext::ReadValue(const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
MaybeLocal<Value> ret = ctx->deserializer_.ReadValue(ctx->env()->context());
|
|
|
|
if (!ret.IsEmpty()) args.GetReturnValue().Set(ret.ToLocalChecked());
|
|
}
|
|
|
|
void DeserializerContext::TransferArrayBuffer(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<uint32_t> id = args[0]->Uint32Value(ctx->env()->context());
|
|
if (id.IsNothing()) return;
|
|
|
|
if (args[1]->IsArrayBuffer()) {
|
|
Local<ArrayBuffer> ab = args[1].As<ArrayBuffer>();
|
|
ctx->deserializer_.TransferArrayBuffer(id.FromJust(), ab);
|
|
return;
|
|
}
|
|
|
|
if (args[1]->IsSharedArrayBuffer()) {
|
|
Local<SharedArrayBuffer> sab = args[1].As<SharedArrayBuffer>();
|
|
ctx->deserializer_.TransferSharedArrayBuffer(id.FromJust(), sab);
|
|
return;
|
|
}
|
|
|
|
return node::THROW_ERR_INVALID_ARG_TYPE(
|
|
ctx->env(), "arrayBuffer must be an ArrayBuffer or SharedArrayBuffer");
|
|
}
|
|
|
|
void DeserializerContext::GetWireFormatVersion(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
args.GetReturnValue().Set(ctx->deserializer_.GetWireFormatVersion());
|
|
}
|
|
|
|
void DeserializerContext::ReadUint32(const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
uint32_t value;
|
|
bool ok = ctx->deserializer_.ReadUint32(&value);
|
|
if (!ok) return ctx->env()->ThrowError("ReadUint32() failed");
|
|
return args.GetReturnValue().Set(value);
|
|
}
|
|
|
|
void DeserializerContext::ReadUint64(const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
uint64_t value;
|
|
bool ok = ctx->deserializer_.ReadUint64(&value);
|
|
if (!ok) return ctx->env()->ThrowError("ReadUint64() failed");
|
|
|
|
uint32_t hi = static_cast<uint32_t>(value >> 32);
|
|
uint32_t lo = static_cast<uint32_t>(value);
|
|
|
|
Isolate* isolate = ctx->env()->isolate();
|
|
|
|
Local<Value> ret[] = {
|
|
Integer::NewFromUnsigned(isolate, hi),
|
|
Integer::NewFromUnsigned(isolate, lo)
|
|
};
|
|
return args.GetReturnValue().Set(Array::New(isolate, ret, arraysize(ret)));
|
|
}
|
|
|
|
void DeserializerContext::ReadDouble(const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
double value;
|
|
bool ok = ctx->deserializer_.ReadDouble(&value);
|
|
if (!ok) return ctx->env()->ThrowError("ReadDouble() failed");
|
|
return args.GetReturnValue().Set(value);
|
|
}
|
|
|
|
void DeserializerContext::ReadRawBytes(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
DeserializerContext* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
Maybe<int64_t> length_arg = args[0]->IntegerValue(ctx->env()->context());
|
|
if (length_arg.IsNothing()) return;
|
|
size_t length = length_arg.FromJust();
|
|
|
|
const void* data;
|
|
bool ok = ctx->deserializer_.ReadRawBytes(length, &data);
|
|
if (!ok) return ctx->env()->ThrowError("ReadRawBytes() failed");
|
|
|
|
const uint8_t* position = reinterpret_cast<const uint8_t*>(data);
|
|
CHECK_GE(position, ctx->data_);
|
|
CHECK_LE(position + length, ctx->data_ + ctx->length_);
|
|
|
|
const uint32_t offset = position - ctx->data_;
|
|
CHECK_EQ(ctx->data_ + offset, position);
|
|
|
|
args.GetReturnValue().Set(offset);
|
|
}
|
|
|
|
void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
Local<FunctionTemplate> ser =
|
|
env->NewFunctionTemplate(SerializerContext::New);
|
|
|
|
ser->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
env->SetProtoMethod(ser, "writeHeader", SerializerContext::WriteHeader);
|
|
env->SetProtoMethod(ser, "writeValue", SerializerContext::WriteValue);
|
|
env->SetProtoMethod(ser, "releaseBuffer", SerializerContext::ReleaseBuffer);
|
|
env->SetProtoMethod(ser,
|
|
"transferArrayBuffer",
|
|
SerializerContext::TransferArrayBuffer);
|
|
env->SetProtoMethod(ser, "writeUint32", SerializerContext::WriteUint32);
|
|
env->SetProtoMethod(ser, "writeUint64", SerializerContext::WriteUint64);
|
|
env->SetProtoMethod(ser, "writeDouble", SerializerContext::WriteDouble);
|
|
env->SetProtoMethod(ser, "writeRawBytes", SerializerContext::WriteRawBytes);
|
|
env->SetProtoMethod(ser,
|
|
"_setTreatArrayBufferViewsAsHostObjects",
|
|
SerializerContext::SetTreatArrayBufferViewsAsHostObjects);
|
|
|
|
Local<String> serializerString =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Serializer");
|
|
ser->SetClassName(serializerString);
|
|
target->Set(env->context(),
|
|
serializerString,
|
|
ser->GetFunction(env->context()).ToLocalChecked()).FromJust();
|
|
|
|
Local<FunctionTemplate> des =
|
|
env->NewFunctionTemplate(DeserializerContext::New);
|
|
|
|
des->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
env->SetProtoMethod(des, "readHeader", DeserializerContext::ReadHeader);
|
|
env->SetProtoMethod(des, "readValue", DeserializerContext::ReadValue);
|
|
env->SetProtoMethod(des,
|
|
"getWireFormatVersion",
|
|
DeserializerContext::GetWireFormatVersion);
|
|
env->SetProtoMethod(des,
|
|
"transferArrayBuffer",
|
|
DeserializerContext::TransferArrayBuffer);
|
|
env->SetProtoMethod(des, "readUint32", DeserializerContext::ReadUint32);
|
|
env->SetProtoMethod(des, "readUint64", DeserializerContext::ReadUint64);
|
|
env->SetProtoMethod(des, "readDouble", DeserializerContext::ReadDouble);
|
|
env->SetProtoMethod(des, "_readRawBytes", DeserializerContext::ReadRawBytes);
|
|
|
|
Local<String> deserializerString =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Deserializer");
|
|
des->SetClassName(deserializerString);
|
|
target->Set(env->context(),
|
|
deserializerString,
|
|
des->GetFunction(env->context()).ToLocalChecked()).FromJust();
|
|
}
|
|
|
|
} // anonymous namespace
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_INTERNAL(serdes, node::Initialize)
|