@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/common.h
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/napi — Node-API (N-API) host over GJS's SpiderMonkey (mozjs-140).
|
|
3
|
+
//
|
|
4
|
+
// Reference: refs/node/src/js_native_api_v8.{h,cc} + node_api.cc (Node.js
|
|
5
|
+
// contributors, MIT) — the normative Node-API reference implementation whose
|
|
6
|
+
// observable semantics this shim mirrors over JSAPI. GJS's own production
|
|
7
|
+
// patterns are the mechanism reference: extra-GC-roots tracer
|
|
8
|
+
// (refs/gjs/gjs/context.cpp), natives-with-reserved-slots
|
|
9
|
+
// (refs/gjs/gjs/jsapi-dynamic-class.cpp), public context accessors
|
|
10
|
+
// (refs/gjs/gjs/context.h). GNOME contributors, MIT/LGPLv2+.
|
|
11
|
+
//
|
|
12
|
+
// Cross-TU layout (node-gi common.h idiom): this header owns every
|
|
13
|
+
// cross-TU declaration in `namespace gjsify_napi` plus the `napi_env__`
|
|
14
|
+
// definition (global scope — the tag is declared by the vendored
|
|
15
|
+
// js_native_api_types.h). Functional slices:
|
|
16
|
+
// env.cc — arena, tracer, handle scopes, env lifecycle/teardown
|
|
17
|
+
// value.cc — minimal value surface (undefined/null/object/string/typeof)
|
|
18
|
+
// function.cc — JSNative trampoline, napi_create_function, napi_get_cb_info
|
|
19
|
+
// error.cc — status bookkeeping, throw/pending mapping, stubs
|
|
20
|
+
// module.cc — gjsify_napi_install, loadAddon, dlopen + registration,
|
|
21
|
+
// napi_get_version, napi_fatal_error, teardown seam
|
|
22
|
+
|
|
23
|
+
#ifndef GJSIFY_NAPI_SRC_CC_COMMON_H_
|
|
24
|
+
#define GJSIFY_NAPI_SRC_CC_COMMON_H_
|
|
25
|
+
|
|
26
|
+
// Surface every declaration up to the max version this host reports
|
|
27
|
+
// (napi_get_version = 10) when compiling the shim itself.
|
|
28
|
+
#define NAPI_VERSION 10
|
|
29
|
+
|
|
30
|
+
#include "js_native_api.h" // vendored, src/napi-headers/
|
|
31
|
+
#include "node_api.h" // vendored, src/napi-headers/
|
|
32
|
+
|
|
33
|
+
#include <glib-object.h>
|
|
34
|
+
#include <glib.h>
|
|
35
|
+
|
|
36
|
+
#include <gjs/gjs.h> // umbrella: gjs/context.h forbids direct inclusion
|
|
37
|
+
|
|
38
|
+
#include <jsapi.h>
|
|
39
|
+
#include <jsfriendapi.h>
|
|
40
|
+
|
|
41
|
+
#include <js/Array.h>
|
|
42
|
+
#include <js/BigInt.h>
|
|
43
|
+
#include <js/CallAndConstruct.h>
|
|
44
|
+
#include <js/CallArgs.h>
|
|
45
|
+
#include <js/CharacterEncoding.h>
|
|
46
|
+
#include <js/Class.h>
|
|
47
|
+
#include <js/CompilationAndEvaluation.h>
|
|
48
|
+
#include <js/Conversions.h>
|
|
49
|
+
#include <js/Equality.h>
|
|
50
|
+
#include <js/GCVector.h>
|
|
51
|
+
#include <js/Id.h>
|
|
52
|
+
#include <js/Object.h>
|
|
53
|
+
#include <js/PropertyDescriptor.h>
|
|
54
|
+
#include <js/SourceText.h>
|
|
55
|
+
#include <js/Context.h>
|
|
56
|
+
#include <js/ErrorReport.h>
|
|
57
|
+
#include <js/Exception.h>
|
|
58
|
+
#include <js/GCAPI.h>
|
|
59
|
+
#include <js/GlobalObject.h>
|
|
60
|
+
#include <js/PropertyAndElement.h>
|
|
61
|
+
#include <js/RootingAPI.h>
|
|
62
|
+
#include <js/String.h>
|
|
63
|
+
#include <js/Symbol.h>
|
|
64
|
+
#include <js/TracingAPI.h>
|
|
65
|
+
#include <js/Value.h>
|
|
66
|
+
|
|
67
|
+
#include <cstdint>
|
|
68
|
+
#include <memory>
|
|
69
|
+
#include <string>
|
|
70
|
+
#include <vector>
|
|
71
|
+
|
|
72
|
+
namespace gjsify_napi {
|
|
73
|
+
|
|
74
|
+
// NODE_API_SUPPORTED_VERSION_MAX / NODE_API_DEFAULT_MODULE_API_VERSION
|
|
75
|
+
// (refs/node/src/node_version.h:103-108).
|
|
76
|
+
constexpr int32_t kNapiVersionMax = 10;
|
|
77
|
+
constexpr int32_t kNapiDefaultModuleApiVersion = 8;
|
|
78
|
+
|
|
79
|
+
// ---- handle arena (§5a) ----
|
|
80
|
+
//
|
|
81
|
+
// `napi_value` = pointer to a `JS::Heap<JS::Value>` slot in a per-env chunked
|
|
82
|
+
// arena. Chunks are fixed-size heap blocks that never move or realloc, so a
|
|
83
|
+
// slot address stays stable for the lifetime of its handle scope — the
|
|
84
|
+
// property V8's handle blocks give Node's `napi_value`. `JS::Heap` provides
|
|
85
|
+
// the pre/post barriers a heap-stored GC pointer needs under SpiderMonkey's
|
|
86
|
+
// generational GC; the per-env extra-GC-roots tracer (env.cc) marks *and
|
|
87
|
+
// relocates* live slots, so outstanding `napi_value` pointers survive moving
|
|
88
|
+
// GC. NOT `JS::Rooted` (stack-only, LIFO) and NOT a raw `JS::Value` (goes
|
|
89
|
+
// stale across nursery evacuation) — those cannot back a C ABI.
|
|
90
|
+
|
|
91
|
+
constexpr size_t kArenaChunkSlots = 256;
|
|
92
|
+
|
|
93
|
+
struct ArenaChunk {
|
|
94
|
+
JS::Heap<JS::Value> slots[kArenaChunkSlots];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
class HandleArena {
|
|
98
|
+
public:
|
|
99
|
+
// Store `value` in the next slot; returns the stable slot address.
|
|
100
|
+
JS::Heap<JS::Value>* push(const JS::Value& value);
|
|
101
|
+
size_t top() const { return top_; }
|
|
102
|
+
// Release slots [mark, top): clear them (write barrier via JS::Heap
|
|
103
|
+
// assignment) and rewind. Chunk storage is retained for reuse.
|
|
104
|
+
void popTo(size_t mark);
|
|
105
|
+
// Trace live slots [0, top) — called from the env's extra-roots tracer.
|
|
106
|
+
void trace(JSTracer* trc);
|
|
107
|
+
// Env teardown: clear every live slot and free the chunks. Must run
|
|
108
|
+
// before JS_DestroyContext (a live JS::Heap past that point is UAF).
|
|
109
|
+
void clearAll();
|
|
110
|
+
|
|
111
|
+
private:
|
|
112
|
+
JS::Heap<JS::Value>* slotAt(size_t index);
|
|
113
|
+
std::vector<std::unique_ptr<ArenaChunk>> chunks_;
|
|
114
|
+
size_t top_ = 0;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// A handle scope is a saved arena top; scopes close LIFO
|
|
118
|
+
// (napi_handle_scope_mismatch otherwise), matching
|
|
119
|
+
// js_native_api_v8.cc:2927-2953. The napi_handle_scope /
|
|
120
|
+
// napi_escapable_handle_scope pointer encodes the 1-based scope-stack depth
|
|
121
|
+
// (no heap allocation, trivially validated). Escapable scopes (cc:2955-3002)
|
|
122
|
+
// reserve ONE slot in the PARENT region at open (Bun's trick,
|
|
123
|
+
// refs/bun/src/jsc/bindings/napi_handle_scope.cpp:24-27,66-75);
|
|
124
|
+
// napi_escape_handle copies the escapee into that slot once.
|
|
125
|
+
struct HandleScopeRec {
|
|
126
|
+
size_t mark;
|
|
127
|
+
bool escapable = false;
|
|
128
|
+
bool escape_called = false;
|
|
129
|
+
// The parent-region slot (address stable — chunks never move); only
|
|
130
|
+
// set for escapable scopes. Lives BELOW mark, so close leaves it alive.
|
|
131
|
+
JS::Heap<JS::Value>* escape_slot = nullptr;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ---- callbacks (§8) ----
|
|
135
|
+
|
|
136
|
+
// Heap bundle behind every napi-created function; owned by the env, freed at
|
|
137
|
+
// env destruction (Phase-0 simplification of V8-Node's weak-ref-on-function
|
|
138
|
+
// early free, js_native_api_v8.cc:398-402). Stored in reserved slot 0 of the
|
|
139
|
+
// js::NewFunctionWithReserved native as a JS::PrivateValue.
|
|
140
|
+
struct CallbackBundle {
|
|
141
|
+
napi_env env;
|
|
142
|
+
napi_callback cb;
|
|
143
|
+
void* data;
|
|
144
|
+
// napi_define_class constructor: constructing creates the instance with
|
|
145
|
+
// the fast-tier instance JSClass (reserved slots) instead of a plain
|
|
146
|
+
// object (§5d).
|
|
147
|
+
bool construct_as_class = false;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// ---- §5b references + finalizer pipeline ----
|
|
151
|
+
|
|
152
|
+
enum class ReferenceOwnership : uint8_t {
|
|
153
|
+
kRuntime, // the runtime deletes the reference after its finalizer ran
|
|
154
|
+
kUserland, // userland must delete it via napi_delete_reference
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Intrusive doubly-linked base for everything finalized at env teardown —
|
|
158
|
+
// mirror of js_native_api_v8.h:101-130 RefTracker. Env holds two sentinel
|
|
159
|
+
// heads: `finalizing_reflist` (user finalizer present) and `reflist`
|
|
160
|
+
// (plain), finalized in that order at teardown (the v8.h:121-130 comment
|
|
161
|
+
// explains why finalizing refs go first: their finalizers may still use
|
|
162
|
+
// plain refs).
|
|
163
|
+
class RefTracker {
|
|
164
|
+
public:
|
|
165
|
+
RefTracker() = default;
|
|
166
|
+
virtual ~RefTracker() { Unlink(); }
|
|
167
|
+
// Finalization: unlinks + (subclass) runs the user callback. May be
|
|
168
|
+
// called at most once; implementations must Unlink() first.
|
|
169
|
+
virtual void Finalize() {}
|
|
170
|
+
void Link(RefTracker* list_head);
|
|
171
|
+
void Unlink();
|
|
172
|
+
static void FinalizeAll(RefTracker* list_head);
|
|
173
|
+
// Trace hook for the env's extra-roots tracer (Reference overrides).
|
|
174
|
+
virtual void Trace(JSTracer* trc) {}
|
|
175
|
+
RefTracker* next() const { return next_; }
|
|
176
|
+
|
|
177
|
+
protected:
|
|
178
|
+
RefTracker* next_ = nullptr;
|
|
179
|
+
RefTracker* prev_ = nullptr;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// A queued napi_finalize callback that is not attached to a JS value:
|
|
183
|
+
// node_api_post_finalizer + napi_set_instance_data (§5d instance data).
|
|
184
|
+
// Finalize() runs the callback (JS callable, fresh handle scope) and
|
|
185
|
+
// deletes itself.
|
|
186
|
+
class TrackedFinalizer : public RefTracker {
|
|
187
|
+
public:
|
|
188
|
+
TrackedFinalizer(napi_env env, napi_finalize cb, void* data, void* hint);
|
|
189
|
+
~TrackedFinalizer() override;
|
|
190
|
+
void Finalize() override;
|
|
191
|
+
void* data() const { return data_; }
|
|
192
|
+
|
|
193
|
+
private:
|
|
194
|
+
napi_env env_;
|
|
195
|
+
napi_finalize cb_;
|
|
196
|
+
void* data_;
|
|
197
|
+
void* hint_;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// napi_ref (§5b): strong/weak/refcounted, v10 primitive semantics. State
|
|
201
|
+
// machine mirrors js_native_api_v8.cc:690-757 (Ref/Unref/SetWeak):
|
|
202
|
+
// refcount 0→1 = leave the weak set + become traced-strong; 1→0 = SetWeak;
|
|
203
|
+
// SetWeak on a non-weakable value releases immediately (primitives at
|
|
204
|
+
// refcount 0 read back as empty — the v10 semantic better-sqlite3 relies
|
|
205
|
+
// on). SpiderMonkey mechanics:
|
|
206
|
+
// - strong state: value lives in a JS::Heap<JS::Value> traced (marks AND
|
|
207
|
+
// relocates) from the env's extra-roots tracer;
|
|
208
|
+
// - weak state: the object lives in a JS::Heap<JSObject*> that is NOT
|
|
209
|
+
// traced; the weak sweep callback updates it via
|
|
210
|
+
// JS_UpdateWeakPointerAfterGC — moved ⇒ pointer updated, dead ⇒ nulled
|
|
211
|
+
// (GCAPI.h contract; GJS jsapi-util-root.h update_after_gc pattern);
|
|
212
|
+
// - both flips stack-root the object across the transition (GJS
|
|
213
|
+
// switch_to_rooted/switch_to_unrooted pattern: never leave the thing in
|
|
214
|
+
// neither slot); JS::Heap assignments run the pre/post write barriers,
|
|
215
|
+
// JS::Heap::get() runs the read barrier (gray-unmarking on
|
|
216
|
+
// resurrection) — js/RootingAPI.h Heap<T> barrier contract.
|
|
217
|
+
class Reference : public RefTracker {
|
|
218
|
+
public:
|
|
219
|
+
static Reference* New(napi_env env, JS::HandleValue value,
|
|
220
|
+
uint32_t initial_refcount,
|
|
221
|
+
ReferenceOwnership ownership,
|
|
222
|
+
napi_finalize cb = nullptr, void* data = nullptr,
|
|
223
|
+
void* hint = nullptr);
|
|
224
|
+
~Reference() override;
|
|
225
|
+
|
|
226
|
+
uint32_t Ref();
|
|
227
|
+
uint32_t Unref();
|
|
228
|
+
uint32_t RefCount() const { return refcount_; }
|
|
229
|
+
ReferenceOwnership Ownership() const { return ownership_; }
|
|
230
|
+
void* Data() const { return data_; }
|
|
231
|
+
bool HasFinalizer() const { return cb_ != nullptr; }
|
|
232
|
+
// false when released/dead (napi_get_reference_value → NULL).
|
|
233
|
+
bool GetValue(JS::MutableHandleValue out);
|
|
234
|
+
// Weak sweep: update weak_obj_; on death → released + dequeued from the
|
|
235
|
+
// weak set by the caller; returns true when the referent died.
|
|
236
|
+
bool UpdateAfterGC(JSTracer* trc);
|
|
237
|
+
void Finalize() override;
|
|
238
|
+
void Trace(JSTracer* trc) override;
|
|
239
|
+
void RemoveFromWeakSet();
|
|
240
|
+
|
|
241
|
+
napi_env env() const { return env_; }
|
|
242
|
+
|
|
243
|
+
private:
|
|
244
|
+
Reference(napi_env env, JS::HandleValue value, uint32_t initial_refcount,
|
|
245
|
+
ReferenceOwnership ownership, napi_finalize cb, void* data,
|
|
246
|
+
void* hint);
|
|
247
|
+
void SetWeakOrRelease(); // 1→0 transition (or weakable init at 0)
|
|
248
|
+
|
|
249
|
+
enum class State : uint8_t { kStrong, kWeak, kReleased };
|
|
250
|
+
napi_env env_;
|
|
251
|
+
State state_;
|
|
252
|
+
uint32_t refcount_;
|
|
253
|
+
ReferenceOwnership ownership_;
|
|
254
|
+
bool can_be_weak_; // value.isObject(); symbols = primitives (§5b ledger)
|
|
255
|
+
napi_finalize cb_;
|
|
256
|
+
void* data_;
|
|
257
|
+
void* hint_;
|
|
258
|
+
JS::Heap<JS::Value> strong_; // valid iff state == kStrong
|
|
259
|
+
JS::Heap<JSObject*> weak_obj_; // valid iff state == kWeak
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Finalizer pipeline (§5c): GC callbacks only QUEUE; ONE idle drain
|
|
263
|
+
// (finalization_scheduled guard) runs finalizers with JS callable, each
|
|
264
|
+
// under a fresh handle scope, exceptions logged-and-cleared between
|
|
265
|
+
// finalizers. A tight synchronous script never iterates the main context,
|
|
266
|
+
// so it observes finalizers only at env teardown (documented; matches
|
|
267
|
+
// Node's SetImmediate needing a loop turn). ref.cc.
|
|
268
|
+
void enqueue_finalizer(napi_env env, RefTracker* tracker);
|
|
269
|
+
void dequeue_finalizer(napi_env env, RefTracker* tracker);
|
|
270
|
+
void drain_finalizer_queue(napi_env env);
|
|
271
|
+
// The per-process weak sweep callback (JSWeakPointerZonesCallback);
|
|
272
|
+
// registered once at install, walks every env's weak set. ref.cc.
|
|
273
|
+
void weak_sweep_callback(JSTracer* trc, void* data);
|
|
274
|
+
// Process-wide env registry (module.cc owns the storage).
|
|
275
|
+
std::vector<napi_env>& env_registry();
|
|
276
|
+
// Ask module.cc to arm the one idle drain source (no-op if armed).
|
|
277
|
+
void schedule_finalizer_drain();
|
|
278
|
+
|
|
279
|
+
// Log + clear a pending exception on env->cx (shared by the finalizer
|
|
280
|
+
// pipeline and the tsfn/make_callback boundary — the "no JS frame below us"
|
|
281
|
+
// posture). No-op when nothing is pending. ref.cc.
|
|
282
|
+
void log_and_clear_pending_exception(napi_env env, const char* where);
|
|
283
|
+
|
|
284
|
+
// ---- §7 threadsafe functions (tsfn.cc) ----
|
|
285
|
+
|
|
286
|
+
// Env teardown step 1b: synchronously finalize every tsfn still registered on
|
|
287
|
+
// the env (their deferred JS-thread finalize dispatch can no longer run once
|
|
288
|
+
// GjsContext dispose has begun). Runs with JS fully callable.
|
|
289
|
+
void finalize_env_tsfns(napi_env env);
|
|
290
|
+
|
|
291
|
+
// ---- §? asynchronous work items (async_work.cc) ----
|
|
292
|
+
|
|
293
|
+
// Env teardown step 1c (§5e crash-class boundary): drain the worker pool
|
|
294
|
+
// deterministically — g_thread_pool_free(wait=TRUE) lets in-flight execute
|
|
295
|
+
// finish and JOINS every worker, so none touches the tearing-down env — then
|
|
296
|
+
// run every still-pending completion synchronously, JS callable (the deferred
|
|
297
|
+
// g_idle can no longer fire once GjsContext dispose has begun — mirror
|
|
298
|
+
// finalize_env_tsfns).
|
|
299
|
+
void drain_env_async_works(napi_env env);
|
|
300
|
+
// Env destruction: free every async_work the addon never deleted (Node leaks
|
|
301
|
+
// its equivalent — the struct is addon-owned). Runs after teardown, no JS.
|
|
302
|
+
void destroy_env_async_works(napi_env env);
|
|
303
|
+
|
|
304
|
+
// ---- §5d instance state: fast tier + WeakMap tier ----
|
|
305
|
+
//
|
|
306
|
+
// Instances of napi_define_class classes and napi_create_external externals
|
|
307
|
+
// carry ONE reserved slot: undefined or JS::PrivateValue(InstanceState*).
|
|
308
|
+
// The InstanceState is freed by a FOREGROUND finalize hook that frees
|
|
309
|
+
// memory only (legal even for nursery deaths — finalizable classes are
|
|
310
|
+
// nursery-allocatable in 140; JSFinalizeOp runs with JS::GCContext*,
|
|
311
|
+
// js/Class.h:301, and must not call GC-capable JSAPI). Foreign objects get
|
|
312
|
+
// the same InstanceState via a lazily-created env WeakMap (js/WeakMap.h)
|
|
313
|
+
// whose value is a hostdata_class carrier object. Pattern: GJS
|
|
314
|
+
// cwrapper.h reserved-slot storage + JSCLASS_FOREGROUND_FINALIZE like
|
|
315
|
+
// GJS's GObject wrapper (gi/object.cpp:3404-3407).
|
|
316
|
+
constexpr size_t kInstanceSlotState = 0;
|
|
317
|
+
|
|
318
|
+
struct InstanceState {
|
|
319
|
+
Reference* wrap = nullptr; // §5d uniform wrap lifecycle
|
|
320
|
+
bool has_tag = false;
|
|
321
|
+
napi_type_tag tag = {0, 0};
|
|
322
|
+
bool is_external = false;
|
|
323
|
+
void* external_data = nullptr;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
extern const JSClass instance_class; // napi_define_class instances
|
|
327
|
+
extern const JSClass external_class; // napi_create_external
|
|
328
|
+
extern const JSClass hostdata_class; // WeakMap-tier value carrier
|
|
329
|
+
|
|
330
|
+
// Look up (or create) the InstanceState for any object; *out == nullptr
|
|
331
|
+
// when absent and !create. wrap.cc.
|
|
332
|
+
napi_status get_instance_state(napi_env env, JS::HandleObject obj,
|
|
333
|
+
bool create, InstanceState** out);
|
|
334
|
+
|
|
335
|
+
// Shared property-descriptor applier (object.cc) — the body of
|
|
336
|
+
// napi_define_properties, reused by napi_define_class for both the
|
|
337
|
+
// prototype (non-static) and constructor (napi_static) targets.
|
|
338
|
+
napi_status apply_property_descriptor(napi_env env, JS::HandleObject target,
|
|
339
|
+
const napi_property_descriptor& p);
|
|
340
|
+
|
|
341
|
+
// Property id from a descriptor's utf8name XOR name (napi_name_expected
|
|
342
|
+
// otherwise); name must be a string or symbol. (object.cc)
|
|
343
|
+
napi_status property_key_to_id(napi_env env, const char* utf8name,
|
|
344
|
+
napi_value name, JS::MutableHandleId id);
|
|
345
|
+
|
|
346
|
+
// Lossy UTF-8 → JSString (invalid sequences → U+FFFD, matching V8's
|
|
347
|
+
// NewFromUtf8; SpiderMonkey's JS_NewStringCopyUTF8N throws on malformed
|
|
348
|
+
// input, so this falls back to LossyUTF8CharsToNewTwoByteCharsZ). Returns
|
|
349
|
+
// nullptr on OOM with the exception cleared. (value.cc)
|
|
350
|
+
JSString* new_string_utf8_lossy(JSContext* cx, const char* str, size_t len);
|
|
351
|
+
|
|
352
|
+
// Bundle-function factory shared by napi_create_function,
|
|
353
|
+
// napi_define_class and the accessor/method descriptors. (function.cc)
|
|
354
|
+
JSObject* new_bundle_function(napi_env env, const char* utf8name,
|
|
355
|
+
size_t length, napi_callback cb, void* data,
|
|
356
|
+
unsigned flags, bool construct_as_class);
|
|
357
|
+
|
|
358
|
+
// Stack-allocated per trampoline invocation; napi_callback_info points here.
|
|
359
|
+
struct CallbackInfo {
|
|
360
|
+
napi_value* argv; // arena slots, one per provided argument
|
|
361
|
+
size_t argc;
|
|
362
|
+
napi_value this_arg; // arena slot
|
|
363
|
+
napi_value new_target; // nullptr when not constructing (P0.2)
|
|
364
|
+
void* data; // bundle->data
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// The shared JSNative all napi-created functions dispatch through.
|
|
368
|
+
bool function_trampoline(JSContext* cx, unsigned argc, JS::Value* vp);
|
|
369
|
+
|
|
370
|
+
// ---- status bookkeeping (§6, error.cc) ----
|
|
371
|
+
|
|
372
|
+
napi_status set_last_error(napi_env env, napi_status status,
|
|
373
|
+
uint32_t engine_error_code = 0,
|
|
374
|
+
void* engine_reserved = nullptr);
|
|
375
|
+
napi_status clear_last_error(napi_env env);
|
|
376
|
+
|
|
377
|
+
// ---- value boxing (env.cc) ----
|
|
378
|
+
|
|
379
|
+
// The single choke point boxing a JSAPI value into an env arena slot.
|
|
380
|
+
napi_value arena_push(napi_env env, const JS::Value& value);
|
|
381
|
+
|
|
382
|
+
// Deref a napi_value back to the engine value.
|
|
383
|
+
inline JS::Value napi_value_to_js(napi_value value) {
|
|
384
|
+
return reinterpret_cast<JS::Heap<JS::Value>*>(value)->get();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// The env's extra-GC-roots trace hook (env.cc).
|
|
388
|
+
void trace_env(JSTracer* trc, void* data);
|
|
389
|
+
|
|
390
|
+
} // namespace gjsify_napi
|
|
391
|
+
|
|
392
|
+
// ---- napi_env (§5e, minimal P0.0 shape) ----
|
|
393
|
+
//
|
|
394
|
+
// Global scope: the struct tag is fixed by the vendored
|
|
395
|
+
// `typedef struct napi_env__* napi_env`.
|
|
396
|
+
struct napi_env__ {
|
|
397
|
+
JSContext* cx;
|
|
398
|
+
GjsContext* gjs_context;
|
|
399
|
+
// Per-addon Node-API version (§3c discovery + clamp); gates §5b/§6
|
|
400
|
+
// behavior from P0.1 on.
|
|
401
|
+
int32_t module_api_version;
|
|
402
|
+
gjsify_napi::HandleArena arena;
|
|
403
|
+
std::vector<gjsify_napi::HandleScopeRec> scopes;
|
|
404
|
+
napi_extended_error_info last_error = {nullptr, nullptr, 0, napi_ok};
|
|
405
|
+
// dlopen handle of the addon; kept for the process lifetime (Node never
|
|
406
|
+
// dlcloses registered addons either).
|
|
407
|
+
void* dl_handle = nullptr;
|
|
408
|
+
// file:// URL of the addon (node_api_get_module_file_name, P0.5).
|
|
409
|
+
std::string filename;
|
|
410
|
+
// false during/after teardown — NAPI_PREAMBLE gate.
|
|
411
|
+
bool can_call_into_js = true;
|
|
412
|
+
bool torn_down = false;
|
|
413
|
+
bool tearing_down = false;
|
|
414
|
+
bool draining_finalizers = false;
|
|
415
|
+
// Bundle ownership (§8): freed at destruction.
|
|
416
|
+
std::vector<gjsify_napi::CallbackBundle*> bundles;
|
|
417
|
+
// The addon's exports object, rooted for the exports cache. Reset in
|
|
418
|
+
// teardown() — a live PersistentRooted past JS_DestroyContext is UAF.
|
|
419
|
+
JS::PersistentRooted<JSObject*> exports;
|
|
420
|
+
// The realm global captured at env creation (napi_get_global — Node's
|
|
421
|
+
// context()->Global() analog). Reset in teardown().
|
|
422
|
+
JS::PersistentRooted<JSObject*> global;
|
|
423
|
+
// Foreign-object InstanceState WeakMap (§5d fallback tier), lazily
|
|
424
|
+
// created. Reset in teardown().
|
|
425
|
+
JS::PersistentRooted<JSObject*> wrap_map;
|
|
426
|
+
|
|
427
|
+
// §5b ref lists (sentinel heads; two-list teardown order per
|
|
428
|
+
// js_native_api_v8.h:121-130).
|
|
429
|
+
gjsify_napi::RefTracker reflist;
|
|
430
|
+
gjsify_napi::RefTracker finalizing_reflist;
|
|
431
|
+
// Weak set: references currently in the kWeak state, walked by the
|
|
432
|
+
// sweep callback.
|
|
433
|
+
std::vector<gjsify_napi::Reference*> weak_refs;
|
|
434
|
+
// §5c pending finalizers (FIFO), drained on idle or at teardown.
|
|
435
|
+
std::vector<gjsify_napi::RefTracker*> pending_finalizers;
|
|
436
|
+
// §5d instance data (TrackedFinalizer; overwrite deletes un-finalized).
|
|
437
|
+
gjsify_napi::TrackedFinalizer* instance_data = nullptr;
|
|
438
|
+
// §7 live threadsafe functions (JS-thread-only access: registered at
|
|
439
|
+
// create, unregistered by the JS-thread finalize; teardown force-
|
|
440
|
+
// finalizes leftovers — tsfn.cc).
|
|
441
|
+
std::vector<napi_threadsafe_function> tsfns;
|
|
442
|
+
// Live async_work items (registered at create on the JS thread, unlinked by
|
|
443
|
+
// the completion dispatch's self-delete; teardown drains pending
|
|
444
|
+
// completions, the destructor frees leftovers — async_work.cc).
|
|
445
|
+
std::vector<napi_async_work> async_works;
|
|
446
|
+
// Per-env worker pool backing napi_queue_async_work (libuv-thread-pool
|
|
447
|
+
// analog). Lazily created on first queue; drained + freed deterministically
|
|
448
|
+
// at teardown (g_thread_pool_free wait=TRUE) BEFORE any root is dropped, so
|
|
449
|
+
// no worker outlives the env. nullptr until the first queue — async_work.cc.
|
|
450
|
+
GThreadPool* async_pool = nullptr;
|
|
451
|
+
// Env cleanup hooks, run LIFO as teardown step 1.
|
|
452
|
+
struct CleanupHook {
|
|
453
|
+
void(NAPI_CDECL* fn)(void* arg);
|
|
454
|
+
void* arg;
|
|
455
|
+
};
|
|
456
|
+
std::vector<CleanupHook> cleanup_hooks;
|
|
457
|
+
|
|
458
|
+
napi_env__(JSContext* cx, GjsContext* gjs, int32_t api_version,
|
|
459
|
+
void* handle, std::string file);
|
|
460
|
+
~napi_env__();
|
|
461
|
+
|
|
462
|
+
// §5e teardown order (the node-gi SIGSEGV lesson — full sequence):
|
|
463
|
+
// 1. run env cleanup hooks LIFO (JS fully available);
|
|
464
|
+
// 2. drain pending finalizers (finalizers may call napi and create
|
|
465
|
+
// values — arena, tracer and can_call_into_js MUST still be live);
|
|
466
|
+
// 3. RefTracker::FinalizeAll(finalizing_reflist) then (reflist);
|
|
467
|
+
// 4. instance-data finalizer;
|
|
468
|
+
// 5. flip can_call_into_js/torn_down (subsequent napi entry → status);
|
|
469
|
+
// 6. drop every root: remove the extra-roots tracer, reset
|
|
470
|
+
// PersistentRooteds, clear arena/scopes/weak set — all BEFORE
|
|
471
|
+
// JS_DestroyContext runs (a live root past that point is the UAF
|
|
472
|
+
// class). Callback bundles are NOT freed here: live function
|
|
473
|
+
// objects still reference them and the trampoline guards on
|
|
474
|
+
// torn_down; the destructor frees them at context dispose.
|
|
475
|
+
void teardown();
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
namespace gjsify_napi {
|
|
479
|
+
|
|
480
|
+
// ---- status check macros (mirroring js_native_api_v8.h:233-243) ----
|
|
481
|
+
|
|
482
|
+
#define GJSIFY_NAPI_CHECK_ENV(env) \
|
|
483
|
+
do { \
|
|
484
|
+
if ((env) == nullptr) { \
|
|
485
|
+
return napi_invalid_arg; \
|
|
486
|
+
} \
|
|
487
|
+
} while (0)
|
|
488
|
+
|
|
489
|
+
#define GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, condition, status) \
|
|
490
|
+
do { \
|
|
491
|
+
if (!(condition)) { \
|
|
492
|
+
return gjsify_napi::set_last_error((env), (status)); \
|
|
493
|
+
} \
|
|
494
|
+
} while (0)
|
|
495
|
+
|
|
496
|
+
#define GJSIFY_NAPI_CHECK_ARG(env, arg) \
|
|
497
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg)
|
|
498
|
+
|
|
499
|
+
// The NAPI_PREAMBLE partition (§6): only for functions that can run JS.
|
|
500
|
+
// Pending exception ⇒ napi_pending_exception immediately; torn-down env ⇒
|
|
501
|
+
// napi_cannot_run_js (module_api_version >= 10) / napi_pending_exception
|
|
502
|
+
// (< 10); then clear last_error. Pure value/scope ops must NOT use this —
|
|
503
|
+
// they keep working while an exception is pending.
|
|
504
|
+
#define GJSIFY_NAPI_PREAMBLE(env) \
|
|
505
|
+
GJSIFY_NAPI_CHECK_ENV(env); \
|
|
506
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE( \
|
|
507
|
+
(env), !JS_IsExceptionPending((env)->cx), napi_pending_exception); \
|
|
508
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE( \
|
|
509
|
+
(env), (env)->can_call_into_js, \
|
|
510
|
+
((env)->module_api_version >= 10 ? napi_cannot_run_js \
|
|
511
|
+
: napi_pending_exception)); \
|
|
512
|
+
gjsify_napi::clear_last_error(env)
|
|
513
|
+
|
|
514
|
+
} // namespace gjsify_napi
|
|
515
|
+
|
|
516
|
+
// The C bridge the Vala `GjsifyNapi.init()` body calls (module.cc).
|
|
517
|
+
extern "C" gboolean gjsify_napi_install(void);
|
|
518
|
+
|
|
519
|
+
#endif // GJSIFY_NAPI_SRC_CC_COMMON_H_
|