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