GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_blob.cc Lines: 240 279 86.0 %
Date: 2022-11-04 03:21:42 Branches: 76 134 56.7 %

Line Branch Exec Source
1
#include "node_blob.h"
2
#include "async_wrap-inl.h"
3
#include "base_object-inl.h"
4
#include "env-inl.h"
5
#include "memory_tracker-inl.h"
6
#include "node_errors.h"
7
#include "node_external_reference.h"
8
#include "threadpoolwork-inl.h"
9
#include "v8.h"
10
11
#include <algorithm>
12
13
namespace node {
14
15
using v8::Array;
16
using v8::ArrayBuffer;
17
using v8::ArrayBufferView;
18
using v8::BackingStore;
19
using v8::Context;
20
using v8::EscapableHandleScope;
21
using v8::Function;
22
using v8::FunctionCallbackInfo;
23
using v8::FunctionTemplate;
24
using v8::HandleScope;
25
using v8::Isolate;
26
using v8::Local;
27
using v8::MaybeLocal;
28
using v8::Number;
29
using v8::Object;
30
using v8::String;
31
using v8::Uint32;
32
using v8::Undefined;
33
using v8::Value;
34
35
784
void Blob::Initialize(
36
    Local<Object> target,
37
    Local<Value> unused,
38
    Local<Context> context,
39
    void* priv) {
40
784
  Environment* env = Environment::GetCurrent(context);
41
42
  BlobBindingData* const binding_data =
43
784
      env->AddBindingData<BlobBindingData>(context, target);
44
784
  if (binding_data == nullptr) return;
45
46
784
  SetMethod(context, target, "createBlob", New);
47
784
  SetMethod(context, target, "storeDataObject", StoreDataObject);
48
784
  SetMethod(context, target, "getDataObject", GetDataObject);
49
784
  SetMethod(context, target, "revokeDataObject", RevokeDataObject);
50
784
  FixedSizeBlobCopyJob::Initialize(env, target);
51
}
52
53
197
Local<FunctionTemplate> Blob::GetConstructorTemplate(Environment* env) {
54
197
  Local<FunctionTemplate> tmpl = env->blob_constructor_template();
55
197
  if (tmpl.IsEmpty()) {
56
11
    Isolate* isolate = env->isolate();
57
11
    tmpl = NewFunctionTemplate(isolate, nullptr);
58
22
    tmpl->InstanceTemplate()->SetInternalFieldCount(
59
        BaseObject::kInternalFieldCount);
60
11
    tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
61
11
    tmpl->SetClassName(
62
        FIXED_ONE_BYTE_STRING(env->isolate(), "Blob"));
63
11
    SetProtoMethod(isolate, tmpl, "toArrayBuffer", ToArrayBuffer);
64
11
    SetProtoMethod(isolate, tmpl, "slice", ToSlice);
65
11
    env->set_blob_constructor_template(tmpl);
66
  }
67
197
  return tmpl;
68
}
69
70
57
bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) {
71
114
  return GetConstructorTemplate(env)->HasInstance(object);
72
}
73
74
140
BaseObjectPtr<Blob> Blob::Create(Environment* env,
75
                                 const std::vector<BlobEntry>& store,
76
                                 size_t length) {
77
280
  HandleScope scope(env->isolate());
78
79
  Local<Function> ctor;
80
420
  if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
81
    return BaseObjectPtr<Blob>();
82
83
  Local<Object> obj;
84
280
  if (!ctor->NewInstance(env->context()).ToLocal(&obj))
85
    return BaseObjectPtr<Blob>();
86
87
140
  return MakeBaseObject<Blob>(env, obj, store, length);
88
}
89
90
126
void Blob::New(const FunctionCallbackInfo<Value>& args) {
91
126
  Environment* env = Environment::GetCurrent(args);
92
126
  CHECK(args[0]->IsArray());  // sources
93
126
  CHECK(args[1]->IsUint32());  // length
94
95
126
  std::vector<BlobEntry> entries;
96
97
252
  size_t length = args[1].As<Uint32>()->Value();
98
126
  size_t len = 0;
99
126
  Local<Array> ary = args[0].As<Array>();
100
546
  for (size_t n = 0; n < ary->Length(); n++) {
101
    Local<Value> entry;
102
294
    if (!ary->Get(env->context(), n).ToLocal(&entry))
103
      return;
104

147
    CHECK(entry->IsArrayBufferView() || Blob::HasInstance(env, entry));
105
147
    if (entry->IsArrayBufferView()) {
106
143
      Local<ArrayBufferView> view = entry.As<ArrayBufferView>();
107
143
      CHECK_EQ(view->ByteOffset(), 0);
108
286
      std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
109
143
      size_t byte_length = view->ByteLength();
110
286
      view->Buffer()->Detach();  // The Blob will own the backing store now.
111
143
      entries.emplace_back(BlobEntry{std::move(store), byte_length, 0});
112
143
      len += byte_length;
113
    } else {
114
      Blob* blob;
115
4
      ASSIGN_OR_RETURN_UNWRAP(&blob, entry);
116
4
      auto source = blob->entries();
117
4
      entries.insert(entries.end(), source.begin(), source.end());
118
4
      len += blob->length();
119
    }
120
  }
121
126
  CHECK_EQ(length, len);
122
123
252
  BaseObjectPtr<Blob> blob = Create(env, entries, length);
124
126
  if (blob)
125
252
    args.GetReturnValue().Set(blob->object());
126
}
127
128
void Blob::ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
129
  Environment* env = Environment::GetCurrent(args);
130
  Blob* blob;
131
  ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
132
  Local<Value> ret;
133
  if (blob->GetArrayBuffer(env).ToLocal(&ret))
134
    args.GetReturnValue().Set(ret);
135
}
136
137
13
void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) {
138
13
  Environment* env = Environment::GetCurrent(args);
139
  Blob* blob;
140
13
  ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
141
13
  CHECK(args[0]->IsUint32());
142
13
  CHECK(args[1]->IsUint32());
143
26
  size_t start = args[0].As<Uint32>()->Value();
144
26
  size_t end = args[1].As<Uint32>()->Value();
145
26
  BaseObjectPtr<Blob> slice = blob->Slice(env, start, end);
146
13
  if (slice)
147
26
    args.GetReturnValue().Set(slice->object());
148
}
149
150
void Blob::MemoryInfo(MemoryTracker* tracker) const {
151
  tracker->TrackFieldWithSize("store", length_);
152
}
153
154
MaybeLocal<Value> Blob::GetArrayBuffer(Environment* env) {
155
  EscapableHandleScope scope(env->isolate());
156
  size_t len = length();
157
  std::shared_ptr<BackingStore> store =
158
      ArrayBuffer::NewBackingStore(env->isolate(), len);
159
  if (len > 0) {
160
    unsigned char* dest = static_cast<unsigned char*>(store->Data());
161
    size_t total = 0;
162
    for (const auto& entry : entries()) {
163
      unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
164
      src += entry.offset;
165
      memcpy(dest, src, entry.length);
166
      dest += entry.length;
167
      total += entry.length;
168
      CHECK_LE(total, len);
169
    }
170
  }
171
172
  return scope.Escape(ArrayBuffer::New(env->isolate(), store));
173
}
174
175
13
BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) {
176
13
  CHECK_LE(start, length());
177
13
  CHECK_LE(end, length());
178
13
  CHECK_LE(start, end);
179
180
26
  std::vector<BlobEntry> slices;
181
13
  size_t total = end - start;
182
13
  size_t remaining = total;
183
184
13
  if (total == 0) return Create(env, slices, 0);
185
186
14
  for (const auto& entry : entries()) {
187
14
    if (start + entry.offset > entry.store->ByteLength()) {
188
      start -= entry.length;
189
      continue;
190
    }
191
192
14
    size_t offset = entry.offset + start;
193
14
    size_t len = std::min(remaining, entry.store->ByteLength() - offset);
194
14
    slices.emplace_back(BlobEntry{entry.store, len, offset});
195
196
14
    remaining -= len;
197
14
    start = 0;
198
199
14
    if (remaining == 0)
200
9
      break;
201
  }
202
203
9
  return Create(env, slices, total);
204
}
205
206
140
Blob::Blob(
207
    Environment* env,
208
    v8::Local<v8::Object> obj,
209
    const std::vector<BlobEntry>& store,
210
140
    size_t length)
211
    : BaseObject(env, obj),
212
      store_(store),
213
140
      length_(length) {
214
140
  MakeWeak();
215
140
}
216
217
BaseObjectPtr<BaseObject>
218
1
Blob::BlobTransferData::Deserialize(
219
    Environment* env,
220
    Local<Context> context,
221
    std::unique_ptr<worker::TransferData> self) {
222
2
  if (context != env->context()) {
223
    THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
224
    return {};
225
  }
226
1
  return Blob::Create(env, store_, length_);
227
}
228
229
1
BaseObject::TransferMode Blob::GetTransferMode() const {
230
1
  return BaseObject::TransferMode::kCloneable;
231
}
232
233
1
std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const {
234
1
  return std::make_unique<BlobTransferData>(store_, length_);
235
}
236
237
2
void Blob::StoreDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
238
2
  Environment* env = Environment::GetCurrent(args);
239
  BlobBindingData* binding_data =
240
2
      Environment::GetBindingData<BlobBindingData>(args);
241
242
4
  CHECK(args[0]->IsString());  // ID key
243
2
  CHECK(Blob::HasInstance(env, args[1]));  // Blob
244
2
  CHECK(args[2]->IsUint32());  // Length
245
4
  CHECK(args[3]->IsString());  // Type
246
247
2
  Utf8Value key(env->isolate(), args[0]);
248
  Blob* blob;
249
2
  ASSIGN_OR_RETURN_UNWRAP(&blob, args[1]);
250
251
4
  size_t length = args[2].As<Uint32>()->Value();
252
2
  Utf8Value type(env->isolate(), args[3]);
253
254
2
  binding_data->store_data_object(
255
4
      std::string(*key, key.length()),
256
4
      BlobBindingData::StoredDataObject(
257
4
        BaseObjectPtr<Blob>(blob),
258
        length,
259
4
        std::string(*type, type.length())));
260
}
261
262
2
void Blob::RevokeDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
263
  BlobBindingData* binding_data =
264
2
      Environment::GetBindingData<BlobBindingData>(args);
265
266
2
  Environment* env = Environment::GetCurrent(args);
267
4
  CHECK(args[0]->IsString());  // ID key
268
269
2
  Utf8Value key(env->isolate(), args[0]);
270
271
2
  binding_data->revoke_data_object(std::string(*key, key.length()));
272
2
}
273
274
2
void Blob::GetDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
275
  BlobBindingData* binding_data =
276
2
      Environment::GetBindingData<BlobBindingData>(args);
277
278
2
  Environment* env = Environment::GetCurrent(args);
279
4
  CHECK(args[0]->IsString());
280
281
2
  Utf8Value key(env->isolate(), args[0]);
282
283
  BlobBindingData::StoredDataObject stored =
284
4
      binding_data->get_data_object(std::string(*key, key.length()));
285
2
  if (stored.blob) {
286
    Local<Value> type;
287
1
    if (!String::NewFromUtf8(
288
            env->isolate(),
289
            stored.type.c_str(),
290
            v8::NewStringType::kNormal,
291
2
            static_cast<int>(stored.type.length())).ToLocal(&type)) {
292
      return;
293
    }
294
295
    Local<Value> values[] = {
296
1
      stored.blob->object(),
297
1
      Uint32::NewFromUnsigned(env->isolate(), stored.length),
298
      type
299
3
    };
300
301
2
    args.GetReturnValue().Set(
302
        Array::New(
303
            env->isolate(),
304
            values,
305
            arraysize(values)));
306
  }
307
}
308
309
51
FixedSizeBlobCopyJob::FixedSizeBlobCopyJob(Environment* env,
310
                                           Local<Object> object,
311
                                           Blob* blob,
312
51
                                           FixedSizeBlobCopyJob::Mode mode)
313
    : AsyncWrap(env, object, AsyncWrap::PROVIDER_FIXEDSIZEBLOBCOPY),
314
      ThreadPoolWork(env, "blob"),
315
51
      mode_(mode) {
316
51
  if (mode == FixedSizeBlobCopyJob::Mode::SYNC) MakeWeak();
317
51
  source_ = blob->entries();
318
51
  length_ = blob->length();
319
51
}
320
321
3
void FixedSizeBlobCopyJob::AfterThreadPoolWork(int status) {
322
3
  Environment* env = AsyncWrap::env();
323
3
  CHECK_EQ(mode_, Mode::ASYNC);
324

3
  CHECK(status == 0 || status == UV_ECANCELED);
325
6
  std::unique_ptr<FixedSizeBlobCopyJob> ptr(this);
326
6
  HandleScope handle_scope(env->isolate());
327
3
  Context::Scope context_scope(env->context());
328
9
  Local<Value> args[2];
329
330
3
  if (status == UV_ECANCELED) {
331
    args[0] = Number::New(env->isolate(), status),
332
    args[1] = Undefined(env->isolate());
333
  } else {
334
3
    args[0] = Undefined(env->isolate());
335
6
    args[1] = ArrayBuffer::New(env->isolate(), destination_);
336
  }
337
338
3
  ptr->MakeCallback(env->ondone_string(), arraysize(args), args);
339
3
}
340
341
51
void FixedSizeBlobCopyJob::DoThreadPoolWork() {
342
51
  unsigned char* dest = static_cast<unsigned char*>(destination_->Data());
343
51
  if (length_ > 0) {
344
45
    size_t total = 0;
345
113
    for (const auto& entry : source_) {
346
68
      unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
347
68
      src += entry.offset;
348
68
      memcpy(dest, src, entry.length);
349
68
      dest += entry.length;
350
68
      total += entry.length;
351
68
      CHECK_LE(total, length_);
352
    }
353
  }
354
51
}
355
356
void FixedSizeBlobCopyJob::MemoryInfo(MemoryTracker* tracker) const {
357
  tracker->TrackFieldWithSize("source", length_);
358
  tracker->TrackFieldWithSize(
359
      "destination",
360
      destination_ ? destination_->ByteLength() : 0);
361
}
362
363
784
void FixedSizeBlobCopyJob::Initialize(Environment* env, Local<Object> target) {
364
784
  Isolate* isolate = env->isolate();
365
784
  v8::Local<v8::FunctionTemplate> job = NewFunctionTemplate(isolate, New);
366
784
  job->Inherit(AsyncWrap::GetConstructorTemplate(env));
367
1568
  job->InstanceTemplate()->SetInternalFieldCount(
368
      AsyncWrap::kInternalFieldCount);
369
784
  SetProtoMethod(isolate, job, "run", Run);
370
784
  SetConstructorFunction(env->context(), target, "FixedSizeBlobCopyJob", job);
371
784
}
372
373
51
void FixedSizeBlobCopyJob::New(const FunctionCallbackInfo<Value>& args) {
374
  static constexpr size_t kMaxSyncLength = 4096;
375
  static constexpr size_t kMaxEntryCount = 4;
376
377
51
  Environment* env = Environment::GetCurrent(args);
378
51
  CHECK(args.IsConstructCall());
379
51
  CHECK(args[0]->IsObject());
380
51
  CHECK(Blob::HasInstance(env, args[0]));
381
382
  Blob* blob;
383
51
  ASSIGN_OR_RETURN_UNWRAP(&blob, args[0]);
384
385
  // This is a fairly arbitrary heuristic. We want to avoid deferring to
386
  // the threadpool if the amount of data being copied is small and there
387
  // aren't that many entries to copy.
388
  FixedSizeBlobCopyJob::Mode mode =
389
100
      (blob->length() < kMaxSyncLength &&
390
100
       blob->entries().size() < kMaxEntryCount) ?
391
          FixedSizeBlobCopyJob::Mode::SYNC :
392
51
          FixedSizeBlobCopyJob::Mode::ASYNC;
393
394
51
  new FixedSizeBlobCopyJob(env, args.This(), blob, mode);
395
}
396
397
51
void FixedSizeBlobCopyJob::Run(const FunctionCallbackInfo<Value>& args) {
398
51
  Environment* env = Environment::GetCurrent(args);
399
  FixedSizeBlobCopyJob* job;
400
54
  ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder());
401
51
  job->destination_ =
402
51
      ArrayBuffer::NewBackingStore(env->isolate(), job->length_);
403
51
  if (job->mode() == FixedSizeBlobCopyJob::Mode::ASYNC)
404
3
    return job->ScheduleWork();
405
406
48
  job->DoThreadPoolWork();
407
96
  args.GetReturnValue().Set(
408
48
      ArrayBuffer::New(env->isolate(), job->destination_));
409
}
410
411
5586
void FixedSizeBlobCopyJob::RegisterExternalReferences(
412
    ExternalReferenceRegistry* registry) {
413
5586
  registry->Register(New);
414
5586
  registry->Register(Run);
415
5586
}
416
417
void BlobBindingData::StoredDataObject::MemoryInfo(
418
    MemoryTracker* tracker) const {
419
  tracker->TrackField("blob", blob);
420
  tracker->TrackFieldWithSize("type", type.length());
421
}
422
423
2
BlobBindingData::StoredDataObject::StoredDataObject(
424
    const BaseObjectPtr<Blob>& blob_,
425
    size_t length_,
426
2
    const std::string& type_)
427
    : blob(blob_),
428
      length(length_),
429
2
      type(type_) {}
430
431
6362
BlobBindingData::BlobBindingData(Environment* env, Local<Object> wrap)
432
6362
    : SnapshotableObject(env, wrap, type_int) {
433
6362
  MakeWeak();
434
6362
}
435
436
25
void BlobBindingData::MemoryInfo(MemoryTracker* tracker) const {
437
25
  tracker->TrackField("data_objects", data_objects_);
438
25
}
439
440
2
void BlobBindingData::store_data_object(
441
    const std::string& uuid,
442
    const BlobBindingData::StoredDataObject& object) {
443
2
  data_objects_[uuid] = object;
444
2
}
445
446
2
void BlobBindingData::revoke_data_object(const std::string& uuid) {
447
2
  if (data_objects_.find(uuid) == data_objects_.end()) {
448
1
    return;
449
  }
450
1
  data_objects_.erase(uuid);
451
1
  CHECK_EQ(data_objects_.find(uuid), data_objects_.end());
452
}
453
454
2
BlobBindingData::StoredDataObject BlobBindingData::get_data_object(
455
    const std::string& uuid) {
456
2
  auto entry = data_objects_.find(uuid);
457
2
  if (entry == data_objects_.end())
458
1
    return BlobBindingData::StoredDataObject {};
459
1
  return entry->second;
460
}
461
462
5578
void BlobBindingData::Deserialize(Local<Context> context,
463
                                  Local<Object> holder,
464
                                  int index,
465
                                  InternalFieldInfoBase* info) {
466
  DCHECK_EQ(index, BaseObject::kEmbedderType);
467
11156
  HandleScope scope(context->GetIsolate());
468
5578
  Environment* env = Environment::GetCurrent(context);
469
  BlobBindingData* binding =
470
5578
      env->AddBindingData<BlobBindingData>(context, holder);
471
5578
  CHECK_NOT_NULL(binding);
472
5578
}
473
474
7
bool BlobBindingData::PrepareForSerialization(Local<Context> context,
475
                                              v8::SnapshotCreator* creator) {
476
  // Stored blob objects are not actually persisted.
477
  // Return true because we need to maintain the reference to the binding from
478
  // JS land.
479
7
  return true;
480
}
481
482
7
InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
483
  DCHECK_EQ(index, BaseObject::kEmbedderType);
484
  InternalFieldInfo* info =
485
7
      InternalFieldInfoBase::New<InternalFieldInfo>(type());
486
7
  return info;
487
}
488
489
5586
void Blob::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
490
5586
  registry->Register(Blob::New);
491
5586
  registry->Register(Blob::ToArrayBuffer);
492
5586
  registry->Register(Blob::ToSlice);
493
5586
  registry->Register(Blob::StoreDataObject);
494
5586
  registry->Register(Blob::GetDataObject);
495
5586
  registry->Register(Blob::RevokeDataObject);
496
497
5586
  FixedSizeBlobCopyJob::RegisterExternalReferences(registry);
498
5586
}
499
500
}  // namespace node
501
502
5657
NODE_MODULE_CONTEXT_AWARE_INTERNAL(blob, node::Blob::Initialize)
503
5586
NODE_MODULE_EXTERNAL_REFERENCE(blob, node::Blob::RegisterExternalReferences)