GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_blob.cc Lines: 241 280 86.1 %
Date: 2022-12-07 04:23:16 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
800
void Blob::Initialize(
36
    Local<Object> target,
37
    Local<Value> unused,
38
    Local<Context> context,
39
    void* priv) {
40
800
  Environment* env = Environment::GetCurrent(context);
41
42
  BlobBindingData* const binding_data =
43
800
      env->AddBindingData<BlobBindingData>(context, target);
44
800
  if (binding_data == nullptr) return;
45
46
800
  SetMethod(context, target, "createBlob", New);
47
800
  SetMethod(context, target, "storeDataObject", StoreDataObject);
48
800
  SetMethod(context, target, "getDataObject", GetDataObject);
49
800
  SetMethod(context, target, "revokeDataObject", RevokeDataObject);
50
800
  FixedSizeBlobCopyJob::Initialize(env, target);
51
}
52
53
348
Local<FunctionTemplate> Blob::GetConstructorTemplate(Environment* env) {
54
348
  Local<FunctionTemplate> tmpl = env->blob_constructor_template();
55
348
  if (tmpl.IsEmpty()) {
56
14
    Isolate* isolate = env->isolate();
57
14
    tmpl = NewFunctionTemplate(isolate, nullptr);
58
28
    tmpl->InstanceTemplate()->SetInternalFieldCount(
59
        BaseObject::kInternalFieldCount);
60
14
    tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
61
14
    tmpl->SetClassName(
62
        FIXED_ONE_BYTE_STRING(env->isolate(), "Blob"));
63
14
    SetProtoMethod(isolate, tmpl, "toArrayBuffer", ToArrayBuffer);
64
14
    SetProtoMethod(isolate, tmpl, "slice", ToSlice);
65
14
    env->set_blob_constructor_template(tmpl);
66
  }
67
348
  return tmpl;
68
}
69
70
104
bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) {
71
208
  return GetConstructorTemplate(env)->HasInstance(object);
72
}
73
74
244
BaseObjectPtr<Blob> Blob::Create(Environment* env,
75
                                 const std::vector<BlobEntry>& store,
76
                                 size_t length) {
77
488
  HandleScope scope(env->isolate());
78
79
  Local<Function> ctor;
80
732
  if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
81
    return BaseObjectPtr<Blob>();
82
83
  Local<Object> obj;
84
488
  if (!ctor->NewInstance(env->context()).ToLocal(&obj))
85
    return BaseObjectPtr<Blob>();
86
87
244
  return MakeBaseObject<Blob>(env, obj, store, length);
88
}
89
90
209
void Blob::New(const FunctionCallbackInfo<Value>& args) {
91
209
  Environment* env = Environment::GetCurrent(args);
92
209
  CHECK(args[0]->IsArray());  // sources
93
209
  CHECK(args[1]->IsUint32());  // length
94
95
209
  std::vector<BlobEntry> entries;
96
97
418
  size_t length = args[1].As<Uint32>()->Value();
98
209
  size_t len = 0;
99
209
  Local<Array> ary = args[0].As<Array>();
100
850
  for (size_t n = 0; n < ary->Length(); n++) {
101
    Local<Value> entry;
102
432
    if (!ary->Get(env->context(), n).ToLocal(&entry))
103
      return;
104

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

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