1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "jsvm_reference.h"
17
18 #include "js_native_api_v8.h"
19
20 namespace v8impl {
21 namespace {
22 // In JavaScript, weak references can be created for object types (Object,
23 // Function, and external Object) and for local symbols that are created with
24 // the `Symbol` function call. Global symbols created with the `Symbol.for`
25 // method cannot be weak references because they are never collected.
26 // Currently, V8 has no API to detect if a symbol is local or global.
27 // Until we have a V8 API for it, we consider that all symbols can be weak.
CanBeHeldWeakly(v8::Local<v8::Value> value)28 inline bool CanBeHeldWeakly(v8::Local<v8::Value> value)
29 {
30 return value->IsObject() || value->IsSymbol();
31 }
32 } // namespace
33
34 // RefTracker
Link(RefList * list)35 inline void RefTracker::Link(RefList* list)
36 {
37 DCHECK(list != nullptr);
38 prev = list;
39 next = list->next;
40 if (next != nullptr) {
41 next->prev = this;
42 }
43 list->next = this;
44 }
45
FinalizeAll(RefList * list)46 void RefTracker::FinalizeAll(RefList* list)
47 {
48 while (list->next != nullptr) {
49 list->next->Finalize();
50 }
51 }
52
Finalize()53 void RefTracker::Finalize()
54 {
55 UNREACHABLE("Finalize need to be realized");
56 }
57
58 // UserReference
New(JSVM_Env env,v8::Local<v8::Value> value,uint32_t initialRefcount)59 UserReference* UserReference::New(JSVM_Env env, v8::Local<v8::Value> value, uint32_t initialRefcount)
60 {
61 auto ref = new UserReference(env, value, true, initialRefcount);
62
63 return ref;
64 }
65
NewData(JSVM_Env env,v8::Local<v8::Data> value,uint32_t initialRefcount)66 UserReference* UserReference::NewData(JSVM_Env env, v8::Local<v8::Data> value, uint32_t initialRefcount)
67 {
68 auto ref = new UserReference(env, value, false, initialRefcount);
69
70 return ref;
71 }
72
UserReference(JSVM_Env env,v8::Local<v8::Data> value,bool isValue,uint32_t initialRefcount)73 UserReference::UserReference(JSVM_Env env, v8::Local<v8::Data> value, bool isValue, uint32_t initialRefcount)
74 : persistent(env->isolate, value), isValue(isValue), env(env), refcount(initialRefcount),
75 canBeWeak(isValue && CanBeHeldWeakly(value.As<v8::Value>()))
76 {
77 if (refcount == 0) {
78 SetWeak();
79 }
80
81 Link(&env->userReferenceList);
82 }
83
~UserReference()84 UserReference::~UserReference()
85 {
86 persistent.Reset();
87 Unlink();
88 }
89
Finalize()90 void UserReference::Finalize()
91 {
92 persistent.Reset();
93 Unlink();
94 }
95
Get()96 v8::Local<v8::Value> UserReference::Get()
97 {
98 DCHECK(isValue);
99 if (persistent.IsEmpty()) {
100 return v8::Local<v8::Value>();
101 } else {
102 return v8::Local<v8::Data>::New(env->isolate, persistent).As<v8::Value>();
103 }
104 }
105
GetData()106 v8::Local<v8::Data> UserReference::GetData()
107 {
108 if (persistent.IsEmpty()) {
109 return v8::Local<v8::Data>();
110 } else {
111 return v8::Local<v8::Data>::New(env->isolate, persistent);
112 }
113 }
114
SetWeak()115 void UserReference::SetWeak()
116 {
117 if (canBeWeak) {
118 persistent.SetWeak();
119 } else {
120 persistent.Reset();
121 }
122 }
123
Ref()124 uint32_t UserReference::Ref()
125 {
126 // If persistent is cleared by GC, return 0 unconditionally.
127 if (persistent.IsEmpty()) {
128 return 0;
129 }
130
131 if (++refcount == 1) {
132 // If persistent can not be weak, it will be cleared in SetWeak().
133 DCHECK(canBeWeak);
134 persistent.ClearWeak();
135 }
136
137 return refcount;
138 }
139
Unref()140 uint32_t UserReference::Unref()
141 {
142 // If persistent is cleared by GC, return 0 unconditionally.
143 if (persistent.IsEmpty() || refcount == 0) {
144 return 0;
145 }
146
147 if (--refcount == 0) {
148 SetWeak();
149 }
150
151 return refcount;
152 }
153
RefCount()154 uint32_t UserReference::RefCount()
155 {
156 return refcount;
157 }
158
159 // FinalizerTracker
New(JSVM_Env env,JSVM_Finalize cb,void * finalizeData,void * finalizeHint)160 FinalizerTracker* FinalizerTracker::New(JSVM_Env env, JSVM_Finalize cb, void* finalizeData, void* finalizeHint)
161 {
162 return new FinalizerTracker(env, cb, finalizeData, finalizeHint);
163 }
164
FinalizerTracker(JSVM_Env env,JSVM_Finalize cb,void * data,void * hint)165 FinalizerTracker::FinalizerTracker(JSVM_Env env, JSVM_Finalize cb, void* data, void* hint)
166 : env(env), cb(cb), data(data), hint(hint)
167 {
168 Link(&env->finalizerList);
169 }
170
~FinalizerTracker()171 FinalizerTracker::~FinalizerTracker()
172 {
173 Unlink();
174 }
175
ResetFinalizer()176 void FinalizerTracker::ResetFinalizer()
177 {
178 cb = nullptr;
179 data = nullptr;
180 hint = nullptr;
181 }
182
CallFinalizer()183 void FinalizerTracker::CallFinalizer()
184 {
185 if (!cb) {
186 return;
187 }
188
189 JSVM_Finalize cbTemp = cb;
190 void* dataTemp = data;
191 void* hintTemp = hint;
192 ResetFinalizer();
193
194 if (!env) {
195 cbTemp(env, dataTemp, hintTemp);
196 } else {
197 env->CallIntoModule([&](JSVM_Env env) { cbTemp(env, dataTemp, hintTemp); });
198 }
199 }
200
Finalize()201 void FinalizerTracker::Finalize()
202 {
203 CallFinalizer();
204 delete this;
205 }
206
RuntimeReference(JSVM_Env env,v8::Local<v8::Value> value,JSVM_Finalize cb,void * data,void * hint)207 RuntimeReference::RuntimeReference(JSVM_Env env, v8::Local<v8::Value> value, JSVM_Finalize cb, void* data, void* hint)
208 : FinalizerTracker(env, cb, data, hint), persistent(env->isolate, value)
209 {
210 DCHECK(CanBeHeldWeakly(value));
211 }
212
New(JSVM_Env env,v8::Local<v8::Value> value,void * data)213 RuntimeReference* RuntimeReference::New(JSVM_Env env, v8::Local<v8::Value> value, void* data)
214 {
215 auto* ref = new RuntimeReference(env, value, nullptr, data, nullptr);
216 // Delete self in first pass callback
217 ref->SetWeak(false);
218
219 return ref;
220 }
221
New(JSVM_Env env,v8::Local<v8::Value> value,JSVM_Finalize cb,void * data,void * hint)222 RuntimeReference* RuntimeReference::New(JSVM_Env env,
223 v8::Local<v8::Value> value,
224 JSVM_Finalize cb,
225 void* data,
226 void* hint)
227 {
228 auto* ref = new RuntimeReference(env, value, cb, data, hint);
229 // Need second pass callback to call finalizer
230 ref->SetWeak(cb != nullptr);
231
232 return ref;
233 }
234
DeleteReference(RuntimeReference * ref)235 void RuntimeReference::DeleteReference(RuntimeReference* ref)
236 {
237 // If reference is not added into first pass callbacks, delete this direct.
238 if (ref->persistent.IsWeak()) {
239 delete ref;
240 return;
241 }
242
243 // If reference is added into first pass callbacks, reset finalizer function.
244 ref->ResetFinalizer();
245 }
246
SetWeak(bool needSecondPass)247 inline void RuntimeReference::SetWeak(bool needSecondPass)
248 {
249 if (needSecondPass) {
250 persistent.SetWeak(this, FirstPassCallback, v8::WeakCallbackType::kParameter);
251 } else {
252 persistent.SetWeak(this, FirstPassCallbackWithoutFinalizer, v8::WeakCallbackType::kParameter);
253 }
254 }
255
FirstPassCallback(const v8::WeakCallbackInfo<RuntimeReference> & data)256 void RuntimeReference::FirstPassCallback(const v8::WeakCallbackInfo<RuntimeReference>& data)
257 {
258 RuntimeReference* reference = data.GetParameter();
259
260 reference->persistent.Reset();
261 data.SetSecondPassCallback(RuntimeReference::SecondPassCallback);
262 }
263
SecondPassCallback(const v8::WeakCallbackInfo<RuntimeReference> & data)264 void RuntimeReference::SecondPassCallback(const v8::WeakCallbackInfo<RuntimeReference>& data)
265 {
266 RuntimeReference* reference = data.GetParameter();
267
268 reference->Finalize();
269 }
270
FirstPassCallbackWithoutFinalizer(const v8::WeakCallbackInfo<RuntimeReference> & data)271 void RuntimeReference::FirstPassCallbackWithoutFinalizer(const v8::WeakCallbackInfo<RuntimeReference>& data)
272 {
273 RuntimeReference* reference = data.GetParameter();
274
275 reference->persistent.Reset();
276 delete reference;
277 }
278
279 } // namespace v8impl