• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "sharedarraybuffer_metadata.h"
2 
3 #include "base_object-inl.h"
4 #include "memory_tracker-inl.h"
5 #include "node_errors.h"
6 #include "node_worker.h"
7 #include "util-inl.h"
8 
9 #include <utility>
10 
11 using v8::Context;
12 using v8::Function;
13 using v8::FunctionTemplate;
14 using v8::Local;
15 using v8::Maybe;
16 using v8::MaybeLocal;
17 using v8::Nothing;
18 using v8::Object;
19 using v8::SharedArrayBuffer;
20 using v8::Value;
21 
22 namespace node {
23 namespace worker {
24 
25 namespace {
26 
27 // Yield a JS constructor for SABLifetimePartner objects in the form of a
28 // standard API object, that has a single field for containing the raw
29 // SABLifetimePartner* pointer.
GetSABLifetimePartnerConstructor(Environment * env,Local<Context> context)30 Local<Function> GetSABLifetimePartnerConstructor(
31     Environment* env, Local<Context> context) {
32   Local<FunctionTemplate> templ;
33   templ = env->sab_lifetimepartner_constructor_template();
34   if (!templ.IsEmpty())
35     return templ->GetFunction(context).ToLocalChecked();
36 
37   templ = BaseObject::MakeLazilyInitializedJSTemplate(env);
38   templ->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(),
39                                             "SABLifetimePartner"));
40   env->set_sab_lifetimepartner_constructor_template(templ);
41 
42   return GetSABLifetimePartnerConstructor(env, context);
43 }
44 
45 class SABLifetimePartner : public BaseObject {
46  public:
SABLifetimePartner(Environment * env,Local<Object> obj,SharedArrayBufferMetadataReference r)47   SABLifetimePartner(Environment* env,
48                      Local<Object> obj,
49                      SharedArrayBufferMetadataReference r)
50     : BaseObject(env, obj),
51       reference(std::move(r)) {
52     MakeWeak();
53     env->AddCleanupHook(CleanupHook, static_cast<void*>(this));
54   }
55 
~SABLifetimePartner()56   ~SABLifetimePartner() {
57     env()->RemoveCleanupHook(CleanupHook, static_cast<void*>(this));
58   }
59 
CleanupHook(void * data)60   static void CleanupHook(void* data) {
61     // There is another cleanup hook attached to this object because it is a
62     // BaseObject. Cleanup hooks are triggered in reverse order of addition,
63     // so if this object is destroyed through GC, the destructor removes all
64     // hooks associated with this object, meaning that this cleanup hook
65     // only runs at the end of the Environment’s lifetime.
66     // In that case, V8 still knows about the SharedArrayBuffer and tries to
67     // free it when the last Isolate with access to it is disposed; for that,
68     // the ArrayBuffer::Allocator needs to be kept alive longer than this
69     // object and longer than the Environment instance.
70     //
71     // This is a workaround for https://github.com/nodejs/node-v8/issues/115
72     // (introduced in V8 7.9) and we should be able to remove it once V8
73     // ArrayBuffer::Allocator refactoring/removal is complete.
74     SABLifetimePartner* self = static_cast<SABLifetimePartner*>(data);
75     self->env()->AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
76         self->reference->allocator());
77   }
78 
79   SET_NO_MEMORY_INFO()
80   SET_MEMORY_INFO_NAME(SABLifetimePartner)
81   SET_SELF_SIZE(SABLifetimePartner)
82 
83   SharedArrayBufferMetadataReference reference;
84 };
85 
86 }  // anonymous namespace
87 
88 SharedArrayBufferMetadataReference
ForSharedArrayBuffer(Environment * env,Local<Context> context,Local<SharedArrayBuffer> source)89 SharedArrayBufferMetadata::ForSharedArrayBuffer(
90     Environment* env,
91     Local<Context> context,
92     Local<SharedArrayBuffer> source) {
93   Local<Value> lifetime_partner;
94 
95   if (!source->GetPrivate(context,
96                           env->sab_lifetimepartner_symbol())
97                               .ToLocal(&lifetime_partner)) {
98     return nullptr;
99   }
100 
101   if (lifetime_partner->IsObject() &&
102       env->sab_lifetimepartner_constructor_template()
103          ->HasInstance(lifetime_partner)) {
104     CHECK(source->IsExternal());
105     SABLifetimePartner* partner =
106         Unwrap<SABLifetimePartner>(lifetime_partner.As<Object>());
107     CHECK_NOT_NULL(partner);
108     return partner->reference;
109   }
110 
111   if (source->IsExternal()) {
112     // If this is an external SharedArrayBuffer but we do not see a lifetime
113     // partner object, it was not us who externalized it. In that case, there
114     // is no way to serialize it, because it's unclear how the memory
115     // is actually owned.
116     THROW_ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER(env);
117     return nullptr;
118   }
119 
120   // If the SharedArrayBuffer is coming from a Worker, we need to make sure
121   // that the corresponding ArrayBuffer::Allocator lives at least as long as
122   // the SharedArrayBuffer itself.
123   worker::Worker* w = env->worker_context();
124   std::shared_ptr<v8::ArrayBuffer::Allocator> allocator =
125       w != nullptr ? w->array_buffer_allocator() : nullptr;
126 
127   SharedArrayBuffer::Contents contents = source->Externalize();
128   SharedArrayBufferMetadataReference r(
129       new SharedArrayBufferMetadata(contents, allocator));
130   if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
131     return nullptr;
132   return r;
133 }
134 
AssignToSharedArrayBuffer(Environment * env,Local<Context> context,Local<SharedArrayBuffer> target)135 Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
136     Environment* env, Local<Context> context,
137     Local<SharedArrayBuffer> target) {
138   CHECK(target->IsExternal());
139   Local<Function> ctor = GetSABLifetimePartnerConstructor(env, context);
140   Local<Object> obj;
141   if (!ctor->NewInstance(context).ToLocal(&obj))
142     return Nothing<bool>();
143 
144   new SABLifetimePartner(env, obj, shared_from_this());
145   return target->SetPrivate(context,
146                             env->sab_lifetimepartner_symbol(),
147                             obj);
148 }
149 
SharedArrayBufferMetadata(const SharedArrayBuffer::Contents & contents,std::shared_ptr<v8::ArrayBuffer::Allocator> allocator)150 SharedArrayBufferMetadata::SharedArrayBufferMetadata(
151     const SharedArrayBuffer::Contents& contents,
152     std::shared_ptr<v8::ArrayBuffer::Allocator> allocator)
153   : contents_(contents), allocator_(allocator) { }
154 
~SharedArrayBufferMetadata()155 SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
156   contents_.Deleter()(contents_.Data(),
157                       contents_.ByteLength(),
158                       contents_.DeleterData());
159 }
160 
GetSharedArrayBuffer(Environment * env,Local<Context> context)161 MaybeLocal<SharedArrayBuffer> SharedArrayBufferMetadata::GetSharedArrayBuffer(
162     Environment* env, Local<Context> context) {
163   Local<SharedArrayBuffer> obj =
164       SharedArrayBuffer::New(env->isolate(),
165                              contents_.Data(),
166                              contents_.ByteLength());
167 
168   if (AssignToSharedArrayBuffer(env, context, obj).IsNothing())
169     return MaybeLocal<SharedArrayBuffer>();
170 
171   return obj;
172 }
173 
174 }  // namespace worker
175 }  // namespace node
176