Skip to content

Commit

Permalink
src: add and use MaybeStackBuffer v8::LocalVector
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Sep 21, 2024
1 parent b264cbe commit 92fbd83
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 22 deletions.
4 changes: 2 additions & 2 deletions src/api/callback.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,

Local<Context> context = env->context();
if (use_async_hooks_trampoline) {
MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
MaybeStackBuffer<Value, 16> args(env->isolate(), 3 + argc);
args[0] = v8::Number::New(env->isolate(), asyncContext.async_id);
args[1] = resource;
args[2] = callback;
for (int i = 0; i < argc; i++) {
args[i + 3] = argv[i];
}
ret = hook_cb->Call(context, recv, args.length(), &args[0]);
ret = hook_cb->Call(context, recv, args.length(), args.out());
} else {
ret = callback->Call(context, recv, argc, argv);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cares_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Local<Array> AddrTTLToArray(
Environment* env,
const T* addrttls,
size_t naddrttls) {
MaybeStackBuffer<Local<Value>, 8> ttls(naddrttls);
MaybeStackBuffer<Value, 8> ttls(env->isolate(), naddrttls);
for (size_t i = 0; i < naddrttls; i++)
ttls[i] = Integer::NewFromUnsigned(env->isolate(), addrttls[i].ttl);

Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ MaybeLocal<Array> GetClientHelloCiphers(
const unsigned char* buf;
size_t len = SSL_client_hello_get0_ciphers(ssl.get(), &buf);
size_t count = len / 2;
MaybeStackBuffer<Local<Value>, 16> ciphers(count);
MaybeStackBuffer<Value, 16> ciphers(env->isolate(), count);
int j = 0;
for (size_t n = 0; n < len; n += 2) {
const SSL_CIPHER* cipher = SSL_CIPHER_find(ssl.get(), buf);
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,7 @@ void TLSWrap::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
SSL* ssl = w->ssl_.get();
int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
nullptr);
MaybeStackBuffer<Local<Value>, 16> ret_arr(nsig);
MaybeStackBuffer<Value, 16> ret_arr(env->isolate(), nsig);

for (int i = 0; i < nsig; i++) {
int hash_nid;
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ MaybeLocal<Value> GetKeyUsage(Environment* env, const ncrypto::X509View& cert) {
X509_get_ext_d2i(cert.get(), NID_ext_key_usage, nullptr, nullptr)));
if (eku) {
const int count = sk_ASN1_OBJECT_num(eku.get());
MaybeStackBuffer<Local<Value>, 16> ext_key_usage(count);
MaybeStackBuffer<Value, 16> ext_key_usage(env->isolate(), count);
char buf[256];

int j = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/js_stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ int JSStream::DoWrite(WriteWrap* w,
HandleScope scope(env()->isolate());
Context::Scope context_scope(env()->context());

MaybeStackBuffer<Local<Value>, 16> bufs_arr(count);
MaybeStackBuffer<Value, 16> bufs_arr(env()->isolate(), count);
for (size_t i = 0; i < count; i++) {
bufs_arr[i] =
Buffer::Copy(env(), bufs[i].base, bufs[i].len).ToLocalChecked();
Expand Down
2 changes: 1 addition & 1 deletion src/js_udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ ssize_t JSUDPWrap::Send(uv_buf_t* bufs,
int64_t value_int = JS_EXCEPTION_PENDING;
size_t total_len = 0;

MaybeStackBuffer<Local<Value>, 16> buffers(nbufs);
MaybeStackBuffer<Value, 16> buffers(env()->isolate(), nbufs);
for (size_t i = 0; i < nbufs; i++) {
buffers[i] = Buffer::Copy(env(), bufs[i].base, bufs[i].len)
.ToLocalChecked();
Expand Down
2 changes: 1 addition & 1 deletion src/node_dir.cc
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ static MaybeLocal<Array> DirentListToArray(
int num,
enum encoding encoding,
Local<Value>* err_out) {
MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
MaybeStackBuffer<Value, 64> entries(env->isolate(), num * 2);

// Return an array of all read filenames.
int j = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/node_env_var.cc
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Local<Array> RealEnvStore::Enumerate(Isolate* isolate) const {
auto cleanup = OnScopeLeave([&]() { uv_os_free_environ(items, count); });
CHECK_EQ(uv_os_environ(&items, &count), 0);

MaybeStackBuffer<Local<Value>, 256> env_v(count);
MaybeStackBuffer<Value, 256> env_v(isolate, count);
int env_v_index = 0;
for (int i = 0; i < count; i++) {
#ifdef _WIN32
Expand Down
4 changes: 2 additions & 2 deletions src/node_http2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1447,8 +1447,8 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
// this way for performance reasons (it's faster to generate and pass an
// array than it is to generate and pass the object).

MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2);
MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count());
MaybeStackBuffer<Value, 64> headers_v(isolate, stream->headers_count() * 2);
MaybeStackBuffer<Value, 32> sensitive_v(isolate, stream->headers_count());
size_t sensitive_count = 0;

stream->TransferHeaders([&](const Http2Header& header, size_t i) {
Expand Down
4 changes: 2 additions & 2 deletions src/node_messaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ static Maybe<bool> ReadIterable(Environment* env,
if (object->IsArray()) {
Local<Array> arr = object.As<Array>();
size_t length = arr->Length();
transfer_list.AllocateSufficientStorage(length);
transfer_list.AllocateSufficientStorage(env->isolate(), length);
for (size_t i = 0; i < length; i++) {
if (!arr->Get(context, i).ToLocal(&transfer_list[i]))
return Nothing<bool>();
Expand Down Expand Up @@ -1013,7 +1013,7 @@ static Maybe<bool> ReadIterable(Environment* env,
}

if (!entries.empty()) {
transfer_list.AllocateSufficientStorage(entries.size());
transfer_list.AllocateSufficientStorage(env->isolate(), entries.size());
std::copy(entries.begin(), entries.end(), &transfer_list[0]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/node_messaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace worker {
class MessagePortData;
class MessagePort;

typedef MaybeStackBuffer<v8::Local<v8::Value>, 8> TransferList;
typedef MaybeStackBuffer<v8::Value, 8> TransferList;

// Used to represent the in-flight structure of an object that is being
// transferred or cloned using postMessage().
Expand Down
3 changes: 2 additions & 1 deletion src/node_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,8 @@ void Initialize(Local<Object> target,
// Heap space names are extracted once and exposed to JavaScript to
// avoid excessive creation of heap space name Strings.
HeapSpaceStatistics s;
MaybeStackBuffer<Local<Value>, 16> heap_spaces(number_of_heap_spaces);
MaybeStackBuffer<Value, 16> heap_spaces(env->isolate(),
number_of_heap_spaces);
for (size_t i = 0; i < number_of_heap_spaces; i++) {
env->isolate()->GetHeapSpaceStatistics(&s, i);
heap_spaces[i] = String::NewFromUtf8(env->isolate(), s.space_name())
Expand Down
3 changes: 1 addition & 2 deletions src/quic/session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1705,8 +1705,7 @@ void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
// version() is the version that was actually configured for this session.

// versions are the versions requested by the peer.
MaybeStackBuffer<Local<Value>, 5> versions;
versions.AllocateSufficientStorage(nsv);
MaybeStackBuffer<Value, 5> versions(isolate, nsv);
for (size_t n = 0; n < nsv; n++) versions[n] = to_integer(sv[n]);

// supported are the versons we acutually support expressed as a range.
Expand Down
2 changes: 1 addition & 1 deletion src/spawn_sync.cc
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ Local<Array> SyncProcessRunner::BuildOutputArray() {
CHECK(!stdio_pipes_.empty());

EscapableHandleScope scope(env()->isolate());
MaybeStackBuffer<Local<Value>, 8> js_output(stdio_pipes_.size());
MaybeStackBuffer<Value, 8> js_output(env()->isolate(), stdio_pipes_.size());

for (uint32_t i = 0; i < stdio_pipes_.size(); i++) {
SyncProcessStdioPipe* h = stdio_pipes_[i].get();
Expand Down
25 changes: 23 additions & 2 deletions src/util-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ v8::MaybeLocal<v8::Value> ToV8Value(v8::Local<v8::Context> context,
if (isolate == nullptr) isolate = context->GetIsolate();
v8::EscapableHandleScope handle_scope(isolate);

MaybeStackBuffer<v8::Local<v8::Value>, 128> arr(vec.size());
MaybeStackBuffer<v8::Value, 128> arr(isolate, vec.size());
arr.SetLength(vec.size());
for (size_t i = 0; i < vec.size(); ++i) {
if (!ToV8Value(context, vec[i], isolate).ToLocal(&arr[i]))
Expand Down Expand Up @@ -426,7 +426,7 @@ SlicedArguments::SlicedArguments(
if (start >= length) return;
const size_t size = length - start;

AllocateSufficientStorage(size);
AllocateSufficientStorage(args.GetIsolate(), size);
for (size_t i = 0; i < size; ++i)
(*this)[i] = args[i + start];
}
Expand All @@ -447,6 +447,27 @@ void MaybeStackBuffer<T, kStackStorageSize>::AllocateSufficientStorage(
length_ = storage;
}

template <V8Value T, size_t kStackStorageSize>
void MaybeStackBuffer<T, kStackStorageSize>::AllocateSufficientStorage(
v8::Isolate* isolate, size_t storage) {
CHECK(!IsInvalidated());
if (storage > capacity()) {
if (IsAllocated()) {
vec->resize(storage);
} else {
// Previously was not allocated. Moving to allocated storage.
auto& v = vec.emplace(isolate, storage);
for (size_t i = 0; i < length_; ++i) {
v[i] = buf_st_[i];
buf_st_[i] = {};
}
}
buf_ = vec->data();
capacity_ = storage;
}
length_ = storage;
}

template <typename T, size_t S>
ArrayBufferViewContents<T, S>::ArrayBufferViewContents(
v8::Local<v8::Value> value) {
Expand Down
97 changes: 96 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,9 @@ constexpr size_t strsize(const T (&)[N]) {
return N - 1;
}

template <typename T>
concept V8Value = std::convertible_to<T, v8::Value>;

// Allocates an array of member type T. For up to kStackStorageSize items,
// the stack is used, otherwise malloc().
template <typename T, size_t kStackStorageSize = 1024>
Expand Down Expand Up @@ -501,6 +504,98 @@ class MaybeStackBuffer {
T buf_st_[kStackStorageSize];
};

template <V8Value T, size_t kStackStorageSize>
class MaybeStackBuffer<T, kStackStorageSize> {
public:
const v8::Local<T>* out() const { return buf_; }

v8::Local<T>* out() { return buf_; }

// operator* for compatibility with `v8::String::(Utf8)Value`
v8::Local<T>* operator*() { return buf_; }

const v8::Local<T>* operator*() const { return buf_; }

v8::Local<T>& operator[](size_t index) {
CHECK_LT(index, length());
return buf_[index];
}

const v8::Local<T>& operator[](size_t index) const {
CHECK_LT(index, length());
return buf_[index];
}

size_t length() const { return length_; }

// Current maximum capacity of the buffer with which SetLength() can be used
// without first calling AllocateSufficientStorage().
size_t capacity() const { return capacity_; }

// Make sure enough space for `storage` entries is available.
// This method can be called multiple times throughout the lifetime of the
// buffer, but once this has been called Invalidate() cannot be used.
// Content of the buffer in the range [0, length()) is preserved.
void AllocateSufficientStorage(v8::Isolate* isolate, size_t storage);

void SetLength(size_t length) {
// capacity() returns how much memory is actually available.
CHECK_LE(length, capacity());
size_t original_length = length_;
length_ = length;
for (size_t i = length_; i < original_length; i++) {
buf_[i] = v8::Local<T>();
}
}

void SetLengthAndZeroTerminate(size_t length) {
// capacity() returns how much memory is actually available.
CHECK_LE(length + 1, capacity());
SetLength(length);

// T() is 0 for integer types, nullptr for pointers, etc.
buf_[length] = T();
}

// Make dereferencing this object return nullptr.
// This method can be called multiple times throughout the lifetime of the
// buffer, but once this has been called AllocateSufficientStorage() cannot
// be used.
void Invalidate() {
capacity_ = 0;
length_ = 0;
buf_ = nullptr;
vec = std::nullopt;
}

// If the buffer is stored in the heap rather than on the stack.
bool IsAllocated() const { return !IsInvalidated() && vec.has_value(); }

// If Invalidate() has been called.
bool IsInvalidated() const { return buf_ == nullptr; }

MaybeStackBuffer()
: length_(0), capacity_(arraysize(buf_st_)), buf_(buf_st_) {
// Default to a zero-length, null-terminated buffer.
buf_[0] = v8::Local<T>();
}

explicit MaybeStackBuffer(v8::Isolate* isolate, size_t storage)
: MaybeStackBuffer() {
AllocateSufficientStorage(isolate, storage);
}

~MaybeStackBuffer() { vec = std::nullopt; }

private:
size_t length_;
// capacity of the malloc'ed buf_
size_t capacity_;
v8::Local<T>* buf_ = nullptr;
std::optional<v8::LocalVector<T>> vec = std::nullopt;
v8::Local<T> buf_st_[kStackStorageSize];
};

// Provides access to an ArrayBufferView's storage, either the original,
// or for small data, a copy of it. This object's lifetime is bound to the
// original ArrayBufferView's lifetime.
Expand Down Expand Up @@ -788,7 +883,7 @@ constexpr inline bool IsBigEndian() {
static_assert(IsLittleEndian() || IsBigEndian(),
"Node.js does not support mixed-endian systems");

class SlicedArguments : public MaybeStackBuffer<v8::Local<v8::Value>> {
class SlicedArguments : public MaybeStackBuffer<v8::Value> {
public:
inline explicit SlicedArguments(
const v8::FunctionCallbackInfo<v8::Value>& args, size_t start = 0);
Expand Down
50 changes: 50 additions & 0 deletions test/cctest/test_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,53 @@ TEST_F(UtilTest, DetermineSpecificErrorType) {
node::DetermineSpecificErrorType(*env, v8::Uint32::New(isolate_, 255)),
"type number (255)");
}

TEST_F(UtilTest, MaybeStackBufferWithV8) {
const v8::HandleScope handle_scope(isolate_);
Argv argv;
Env env{handle_scope, argv, node::EnvironmentFlags::kNoBrowserGlobals};

MaybeStackBuffer<v8::Value, 2> buf;

EXPECT_EQ(buf.IsAllocated(), false);
EXPECT_EQ(buf.IsInvalidated(), false);
EXPECT_EQ(buf.capacity(), 2U);
EXPECT_EQ(buf.length(), 0U);
EXPECT_EQ(v8::Array::New(isolate_, buf.out(), buf.length())->Length(),
buf.length());

buf.AllocateSufficientStorage(isolate_, 4);

EXPECT_EQ(buf.IsAllocated(), true);
EXPECT_EQ(buf.IsInvalidated(), false);
EXPECT_EQ(buf.capacity(), 4U);
EXPECT_EQ(buf.length(), 4U);
auto c = v8::String::NewFromUtf8(isolate_, "c").ToLocalChecked();
auto d = v8::String::NewFromUtf8(isolate_, "d").ToLocalChecked();
auto e = v8::String::NewFromUtf8(isolate_, "e").ToLocalChecked();
auto f = v8::String::NewFromUtf8(isolate_, "f").ToLocalChecked();
buf[0] = c;
buf[1] = d;
buf[2] = e;
buf[3] = f;
EXPECT_EQ(buf[0], c);
EXPECT_EQ(buf[1], d);
EXPECT_EQ(buf[2], e);
EXPECT_EQ(buf[3], f);
EXPECT_EQ(v8::Array::New(isolate_, buf.out(), buf.length())->Length(),
buf.length());

buf.SetLength(2);
EXPECT_EQ(buf.IsAllocated(), true);
EXPECT_EQ(buf.IsInvalidated(), false);
EXPECT_EQ(buf.capacity(), 4U);
EXPECT_EQ(buf.length(), 2U);
EXPECT_EQ(v8::Array::New(isolate_, buf.out(), buf.length())->Length(),
buf.length());

buf.Invalidate();
EXPECT_EQ(buf.IsAllocated(), false);
EXPECT_EQ(buf.IsInvalidated(), true);
EXPECT_EQ(buf.capacity(), 0U);
EXPECT_EQ(buf.length(), 0U);
}

0 comments on commit 92fbd83

Please sign in to comment.