Skip to content

Commit 9b4eec0

Browse files
committed
src: allow not materializing ArrayBuffers from C++
Where appropriate, use a helper that wraps around `ArrayBufferView::Buffer()` or `ArrayBufferView::CopyContents()` rather than `Buffer::Data()`, as that may help to avoid materializing the underlying `ArrayBuffer` when reading small typed arrays from C++. This allows keeping the performance benefits of the faster creation of heap-allocated small typed arrays in many cases. PR-URL: #26301 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent e2baa68 commit 9b4eec0

File tree

8 files changed

+183
-145
lines changed

8 files changed

+183
-145
lines changed

src/node_crypto.cc

+107-120
Large diffs are not rendered by default.

src/node_crypto.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class SSLWrap {
333333

334334
ClientHelloParser hello_parser_;
335335

336-
Persistent<v8::Object> ocsp_response_;
336+
Persistent<v8::ArrayBufferView> ocsp_response_;
337337
Persistent<v8::Value> sni_context_;
338338

339339
friend class SecureContext;
@@ -462,7 +462,7 @@ class KeyObject : public BaseObject {
462462
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
463463

464464
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
465-
void InitSecret(const char* key, size_t key_len);
465+
void InitSecret(v8::Local<v8::ArrayBufferView> abv);
466466
void InitPublic(const ManagedEVPPKey& pkey);
467467
void InitPrivate(const ManagedEVPPKey& pkey);
468468

@@ -803,8 +803,7 @@ class ECDH : public BaseObject {
803803
static void Initialize(Environment* env, v8::Local<v8::Object> target);
804804
static ECPointPointer BufferToPoint(Environment* env,
805805
const EC_GROUP* group,
806-
char* data,
807-
size_t len);
806+
v8::Local<v8::Value> buf);
808807

809808
// TODO(joyeecheung): track the memory used by OpenSSL types
810809
SET_NO_MEMORY_INFO()

src/node_http2.cc

+12-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace node {
1313

1414
using v8::ArrayBuffer;
15+
using v8::ArrayBufferView;
1516
using v8::Boolean;
1617
using v8::Context;
1718
using v8::Float64Array;
@@ -2483,7 +2484,7 @@ void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
24832484
// state of the Http2Session, it's simply a notification.
24842485
void Http2Session::Goaway(uint32_t code,
24852486
int32_t lastStreamID,
2486-
uint8_t* data,
2487+
const uint8_t* data,
24872488
size_t len) {
24882489
if (IsDestroyed())
24892490
return;
@@ -2508,16 +2509,13 @@ void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
25082509

25092510
uint32_t code = args[0]->Uint32Value(context).ToChecked();
25102511
int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2511-
Local<Value> opaqueData = args[2];
2512-
uint8_t* data = nullptr;
2513-
size_t length = 0;
2512+
ArrayBufferViewContents<uint8_t> opaque_data;
25142513

2515-
if (Buffer::HasInstance(opaqueData)) {
2516-
data = reinterpret_cast<uint8_t*>(Buffer::Data(opaqueData));
2517-
length = Buffer::Length(opaqueData);
2514+
if (args[2]->IsArrayBufferView()) {
2515+
opaque_data.Read(args[2].As<ArrayBufferView>());
25182516
}
25192517

2520-
session->Goaway(code, lastStreamID, data, length);
2518+
session->Goaway(code, lastStreamID, opaque_data.data(), opaque_data.length());
25212519
}
25222520

25232521
// Update accounting of data chunks. This is used primarily to manage timeout
@@ -2772,10 +2770,10 @@ void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
27722770

27732771
// A PING frame may have exactly 8 bytes of payload data. If not provided,
27742772
// then the current hrtime will be used as the payload.
2775-
uint8_t* payload = nullptr;
2776-
if (Buffer::HasInstance(args[0])) {
2777-
payload = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
2778-
CHECK_EQ(Buffer::Length(args[0]), 8);
2773+
ArrayBufferViewContents<uint8_t, 8> payload;
2774+
if (args[0]->IsArrayBufferView()) {
2775+
payload.Read(args[0].As<ArrayBufferView>());
2776+
CHECK_EQ(payload.length(), 8);
27792777
}
27802778

27812779
Local<Object> obj;
@@ -2800,7 +2798,7 @@ void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
28002798
// the callback will be invoked and a notification sent out to JS land. The
28012799
// notification will include the duration of the ping, allowing the round
28022800
// trip to be measured.
2803-
ping->Send(payload);
2801+
ping->Send(payload.data());
28042802
args.GetReturnValue().Set(true);
28052803
}
28062804

@@ -2872,7 +2870,7 @@ Http2Session::Http2Ping::Http2Ping(Http2Session* session, Local<Object> obj)
28722870
session_(session),
28732871
startTime_(uv_hrtime()) {}
28742872

2875-
void Http2Session::Http2Ping::Send(uint8_t* payload) {
2873+
void Http2Session::Http2Ping::Send(const uint8_t* payload) {
28762874
uint8_t data[8];
28772875
if (payload == nullptr) {
28782876
memcpy(&data, &startTime_, arraysize(data));

src/node_http2.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,8 @@ class Http2Session : public AsyncWrap, public StreamListener {
699699
void Close(uint32_t code = NGHTTP2_NO_ERROR,
700700
bool socket_closed = false);
701701
void Consume(Local<External> external);
702-
void Goaway(uint32_t code, int32_t lastStreamID, uint8_t* data, size_t len);
702+
void Goaway(uint32_t code, int32_t lastStreamID,
703+
const uint8_t* data, size_t len);
703704
void AltSvc(int32_t id,
704705
uint8_t* origin,
705706
size_t origin_len,
@@ -1089,7 +1090,7 @@ class Http2Session::Http2Ping : public AsyncWrap {
10891090
SET_MEMORY_INFO_NAME(Http2Ping)
10901091
SET_SELF_SIZE(Http2Ping)
10911092

1092-
void Send(uint8_t* payload);
1093+
void Send(const uint8_t* payload);
10931094
void Done(bool ack, const uint8_t* payload = nullptr);
10941095

10951096
private:

src/string_decoder.cc

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "string_decoder-inl.h"
55

66
using v8::Array;
7+
using v8::ArrayBufferView;
78
using v8::Context;
89
using v8::FunctionCallbackInfo;
910
using v8::Integer;
@@ -252,9 +253,13 @@ void DecodeData(const FunctionCallbackInfo<Value>& args) {
252253
StringDecoder* decoder =
253254
reinterpret_cast<StringDecoder*>(Buffer::Data(args[0]));
254255
CHECK_NOT_NULL(decoder);
255-
size_t nread = Buffer::Length(args[1]);
256+
257+
CHECK(args[1]->IsArrayBufferView());
258+
ArrayBufferViewContents<char> content(args[1].As<ArrayBufferView>());
259+
size_t length = content.length();
260+
256261
MaybeLocal<String> ret =
257-
decoder->DecodeData(args.GetIsolate(), Buffer::Data(args[1]), &nread);
262+
decoder->DecodeData(args.GetIsolate(), content.data(), &length);
258263
if (!ret.IsEmpty())
259264
args.GetReturnValue().Set(ret.ToLocalChecked());
260265
}

src/util-inl.h

+26
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,32 @@ SlicedArguments::SlicedArguments(
466466
(*this)[i] = args[i + start];
467467
}
468468

469+
template <typename T, size_t S>
470+
ArrayBufferViewContents<T, S>::ArrayBufferViewContents(
471+
v8::Local<v8::Value> value) {
472+
CHECK(value->IsArrayBufferView());
473+
Read(value.As<v8::ArrayBufferView>());
474+
}
475+
476+
template <typename T, size_t S>
477+
ArrayBufferViewContents<T, S>::ArrayBufferViewContents(
478+
v8::Local<v8::ArrayBufferView> abv) {
479+
Read(abv);
480+
}
481+
482+
template <typename T, size_t S>
483+
void ArrayBufferViewContents<T, S>::Read(v8::Local<v8::ArrayBufferView> abv) {
484+
static_assert(sizeof(T) == 1, "Only supports one-byte data at the moment");
485+
length_ = abv->ByteLength();
486+
if (length_ > sizeof(stack_storage_) || abv->HasBuffer()) {
487+
data_ = static_cast<T*>(abv->Buffer()->GetContents().Data()) +
488+
abv->ByteOffset();
489+
} else {
490+
abv->CopyContents(stack_storage_, sizeof(stack_storage_));
491+
data_ = stack_storage_;
492+
}
493+
}
494+
469495
} // namespace node
470496

471497
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/util.cc

+4-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
namespace node {
3131

32+
using v8::ArrayBufferView;
3233
using v8::Isolate;
3334
using v8::Local;
3435
using v8::String;
@@ -89,11 +90,11 @@ BufferValue::BufferValue(Isolate* isolate, Local<Value> value) {
8990

9091
if (value->IsString()) {
9192
MakeUtf8String(isolate, value, this);
92-
} else if (Buffer::HasInstance(value)) {
93-
const size_t len = Buffer::Length(value);
93+
} else if (value->IsArrayBufferView()) {
94+
const size_t len = value.As<ArrayBufferView>()->ByteLength();
9495
// Leave place for the terminating '\0' byte.
9596
AllocateSufficientStorage(len + 1);
96-
memcpy(out(), Buffer::Data(value), len);
97+
value.As<ArrayBufferView>()->CopyContents(out(), len);
9798
SetLengthAndZeroTerminate(len);
9899
} else {
99100
Invalidate();

src/util.h

+21
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,27 @@ class MaybeStackBuffer {
417417
T buf_st_[kStackStorageSize];
418418
};
419419

420+
// Provides access to an ArrayBufferView's storage, either the original,
421+
// or for small data, a copy of it. This object's lifetime is bound to the
422+
// original ArrayBufferView's lifetime.
423+
template <typename T, size_t kStackStorageSize = 64>
424+
class ArrayBufferViewContents {
425+
public:
426+
ArrayBufferViewContents() {}
427+
428+
explicit inline ArrayBufferViewContents(v8::Local<v8::Value> value);
429+
explicit inline ArrayBufferViewContents(v8::Local<v8::ArrayBufferView> abv);
430+
inline void Read(v8::Local<v8::ArrayBufferView> abv);
431+
432+
inline const T* data() const { return data_; }
433+
inline size_t length() const { return length_; }
434+
435+
private:
436+
T stack_storage_[kStackStorageSize];
437+
T* data_ = nullptr;
438+
size_t length_ = 0;
439+
};
440+
420441
class Utf8Value : public MaybeStackBuffer<char> {
421442
public:
422443
explicit Utf8Value(v8::Isolate* isolate, v8::Local<v8::Value> value);

0 commit comments

Comments
 (0)