2025-04-28 12:25:20 +08:00

706 lines
22 KiB
C++

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unordered_map>
#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 <inttypes.h>
#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<ArrayBuffer> ab;
size_t finalizer_count;
};
Env env;
std::unordered_map<char*, ArrayBufferEntry> 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<char*>(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<ArrayBuffer>::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<size_t>(length, kMaxLength);
ab = Buffer<char>::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<InstanceData*>(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<char>::New(env, ptr, length, [](Env,char*){});
}
char* GetBufferData(Value val) {
Buffer<char> buf = val.As<Buffer<char>>();
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<uintptr_t>(ptr);
return Number::New(args.Env(), static_cast<double>(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<Object>* rptr = reinterpret_cast<Reference<Object>*>(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<Object>* rptr = reinterpret_cast<Reference<Object>*>(ptr);
if (args[2].IsObject()) {
Object val = args[2].As<Object>();
*rptr = std::move(Reference<Object>::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<char**>(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<char**>(ptr) = nullptr;
} else {
if ((args.Length() == 4) && (args[3].As<Boolean>() == 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<Value>* ref = new Reference<Value>;
*ref = Persistent(args[2]);
args[0].As<Object>().AddFinalizer([](Env env, Reference<Value>* ref) {
delete ref;
}, ref);
}
char* input_ptr = GetBufferData(input);
*reinterpret_cast<char**>(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<int64_t*>(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<Number>();
} else if (in.IsString()) {
char* endptr;
char* str;
int base = 0;
std::string _str = in.As<String>();
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<int64_t*>(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<uint64_t*>(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<int64_t>(in.As<Number>());
} else if (in.IsString()) {
char* endptr;
char* str;
int base = 0;
std::string _str = in.As<String>();
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<uint64_t*>(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<Object>()["from"];
data->buffer_from.Reset(buffer_from.As<Function>(), 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<InstanceData*>(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<Object>().AddFinalizer([](Env env, InstanceData* data) {
data->buffer_from.Reset();
}, data);
}
}
exports["instance"] = External<RefNapi::Instance>::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<Object>);
// "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<Object>);
// 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)