#include "native/customlabels.h"
#include <node.h>
#include <node_object_wrap.h>
#include <v8-internal.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
extern "C" {
using v8::Global;
using v8::Object;
thread_local int custom_labels_als_identity_hash;
thread_local Global<Object> custom_labels_als_handle;
}
namespace custom_labels {
using node::ObjectWrap;
using v8::Context;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::ObjectTemplate;
using v8::String;
using v8::Value;
#define hm custom_labels_async_hashmap
#define hm_alloc custom_labels_hm_alloc
#define hm_free custom_labels_hm_free
#define hm_insert custom_labels_hm_insert
#define hm_get custom_labels_hm_get
#define hm_delete custom_labels_hm_delete
const uint64_t CLWRAP_TOKEN_VALUE = 0xEC9EB507FB5D7903;
static bool IsAllowedLabelValue(Local<Value> value) {
return value->IsString() || value->IsBoolean() || value->IsNumber() ||
value->IsUndefined() || value->IsNull();
}
static bool ToLabelString(Local<Context> context, Local<Value> value,
Local<String> *out) {
if (value->IsString()) {
*out = value.As<String>();
return true;
}
return value->ToString(context).ToLocal(out);
}
class ClWrap : public ObjectWrap {
public:
~ClWrap() override;
static void Init(Local<Object> exports);
ClWrap(const ClWrap &) = delete;
ClWrap &operator=(const ClWrap &) = delete;
ClWrap(ClWrap &&) = delete;
ClWrap &operator=(ClWrap &&) noexcept = delete;
private:
static void New(const v8::FunctionCallbackInfo<v8::Value> &args);
static void ToString(const v8::FunctionCallbackInfo<v8::Value> &args);
custom_labels_labelset_t *underlying_;
uint64_t __attribute__((unused)) token_;
explicit ClWrap(custom_labels_labelset_t *underlying);
};
ClWrap::~ClWrap() { custom_labels_free(underlying_); }
ClWrap::ClWrap(custom_labels_labelset_t *underlying)
: underlying_(underlying), token_(CLWRAP_TOKEN_VALUE) {}
void ClWrap::New(const v8::FunctionCallbackInfo<v8::Value> &args) {
Isolate *isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!args.IsConstructCall()) [[unlikely]] {
isolate->ThrowError("Must be called like `new ClWrap(old, (k, v)*`");
return;
}
if (args.Length() % 2 == 0) {
isolate->ThrowError("Must be called like `new ClWrap(old, (k, v)*)`");
return;
}
size_t new_labels = args.Length() / 2;
custom_labels_labelset_t *old = NULL;
if (!args[0]->IsUndefined()) {
if (!args[0]->IsObject()) {
isolate->ThrowError(
"First argument must be the old object or `undefined`");
return;
}
ClWrap *old_wrap = ObjectWrap::Unwrap<ClWrap>(args[0].As<Object>());
if (!old_wrap || old_wrap->token_ != CLWRAP_TOKEN_VALUE) {
isolate->ThrowError(
"First argument must be the old object or `undefined`");
return;
}
old = old_wrap->underlying_;
}
custom_labels_labelset_t *underlying;
if (old) {
underlying = custom_labels_clone_with_capacity(
old, custom_labels_count(old) + new_labels);
if (!underlying) {
isolate->ThrowError("allocation failed");
return;
}
} else {
underlying = custom_labels_new(new_labels);
if (!underlying) {
isolate->ThrowError("allocation failed");
return;
}
}
ClWrap *new_ = new ClWrap(underlying);
auto me = std::unique_ptr<ClWrap>(new_);
for (size_t i = 0; i < new_labels; ++i) {
int k_idx = 2 * i + 1;
int v_idx = 2 * i + 2;
if (!IsAllowedLabelValue(args[k_idx]) ||
!IsAllowedLabelValue(args[v_idx])) {
isolate->ThrowError(
"Arguments other than the first must be strings, booleans, numbers, "
"null, or undefined");
return;
}
Local<String> k;
Local<String> v;
if (!ToLabelString(context, args[k_idx], &k) ||
!ToLabelString(context, args[v_idx], &v)) {
isolate->ThrowError("Failed to convert label to string");
return;
}
int k_len = k->Utf8Length(isolate);
auto k_buf = std::make_unique<char[]>(k_len);
int v_len = v->Utf8Length(isolate);
auto v_buf = std::make_unique<char[]>(v_len);
k->WriteUtf8(isolate, k_buf.get(), k_len, nullptr,
String::NO_NULL_TERMINATION);
v->WriteUtf8(isolate, v_buf.get(), v_len, nullptr,
String::NO_NULL_TERMINATION);
custom_labels_string_t key{(size_t)k_len, (unsigned char *)k_buf.get()};
custom_labels_string_t value{(size_t)v_len, (unsigned char *)v_buf.get()};
int err = custom_labels_set(underlying, key, value, nullptr);
if (err) {
isolate->ThrowError("Underlying custom_labels_set call failed: probably "
"an allocation error.");
return;
}
}
me.release()->Wrap(args.This());
args.GetReturnValue().Set(args.This());
}
void ClWrap::ToString(const v8::FunctionCallbackInfo<v8::Value> &args) {
Isolate *isolate = args.GetIsolate();
ClWrap *obj = ObjectWrap::Unwrap<ClWrap>(args.This());
if (!obj) {
isolate->ThrowError("Invalid ClWrap object");
return;
}
custom_labels_string_t debug_str;
int result = custom_labels_debug_string(obj->underlying_, &debug_str);
if (result != 0) {
isolate->ThrowError("Failed to generate debug string");
return;
}
Local<String> js_string =
String::NewFromUtf8(isolate, (const char *)debug_str.buf,
NewStringType::kNormal, debug_str.len)
.ToLocalChecked();
free((void *)debug_str.buf);
args.GetReturnValue().Set(js_string);
}
void ClWrap::Init(Local<Object> exports) {
#if NODE_MAJOR_VERSION >= 26
Isolate *isolate = Isolate::GetCurrent();
#else
Isolate *isolate = exports->GetIsolate();
#endif
Local<Context> context = isolate->GetCurrentContext();
Local<ObjectTemplate> addon_data_tpl = ObjectTemplate::New(isolate);
addon_data_tpl->SetInternalFieldCount(1); Local<Object> addon_data =
addon_data_tpl->NewInstance(context).ToLocalChecked();
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, addon_data);
tpl->SetClassName(String::NewFromUtf8(isolate, "ClWrap").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
tpl->PrototypeTemplate()->Set(
String::NewFromUtf8(isolate, "toString").ToLocalChecked(),
FunctionTemplate::New(isolate, ToString));
Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked();
addon_data->SetInternalField(0, constructor);
exports
->Set(context, String::NewFromUtf8(isolate, "ClWrap").ToLocalChecked(),
constructor)
.FromJust();
}
void StoreHash(const v8::FunctionCallbackInfo<v8::Value> &args) {
Isolate *isolate = args.GetIsolate();
if (!args[0]->IsObject()) {
isolate->ThrowError("First argument must be an object.");
}
Local<Object> obj = args[0].As<Object>();
int hash = obj->GetIdentityHash();
custom_labels_als_identity_hash = hash;
custom_labels_als_handle = Global<Object>(isolate, obj);
}
void GetStoredHash(const v8::FunctionCallbackInfo<v8::Value> &args) {
Isolate *isolate = args.GetIsolate();
Local<v8::Integer> ret{
v8::Integer::New(isolate, custom_labels_als_identity_hash)};
args.GetReturnValue().Set(ret);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
NODE_MODULE_INIT() {
ClWrap::Init(exports);
NODE_SET_METHOD(exports, "storeHash", StoreHash);
}
}
#pragma GCC diagnostic pop