• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "threadpoolwork-inl.h"
8 #include "v8.h"
9 
10 #include <algorithm>
11 
12 namespace node {
13 
14 using v8::Array;
15 using v8::ArrayBuffer;
16 using v8::ArrayBufferView;
17 using v8::BackingStore;
18 using v8::Context;
19 using v8::EscapableHandleScope;
20 using v8::Function;
21 using v8::FunctionCallbackInfo;
22 using v8::FunctionTemplate;
23 using v8::HandleScope;
24 using v8::Local;
25 using v8::MaybeLocal;
26 using v8::Number;
27 using v8::Object;
28 using v8::Uint32;
29 using v8::Undefined;
30 using v8::Value;
31 
Initialize(Environment * env,v8::Local<v8::Object> target)32 void Blob::Initialize(Environment* env, v8::Local<v8::Object> target) {
33   env->SetMethod(target, "createBlob", New);
34   FixedSizeBlobCopyJob::Initialize(env, target);
35 }
36 
GetConstructorTemplate(Environment * env)37 Local<FunctionTemplate> Blob::GetConstructorTemplate(Environment* env) {
38   Local<FunctionTemplate> tmpl = env->blob_constructor_template();
39   if (tmpl.IsEmpty()) {
40     tmpl = FunctionTemplate::New(env->isolate());
41     tmpl->InstanceTemplate()->SetInternalFieldCount(
42         BaseObject::kInternalFieldCount);
43     tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
44     tmpl->SetClassName(
45         FIXED_ONE_BYTE_STRING(env->isolate(), "Blob"));
46     env->SetProtoMethod(tmpl, "toArrayBuffer", ToArrayBuffer);
47     env->SetProtoMethod(tmpl, "slice", ToSlice);
48     env->set_blob_constructor_template(tmpl);
49   }
50   return tmpl;
51 }
52 
HasInstance(Environment * env,v8::Local<v8::Value> object)53 bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) {
54   return GetConstructorTemplate(env)->HasInstance(object);
55 }
56 
Create(Environment * env,const std::vector<BlobEntry> store,size_t length)57 BaseObjectPtr<Blob> Blob::Create(
58     Environment* env,
59     const std::vector<BlobEntry> store,
60     size_t length) {
61 
62   HandleScope scope(env->isolate());
63 
64   Local<Function> ctor;
65   if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
66     return BaseObjectPtr<Blob>();
67 
68   Local<Object> obj;
69   if (!ctor->NewInstance(env->context()).ToLocal(&obj))
70     return BaseObjectPtr<Blob>();
71 
72   return MakeBaseObject<Blob>(env, obj, store, length);
73 }
74 
New(const FunctionCallbackInfo<Value> & args)75 void Blob::New(const FunctionCallbackInfo<Value>& args) {
76   Environment* env = Environment::GetCurrent(args);
77   CHECK(args[0]->IsArray());  // sources
78   CHECK(args[1]->IsUint32());  // length
79 
80   std::vector<BlobEntry> entries;
81 
82   size_t length = args[1].As<Uint32>()->Value();
83   size_t len = 0;
84   Local<Array> ary = args[0].As<Array>();
85   for (size_t n = 0; n < ary->Length(); n++) {
86     Local<Value> entry;
87     if (!ary->Get(env->context(), n).ToLocal(&entry))
88       return;
89     CHECK(entry->IsArrayBufferView() || Blob::HasInstance(env, entry));
90     if (entry->IsArrayBufferView()) {
91       Local<ArrayBufferView> view = entry.As<ArrayBufferView>();
92       CHECK_EQ(view->ByteOffset(), 0);
93       std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
94       size_t byte_length = view->ByteLength();
95       view->Buffer()->Detach();  // The Blob will own the backing store now.
96       entries.emplace_back(BlobEntry{std::move(store), byte_length, 0});
97       len += byte_length;
98     } else {
99       Blob* blob;
100       ASSIGN_OR_RETURN_UNWRAP(&blob, entry);
101       auto source = blob->entries();
102       entries.insert(entries.end(), source.begin(), source.end());
103       len += blob->length();
104     }
105   }
106   CHECK_EQ(length, len);
107 
108   BaseObjectPtr<Blob> blob = Create(env, entries, length);
109   if (blob)
110     args.GetReturnValue().Set(blob->object());
111 }
112 
ToArrayBuffer(const FunctionCallbackInfo<Value> & args)113 void Blob::ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
114   Environment* env = Environment::GetCurrent(args);
115   Blob* blob;
116   ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
117   Local<Value> ret;
118   if (blob->GetArrayBuffer(env).ToLocal(&ret))
119     args.GetReturnValue().Set(ret);
120 }
121 
ToSlice(const FunctionCallbackInfo<Value> & args)122 void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) {
123   Environment* env = Environment::GetCurrent(args);
124   Blob* blob;
125   ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
126   CHECK(args[0]->IsUint32());
127   CHECK(args[1]->IsUint32());
128   size_t start = args[0].As<Uint32>()->Value();
129   size_t end = args[1].As<Uint32>()->Value();
130   BaseObjectPtr<Blob> slice = blob->Slice(env, start, end);
131   if (slice)
132     args.GetReturnValue().Set(slice->object());
133 }
134 
MemoryInfo(MemoryTracker * tracker) const135 void Blob::MemoryInfo(MemoryTracker* tracker) const {
136   tracker->TrackFieldWithSize("store", length_);
137 }
138 
GetArrayBuffer(Environment * env)139 MaybeLocal<Value> Blob::GetArrayBuffer(Environment* env) {
140   EscapableHandleScope scope(env->isolate());
141   size_t len = length();
142   std::shared_ptr<BackingStore> store =
143       ArrayBuffer::NewBackingStore(env->isolate(), len);
144   if (len > 0) {
145     unsigned char* dest = static_cast<unsigned char*>(store->Data());
146     size_t total = 0;
147     for (const auto& entry : entries()) {
148       unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
149       src += entry.offset;
150       memcpy(dest, src, entry.length);
151       dest += entry.length;
152       total += entry.length;
153       CHECK_LE(total, len);
154     }
155   }
156 
157   return scope.Escape(ArrayBuffer::New(env->isolate(), store));
158 }
159 
Slice(Environment * env,size_t start,size_t end)160 BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) {
161   CHECK_LE(start, length());
162   CHECK_LE(end, length());
163   CHECK_LE(start, end);
164 
165   std::vector<BlobEntry> slices;
166   size_t total = end - start;
167   size_t remaining = total;
168 
169   if (total == 0) return Create(env, slices, 0);
170 
171   for (const auto& entry : entries()) {
172     if (start + entry.offset > entry.store->ByteLength()) {
173       start -= entry.length;
174       continue;
175     }
176 
177     size_t offset = entry.offset + start;
178     size_t len = std::min(remaining, entry.store->ByteLength() - offset);
179     slices.emplace_back(BlobEntry{entry.store, len, offset});
180 
181     remaining -= len;
182     start = 0;
183 
184     if (remaining == 0)
185       break;
186   }
187 
188   return Create(env, slices, total);
189 }
190 
Blob(Environment * env,v8::Local<v8::Object> obj,const std::vector<BlobEntry> & store,size_t length)191 Blob::Blob(
192     Environment* env,
193     v8::Local<v8::Object> obj,
194     const std::vector<BlobEntry>& store,
195     size_t length)
196     : BaseObject(env, obj),
197       store_(store),
198       length_(length) {
199   MakeWeak();
200 }
201 
202 BaseObjectPtr<BaseObject>
Deserialize(Environment * env,Local<Context> context,std::unique_ptr<worker::TransferData> self)203 Blob::BlobTransferData::Deserialize(
204     Environment* env,
205     Local<Context> context,
206     std::unique_ptr<worker::TransferData> self) {
207   if (context != env->context()) {
208     THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
209     return {};
210   }
211   return Blob::Create(env, store_, length_);
212 }
213 
GetTransferMode() const214 BaseObject::TransferMode Blob::GetTransferMode() const {
215   return BaseObject::TransferMode::kCloneable;
216 }
217 
CloneForMessaging() const218 std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const {
219   return std::make_unique<BlobTransferData>(store_, length_);
220 }
221 
FixedSizeBlobCopyJob(Environment * env,Local<Object> object,Blob * blob,FixedSizeBlobCopyJob::Mode mode)222 FixedSizeBlobCopyJob::FixedSizeBlobCopyJob(
223     Environment* env,
224     Local<Object> object,
225     Blob* blob,
226     FixedSizeBlobCopyJob::Mode mode)
227     : AsyncWrap(env, object, AsyncWrap::PROVIDER_FIXEDSIZEBLOBCOPY),
228       ThreadPoolWork(env),
229       mode_(mode) {
230   if (mode == FixedSizeBlobCopyJob::Mode::SYNC) MakeWeak();
231   source_ = blob->entries();
232   length_ = blob->length();
233 }
234 
AfterThreadPoolWork(int status)235 void FixedSizeBlobCopyJob::AfterThreadPoolWork(int status) {
236   Environment* env = AsyncWrap::env();
237   CHECK_EQ(mode_, Mode::ASYNC);
238   CHECK(status == 0 || status == UV_ECANCELED);
239   std::unique_ptr<FixedSizeBlobCopyJob> ptr(this);
240   HandleScope handle_scope(env->isolate());
241   Context::Scope context_scope(env->context());
242   Local<Value> args[2];
243 
244   if (status == UV_ECANCELED) {
245     args[0] = Number::New(env->isolate(), status),
246     args[1] = Undefined(env->isolate());
247   } else {
248     args[0] = Undefined(env->isolate());
249     args[1] = ArrayBuffer::New(env->isolate(), destination_);
250   }
251 
252   ptr->MakeCallback(env->ondone_string(), arraysize(args), args);
253 }
254 
DoThreadPoolWork()255 void FixedSizeBlobCopyJob::DoThreadPoolWork() {
256   unsigned char* dest = static_cast<unsigned char*>(destination_->Data());
257   if (length_ > 0) {
258     size_t total = 0;
259     for (const auto& entry : source_) {
260       unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
261       src += entry.offset;
262       memcpy(dest, src, entry.length);
263       dest += entry.length;
264       total += entry.length;
265       CHECK_LE(total, length_);
266     }
267   }
268 }
269 
MemoryInfo(MemoryTracker * tracker) const270 void FixedSizeBlobCopyJob::MemoryInfo(MemoryTracker* tracker) const {
271   tracker->TrackFieldWithSize("source", length_);
272   tracker->TrackFieldWithSize(
273       "destination",
274       destination_ ? destination_->ByteLength() : 0);
275 }
276 
Initialize(Environment * env,Local<Object> target)277 void FixedSizeBlobCopyJob::Initialize(Environment* env, Local<Object> target) {
278   v8::Local<v8::FunctionTemplate> job = env->NewFunctionTemplate(New);
279   job->Inherit(AsyncWrap::GetConstructorTemplate(env));
280   job->InstanceTemplate()->SetInternalFieldCount(
281       AsyncWrap::kInternalFieldCount);
282   env->SetProtoMethod(job, "run", Run);
283   env->SetConstructorFunction(target, "FixedSizeBlobCopyJob", job);
284 }
285 
New(const FunctionCallbackInfo<Value> & args)286 void FixedSizeBlobCopyJob::New(const FunctionCallbackInfo<Value>& args) {
287   static constexpr size_t kMaxSyncLength = 4096;
288   static constexpr size_t kMaxEntryCount = 4;
289 
290   Environment* env = Environment::GetCurrent(args);
291   CHECK(args.IsConstructCall());
292   CHECK(args[0]->IsObject());
293   CHECK(Blob::HasInstance(env, args[0]));
294 
295   Blob* blob;
296   ASSIGN_OR_RETURN_UNWRAP(&blob, args[0]);
297 
298   // This is a fairly arbitrary heuristic. We want to avoid deferring to
299   // the threadpool if the amount of data being copied is small and there
300   // aren't that many entries to copy.
301   FixedSizeBlobCopyJob::Mode mode =
302       (blob->length() < kMaxSyncLength &&
303        blob->entries().size() < kMaxEntryCount) ?
304           FixedSizeBlobCopyJob::Mode::SYNC :
305           FixedSizeBlobCopyJob::Mode::ASYNC;
306 
307   new FixedSizeBlobCopyJob(env, args.This(), blob, mode);
308 }
309 
Run(const FunctionCallbackInfo<Value> & args)310 void FixedSizeBlobCopyJob::Run(const FunctionCallbackInfo<Value>& args) {
311   Environment* env = Environment::GetCurrent(args);
312   FixedSizeBlobCopyJob* job;
313   ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder());
314   job->destination_ =
315       ArrayBuffer::NewBackingStore(env->isolate(), job->length_);
316   if (job->mode() == FixedSizeBlobCopyJob::Mode::ASYNC)
317     return job->ScheduleWork();
318 
319   job->DoThreadPoolWork();
320   args.GetReturnValue().Set(
321       ArrayBuffer::New(env->isolate(), job->destination_));
322 }
323 
324 }  // namespace node
325