/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/node_ipc_serdes.cc
385 строк
13 KB
Yagiz Nizipli
child_process: serialize advanced IPC messages natively
18 июн 2026, 21:14
Не верифицирован
18 июн 2026, 21:14
a1074b8
Код
Авторство
О чём код?
#include "env-inl.h" #include "node_buffer.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "util-inl.h" #include <cstring> #include <utility> // Native implementation of the `advanced` child_process IPC serialization // codec previously implemented in lib/internal/child_process/serialization.js // (the ChildProcessSerializer / ChildProcessDeserializer classes). The wire // format is preserved byte-for-byte: // [4-byte big-endian payload length][V8 ValueSerializer payload] // ArrayBufferViews are treated as host objects (matching v8.DefaultSerializer) // and tagged so that Node Buffers round-trip as Buffers rather than plain // Uint8Arrays. namespace node { using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BackingStore; using v8::BigInt64Array; using v8::BigUint64Array; using v8::Context; using v8::DataView; using v8::Exception; using v8::Float16Array; using v8::Float32Array; using v8::Float64Array; using v8::FunctionCallbackInfo; using v8::Int16Array; using v8::Int32Array; using v8::Int8Array; using v8::Isolate; using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::Nothing; using v8::Object; using v8::String; using v8::Uint16Array; using v8::Uint32Array; using v8::Uint8Array; using v8::Uint8ClampedArray; using v8::Value; using v8::ValueDeserializer; using v8::ValueSerializer; namespace ipc_serdes { // Tags written before each host object, matching serialization.js. static constexpr uint32_t kArrayBufferViewTag = 0; static constexpr uint32_t kNotArrayBufferViewTag = 1; // ArrayBufferView type indices, matching arrayBufferViewTypeToIndex() in // lib/v8.js. Index 10 is reserved for Node's Buffer (FastBuffer). static constexpr uint32_t kInvalidViewIndex = 0xFFFFFFFF; static constexpr uint32_t kBufferIndex = 10; static uint32_t GetViewTypeIndex(Local<Object> view) { // Mirrors arrayBufferViewTypeToIndex() in lib/v8.js, classifying the view by // its class tag. Node Buffers are detected separately by the caller via the // `value.constructor === Buffer` check, matching DefaultSerializer. if (view->IsInt8Array()) return 0; if (view->IsUint8Array()) return 1; if (view->IsUint8ClampedArray()) return 2; if (view->IsInt16Array()) return 3; if (view->IsUint16Array()) return 4; if (view->IsInt32Array()) return 5; if (view->IsUint32Array()) return 6; if (view->IsFloat32Array()) return 7; if (view->IsFloat64Array()) return 8; if (view->IsDataView()) return 9; if (view->IsBigInt64Array()) return 11; if (view->IsBigUint64Array()) return 12; if (view->IsFloat16Array()) return 13; return kInvalidViewIndex; } static size_t BytesPerElement(uint32_t type_index) { switch (type_index) { case 3: // Int16Array case 4: // Uint16Array case 13: // Float16Array return 2; case 5: // Int32Array case 6: // Uint32Array case 7: // Float32Array return 4; case 8: // Float64Array case 11: // BigInt64Array case 12: // BigUint64Array return 8; default: // Int8/Uint8/Uint8Clamped/DataView/Buffer return 1; } } static MaybeLocal<Object> MakeView(Environment* env, uint32_t type_index, Local<ArrayBuffer> ab, size_t byte_offset, size_t byte_length) { const size_t length = byte_length / BytesPerElement(type_index); Local<Object> result; switch (type_index) { case 0: result = Int8Array::New(ab, byte_offset, length); break; case 1: result = Uint8Array::New(ab, byte_offset, length); break; case 2: result = Uint8ClampedArray::New(ab, byte_offset, length); break; case 3: result = Int16Array::New(ab, byte_offset, length); break; case 4: result = Uint16Array::New(ab, byte_offset, length); break; case 5: result = Int32Array::New(ab, byte_offset, length); break; case 6: result = Uint32Array::New(ab, byte_offset, length); break; case 7: result = Float32Array::New(ab, byte_offset, length); break; case 8: result = Float64Array::New(ab, byte_offset, length); break; case 9: result = DataView::New(ab, byte_offset, byte_length); break; case kBufferIndex: { Local<Uint8Array> buf; if (!Buffer::New(env, ab, byte_offset, byte_length).ToLocal(&buf)) { return {}; } result = buf; break; } case 11: result = BigInt64Array::New(ab, byte_offset, length); break; case 12: result = BigUint64Array::New(ab, byte_offset, length); break; case 13: result = Float16Array::New(ab, byte_offset, length); break; default: THROW_ERR_INVALID_STATE(env, "Invalid host object type index"); return {}; } return result; } class IPCSerializerDelegate : public ValueSerializer::Delegate { public: IPCSerializerDelegate(Environment* env, Local<Value> buffer_constructor) : env_(env), buffer_constructor_(buffer_constructor) {} void set_serializer(ValueSerializer* serializer) { serializer_ = serializer; } void ThrowDataCloneError(Local<String> message) override { env_->isolate()->ThrowException(Exception::Error(message)); } Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override { if (object->IsArrayBufferView()) { serializer_->WriteUint32(kArrayBufferViewTag); // Matches v8.js DefaultSerializer._writeHostObject: a Node Buffer is // identified by `value.constructor === Buffer`. The comparison is made // against the stable Buffer reference captured by serialization.js // (`require('buffer').Buffer`), not a value read back from // Buffer.prototype, which is itself tamperable. Local<Context> context = env_->context(); Local<Value> view_constructor; if (!object->Get(context, FIXED_ONE_BYTE_STRING(isolate, "constructor")) .ToLocal(&view_constructor)) { return Nothing<bool>(); } uint32_t type_index; if (view_constructor->StrictEquals(buffer_constructor_)) { type_index = kBufferIndex; } else { type_index = GetViewTypeIndex(object); if (type_index == kInvalidViewIndex) { THROW_ERR_INVALID_STATE(env_, "Unserializable host object"); return Nothing<bool>(); } } ArrayBufferViewContents<char> contents(object); serializer_->WriteUint32(type_index); serializer_->WriteUint32(static_cast<uint32_t>(contents.length())); serializer_->WriteRawBytes(contents.data(), contents.length()); return Just(true); } // Non-view host object: serialize a shallow copy of its own enumerable // properties, matching `writeValue({ ...object })` in serialization.js. // Use CreateDataProperty (not Set) so inherited setters are not invoked, // exactly as the object-spread did. serializer_->WriteUint32(kNotArrayBufferViewTag); Local<Context> context = env_->context(); Local<v8::Array> names; if (!object->GetOwnPropertyNames(context).ToLocal(&names)) { return Nothing<bool>(); } Local<Object> copy = Object::New(isolate); const uint32_t len = names->Length(); for (uint32_t i = 0; i < len; i++) { Local<Value> key; if (!names->Get(context, i).ToLocal(&key)) return Nothing<bool>(); Local<Value> val; if (!object->Get(context, key).ToLocal(&val)) return Nothing<bool>(); if (copy->CreateDataProperty(context, key.As<Name>(), val).IsNothing()) return Nothing<bool>(); } return serializer_->WriteValue(context, copy); } private: Environment* env_; // Stable `require('buffer').Buffer` reference passed from serialization.js, // used to classify Node Buffers without reading Buffer.prototype.constructor // (which can be tampered with). Local<Value> buffer_constructor_; ValueSerializer* serializer_ = nullptr; }; class IPCDeserializerDelegate : public ValueDeserializer::Delegate { public: IPCDeserializerDelegate(Environment* env, Local<ArrayBuffer> ab) : env_(env), ab_(ab) {} void set_deserializer(ValueDeserializer* deserializer) { deserializer_ = deserializer; } MaybeLocal<Object> ReadHostObject(Isolate* isolate) override { uint32_t tag; if (!deserializer_->ReadUint32(&tag)) return {}; if (tag == kNotArrayBufferViewTag) { Local<Value> value; if (!deserializer_->ReadValue(env_->context()).ToLocal(&value)) { return {}; } if (!value->IsObject()) { THROW_ERR_INVALID_STATE(env_, "Host object must be an object"); return {}; } return value.As<Object>(); } // Only the two tags written by WriteHostObject are valid. Reject anything // else, matching `assert(tag === kNotArrayBufferViewTag)` in the JS codec. if (tag != kArrayBufferViewTag) { THROW_ERR_INVALID_STATE(env_, "Invalid host object tag"); return {}; } uint32_t type_index; uint32_t byte_length; if (!deserializer_->ReadUint32(&type_index) || !deserializer_->ReadUint32(&byte_length)) { return {}; } const void* data; if (!deserializer_->ReadRawBytes(byte_length, &data)) return {}; const size_t bytes_per_element = BytesPerElement(type_index); const size_t offset_in_ab = static_cast<const uint8_t*>(data) - static_cast<const uint8_t*>(ab_->Data()); // Reuse the backing ArrayBuffer when the data is suitably aligned, // otherwise copy into a fresh aligned buffer. Mirrors _readHostObject() // in lib/v8.js. if (offset_in_ab % bytes_per_element == 0) { return MakeView(env_, type_index, ab_, offset_in_ab, byte_length); } std::shared_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(isolate, byte_length); memcpy(store->Data(), data, byte_length); Local<ArrayBuffer> copy = ArrayBuffer::New(isolate, std::move(store)); return MakeView(env_, type_index, copy, 0, byte_length); } private: Environment* env_; Local<ArrayBuffer> ab_; ValueDeserializer* deserializer_ = nullptr; }; static void Serialize(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Local<Context> context = env->context(); IPCSerializerDelegate delegate(env, args[1]); ValueSerializer serializer(isolate, &delegate); delegate.set_serializer(&serializer); serializer.SetTreatArrayBufferViewsAsHostObjects(true); // Reserve 4 bytes for the big-endian payload length, then write the // standard V8 header and the value, matching serialization.js. const uint8_t length_placeholder[4] = {0, 0, 0, 0}; serializer.WriteRawBytes(length_placeholder, sizeof(length_placeholder)); serializer.WriteHeader(); bool wrote; if (!serializer.WriteValue(context, args[0]).To(&wrote) || !wrote) { // A pending exception was set by the delegate or V8. return; } std::pair<uint8_t*, size_t> result = serializer.Release(); uint8_t* buf = result.first; const size_t size = result.second; const uint32_t payload_length = static_cast<uint32_t>(size - 4); buf[0] = (payload_length >> 24) & 0xFF; buf[1] = (payload_length >> 16) & 0xFF; buf[2] = (payload_length >> 8) & 0xFF; buf[3] = payload_length & 0xFF; Local<Object> out; if (Buffer::New(env, reinterpret_cast<char*>(buf), size).ToLocal(&out)) { args.GetReturnValue().Set(out); } } static void Deserialize(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Local<Context> context = env->context(); CHECK(args[0]->IsArrayBufferView()); Local<ArrayBufferView> view = args[0].As<ArrayBufferView>(); Local<ArrayBuffer> ab = view->Buffer(); const size_t byte_offset = view->ByteOffset(); const size_t byte_length = view->ByteLength(); const uint8_t* data = static_cast<const uint8_t*>(ab->Data()) + byte_offset; IPCDeserializerDelegate delegate(env, ab); ValueDeserializer deserializer(isolate, data, byte_length, &delegate); delegate.set_deserializer(&deserializer); bool read_header; if (!deserializer.ReadHeader(context).To(&read_header)) return; Local<Value> value; if (deserializer.ReadValue(context).ToLocal(&value)) { args.GetReturnValue().Set(value); } } static void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { SetMethod(context, target, "serialize", Serialize); SetMethod(context, target, "deserialize", Deserialize); } static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(Serialize); registry->Register(Deserialize); } } // namespace ipc_serdes } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(ipc_serdes, node::ipc_serdes::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(ipc_serdes, node::ipc_serdes::RegisterExternalReferences)