@mikrojs/native 0.12.0 → 0.14.0-pr-229.g0d8db1b
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/CMakeLists.txt +3 -1
- package/deps/nanocbor/LICENSE +121 -0
- package/deps/nanocbor/include/nanocbor/config.h +87 -0
- package/deps/nanocbor/include/nanocbor/nanocbor.h +1012 -0
- package/deps/nanocbor/src/decoder.c +706 -0
- package/deps/nanocbor/src/encoder.c +418 -0
- package/deps/nanocbor/src/meson.build +12 -0
- package/include/mikrojs/mikrojs.h +7 -0
- package/include/mikrojs/platform.h +9 -0
- package/include/mikrojs/private.h +32 -5
- package/package.json +5 -2
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/abort/abort.ts +120 -0
- package/runtime/internal.d.ts +2 -1
- package/runtime/sys/sys.ts +2 -0
- package/runtime/sys/types.ts +27 -1
- package/src/builtins.cpp +10 -5
- package/src/mik_abort.cpp +40 -107
- package/src/mik_console.cpp +26 -24
- package/src/mik_inspect.cpp +3 -3
- package/src/mik_repl.cpp +4 -0
- package/src/mik_sys.cpp +11 -1
- package/src/mikrojs.cpp +224 -78
- package/src/modules.cpp +7 -16
- package/src/platform_posix.cpp +5 -0
- package/LICENSE +0 -21
package/src/mikrojs.cpp
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include <quickjs.h>
|
|
4
4
|
|
|
5
|
+
#include <atomic>
|
|
5
6
|
#include <cerrno>
|
|
6
7
|
#include <cstdio>
|
|
7
8
|
#include <fcntl.h>
|
|
@@ -91,7 +92,7 @@ static void mik__add_exports(JSContext* ctx, JSModuleDef* m, const char* const*
|
|
|
91
92
|
static const char* const sys_exports[] = {
|
|
92
93
|
"evalScript", "memoryUsage", "jsMemoryUsage", "gc", "setTime",
|
|
93
94
|
"uptime", "restart", "version", "board", "firmware",
|
|
94
|
-
"deviceId", "
|
|
95
|
+
"deviceId", "resetReason", "activeTimers", "unloadNamespace", "isUnloadableNamespace"};
|
|
95
96
|
|
|
96
97
|
static int mik__sys_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
97
98
|
JSValue ns = JS_NewObjectProto(ctx, JS_NULL);
|
|
@@ -111,21 +112,19 @@ static int mik__stdio_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
|
111
112
|
return 0;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
115
|
+
/* Host promise-rejection tracker. Rather than report eagerly the instant a
|
|
116
|
+
* promise rejects, defer the decision to the end-of-turn flush
|
|
117
|
+
* (mik__flush_unhandled_rejections): a promise that rejects without a
|
|
118
|
+
* handler is queued, and a promise that later gets a handler is removed
|
|
119
|
+
* from the queue. This mirrors the HTML/WinterCG unhandledrejection
|
|
120
|
+
* algorithm. It matters because some promises are born rejected before a
|
|
121
|
+
* handler can be attached. The clearest example is a module's evaluation
|
|
122
|
+
* promise, which the dynamic-import loader rejects (when the module body
|
|
123
|
+
* throws) and only then attaches its .then to. Reporting eagerly surfaced
|
|
124
|
+
* that transient rejection as a spurious second "Uncaught (in promise)".
|
|
125
|
+
*
|
|
126
|
+
* This is the same add-on-reject / remove-on-handle / report-after-drain
|
|
127
|
+
* pattern quickjs-libc's js_std_promise_rejection_tracker uses. */
|
|
129
128
|
static void mik__promise_rejection_tracker(JSContext* ctx, JSValue promise, JSValue reason,
|
|
130
129
|
bool is_handled, void* opaque) {
|
|
131
130
|
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
@@ -135,68 +134,109 @@ static void mik__promise_rejection_tracker(JSContext* ctx, JSValue promise, JSVa
|
|
|
135
134
|
return;
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* Don't JS_Throw here — we're inside a QuickJS callback
|
|
151
|
-
* during promise resolution; throwing would corrupt engine
|
|
152
|
-
* state and cause mik__execute_jobs to dump the error a
|
|
153
|
-
* second time. MIK_Stop gates on whether we're inside an
|
|
154
|
-
* interactive REPL eval (skipping both the stop request and
|
|
155
|
-
* the restart) so a typo at the prompt doesn't reboot the
|
|
156
|
-
* device. */
|
|
157
|
-
if (mik_rt->error_handler_fn) {
|
|
158
|
-
mik_rt->error_handler_fn(ctx, reason, mik_rt->error_handler_opaque);
|
|
137
|
+
auto& pending = mik_rt->pending_rejections;
|
|
138
|
+
void* key = JS_VALUE_GET_PTR(promise);
|
|
139
|
+
|
|
140
|
+
if (is_handled) {
|
|
141
|
+
/* A handler was attached to a previously-rejected promise: cancel its
|
|
142
|
+
* pending report. */
|
|
143
|
+
for (size_t i = 0; i < pending.size(); i++) {
|
|
144
|
+
if (JS_VALUE_GET_PTR(pending[i].promise) == key) {
|
|
145
|
+
JS_FreeValue(ctx, pending[i].promise);
|
|
146
|
+
JS_FreeValue(ctx, pending[i].reason);
|
|
147
|
+
pending.erase(pending.begin() + i);
|
|
148
|
+
return;
|
|
159
149
|
}
|
|
160
|
-
MIK_Stop(mik_rt);
|
|
161
|
-
return;
|
|
162
150
|
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Rejected with no handler (yet): queue it. Hold references to both the
|
|
155
|
+
* promise and the reason so they stay alive until the end-of-turn check. */
|
|
156
|
+
pending.push_back({JS_DupValue(ctx, promise), JS_DupValue(ctx, reason)});
|
|
157
|
+
}
|
|
163
158
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
/* Drop a promise from the pending-rejection queue without reporting it. The
|
|
160
|
+
* REPL eval pump calls this for the result promise it polls and reports via
|
|
161
|
+
* the throw path, so the end-of-turn flush doesn't report it a second time. */
|
|
162
|
+
void mik__forget_rejection(JSContext* ctx, JSValue promise) {
|
|
163
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
164
|
+
if (!mik_rt) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
auto& pending = mik_rt->pending_rejections;
|
|
168
|
+
void* key = JS_VALUE_GET_PTR(promise);
|
|
169
|
+
for (size_t i = 0; i < pending.size(); i++) {
|
|
170
|
+
if (JS_VALUE_GET_PTR(pending[i].promise) == key) {
|
|
171
|
+
JS_FreeValue(ctx, pending[i].promise);
|
|
172
|
+
JS_FreeValue(ctx, pending[i].reason);
|
|
173
|
+
pending.erase(pending.begin() + i);
|
|
175
174
|
return;
|
|
176
175
|
}
|
|
177
|
-
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
/* End-of-turn unhandled-rejection check. Any promise still queued after a
|
|
180
|
+
* microtask drain rejected and never got a handler: report it, notify the
|
|
181
|
+
* host error handler, and halt. */
|
|
182
|
+
void mik__flush_unhandled_rejections(JSContext* ctx) {
|
|
183
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
184
|
+
if (!mik_rt || mik_rt->freeing || mik_rt->pending_rejections.empty()) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
/* During interactive REPL eval, the eval pump polls its result promise
|
|
188
|
+
* and reports rejections via the throw path; leave the queue alone until
|
|
189
|
+
* the eval finishes so we don't fight that model. */
|
|
190
|
+
if (mik__repl_is_evaluating()) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
181
193
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
/* Move the queue aside before reporting. Reporting doesn't enqueue JS
|
|
195
|
+
* jobs, but this keeps the runtime's vector in a clean state regardless. */
|
|
196
|
+
std::vector<MIKRejectedPromise> rejected;
|
|
197
|
+
rejected.swap(mik_rt->pending_rejections);
|
|
198
|
+
|
|
199
|
+
/* One failed dynamic import leaves two never-handled promises with the
|
|
200
|
+
* SAME reason: the module's internal sync-evaluation promise (QuickJS runs
|
|
201
|
+
* a module body as an async function and reads its rejected result by
|
|
202
|
+
* value, never attaching a handler) and the promise the error propagates
|
|
203
|
+
* to up the await chain. mik__report_uncaught dedups by reason-object
|
|
204
|
+
* identity, so the second is a no-op; only act on a fresh report so the
|
|
205
|
+
* host bridge and the halt don't fire twice. (quickjs-libc reports every
|
|
206
|
+
* entry; we collapse same-error duplicates.) */
|
|
207
|
+
for (const MIKRejectedPromise& rp : rejected) {
|
|
208
|
+
if (mik__report_uncaught(ctx, rp.reason, true)) {
|
|
209
|
+
/* Notify the error handler (e.g. host bridge) directly. Don't
|
|
210
|
+
* JS_Throw here: we're between jobs, and throwing would be picked
|
|
211
|
+
* up as a spurious pending exception. */
|
|
212
|
+
if (mik_rt->error_handler_fn) {
|
|
213
|
+
mik_rt->error_handler_fn(ctx, rp.reason, mik_rt->error_handler_opaque);
|
|
196
214
|
}
|
|
215
|
+
MIK_Stop(mik_rt);
|
|
197
216
|
}
|
|
217
|
+
JS_FreeValue(ctx, rp.promise);
|
|
218
|
+
JS_FreeValue(ctx, rp.reason);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
198
221
|
|
|
199
|
-
|
|
222
|
+
/* QuickJS only runs the cycle collector automatically once malloc_size
|
|
223
|
+
* crosses the runtime's GC threshold (256 KB by default), and js_malloc_rt
|
|
224
|
+
* fails against the memory limit without attempting a GC first. On devices
|
|
225
|
+
* where mem_limit is below the default threshold the collector would never
|
|
226
|
+
* fire before allocations start failing, so cyclic garbage (promise chains,
|
|
227
|
+
* closures) accumulates straight into OOM with collectable memory still
|
|
228
|
+
* alive. Cap the threshold below the limit so collection happens first.
|
|
229
|
+
* QuickJS raises the threshold to 1.5x the live size after every GC pass,
|
|
230
|
+
* which can push it back above the limit, so MIK_Loop re-applies the cap
|
|
231
|
+
* each turn. */
|
|
232
|
+
static void mik__clamp_gc_threshold(MIKRuntime* mik_rt) {
|
|
233
|
+
if (mik_rt->options.mem_limit <= 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
size_t limit = (size_t)mik_rt->options.mem_limit;
|
|
237
|
+
size_t cap = limit - limit / 8;
|
|
238
|
+
if (JS_GetGCThreshold(mik_rt->rt) > cap) {
|
|
239
|
+
JS_SetGCThreshold(mik_rt->rt, cap);
|
|
200
240
|
}
|
|
201
241
|
}
|
|
202
242
|
|
|
@@ -289,12 +329,25 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
289
329
|
* exhaust the heap. Overridden via MIK_SetFSReadMax. */
|
|
290
330
|
mik_rt->fs_read_max = 65536;
|
|
291
331
|
|
|
332
|
+
/* Reserve the unhandled-rejection queue up-front so the rejection tracker
|
|
333
|
+
* never has to grow a std::vector on the rejection path. That path fires
|
|
334
|
+
* for a QuickJS null-OOM rejection, and a reallocation there could throw
|
|
335
|
+
* std::bad_alloc — which, with CONFIG_COMPILER_CXX_EXCEPTIONS=n on ESP32,
|
|
336
|
+
* aborts and masks the very OOM we're trying to report. Four slots covers
|
|
337
|
+
* any realistic per-turn rejection count; the runtime halts on the first
|
|
338
|
+
* one anyway. */
|
|
339
|
+
mik_rt->pending_rejections.reserve(4);
|
|
340
|
+
|
|
292
341
|
/* Check for duplicate native module registrations (global registry) */
|
|
293
342
|
mik__check_module_collisions();
|
|
294
343
|
|
|
295
344
|
/* Set memory limit */
|
|
296
345
|
JS_SetMemoryLimit(rt, options->mem_limit);
|
|
297
346
|
|
|
347
|
+
/* Make sure the cycle collector fires before the memory limit does
|
|
348
|
+
* (see mik__clamp_gc_threshold). */
|
|
349
|
+
mik__clamp_gc_threshold(mik_rt);
|
|
350
|
+
|
|
298
351
|
/* Set stack size */
|
|
299
352
|
JS_SetMaxStackSize(rt, options->stack_size);
|
|
300
353
|
/* loader for ES modules */
|
|
@@ -337,11 +390,6 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
337
390
|
mik__text_encoding_init(ctx, global_obj);
|
|
338
391
|
mik__abort_init(ctx, global_obj);
|
|
339
392
|
|
|
340
|
-
/* Load some builtin references for easy access */
|
|
341
|
-
mik_rt->builtins.dispatch_event_func = JS_GetPropertyStr(ctx, global_obj, "dispatchEvent");
|
|
342
|
-
mik_rt->builtins.promise_event_ctor =
|
|
343
|
-
JS_GetPropertyStr(mik_rt->ctx, global_obj, "PromiseRejectionEvent");
|
|
344
|
-
|
|
345
393
|
/* Timers */
|
|
346
394
|
mik_rt->timers = MIK_NewTimerRegistry();
|
|
347
395
|
mik__timers_init(ctx, global_obj);
|
|
@@ -369,6 +417,13 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
|
|
|
369
417
|
mik_rt->stdin_state.on_data = JS_UNDEFINED;
|
|
370
418
|
}
|
|
371
419
|
|
|
420
|
+
/* Release any still-pending unhandled-rejection entries. */
|
|
421
|
+
for (const MIKRejectedPromise& rp : mik_rt->pending_rejections) {
|
|
422
|
+
JS_FreeValue(mik_rt->ctx, rp.promise);
|
|
423
|
+
JS_FreeValue(mik_rt->ctx, rp.reason);
|
|
424
|
+
}
|
|
425
|
+
mik_rt->pending_rejections.clear();
|
|
426
|
+
|
|
372
427
|
/* Destroy registered loop consumers */
|
|
373
428
|
for (const auto& consumer : mik_rt->loop_consumers) {
|
|
374
429
|
if (consumer.destroy_fn) {
|
|
@@ -384,10 +439,6 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
|
|
|
384
439
|
/* Destroy the JS engine. */
|
|
385
440
|
JS_FreeValue(mik_rt->ctx, mik_rt->env_obj);
|
|
386
441
|
mik_rt->env_obj = JS_UNDEFINED;
|
|
387
|
-
JS_FreeValue(mik_rt->ctx, mik_rt->builtins.dispatch_event_func);
|
|
388
|
-
mik_rt->builtins.dispatch_event_func = JS_UNDEFINED;
|
|
389
|
-
JS_FreeValue(mik_rt->ctx, mik_rt->builtins.promise_event_ctor);
|
|
390
|
-
mik_rt->builtins.promise_event_ctor = JS_UNDEFINED;
|
|
391
442
|
JS_FreeValue(mik_rt->ctx, mik_rt->result_ok_void_singleton);
|
|
392
443
|
mik_rt->result_ok_void_singleton = JS_UNDEFINED;
|
|
393
444
|
JS_FreeValue(mik_rt->ctx, mik_rt->result_proto);
|
|
@@ -510,9 +561,43 @@ static void mik__check_module_collisions(void) {
|
|
|
510
561
|
}
|
|
511
562
|
}
|
|
512
563
|
|
|
564
|
+
/* A chain of already-settled promises drains as one uninterrupted storm of
|
|
565
|
+
* jobs, and the test supervisor strings whole files of such chains together
|
|
566
|
+
* with runtime recycles in between. On ESP32 the main task runs above the
|
|
567
|
+
* idle task, so a multi-second stretch like that starves idle: the task
|
|
568
|
+
* watchdog (5 s, watching IDLE0) prints a register dump, and FreeRTOS
|
|
569
|
+
* housekeeping (freeing TCBs of exited tasks, e.g. per-request HTTP tasks)
|
|
570
|
+
* stalls. Force one platform yield per second of continuous job execution
|
|
571
|
+
* so idle always gets a slice. The timestamp is global on purpose: the
|
|
572
|
+
* starvation accumulates across MIK_Loop calls and across runtime recycles.
|
|
573
|
+
* Costs at most one tick (~10 ms) per second of busy work. */
|
|
574
|
+
#define MIK__YIELD_INTERVAL_US (1000 * 1000)
|
|
575
|
+
|
|
576
|
+
/* Atomic because the Node addon can pump two runtimes' loops from
|
|
577
|
+
* different worker threads; relaxed ordering is enough — a missed or
|
|
578
|
+
* extra yield is harmless, a torn 64-bit write is not. */
|
|
579
|
+
static std::atomic<int64_t> mik__last_yield_us{0};
|
|
580
|
+
|
|
581
|
+
static void mik__maybe_yield(void) {
|
|
582
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
583
|
+
int64_t now = platform->get_boot_us();
|
|
584
|
+
int64_t last = mik__last_yield_us.load(std::memory_order_relaxed);
|
|
585
|
+
/* now < last happens when the boot clock resets (deep sleep) or a test
|
|
586
|
+
* swaps in a platform with a different clock; restart the interval. */
|
|
587
|
+
if (last == 0 || now < last) {
|
|
588
|
+
mik__last_yield_us.store(now, std::memory_order_relaxed);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (now - last >= MIK__YIELD_INTERVAL_US) {
|
|
592
|
+
platform->yield();
|
|
593
|
+
mik__last_yield_us.store(platform->get_boot_us(), std::memory_order_relaxed);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
513
597
|
void mik__execute_jobs(JSContext* ctx) {
|
|
514
598
|
JSContext* ctx1;
|
|
515
599
|
int err;
|
|
600
|
+
bool ran_any = false;
|
|
516
601
|
|
|
517
602
|
/* execute the pending jobs */
|
|
518
603
|
for (;;) {
|
|
@@ -529,7 +614,31 @@ void mik__execute_jobs(JSContext* ctx) {
|
|
|
529
614
|
}
|
|
530
615
|
break;
|
|
531
616
|
}
|
|
617
|
+
ran_any = true;
|
|
618
|
+
/* A GC pass inside this job raises the threshold to 1.5x live size,
|
|
619
|
+
* which lands above mem_limit whenever live >= ~2/3 of the limit —
|
|
620
|
+
* silently disabling cycle GC for the rest of the storm. Re-clamp
|
|
621
|
+
* between jobs so collection keeps firing; the check is a field
|
|
622
|
+
* read + compare when no GC ran. */
|
|
623
|
+
MIKRuntime* job_rt = MIK_GetRuntime(ctx1);
|
|
624
|
+
if (job_rt) {
|
|
625
|
+
mik__clamp_gc_threshold(job_rt);
|
|
626
|
+
}
|
|
627
|
+
mik__maybe_yield();
|
|
532
628
|
}
|
|
629
|
+
|
|
630
|
+
/* The yield interval measures continuous BUSY work. An empty drain
|
|
631
|
+
* means the loop is idling (the idle task is getting time between
|
|
632
|
+
* turns), so re-arm the interval — otherwise the first job after a
|
|
633
|
+
* quiet period pays a spurious ~10 ms yield for wall-clock time spent
|
|
634
|
+
* doing nothing. */
|
|
635
|
+
if (!ran_any) {
|
|
636
|
+
mik__last_yield_us.store(MIK_GetPlatform()->get_boot_us(), std::memory_order_relaxed);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* Microtask checkpoint: every reject/handle transition for this turn has
|
|
640
|
+
* now run, so whatever is still queued is a genuine unhandled rejection. */
|
|
641
|
+
mik__flush_unhandled_rejections(ctx);
|
|
533
642
|
}
|
|
534
643
|
|
|
535
644
|
/* main loop which calls the user JS callbacks */
|
|
@@ -576,7 +685,18 @@ int MIK_Loop(MIKRuntime* mik_rt) {
|
|
|
576
685
|
}
|
|
577
686
|
|
|
578
687
|
mik__execute_jobs(mik_rt->ctx);
|
|
579
|
-
|
|
688
|
+
|
|
689
|
+
/* Backstop for GC passes outside the job drain (timer callbacks, loop
|
|
690
|
+
* consumers, top-level eval): a pass there may have raised the
|
|
691
|
+
* threshold above the memory limit; pull it back so the next turn's
|
|
692
|
+
* collection still fires before allocations fail. Within the job
|
|
693
|
+
* drain, mik__execute_jobs re-clamps after every job. */
|
|
694
|
+
mik__clamp_gc_threshold(mik_rt);
|
|
695
|
+
|
|
696
|
+
/* The end-of-turn unhandled-rejection flush inside mik__execute_jobs may
|
|
697
|
+
* have requested a stop; surface it this iteration rather than making the
|
|
698
|
+
* caller loop once more to notice. */
|
|
699
|
+
return mik_rt->stop_requested ? 1 : 0;
|
|
580
700
|
}
|
|
581
701
|
|
|
582
702
|
void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config) {
|
|
@@ -961,6 +1081,13 @@ JSValue MIK_EvalModule(JSContext* ctx, const char* filename, bool is_main) {
|
|
|
961
1081
|
}
|
|
962
1082
|
|
|
963
1083
|
int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
|
|
1084
|
+
return MIK_RunEntryErr(mik_rt, entry, NULL, 0);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
int MIK_RunEntryErr(MIKRuntime* mik_rt, const char* entry, char* err_buf, size_t err_buf_size) {
|
|
1088
|
+
if (err_buf && err_buf_size > 0) {
|
|
1089
|
+
err_buf[0] = '\0';
|
|
1090
|
+
}
|
|
964
1091
|
if (!mik_rt || !entry || entry[0] == '\0') {
|
|
965
1092
|
return -EINVAL;
|
|
966
1093
|
}
|
|
@@ -987,6 +1114,7 @@ int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
|
|
|
987
1114
|
|
|
988
1115
|
JSValue result = MIK_EvalModule(ctx, entry, true);
|
|
989
1116
|
bool failed = JS_IsException(result);
|
|
1117
|
+
bool rejected = false;
|
|
990
1118
|
/* Modules with top-level await return a Promise. A module body that
|
|
991
1119
|
* throws synchronously rejects that promise before JS_EvalFunction
|
|
992
1120
|
* returns, but the value itself is still an Object (rejected Promise),
|
|
@@ -998,7 +1126,25 @@ int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
|
|
|
998
1126
|
JSPromiseStateEnum state = JS_PromiseState(ctx, result);
|
|
999
1127
|
if (state == JS_PROMISE_REJECTED) {
|
|
1000
1128
|
failed = true;
|
|
1129
|
+
rejected = true;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (failed && err_buf && err_buf_size > 0) {
|
|
1133
|
+
/* Capture the failure's string form for the caller. Without an
|
|
1134
|
+
* err_buf the pending exception is intentionally left on ctx,
|
|
1135
|
+
* matching MIK_RunEntry's historical behavior. */
|
|
1136
|
+
JSValue exc = rejected ? JS_PromiseResult(ctx, result) : JS_GetException(ctx);
|
|
1137
|
+
const char* msg = JS_ToCString(ctx, exc);
|
|
1138
|
+
if (msg) {
|
|
1139
|
+
snprintf(err_buf, err_buf_size, "%s", msg);
|
|
1140
|
+
JS_FreeCString(ctx, msg);
|
|
1141
|
+
} else {
|
|
1142
|
+
/* Stringifying the exception itself threw (e.g. OOM) — drop
|
|
1143
|
+
* the secondary exception so it can't leak into later evals. */
|
|
1144
|
+
JSValue stray = JS_GetException(ctx);
|
|
1145
|
+
JS_FreeValue(ctx, stray);
|
|
1001
1146
|
}
|
|
1147
|
+
JS_FreeValue(ctx, exc);
|
|
1002
1148
|
}
|
|
1003
1149
|
JS_FreeValue(ctx, result);
|
|
1004
1150
|
return failed ? -EFAULT : 0;
|
package/src/modules.cpp
CHANGED
|
@@ -245,11 +245,11 @@ static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_n
|
|
|
245
245
|
/* mik__load_builtin returns NULL either because the module isn't in
|
|
246
246
|
* the table, or because deserialization failed (e.g. stack overflow
|
|
247
247
|
* from deeply nested .bjs loading). If deserialization failed, the
|
|
248
|
-
* real exception is already on ctx — don't replace it.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
* real exception is already on ctx — don't replace it. Probe with
|
|
249
|
+
* JS_HasException, NOT JS_GetException + JS_IsNull: quickjs-ng
|
|
250
|
+
* returns JS_UNINITIALIZED from an empty exception slot, and
|
|
251
|
+
* re-throwing that surfaced as a bare "[uninitialized]" error. */
|
|
252
|
+
if (!JS_HasException(ctx)) {
|
|
253
253
|
JS_ThrowReferenceError(ctx, "Builtin module '%s' is not available in this build",
|
|
254
254
|
module_name);
|
|
255
255
|
}
|
|
@@ -285,13 +285,8 @@ JSModuleDef* mik_module_loader(JSContext* ctx, const char* module_name, void* op
|
|
|
285
285
|
JSModuleDef* result = mik_module_loader_inner(ctx, module_name, opaque);
|
|
286
286
|
if (debug) {
|
|
287
287
|
if (result == nullptr) {
|
|
288
|
-
JSValue exc = JS_GetException(ctx);
|
|
289
|
-
bool has_exc = !JS_IsNull(exc) && !JS_IsUndefined(exc);
|
|
290
288
|
fprintf(stderr, "[mik-modules] FAIL: %s (exception=%s)\n", module_name,
|
|
291
|
-
|
|
292
|
-
/* Re-throw so the caller still sees the exception. */
|
|
293
|
-
if (has_exc) JS_Throw(ctx, exc);
|
|
294
|
-
else JS_FreeValue(ctx, exc);
|
|
289
|
+
JS_HasException(ctx) ? "set" : "NULL");
|
|
295
290
|
} else {
|
|
296
291
|
fprintf(stderr, "[mik-modules] ok: %s\n", module_name);
|
|
297
292
|
}
|
|
@@ -319,12 +314,8 @@ JSModuleDef* mik_module_loader(JSContext* ctx, const char* module_name, void* op
|
|
|
319
314
|
JSModuleDef* m = mik_module_loader_inner(ctx, module_name, opaque);
|
|
320
315
|
if (debug) {
|
|
321
316
|
if (m == nullptr) {
|
|
322
|
-
JSValue exc = JS_GetException(ctx);
|
|
323
|
-
bool has_exc = !JS_IsNull(exc) && !JS_IsUndefined(exc);
|
|
324
317
|
fprintf(stderr, "[mik-modules] FAIL: %s (exception=%s)\n", module_name,
|
|
325
|
-
|
|
326
|
-
if (has_exc) JS_Throw(ctx, exc);
|
|
327
|
-
else JS_FreeValue(ctx, exc);
|
|
318
|
+
JS_HasException(ctx) ? "set" : "NULL");
|
|
328
319
|
} else {
|
|
329
320
|
fprintf(stderr, "[mik-modules] ok: %s\n", module_name);
|
|
330
321
|
}
|
package/src/platform_posix.cpp
CHANGED
|
@@ -116,6 +116,10 @@ static const char* posix_get_device_id(void) {
|
|
|
116
116
|
return id;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
static const char* posix_get_reset_reason(void) {
|
|
120
|
+
return "unknown"; /* Desktop processes have no chip reset concept */
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
static void posix_log(int level, const char* tag, const char* fmt, ...) {
|
|
120
124
|
if (level < MIK_LOG_ERROR || level > MIK_LOG_VERBOSE) return;
|
|
121
125
|
fprintf(stderr, "[%s] %s: ", mik_log_level_name(level), tag);
|
|
@@ -147,6 +151,7 @@ static const MIKPlatform posix_platform = {
|
|
|
147
151
|
.stderr_write = posix_stderr_write,
|
|
148
152
|
.stdin_read = posix_stdin_read,
|
|
149
153
|
.get_device_id = posix_get_device_id,
|
|
154
|
+
.get_reset_reason = posix_get_reset_reason,
|
|
150
155
|
};
|
|
151
156
|
|
|
152
157
|
static const MIKPlatform* current_platform = &posix_platform;
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) Bjørge Næss
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|