#include #include #include #include #include "ref-napi.h" #ifdef _WIN32 #define __alignof__ __alignof #define snprintf(buf, bufSize, format, arg) _snprintf_s(buf, bufSize, _TRUNCATE, format, arg) #define strtoll _strtoi64 #define strtoull _strtoui64 #define PRId64 "lld" #define PRIu64 "llu" #else #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #endif using namespace Napi; namespace { #if !defined(NAPI_VERSION) || NAPI_VERSION < 6 napi_status napix_set_instance_data( napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint) { typedef napi_status (*napi_set_instance_data_fn)( napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); static const napi_set_instance_data_fn napi_set_instance_data__ = (napi_set_instance_data_fn) get_symbol_from_current_process("napi_set_instance_data"); if (napi_set_instance_data__ == nullptr) return napi_generic_failure; return napi_set_instance_data__(env, data, finalize_cb, finalize_hint); } napi_status napix_get_instance_data( napi_env env, void** data) { typedef napi_status (*napi_get_instance_data_fn)( napi_env env, void** data); static const napi_get_instance_data_fn napi_get_instance_data__ = (napi_get_instance_data_fn) get_symbol_from_current_process("napi_get_instance_data"); *data = nullptr; if (napi_get_instance_data__ == nullptr) return napi_generic_failure; return napi_get_instance_data__(env, data); } #else // NAPI_VERSION >= 6 napi_status napix_set_instance_data( napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint) { return napi_set_instance_data(env, data, finalize_cb, finalize_hint); } napi_status napix_get_instance_data( napi_env env, void** data) { return napi_get_instance_data(env, data); } #endif // used by the Int64 functions to determine whether to return a Number // or String based on whether or not a Number will lose precision. // http://stackoverflow.com/q/307179/376773 #define JS_MAX_INT +9007199254740992LL #define JS_MIN_INT -9007199254740992LL // mirrors deps/v8/src/objects.h. // we could use `node::Buffer::kMaxLength`, but it's not defined on node v0.6.x static const size_t kMaxLength = 0x3fffffff; enum ArrayBufferMode { AB_CREATED_BY_REF, AB_PASSED_TO_REF }; // Since Node.js v14.0.0, we have to keep a global list of all ArrayBuffer // instances that we work with, in order not to create any duplicates. // Luckily, N-API instance data is available on v14.x and above. class InstanceData final : public RefNapi::Instance { public: InstanceData(Env env) : env(env) {} struct ArrayBufferEntry { Reference ab; size_t finalizer_count; }; Env env; std::unordered_map pointer_to_orig_buffer; FunctionReference buffer_from; void RegisterArrayBuffer(napi_value val) override { ArrayBuffer buf(env, val); RegisterArrayBuffer(buf, AB_PASSED_TO_REF); } inline void RegisterArrayBuffer(ArrayBuffer buf, ArrayBufferMode mode) { char* ptr = static_cast(buf.Data()); if (ptr == nullptr) return; auto it = pointer_to_orig_buffer.find(ptr); if (it != pointer_to_orig_buffer.end()) { if (!it->second.ab.Value().IsEmpty()) { // Already have a valid entry, nothing to do. return; } it->second.ab.Reset(buf, 0); it->second.finalizer_count++; } else { pointer_to_orig_buffer.emplace(ptr, ArrayBufferEntry { Reference::New(buf, 0), 1 }); } // If AB_CREATED_BY_REF, then another finalizer has been added before this // as a "real" backing store finalizer. if (mode != AB_CREATED_BY_REF) { buf.AddFinalizer([this](Env env, char* ptr) { UnregisterArrayBuffer(ptr); }, ptr); } } inline void UnregisterArrayBuffer(char* ptr) { auto it = pointer_to_orig_buffer.find(ptr); if (--it->second.finalizer_count == 0) pointer_to_orig_buffer.erase(it); } inline ArrayBuffer LookupOrCreateArrayBuffer(char* ptr, size_t length) { assert(ptr != nullptr); ArrayBuffer ab; auto it = pointer_to_orig_buffer.find(ptr); if (it != pointer_to_orig_buffer.end()) ab = it->second.ab.Value(); if (ab.IsEmpty()) { length = std::max(length, kMaxLength); ab = Buffer::New(env, ptr, length, [this](Env env, char* ptr) { UnregisterArrayBuffer(ptr); }).ArrayBuffer(); RegisterArrayBuffer(ab, AB_CREATED_BY_REF); } return ab; } napi_value WrapPointer(char* ptr, size_t length) override; char* GetBufferData(napi_value val) override; static InstanceData* Get(Env env) { void* d = nullptr; if (napix_get_instance_data(env, &d) == napi_ok) return static_cast(d); return nullptr; } }; /** * Converts an arbitrary pointer to a node Buffer with specified length */ Value WrapPointer(Env env, char* ptr, size_t length) { if (ptr == nullptr) length = 0; InstanceData* data; if (ptr != nullptr && (data = InstanceData::Get(env)) != nullptr) { ArrayBuffer ab = data->LookupOrCreateArrayBuffer(ptr, length); assert(!ab.IsEmpty()); return data->buffer_from.Call({ ab, Number::New(env, 0), Number::New(env, length) }); } return Buffer::New(env, ptr, length, [](Env,char*){}); } char* GetBufferData(Value val) { Buffer buf = val.As>(); InstanceData* data = InstanceData::Get(val.Env()); if (data != nullptr) data->RegisterArrayBuffer(buf.ArrayBuffer()); return buf.Data(); } napi_value InstanceData::WrapPointer(char* ptr, size_t length) { return ::WrapPointer(env, ptr, length); } char* InstanceData::GetBufferData(napi_value val) { return ::GetBufferData(Value(env, val)); } char* AddressForArgs(const CallbackInfo& args, size_t offset_index = 1) { Value buf = args[0]; if (!buf.IsBuffer()) { throw TypeError::New(args.Env(), "Buffer instance expected"); } int64_t offset = args[offset_index].ToNumber(); return GetBufferData(buf) + offset; } /** * Returns the pointer address as a Number of the given Buffer instance. * It's recommended to use `hexAddress()` in most cases instead of this function. * * WARNING: a JavaScript Number cannot precisely store a full 64-bit memory * address, so there's a possibility of an inaccurate value being returned * on 64-bit systems. * * args[0] - Buffer - the Buffer instance get the memory address of * args[1] - Number - optional (0) - the offset of the Buffer start at */ Value Address (const CallbackInfo& args) { char* ptr = AddressForArgs(args); uintptr_t intptr = reinterpret_cast(ptr); return Number::New(args.Env(), static_cast(intptr)); } /** * Returns the pointer address as a hexadecimal String. This function * is safe to use for displaying memory addresses, as compared to the * `address()` function which could overflow since it returns a Number. * * args[0] - Buffer - the Buffer instance get the memory address of * args[1] - Number - optional (0) - the offset of the Buffer start at */ Value HexAddress(const CallbackInfo& args) { char* ptr = AddressForArgs(args); char strbuf[30]; /* should be plenty... */ snprintf(strbuf, 30, "%p", ptr); if (strbuf[0] == '0' && strbuf[1] == 'x') { /* strip the leading "0x" from the address */ ptr = strbuf + 2; } else { ptr = strbuf; } return String::New(args.Env(), ptr); } /** * Returns "true" if the given Buffer points to nullptr, "false" otherwise. * * args[0] - Buffer - the Buffer instance to check for nullptr * args[1] - Number - optional (0) - the offset of the Buffer start at */ Value IsNull(const CallbackInfo& args) { char* ptr = AddressForArgs(args); return Boolean::New(args.Env(), ptr == nullptr); } /** * Retreives a JS Object instance that was previously stored in * the given Buffer instance at the given offset. * * args[0] - Buffer - the "buf" Buffer instance to read from * args[1] - Number - the offset from the "buf" buffer's address to read from */ Value ReadObject(const CallbackInfo& args) { char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw Error::New(args.Env(), "readObject: Cannot read from nullptr pointer"); } Reference* rptr = reinterpret_cast*>(ptr); return rptr->Value(); } /** * Writes a weak reference to given Object to the given Buffer * instance and offset. * * args[0] - Buffer - the "buf" Buffer instance to write to * args[1] - Number - the offset from the "buf" buffer's address to write to * args[2] - Object - the "obj" Object which will have a new Persistent reference * created for the obj, whose memory address will be written. */ void WriteObject(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw Error::New(env, "readObject: Cannot write to nullptr pointer"); } Reference* rptr = reinterpret_cast*>(ptr); if (args[2].IsObject()) { Object val = args[2].As(); *rptr = std::move(Reference::New(val)); } else if (args[2].IsNull()) { rptr->Reset(); } else { throw TypeError::New(env, "WriteObject's 3rd argument needs to be an object"); } } /** * Reads the memory address of the given "buf" pointer Buffer at the specified * offset, and returns a new SlowBuffer instance from the memory address stored. * * args[0] - Buffer - the "buf" Buffer instance to read from * args[1] - Number - the offset from the "buf" buffer's address to read from * args[2] - Number - the length in bytes of the returned SlowBuffer instance */ Value ReadPointer(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw Error::New(env, "readPointer: Cannot read from nullptr pointer"); } int64_t size = args[2].ToNumber(); char* val = *reinterpret_cast(ptr); return WrapPointer(env, val, size); } /** * Writes the memory address of the "input" buffer (and optional offset) to the * specified "buf" buffer and offset. Essentially making "buf" hold a reference * to the "input" Buffer. * * args[0] - Buffer - the "buf" Buffer instance to write to * args[1] - Number - the offset from the "buf" buffer's address to write to * args[2] - Buffer - the "input" Buffer whose memory address will be written */ void WritePointer(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); Value input = args[2]; if (!input.IsNull() && !input.IsBuffer()) { throw TypeError::New(env, "writePointer: Buffer instance expected as third argument"); } if (input.IsNull()) { *reinterpret_cast(ptr) = nullptr; } else { if ((args.Length() == 4) && (args[3].As() == true)) { // create a node-api reference and finalizer to ensure that // the buffer whoes pointer is written can only be // collected after the finalizers for the buffer // to which the pointer was written have already run Reference* ref = new Reference; *ref = Persistent(args[2]); args[0].As().AddFinalizer([](Env env, Reference* ref) { delete ref; }, ref); } char* input_ptr = GetBufferData(input); *reinterpret_cast(ptr) = input_ptr; } } /** * Reads a machine-endian int64_t from the given Buffer at the given offset. * * args[0] - Buffer - the "buf" Buffer instance to read from * args[1] - Number - the offset from the "buf" buffer's address to read from */ Value ReadInt64(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw TypeError::New(env, "readInt64: Cannot read from nullptr pointer"); } int64_t val = *reinterpret_cast(ptr); if (val < JS_MIN_INT || val > JS_MAX_INT) { char strbuf[128]; snprintf(strbuf, 128, "%" PRId64, val); return String::New(env, strbuf); } else { return Number::New(env, val); } } /** * Writes the input Number/String int64 value as a machine-endian int64_t to * the given Buffer at the given offset. * * args[0] - Buffer - the "buf" Buffer instance to write to * args[1] - Number - the offset from the "buf" buffer's address to write to * args[2] - String/Number - the "input" String or Number which will be written */ void WriteInt64(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); Value in = args[2]; int64_t val; if (in.IsNumber()) { val = in.As(); } else if (in.IsString()) { char* endptr; char* str; int base = 0; std::string _str = in.As(); str = &_str[0]; errno = 0; /* To distinguish success/failure after call */ val = strtoll(str, &endptr, base); if (endptr == str) { throw TypeError::New(env, "writeInt64: no digits we found in input String"); } else if (errno == ERANGE && (val == INT64_MAX || val == INT64_MIN)) { throw TypeError::New(env, "writeInt64: input String numerical value out of range"); } else if (errno != 0 && val == 0) { char errmsg[200]; snprintf(errmsg, sizeof(errmsg), "writeInt64: %s", strerror(errno)); throw TypeError::New(env, errmsg); } } else { throw TypeError::New(env, "writeInt64: Number/String 64-bit value required"); } *reinterpret_cast(ptr) = val; } /** * Reads a machine-endian uint64_t from the given Buffer at the given offset. * * args[0] - Buffer - the "buf" Buffer instance to read from * args[1] - Number - the offset from the "buf" buffer's address to read from */ Value ReadUInt64(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw TypeError::New(env, "readUInt64: Cannot read from nullptr pointer"); } uint64_t val = *reinterpret_cast(ptr); if (val > JS_MAX_INT) { char strbuf[128]; snprintf(strbuf, 128, "%" PRIu64, val); return String::New(env, strbuf); } else { return Number::New(env, val); } } /** * Writes the input Number/String uint64 value as a machine-endian uint64_t to * the given Buffer at the given offset. * * args[0] - Buffer - the "buf" Buffer instance to write to * args[1] - Number - the offset from the "buf" buffer's address to write to * args[2] - String/Number - the "input" String or Number which will be written */ void WriteUInt64(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); Value in = args[2]; uint64_t val; if (in.IsNumber()) { val = static_cast(in.As()); } else if (in.IsString()) { char* endptr; char* str; int base = 0; std::string _str = in.As(); str = &_str[0]; errno = 0; /* To distinguish success/failure after call */ val = strtoull(str, &endptr, base); if (endptr == str) { throw TypeError::New(env, "writeUInt64: no digits we found in input String"); } else if (errno == ERANGE && (val == UINT64_MAX)) { throw TypeError::New(env, "writeUInt64: input String numerical value out of range"); } else if (errno != 0 && val == 0) { char errmsg[200]; snprintf(errmsg, sizeof(errmsg), "writeUInt64: %s", strerror(errno)); throw TypeError::New(env, errmsg); } } else { throw TypeError::New(env, "writeUInt64: Number/String 64-bit value required"); } *reinterpret_cast(ptr) = val; } /** * Reads a Utf8 C String from the given pointer at the given offset (or 0). * I didn't want to add this function but it ends up being necessary for reading * past a 0 or 1 length Buffer's boundary in node-ffi :\ * * args[0] - Buffer - the "buf" Buffer instance to read from * args[1] - Number - the offset from the "buf" buffer's address to read from */ Value ReadCString(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args); if (ptr == nullptr) { throw Error::New(env, "readCString: Cannot read from nullptr pointer"); } return String::New(env, ptr); } /** * Returns a new Buffer instance that has the same memory address * as the given buffer, but with the specified size. * * args[0] - Buffer - the "buf" Buffer instance to read the address from * args[1] - Number - the size in bytes that the returned Buffer should be * args[2] - Number - the offset from the "buf" buffer's address to read from */ Value ReinterpretBuffer(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args, 2); if (ptr == nullptr) { throw Error::New(env, "reinterpret: Cannot reinterpret from nullptr pointer"); } int64_t size = args[1].ToNumber(); return WrapPointer(env, ptr, size); } /** * Returns a new Buffer instance that has the same memory address * as the given buffer, but with a length up to the first aligned set of values of * 0 in a row for the given length. * * args[0] - Buffer - the "buf" Buffer instance to read the address from * args[1] - Number - the number of sequential 0-byte values that need to be read * args[2] - Number - the offset from the "buf" buffer's address to read from */ Value ReinterpretBufferUntilZeros(const CallbackInfo& args) { Env env = args.Env(); char* ptr = AddressForArgs(args, 2); if (ptr == nullptr) { throw Error::New(env, "reinterpretUntilZeros: Cannot reinterpret from nullptr pointer"); } uint32_t numZeros = args[1].ToNumber(); uint32_t i = 0; size_t size = 0; bool end = false; while (!end && size < kMaxLength) { end = true; for (i = 0; i < numZeros; i++) { if (ptr[size + i] != 0) { end = false; break; } } if (!end) { size += numZeros; } } return WrapPointer(env, ptr, size); } } // anonymous namespace Object Init(Env env, Object exports) { InstanceData* data = new InstanceData(env); { Value buffer_ctor = env.Global()["Buffer"]; Value buffer_from = buffer_ctor.As()["from"]; data->buffer_from.Reset(buffer_from.As(), 1); assert(!data->buffer_from.IsEmpty()); napi_status status = napix_set_instance_data( env, data, [](napi_env env, void* data, void* hint) { delete static_cast(data); }, nullptr); if (status != napi_ok) { delete data; data = nullptr; } else { // Hack around the fact that we can't reset buffer_from from the // InstanceData dtor. buffer_from.As().AddFinalizer([](Env env, InstanceData* data) { data->buffer_from.Reset(); }, data); } } exports["instance"] = External::New(env, data); // "sizeof" map Object smap = Object::New(env); // fixed sizes #define SET_SIZEOF(name, type) \ smap[ #name ] = Number::New(env, sizeof(type)); SET_SIZEOF(int8, int8_t); SET_SIZEOF(uint8, uint8_t); SET_SIZEOF(int16, int16_t); SET_SIZEOF(uint16, uint16_t); SET_SIZEOF(int32, int32_t); SET_SIZEOF(uint32, uint32_t); SET_SIZEOF(int64, int64_t); SET_SIZEOF(uint64, uint64_t); SET_SIZEOF(float, float); SET_SIZEOF(double, double); // (potentially) variable sizes SET_SIZEOF(bool, bool); SET_SIZEOF(byte, unsigned char); SET_SIZEOF(char, char); SET_SIZEOF(uchar, unsigned char); SET_SIZEOF(short, short); SET_SIZEOF(ushort, unsigned short); SET_SIZEOF(int, int); SET_SIZEOF(uint, unsigned int); SET_SIZEOF(long, long); SET_SIZEOF(ulong, unsigned long); SET_SIZEOF(longlong, long long); SET_SIZEOF(ulonglong, unsigned long long); SET_SIZEOF(pointer, char *); SET_SIZEOF(size_t, size_t); // size of a weak handle to a JS object SET_SIZEOF(Object, Reference); // "alignof" map Object amap = Object::New(env); #define SET_ALIGNOF(name, type) \ struct s_##name { type a; }; \ amap[ #name ] = Number::New(env, alignof(struct s_##name)); SET_ALIGNOF(int8, int8_t); SET_ALIGNOF(uint8, uint8_t); SET_ALIGNOF(int16, int16_t); SET_ALIGNOF(uint16, uint16_t); SET_ALIGNOF(int32, int32_t); SET_ALIGNOF(uint32, uint32_t); SET_ALIGNOF(int64, int64_t); SET_ALIGNOF(uint64, uint64_t); SET_ALIGNOF(float, float); SET_ALIGNOF(double, double); SET_ALIGNOF(bool, bool); SET_ALIGNOF(char, char); SET_ALIGNOF(uchar, unsigned char); SET_ALIGNOF(short, short); SET_ALIGNOF(ushort, unsigned short); SET_ALIGNOF(int, int); SET_ALIGNOF(uint, unsigned int); SET_ALIGNOF(long, long); SET_ALIGNOF(ulong, unsigned long); SET_ALIGNOF(longlong, long long); SET_ALIGNOF(ulonglong, unsigned long long); SET_ALIGNOF(pointer, char *); SET_ALIGNOF(size_t, size_t); SET_ALIGNOF(Object, Reference); // exports exports["sizeof"] = smap; exports["alignof"] = amap; exports["nullptr"] = exports["NULL"] = WrapPointer(env, nullptr, 0); exports["address"] = Function::New(env, Address); exports["hexAddress"] = Function::New(env, HexAddress); exports["isNull"] = Function::New(env, IsNull); exports["readObject"] = Function::New(env, ReadObject); exports["_writeObject"] = Function::New(env, WriteObject); exports["readPointer"] = Function::New(env, ReadPointer); exports["_writePointer"] = Function::New(env, WritePointer); exports["readInt64"] = Function::New(env, ReadInt64); exports["writeInt64"] = Function::New(env, WriteInt64); exports["readUInt64"] = Function::New(env, ReadUInt64); exports["writeUInt64"] = Function::New(env, WriteUInt64); exports["readCString"] = Function::New(env, ReadCString); exports["_reinterpret"] = Function::New(env, ReinterpretBuffer); exports["_reinterpretUntilZeros"] = Function::New(env, ReinterpretBufferUntilZeros); return exports; } NODE_API_MODULE(binding, Init)