@gjsify/napi 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -0
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/types/index.d.ts +25 -0
- package/meson.build +126 -0
- package/package.json +100 -0
- package/prebuilds/linux-x86_64/GjsifyNapi-1.0.gir +19 -0
- package/prebuilds/linux-x86_64/GjsifyNapi-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifynapi.so +0 -0
- package/src/cc/arraybuffer.cc +772 -0
- package/src/cc/async_work.cc +439 -0
- package/src/cc/common.h +519 -0
- package/src/cc/env.cc +262 -0
- package/src/cc/error.cc +300 -0
- package/src/cc/function.cc +358 -0
- package/src/cc/module.cc +625 -0
- package/src/cc/napi.def +22 -0
- package/src/cc/napi.export.map +16 -0
- package/src/cc/napi.exported-symbols.txt +15 -0
- package/src/cc/object.cc +728 -0
- package/src/cc/ref.cc +510 -0
- package/src/cc/stubs.cc +100 -0
- package/src/cc/tsfn.cc +602 -0
- package/src/cc/value.cc +656 -0
- package/src/cc/wrap.cc +321 -0
- package/src/napi-headers/js_native_api.h +639 -0
- package/src/napi-headers/js_native_api_types.h +249 -0
- package/src/napi-headers/node_api.h +270 -0
- package/src/napi-headers/node_api_types.h +63 -0
- package/src/vala/gjs-1.0.vapi +6 -0
- package/src/vala/mozjs-140.vapi +6 -0
- package/src/vala/napi.vala +49 -0
package/src/cc/ref.cc
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/napi — references, weak machinery, finalizer pipeline (§5b/§5c).
|
|
3
|
+
// THE CRASH CLASS: everything here mirrors the V8 reference implementation's
|
|
4
|
+
// semantics (refs/node/src/js_native_api_v8.{h,cc}, Node.js contributors,
|
|
5
|
+
// MIT) realized with the SpiderMonkey mechanisms GJS itself uses in
|
|
6
|
+
// production (refs/gjs/gjs/jsapi-util-root.h GjsMaybeOwned rooted/weak flip;
|
|
7
|
+
// refs/gjs/gi/object.cpp:2450-2507 weak-pointer sweep; GNOME contributors,
|
|
8
|
+
// MIT/LGPLv2+).
|
|
9
|
+
//
|
|
10
|
+
// Verified mozjs-140 contracts this file is built on:
|
|
11
|
+
// - JS_UpdateWeakPointerAfterGC(JSTracer*, JS::Heap<JSObject*>*)
|
|
12
|
+
// (js/GCAPI.h): in-out param — MOVED referent ⇒ pointer updated in
|
|
13
|
+
// place; DEAD (about to be finalized) ⇒ pointer set to null; return
|
|
14
|
+
// value = "still alive". Update-vs-death discrimination is built into
|
|
15
|
+
// the API (GJS: update_after_gc, jsapi-util-root.h).
|
|
16
|
+
// - Weak-pointer callbacks fire while SWEEPING (major GC). A JS::Heap
|
|
17
|
+
// post-write barrier puts nursery referents into the store buffer, so
|
|
18
|
+
// MINOR GC tenures (and relocates) weakly-Heap-held objects instead of
|
|
19
|
+
// collecting them — death is only observable at major-GC sweep, which is
|
|
20
|
+
// exactly when the callback runs (js/RootingAPI.h Heap<T> barrier
|
|
21
|
+
// contract: pre-write, post-write, read barriers). Consequence: a
|
|
22
|
+
// weakly-held object's death may be delayed to the next full GC
|
|
23
|
+
// (conservative, never unsafe; imports.system.gc() is deterministic).
|
|
24
|
+
// - The strong↔weak flips stack-root the object across the transition so
|
|
25
|
+
// it is never in neither slot (GJS switch_to_rooted/switch_to_unrooted,
|
|
26
|
+
// jsapi-util-root.h:178-201); JS::Heap assignment runs the write
|
|
27
|
+
// barriers, JS::Heap::get() runs the read barrier (gray-unmark on
|
|
28
|
+
// resurrection).
|
|
29
|
+
|
|
30
|
+
#include "common.h"
|
|
31
|
+
|
|
32
|
+
#include <algorithm>
|
|
33
|
+
#include <cstdio>
|
|
34
|
+
|
|
35
|
+
namespace gjsify_napi {
|
|
36
|
+
|
|
37
|
+
// ---- RefTracker ----
|
|
38
|
+
|
|
39
|
+
void RefTracker::Link(RefTracker* list_head) {
|
|
40
|
+
prev_ = list_head;
|
|
41
|
+
next_ = list_head->next_;
|
|
42
|
+
if (next_ != nullptr) {
|
|
43
|
+
next_->prev_ = this;
|
|
44
|
+
}
|
|
45
|
+
list_head->next_ = this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void RefTracker::Unlink() {
|
|
49
|
+
if (prev_ != nullptr) {
|
|
50
|
+
prev_->next_ = next_;
|
|
51
|
+
}
|
|
52
|
+
if (next_ != nullptr) {
|
|
53
|
+
next_->prev_ = prev_;
|
|
54
|
+
}
|
|
55
|
+
prev_ = nullptr;
|
|
56
|
+
next_ = nullptr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void RefTracker::FinalizeAll(RefTracker* list_head) {
|
|
60
|
+
// Each Finalize() unlinks itself first, so this terminates even when a
|
|
61
|
+
// finalizer links new trackers (they land at the head and get seen).
|
|
62
|
+
while (list_head->next_ != nullptr) {
|
|
63
|
+
list_head->next_->Finalize();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Log + clear a pending exception (shared with tsfn.cc — declared in
|
|
68
|
+
// common.h): the "no JS frame below us" posture for finalizers and loop-
|
|
69
|
+
// dispatched tsfn callbacks alike (Bun's clearExceptionsBetweenFinalizers
|
|
70
|
+
// lesson).
|
|
71
|
+
void log_and_clear_pending_exception(napi_env env, const char* where) {
|
|
72
|
+
if (!JS_IsExceptionPending(env->cx)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
JS::RootedValue exc(env->cx);
|
|
76
|
+
JS_GetPendingException(env->cx, &exc);
|
|
77
|
+
JS_ClearPendingException(env->cx);
|
|
78
|
+
JSString* str = JS::ToString(env->cx, exc);
|
|
79
|
+
if (str == nullptr) {
|
|
80
|
+
JS_ClearPendingException(env->cx);
|
|
81
|
+
}
|
|
82
|
+
JS::UniqueChars utf8 =
|
|
83
|
+
str ? JS_EncodeStringToUTF8(env->cx, JS::RootedString(env->cx, str))
|
|
84
|
+
: nullptr;
|
|
85
|
+
fprintf(stderr, "[gjsify-napi] unhandled exception in %s: %s\n", where,
|
|
86
|
+
utf8 ? utf8.get() : "<unprintable>");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Shared "run a user napi_finalize with JS callable" helper (§5c drain
|
|
90
|
+
// semantics): fresh handle scope per finalizer, exceptions logged and
|
|
91
|
+
// cleared between finalizers.
|
|
92
|
+
static void run_user_finalizer(napi_env env, napi_finalize cb, void* data,
|
|
93
|
+
void* hint) {
|
|
94
|
+
if (cb == nullptr) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
napi_handle_scope scope = nullptr;
|
|
98
|
+
napi_open_handle_scope(env, &scope);
|
|
99
|
+
cb(env, data, hint);
|
|
100
|
+
log_and_clear_pending_exception(env, "native finalizer");
|
|
101
|
+
napi_close_handle_scope(env, scope);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- TrackedFinalizer ----
|
|
105
|
+
|
|
106
|
+
TrackedFinalizer::TrackedFinalizer(napi_env env, napi_finalize cb, void* data,
|
|
107
|
+
void* hint)
|
|
108
|
+
: env_(env), cb_(cb), data_(data), hint_(hint) {
|
|
109
|
+
Link(&env->finalizing_reflist);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
TrackedFinalizer::~TrackedFinalizer() {
|
|
113
|
+
dequeue_finalizer(env_, this);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void TrackedFinalizer::Finalize() {
|
|
117
|
+
Unlink();
|
|
118
|
+
dequeue_finalizer(env_, this);
|
|
119
|
+
// Instance data finalizes via FinalizeAll(finalizing_reflist) like Node
|
|
120
|
+
// (TrackedFinalizer links there); clear the env pointer so the zombie
|
|
121
|
+
// env shell never dangles into freed memory.
|
|
122
|
+
if (env_->instance_data == this) {
|
|
123
|
+
env_->instance_data = nullptr;
|
|
124
|
+
}
|
|
125
|
+
run_user_finalizer(env_, cb_, data_, hint_);
|
|
126
|
+
delete this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---- Reference ----
|
|
130
|
+
|
|
131
|
+
Reference* Reference::New(napi_env env, JS::HandleValue value,
|
|
132
|
+
uint32_t initial_refcount,
|
|
133
|
+
ReferenceOwnership ownership, napi_finalize cb,
|
|
134
|
+
void* data, void* hint) {
|
|
135
|
+
return new Reference(env, value, initial_refcount, ownership, cb, data,
|
|
136
|
+
hint);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Reference::Reference(napi_env env, JS::HandleValue value,
|
|
140
|
+
uint32_t initial_refcount, ReferenceOwnership ownership,
|
|
141
|
+
napi_finalize cb, void* data, void* hint)
|
|
142
|
+
: env_(env),
|
|
143
|
+
state_(State::kStrong),
|
|
144
|
+
refcount_(initial_refcount),
|
|
145
|
+
ownership_(ownership),
|
|
146
|
+
can_be_weak_(value.isObject()),
|
|
147
|
+
cb_(cb),
|
|
148
|
+
data_(data),
|
|
149
|
+
hint_(hint) {
|
|
150
|
+
// Two-list membership: refs with a user finalizer are finalized FIRST
|
|
151
|
+
// at teardown (js_native_api_v8.h:121-130).
|
|
152
|
+
Link(cb_ != nullptr ? &env->finalizing_reflist : &env->reflist);
|
|
153
|
+
strong_ = value; // Heap assignment: post-write barrier
|
|
154
|
+
if (refcount_ == 0) {
|
|
155
|
+
SetWeakOrRelease();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Reference::~Reference() {
|
|
160
|
+
// Deleting a ref whose finalizer is still queued skips the finalizer
|
|
161
|
+
// (Node RefTracker semantics); ~RefTracker unlinks from the ref list.
|
|
162
|
+
dequeue_finalizer(env_, this);
|
|
163
|
+
RemoveFromWeakSet();
|
|
164
|
+
// Release the GC edges: JS::Heap destructor runs the pre-write barrier.
|
|
165
|
+
// Must never run after JS_DestroyContext — guaranteed by the teardown
|
|
166
|
+
// order (all references die in teardown step 3 or earlier).
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
void Reference::RemoveFromWeakSet() {
|
|
170
|
+
auto& set = env_->weak_refs;
|
|
171
|
+
set.erase(std::remove(set.begin(), set.end(), this), set.end());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// refcount 1→0 (or construction at 0): objects go weak, primitives (and
|
|
175
|
+
// symbols, §5b ledger deviation) release immediately — the exact
|
|
176
|
+
// SetWeak-on-non-weakable reset of js_native_api_v8.cc:751-757.
|
|
177
|
+
void Reference::SetWeakOrRelease() {
|
|
178
|
+
if (state_ != State::kStrong) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (can_be_weak_) {
|
|
182
|
+
// Flip strong→weak. Stack-root across the transition (the thing
|
|
183
|
+
// must never be in neither slot — GJS switch_to_unrooted).
|
|
184
|
+
JS::RootedObject obj(env_->cx, &strong_.get().toObject());
|
|
185
|
+
strong_ = JS::UndefinedValue(); // pre-write barrier on overwrite
|
|
186
|
+
weak_obj_ = obj.get(); // post-write barrier
|
|
187
|
+
state_ = State::kWeak;
|
|
188
|
+
env_->weak_refs.push_back(this);
|
|
189
|
+
} else {
|
|
190
|
+
strong_ = JS::UndefinedValue();
|
|
191
|
+
state_ = State::kReleased;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
uint32_t Reference::Ref() {
|
|
196
|
+
if (++refcount_ == 1) {
|
|
197
|
+
if (state_ == State::kWeak) {
|
|
198
|
+
// Flip weak→strong (resurrection). JS::Heap::get() runs the
|
|
199
|
+
// read barrier (exposes/unmarks gray — required when making a
|
|
200
|
+
// weakly-held object strongly reachable mid-incremental-GC).
|
|
201
|
+
JS::RootedObject obj(env_->cx, weak_obj_.get());
|
|
202
|
+
RemoveFromWeakSet();
|
|
203
|
+
weak_obj_ = nullptr;
|
|
204
|
+
if (obj) {
|
|
205
|
+
strong_ = JS::ObjectValue(*obj);
|
|
206
|
+
state_ = State::kStrong;
|
|
207
|
+
} else {
|
|
208
|
+
state_ = State::kReleased;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// kReleased stays released (a dead weak ref cannot resurrect —
|
|
212
|
+
// matches Node: Ref() on an emptied Persistent).
|
|
213
|
+
}
|
|
214
|
+
return refcount_;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
uint32_t Reference::Unref() {
|
|
218
|
+
if (refcount_ == 0) {
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
if (--refcount_ == 0) {
|
|
222
|
+
SetWeakOrRelease();
|
|
223
|
+
}
|
|
224
|
+
return refcount_;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
bool Reference::GetValue(JS::MutableHandleValue out) {
|
|
228
|
+
switch (state_) {
|
|
229
|
+
case State::kStrong:
|
|
230
|
+
out.set(strong_.get());
|
|
231
|
+
return true;
|
|
232
|
+
case State::kWeak: {
|
|
233
|
+
JSObject* obj = weak_obj_.get(); // read barrier
|
|
234
|
+
if (obj == nullptr) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
out.set(JS::ObjectValue(*obj));
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
case State::kReleased:
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
void Reference::Trace(JSTracer* trc) {
|
|
247
|
+
if (state_ == State::kStrong) {
|
|
248
|
+
// Marks AND relocates — outstanding pointers stay valid.
|
|
249
|
+
JS::TraceEdge(trc, &strong_, "gjsify-napi reference (strong)");
|
|
250
|
+
}
|
|
251
|
+
// kWeak edges are deliberately NOT traced — the weak sweep callback
|
|
252
|
+
// updates them (tracing would make the reference immortal).
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
bool Reference::UpdateAfterGC(JSTracer* trc) {
|
|
256
|
+
// GCAPI.h contract: moved ⇒ updated in place (returns true),
|
|
257
|
+
// about-to-be-finalized ⇒ nulled (returns false).
|
|
258
|
+
const bool died = !JS_UpdateWeakPointerAfterGC(trc, &weak_obj_);
|
|
259
|
+
if (died) {
|
|
260
|
+
state_ = State::kReleased; // reads back empty from now on
|
|
261
|
+
}
|
|
262
|
+
return died;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
void Reference::Finalize() {
|
|
266
|
+
Unlink();
|
|
267
|
+
dequeue_finalizer(env_, this);
|
|
268
|
+
// Drop the GC edges BEFORE running the callback: the value must not be
|
|
269
|
+
// resurrectable from its own finalizer, and teardown finalization of
|
|
270
|
+
// still-alive objects must release the root either way.
|
|
271
|
+
RemoveFromWeakSet();
|
|
272
|
+
state_ = State::kReleased;
|
|
273
|
+
strong_ = JS::UndefinedValue();
|
|
274
|
+
weak_obj_ = nullptr;
|
|
275
|
+
|
|
276
|
+
const ReferenceOwnership ownership = ownership_;
|
|
277
|
+
const napi_finalize cb = cb_;
|
|
278
|
+
void* data = data_;
|
|
279
|
+
void* hint = hint_;
|
|
280
|
+
cb_ = nullptr;
|
|
281
|
+
run_user_finalizer(env_, cb, data, hint);
|
|
282
|
+
// kRuntime: the runtime owns the reference — delete after the
|
|
283
|
+
// finalizer. kUserland: userland deletes via napi_delete_reference
|
|
284
|
+
// (js_native_api_v8 Reference ownership contract).
|
|
285
|
+
if (ownership == ReferenceOwnership::kRuntime) {
|
|
286
|
+
delete this;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ---- finalizer queue (§5c) ----
|
|
291
|
+
|
|
292
|
+
void enqueue_finalizer(napi_env env, RefTracker* tracker) {
|
|
293
|
+
auto& q = env->pending_finalizers;
|
|
294
|
+
if (std::find(q.begin(), q.end(), tracker) == q.end()) {
|
|
295
|
+
q.push_back(tracker);
|
|
296
|
+
}
|
|
297
|
+
schedule_finalizer_drain();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
void dequeue_finalizer(napi_env env, RefTracker* tracker) {
|
|
301
|
+
auto& q = env->pending_finalizers;
|
|
302
|
+
q.erase(std::remove(q.begin(), q.end(), tracker), q.end());
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
void drain_finalizer_queue(napi_env env) {
|
|
306
|
+
if (env->draining_finalizers) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
env->draining_finalizers = true;
|
|
310
|
+
while (!env->pending_finalizers.empty()) {
|
|
311
|
+
RefTracker* tracker = env->pending_finalizers.front();
|
|
312
|
+
env->pending_finalizers.erase(env->pending_finalizers.begin());
|
|
313
|
+
tracker->Finalize();
|
|
314
|
+
}
|
|
315
|
+
env->draining_finalizers = false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ---- weak sweep (§5b) ----
|
|
319
|
+
//
|
|
320
|
+
// ONE JSWeakPointerZonesCallback per process (module.cc registers it at
|
|
321
|
+
// install). NO JSAPI beyond JS_UpdateWeakPointerAfterGC may run here — this
|
|
322
|
+
// executes during GC sweeping. Deaths are queued; the drain runs later on
|
|
323
|
+
// the main loop (or at teardown).
|
|
324
|
+
void weak_sweep_callback(JSTracer* trc, void* /* data */) {
|
|
325
|
+
for (napi_env env : env_registry()) {
|
|
326
|
+
// Collect first: UpdateAfterGC survivors keep their slot; the dead
|
|
327
|
+
// are detached outside the iteration (mutation-safe).
|
|
328
|
+
std::vector<Reference*> died;
|
|
329
|
+
for (Reference* ref : env->weak_refs) {
|
|
330
|
+
if (ref->UpdateAfterGC(trc)) {
|
|
331
|
+
died.push_back(ref);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (Reference* ref : died) {
|
|
335
|
+
ref->RemoveFromWeakSet();
|
|
336
|
+
if (ref->HasFinalizer()) {
|
|
337
|
+
enqueue_finalizer(env, ref);
|
|
338
|
+
}
|
|
339
|
+
// Plain refs (kUserland by construction via
|
|
340
|
+
// napi_create_reference) just read back empty from now on —
|
|
341
|
+
// UpdateAfterGC already flipped them to kReleased.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
} // namespace gjsify_napi
|
|
347
|
+
|
|
348
|
+
// ---- napi_ref ABI ----
|
|
349
|
+
|
|
350
|
+
napi_status NAPI_CDECL napi_create_reference(napi_env env, napi_value value,
|
|
351
|
+
uint32_t initial_refcount,
|
|
352
|
+
napi_ref* result) {
|
|
353
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
354
|
+
GJSIFY_NAPI_CHECK_ARG(env, value);
|
|
355
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
356
|
+
JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
|
|
357
|
+
if (env->module_api_version < 10) {
|
|
358
|
+
// <10 modules may only reference object/function/symbol
|
|
359
|
+
// (js_native_api_v8.cc:2829-2834).
|
|
360
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
|
|
361
|
+
env, v.isObject() || v.isSymbol(), napi_invalid_arg);
|
|
362
|
+
}
|
|
363
|
+
gjsify_napi::Reference* ref = gjsify_napi::Reference::New(
|
|
364
|
+
env, v, initial_refcount, gjsify_napi::ReferenceOwnership::kUserland);
|
|
365
|
+
*result = reinterpret_cast<napi_ref>(ref);
|
|
366
|
+
return gjsify_napi::clear_last_error(env);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
napi_status NAPI_CDECL napi_delete_reference(napi_env env, napi_ref ref) {
|
|
370
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
371
|
+
GJSIFY_NAPI_CHECK_ARG(env, ref);
|
|
372
|
+
delete reinterpret_cast<gjsify_napi::Reference*>(ref);
|
|
373
|
+
return gjsify_napi::clear_last_error(env);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
napi_status NAPI_CDECL napi_reference_ref(napi_env env, napi_ref ref,
|
|
377
|
+
uint32_t* result) {
|
|
378
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
379
|
+
GJSIFY_NAPI_CHECK_ARG(env, ref);
|
|
380
|
+
uint32_t count = reinterpret_cast<gjsify_napi::Reference*>(ref)->Ref();
|
|
381
|
+
if (result != nullptr) {
|
|
382
|
+
*result = count;
|
|
383
|
+
}
|
|
384
|
+
return gjsify_napi::clear_last_error(env);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
napi_status NAPI_CDECL napi_reference_unref(napi_env env, napi_ref ref,
|
|
388
|
+
uint32_t* result) {
|
|
389
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
390
|
+
GJSIFY_NAPI_CHECK_ARG(env, ref);
|
|
391
|
+
auto* reference = reinterpret_cast<gjsify_napi::Reference*>(ref);
|
|
392
|
+
// Unref below zero = napi_generic_failure (cc:2884-2900).
|
|
393
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, reference->RefCount() > 0,
|
|
394
|
+
napi_generic_failure);
|
|
395
|
+
uint32_t count = reference->Unref();
|
|
396
|
+
if (result != nullptr) {
|
|
397
|
+
*result = count;
|
|
398
|
+
}
|
|
399
|
+
return gjsify_napi::clear_last_error(env);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
napi_status NAPI_CDECL napi_get_reference_value(napi_env env, napi_ref ref,
|
|
403
|
+
napi_value* result) {
|
|
404
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
405
|
+
GJSIFY_NAPI_CHECK_ARG(env, ref);
|
|
406
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
407
|
+
JS::RootedValue v(env->cx);
|
|
408
|
+
if (reinterpret_cast<gjsify_napi::Reference*>(ref)->GetValue(&v)) {
|
|
409
|
+
*result = gjsify_napi::arena_push(env, v);
|
|
410
|
+
} else {
|
|
411
|
+
*result = nullptr; // released/dead — Node returns NULL
|
|
412
|
+
}
|
|
413
|
+
return gjsify_napi::clear_last_error(env);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ---- finalizer-adjacent ABI ----
|
|
417
|
+
|
|
418
|
+
napi_status NAPI_CDECL napi_add_finalizer(napi_env env, napi_value js_object,
|
|
419
|
+
void* finalize_data,
|
|
420
|
+
napi_finalize finalize_cb,
|
|
421
|
+
void* finalize_hint,
|
|
422
|
+
napi_ref* result) {
|
|
423
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
424
|
+
GJSIFY_NAPI_CHECK_ARG(env, js_object);
|
|
425
|
+
GJSIFY_NAPI_CHECK_ARG(env, finalize_cb);
|
|
426
|
+
JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(js_object));
|
|
427
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isObject(),
|
|
428
|
+
napi_object_expected);
|
|
429
|
+
// Weak refcount-0 ReferenceWithFinalizer; ownership by whether the
|
|
430
|
+
// caller wants the ref back (js_native_api_v8.cc:3625+).
|
|
431
|
+
gjsify_napi::Reference* ref = gjsify_napi::Reference::New(
|
|
432
|
+
env, v, 0,
|
|
433
|
+
result != nullptr ? gjsify_napi::ReferenceOwnership::kUserland
|
|
434
|
+
: gjsify_napi::ReferenceOwnership::kRuntime,
|
|
435
|
+
finalize_cb, finalize_data, finalize_hint);
|
|
436
|
+
if (result != nullptr) {
|
|
437
|
+
*result = reinterpret_cast<napi_ref>(ref);
|
|
438
|
+
}
|
|
439
|
+
return gjsify_napi::clear_last_error(env);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
napi_status NAPI_CDECL node_api_post_finalizer(node_api_basic_env basic_env,
|
|
443
|
+
napi_finalize finalize_cb,
|
|
444
|
+
void* finalize_data,
|
|
445
|
+
void* finalize_hint) {
|
|
446
|
+
napi_env env = const_cast<napi_env>(basic_env);
|
|
447
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
448
|
+
GJSIFY_NAPI_CHECK_ARG(env, finalize_cb);
|
|
449
|
+
auto* task = new gjsify_napi::TrackedFinalizer(env, finalize_cb,
|
|
450
|
+
finalize_data,
|
|
451
|
+
finalize_hint);
|
|
452
|
+
gjsify_napi::enqueue_finalizer(env, task);
|
|
453
|
+
return gjsify_napi::clear_last_error(env);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ---- instance data (§5d) ----
|
|
457
|
+
|
|
458
|
+
napi_status NAPI_CDECL napi_set_instance_data(node_api_basic_env basic_env,
|
|
459
|
+
void* data,
|
|
460
|
+
napi_finalize finalize_cb,
|
|
461
|
+
void* finalize_hint) {
|
|
462
|
+
napi_env env = const_cast<napi_env>(basic_env);
|
|
463
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
464
|
+
if (env->instance_data != nullptr) {
|
|
465
|
+
// Overwrite deletes the old data holder UN-finalized (cc:3683-3702).
|
|
466
|
+
delete env->instance_data;
|
|
467
|
+
env->instance_data = nullptr;
|
|
468
|
+
}
|
|
469
|
+
env->instance_data = new gjsify_napi::TrackedFinalizer(
|
|
470
|
+
env, finalize_cb, data, finalize_hint);
|
|
471
|
+
return gjsify_napi::clear_last_error(env);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
napi_status NAPI_CDECL napi_get_instance_data(node_api_basic_env basic_env,
|
|
475
|
+
void** data) {
|
|
476
|
+
napi_env env = const_cast<napi_env>(basic_env);
|
|
477
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
478
|
+
GJSIFY_NAPI_CHECK_ARG(env, data);
|
|
479
|
+
*data = env->instance_data != nullptr ? env->instance_data->data()
|
|
480
|
+
: nullptr;
|
|
481
|
+
return gjsify_napi::clear_last_error(env);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ---- env cleanup hooks ----
|
|
485
|
+
|
|
486
|
+
napi_status NAPI_CDECL napi_add_env_cleanup_hook(node_api_basic_env basic_env,
|
|
487
|
+
napi_cleanup_hook fun,
|
|
488
|
+
void* arg) {
|
|
489
|
+
napi_env env = const_cast<napi_env>(basic_env);
|
|
490
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
491
|
+
GJSIFY_NAPI_CHECK_ARG(env, fun);
|
|
492
|
+
env->cleanup_hooks.push_back({fun, arg});
|
|
493
|
+
return gjsify_napi::clear_last_error(env);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
napi_status NAPI_CDECL napi_remove_env_cleanup_hook(
|
|
497
|
+
node_api_basic_env basic_env, napi_cleanup_hook fun, void* arg) {
|
|
498
|
+
napi_env env = const_cast<napi_env>(basic_env);
|
|
499
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
500
|
+
GJSIFY_NAPI_CHECK_ARG(env, fun);
|
|
501
|
+
auto& hooks = env->cleanup_hooks;
|
|
502
|
+
// Remove the LAST matching registration (Node removes one entry).
|
|
503
|
+
for (auto it = hooks.rbegin(); it != hooks.rend(); ++it) {
|
|
504
|
+
if (it->fn == fun && it->arg == arg) {
|
|
505
|
+
hooks.erase(std::next(it).base());
|
|
506
|
+
return gjsify_napi::clear_last_error(env);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return gjsify_napi::clear_last_error(env);
|
|
510
|
+
}
|
package/src/cc/stubs.cc
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/napi — loud stubs for the not-yet-implemented Node-API surface.
|
|
3
|
+
//
|
|
4
|
+
// GENERATED by scripts/gen-stubs.py (checked in; regenerate after
|
|
5
|
+
// implementing a new ABI function — a newly implemented function that stays
|
|
6
|
+
// listed here fails the link with a duplicate-symbol error, the guard).
|
|
7
|
+
//
|
|
8
|
+
// Every declared `napi_*`/`node_api_*` symbol of the vendored headers
|
|
9
|
+
// (NAPI_VERSION=10, non-experimental) is DEFINED and exported, so an addon's
|
|
10
|
+
// napi_*/node_api_* references always resolve — reaching past the
|
|
11
|
+
// implemented surface fails loud with napi_generic_failure + last_error
|
|
12
|
+
// (plan section 2 posture), never with an unresolved-symbol load failure and
|
|
13
|
+
// never with a crash. 10 stubs.
|
|
14
|
+
|
|
15
|
+
#include "common.h"
|
|
16
|
+
|
|
17
|
+
// env may be napi_env or node_api_basic_env — identical underlying type in
|
|
18
|
+
// the non-experimental configuration (js_native_api_types.h:74-81).
|
|
19
|
+
#define GJSIFY_NAPI_STUB(env) \
|
|
20
|
+
do { \
|
|
21
|
+
GJSIFY_NAPI_CHECK_ENV(env); \
|
|
22
|
+
return gjsify_napi::set_last_error( \
|
|
23
|
+
const_cast<napi_env>(env), napi_generic_failure); \
|
|
24
|
+
} while (0)
|
|
25
|
+
|
|
26
|
+
napi_status NAPI_CDECL node_api_create_external_string_latin1(napi_env env, char* str, size_t length, node_api_basic_finalize finalize_callback, void* finalize_hint, napi_value* result, bool* copied) {
|
|
27
|
+
(void)str;
|
|
28
|
+
(void)length;
|
|
29
|
+
(void)finalize_callback;
|
|
30
|
+
(void)finalize_hint;
|
|
31
|
+
(void)result;
|
|
32
|
+
(void)copied;
|
|
33
|
+
GJSIFY_NAPI_STUB(env);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
napi_status NAPI_CDECL node_api_create_external_string_utf16(napi_env env, char16_t* str, size_t length, node_api_basic_finalize finalize_callback, void* finalize_hint, napi_value* result, bool* copied) {
|
|
37
|
+
(void)str;
|
|
38
|
+
(void)length;
|
|
39
|
+
(void)finalize_callback;
|
|
40
|
+
(void)finalize_hint;
|
|
41
|
+
(void)result;
|
|
42
|
+
(void)copied;
|
|
43
|
+
GJSIFY_NAPI_STUB(env);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
napi_status NAPI_CDECL napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result) {
|
|
47
|
+
(void)external_data;
|
|
48
|
+
(void)byte_length;
|
|
49
|
+
(void)finalize_cb;
|
|
50
|
+
(void)finalize_hint;
|
|
51
|
+
(void)result;
|
|
52
|
+
GJSIFY_NAPI_STUB(env);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
napi_status NAPI_CDECL napi_adjust_external_memory(node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value) {
|
|
56
|
+
(void)change_in_bytes;
|
|
57
|
+
(void)adjusted_value;
|
|
58
|
+
GJSIFY_NAPI_STUB(env);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
napi_status NAPI_CDECL napi_create_bigint_words(napi_env env, int sign_bit, size_t word_count, const uint64_t* words, napi_value* result) {
|
|
62
|
+
(void)sign_bit;
|
|
63
|
+
(void)word_count;
|
|
64
|
+
(void)words;
|
|
65
|
+
(void)result;
|
|
66
|
+
GJSIFY_NAPI_STUB(env);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
napi_status NAPI_CDECL napi_get_value_bigint_words(napi_env env, napi_value value, int* sign_bit, size_t* word_count, uint64_t* words) {
|
|
70
|
+
(void)value;
|
|
71
|
+
(void)sign_bit;
|
|
72
|
+
(void)word_count;
|
|
73
|
+
(void)words;
|
|
74
|
+
GJSIFY_NAPI_STUB(env);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
napi_status NAPI_CDECL node_api_create_buffer_from_arraybuffer(napi_env env, napi_value arraybuffer, size_t byte_offset, size_t byte_length, napi_value* result) {
|
|
78
|
+
(void)arraybuffer;
|
|
79
|
+
(void)byte_offset;
|
|
80
|
+
(void)byte_length;
|
|
81
|
+
(void)result;
|
|
82
|
+
GJSIFY_NAPI_STUB(env);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
napi_status NAPI_CDECL napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop) {
|
|
86
|
+
(void)loop;
|
|
87
|
+
GJSIFY_NAPI_STUB(env);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
napi_status NAPI_CDECL napi_add_async_cleanup_hook(node_api_basic_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle) {
|
|
91
|
+
(void)hook;
|
|
92
|
+
(void)arg;
|
|
93
|
+
(void)remove_handle;
|
|
94
|
+
GJSIFY_NAPI_STUB(env);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
napi_status NAPI_CDECL napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle) {
|
|
98
|
+
(void)remove_handle;
|
|
99
|
+
return napi_generic_failure;
|
|
100
|
+
}
|