@scriptc/runtime 0.0.9 → 0.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
package/src/scr_assert.c CHANGED
@@ -439,6 +439,11 @@ static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
439
439
  case SCR_DYN_PROMISE:
440
440
  /* Identity is the PROMISE (the strict_eq stance). */
441
441
  return a->v.promise == b->v.promise;
442
+ case SCR_DYN_JSVAL:
443
+ /* Identity is the ENGINE VALUE (the strict_eq stance): the engine's
444
+ * === — exact for SameValue too, since wrap-time scalar
445
+ * normalization leaves only reference kinds behind. */
446
+ return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
442
447
  default:
443
448
  return a == b; /* ARR/OBJ/BYTES: node identity */
444
449
  }
@@ -454,7 +459,16 @@ static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
454
459
  * memo here where Node carries one (documented divergence). */
455
460
  static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
456
461
  if (a == b) return true;
457
- if (a->kind != b->kind) return false;
462
+ if (a->kind != b->kind) {
463
+ /* A MIXED comparison with an island side (wrapped engine object vs
464
+ * DOM data): Node walks both structurally — a plain `false` would
465
+ * mint a fabricated AssertionError for values Node may call equal.
466
+ * Loud fence (the long-tail lane's structural walk). */
467
+ if (a->kind == SCR_DYN_JSVAL || b->kind == SCR_DYN_JSVAL) {
468
+ scr_dyn_isl_fence(a->kind == SCR_DYN_JSVAL ? a : b, "deepStrictEqual");
469
+ }
470
+ return false;
471
+ }
458
472
  switch (a->kind) {
459
473
  case SCR_DYN_UNDEF:
460
474
  case SCR_DYN_NULL:
@@ -493,6 +507,7 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
493
507
  return true;
494
508
  }
495
509
  case SCR_DYN_OBJ: {
510
+ if (a->null_proto != b->null_proto) return false; /* the prototype gate */
496
511
  if (a->v.obj.len != b->v.obj.len) return false;
497
512
  for (size_t i = 0; i < a->v.obj.len; i++) {
498
513
  const ScrDynEntry *ent = &a->v.obj.entries[i];
@@ -501,6 +516,14 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
501
516
  }
502
517
  return true;
503
518
  }
519
+ case SCR_DYN_JSVAL:
520
+ /* Node walks the engine objects structurally — a walk this runtime
521
+ * cannot run. Engine-identical answers true honestly; anything else
522
+ * throws the loud ladder (scr_assert_eq_dyn propagates the pending
523
+ * exception INSTEAD of minting an AssertionError). */
524
+ if (scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell)) return true;
525
+ scr_dyn_isl_fence(a, "deepStrictEqual");
526
+ return false;
504
527
  }
505
528
  return false; /* unreachable */
506
529
  }
@@ -653,15 +676,26 @@ static void scr_assert_cf_value(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
653
676
  ab_char(b, ']');
654
677
  return;
655
678
  }
679
+ case SCR_DYN_JSVAL: {
680
+ /* The depth-elided placeholder (the handle stance): Node renders
681
+ * the engine object's property dump — internals this walker does
682
+ * not model; the text only appears inside FAILURE reports, which
683
+ * already diverge in format. */
684
+ ab_cstr(b, "[island value]");
685
+ return;
686
+ }
656
687
  case SCR_DYN_OBJ: {
688
+ /* The null-proto dictionary renders with Node's prefix in the
689
+ * failure diff too (assertion_error.js inspects both sides). */
657
690
  if (d->v.obj.len == 0) {
658
- ab_cstr(b, "{}");
691
+ ab_cstr(b, d->null_proto ? "[Object: null prototype] {}" : "{}");
659
692
  return;
660
693
  }
661
694
  if (depth == 0) {
662
- ab_cstr(b, "[Object]");
695
+ ab_cstr(b, d->null_proto ? "[Object: null prototype]" : "[Object]");
663
696
  return;
664
697
  }
698
+ if (d->null_proto) ab_cstr(b, "[Object: null prototype] ");
665
699
  /* Render every entry, then sort the RENDERED texts (Node's
666
700
  * sorted:true sorts formatted entries, quotes included). */
667
701
  ScrCfEntry *ents = malloc(d->v.obj.len * sizeof *ents);
@@ -893,7 +927,8 @@ static bool scr_assert_print_myers(ScrAssertBuf *b, const ScrDiffOp *diff, size_
893
927
  /* Is this DOM kind `typeof == "object" && != null` to assertion_error.js? */
894
928
  static bool scr_assert_dyn_is_object(const ScrDyn *d) {
895
929
  return d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ || d->kind == SCR_DYN_BYTES ||
896
- d->kind == SCR_DYN_HANDLE || d->kind == SCR_DYN_PROMISE;
930
+ d->kind == SCR_DYN_HANDLE || d->kind == SCR_DYN_PROMISE ||
931
+ scr_dyn_isl_typeof_is(d, "object"); /* engine-held: the engine's typeof */
897
932
  }
898
933
 
899
934
  /* The failing EQUAL operators over dyn operands — createErrDiff. */
@@ -1065,6 +1100,10 @@ static void scr_assert_dyn_neq_fail(ScrDyn *a, bool deep, ScrStr *msg, bool has_
1065
1100
  void scr_assert_eq_dyn(ScrDyn *a, ScrDyn *b, bool negated, bool deep,
1066
1101
  ScrStr *msg, bool has_msg) {
1067
1102
  bool same = deep ? scr_assert_dyn_deep_eq(a, b) : scr_assert_dyn_same_value(a, b);
1103
+ /* The deep walk's JSVAL arm fences loudly (an island value it cannot
1104
+ * compare structurally): propagate THAT, never a fabricated
1105
+ * AssertionError over a comparison that did not run. */
1106
+ if (scr_exc_pending()) return;
1068
1107
  if ((negated && !same) || (!negated && same)) return;
1069
1108
  if (negated) {
1070
1109
  scr_assert_dyn_neq_fail(a, deep, msg, has_msg);
package/src/scr_bytes.c CHANGED
@@ -927,7 +927,7 @@ ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr) {
927
927
  * the '>= 0 && <= max' render, ERR_OUT_OF_RANGE's Received rules — and
928
928
  * copy's C++-side ladder; pinned by corpus 1663) ─────────────────────── */
929
929
 
930
- static size_t scr_bytes_received(double v, char out[48]); /* the numeric section below */
930
+ size_t scr_num_received(double v, char out[48]); /* the numeric section below */
931
931
 
932
932
  /* validateOffset(name, 0, max): non-integers are 'an integer' (Number.
933
933
  * isInteger — ±Infinity render 'an integer' too), the rest '>= 0 && <=
@@ -938,7 +938,7 @@ static size_t scr_bytes_received(double v, char out[48]); /* the numeric section
938
938
  bool scr_bytes_validate_off(const char *name, double value, double max) {
939
939
  if (isfinite(value) && floor(value) == value && value >= 0 && (max < 0 || value <= max)) return true;
940
940
  char recv[48];
941
- scr_bytes_received(value, recv);
941
+ scr_num_received(value, recv);
942
942
  char msg[160];
943
943
  int mlen;
944
944
  if (floor(value) != value || !isfinite(value)) {
@@ -1250,8 +1250,9 @@ ScrBytes *scr_bytes_concat(const ScrArr *list) {
1250
1250
  /* ERR_OUT_OF_RANGE's "Received" rendering: integers with |v| > 2^32 get
1251
1251
  * Node's addNumericalSeparator underscores applied to the plain String()
1252
1252
  * form — INCLUDING its quirks on exponent renderings ("1e_+21",
1253
- * "1e+_300"); everything else is the shortest-roundtrip number. */
1254
- static size_t scr_bytes_received(double v, char out[48]) {
1253
+ * "1e+_300"); everything else is the shortest-roundtrip number. Shared
1254
+ * with the fs/net option-ladder validators (scr_num_received). */
1255
+ size_t scr_num_received(double v, char out[48]) {
1255
1256
  char plain[32];
1256
1257
  size_t n = scr_f64_to_str(v, plain);
1257
1258
  if (!(isfinite(v) && trunc(v) == v && fabs(v) > 4294967296.0)) {
@@ -1281,7 +1282,7 @@ static size_t scr_bytes_received(double v, char out[48]) {
1281
1282
  * "byteLength" renders min 1. All catchable RangeErrors. */
1282
1283
  static void scr_bytes_bounds_error(double value, double length, const char *type) {
1283
1284
  char recv[48];
1284
- scr_bytes_received(value, recv);
1285
+ scr_num_received(value, recv);
1285
1286
  char msg[160];
1286
1287
  int mlen;
1287
1288
  if (floor(value) != value) {
@@ -1322,7 +1323,7 @@ static bool scr_bytes_check_int(const ScrBytes *b, double value, double offset,
1322
1323
  double min = sign ? -exp2((double)(8 * width - 1)) : 0;
1323
1324
  if (value > max || value < min) {
1324
1325
  char recv[48];
1325
- scr_bytes_received(value, recv);
1326
+ scr_num_received(value, recv);
1326
1327
  char msg[160];
1327
1328
  int mlen;
1328
1329
  if (width > 4) {
@@ -12,6 +12,9 @@
12
12
  #include <stdlib.h>
13
13
  #include <string.h>
14
14
  #include <time.h>
15
+ #ifdef __APPLE__
16
+ #include <sys/stat.h> /* lchmod(2) — the fs.lchmodSync ladder's real tail */
17
+ #endif
15
18
 
16
19
  static void scr_bytes_io_oom(void) {
17
20
  scr_trap("scriptc: out of memory\n");
@@ -254,3 +257,410 @@ double scr_fs_to_unix_timestamp(const ScrDyn *t) {
254
257
  scr_dyn_arg_type_fail("time", "an instance of Date or an Time in seconds", t);
255
258
  return 0;
256
259
  }
260
+
261
+ /* ── the fs argument-validation ladders (checked-dynamic lane) ─────────
262
+ * Each fs.*Chk libCall replicates its API's Node-order validation over
263
+ * DOM values and throws Node's exact typed errors; when every validation
264
+ * passes, the honest tail runs — the real operation where one exists
265
+ * (mkdtempSync, lchmodSync on macOS), the compiler-rendered SC2020 fence
266
+ * otherwise (scr_throw_lowering_fence). All arguments borrowed. */
267
+
268
+ static bool scr_fs_dyn_absent(const ScrDyn *v) {
269
+ return v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL;
270
+ }
271
+
272
+ static bool scr_fs_str_is(const ScrStr *s, const char *lit) {
273
+ size_t n = strlen(lit);
274
+ return s->len == n && memcmp(s->data, lit, n) == 0;
275
+ }
276
+
277
+ /* Node's maybeCallback/validateFunction over a callback slot. */
278
+ static bool scr_fs_cb_chk(const ScrDyn *cb, const char *name) {
279
+ if (cb->kind == SCR_DYN_FUNC) return true;
280
+ scr_dyn_arg_type_fail(name, "of type function", cb);
281
+ return false;
282
+ }
283
+
284
+ /* getValidatedPath: strings and Buffers pass (URL instances never reach
285
+ * these ladders — the DOM has no URL kind here, and Node would accept
286
+ * only file: URLs anyway). */
287
+ static bool scr_fs_path_chk(const ScrDyn *p, const char *name) {
288
+ if (p->kind == SCR_DYN_STR || p->kind == SCR_DYN_BYTES) return true;
289
+ scr_dyn_arg_type_fail(name, "of type string or an instance of Buffer or URL", p);
290
+ return false;
291
+ }
292
+
293
+ /* assertEncoding over an options slot (a bare encoding string or an
294
+ * options record's `encoding` member): Node throws ERR_INVALID_ARG_VALUE
295
+ * for any truthy value Buffer.isEncoding rejects. */
296
+ static bool scr_fs_encoding_chk(const ScrDyn *opts) {
297
+ const ScrDyn *enc = opts;
298
+ if (opts->kind == SCR_DYN_OBJ) {
299
+ enc = scr_dyn_obj_get(opts, "encoding", 8);
300
+ if (enc == NULL) return true;
301
+ }
302
+ if (scr_fs_dyn_absent(enc)) return true;
303
+ if (enc->kind == SCR_DYN_STR) {
304
+ if (enc->v.str->len == 0) return true; /* falsy: assertEncoding's `encoding &&` gate */
305
+ if (scr_bytes_is_encoding(enc->v.str)) return true;
306
+ }
307
+ if (enc->kind == SCR_DYN_BOOL && !enc->v.b) return true;
308
+ if (enc->kind == SCR_DYN_NUM && enc->v.num == 0) return true;
309
+ scr_dyn_arg_value_fail("encoding", "is invalid encoding", enc);
310
+ return false;
311
+ }
312
+
313
+ /* parseFileMode: integers 0..2^32-1 pass, octal strings parse, and the
314
+ * rest throw Node's exact ladder (validateUint32's wording). */
315
+ static bool scr_fs_mode_chk(const ScrDyn *m, const char *name) {
316
+ if (m->kind == SCR_DYN_STR) {
317
+ const ScrStr *s = m->v.str;
318
+ bool octal = s->len > 0;
319
+ for (size_t i = 0; octal && i < s->len; i++) {
320
+ if (s->data[i] < '0' || s->data[i] > '7') octal = false;
321
+ }
322
+ if (octal) return true;
323
+ scr_dyn_arg_value_fail(name, "must be a 32-bit unsigned integer or an octal string", m);
324
+ return false;
325
+ }
326
+ if (m->kind != SCR_DYN_NUM) {
327
+ scr_dyn_arg_type_fail(name, "of type number", m);
328
+ return false;
329
+ }
330
+ double v = m->v.num;
331
+ if (!(isfinite(v) && trunc(v) == v)) {
332
+ char recv[48], msg[160];
333
+ scr_num_received(v, recv);
334
+ int len = snprintf(msg, sizeof msg,
335
+ "The value of \"%s\" is out of range. It must be an integer. Received %s",
336
+ name, recv);
337
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
338
+ return false;
339
+ }
340
+ if (v < 0 || v > 4294967295.0) {
341
+ char recv[48], msg[160];
342
+ scr_num_received(v, recv);
343
+ int len = snprintf(msg, sizeof msg,
344
+ "The value of \"%s\" is out of range. It must be >= 0 && <= 4294967295. Received %s",
345
+ name, recv);
346
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
347
+ return false;
348
+ }
349
+ return true;
350
+ }
351
+
352
+ /* fs.exists(path, cb) — the REAL deprecated-API shape: the callback
353
+ * validates synchronously (Node's one throwing arm), and the answer
354
+ * arrives asynchronously through it — string/Buffer paths run the
355
+ * existsSync probe at fire time, every other path kind answers `false`
356
+ * (Node swallows getValidatedPath failures there). The loop-held timer
357
+ * matches Node's I/O-completion timing closely enough for the
358
+ * sequential CLI corpus. */
359
+ #ifndef SCR_LIB
360
+ static void scr_fs_exists_fire(ScrClosure *self) {
361
+ ScrDyn *path = scr_box_get_ref(self->caps[0]); /* +1 */
362
+ ScrDyn *cb = scr_box_get_ref(self->caps[1]); /* +1 */
363
+ bool ans = false;
364
+ if (path->kind == SCR_DYN_STR) {
365
+ ScrStr *p = scr_str_retain(path->v.str);
366
+ ans = scr_fs_exists(p);
367
+ scr_str_release(p);
368
+ } else if (path->kind == SCR_DYN_BYTES) {
369
+ ScrStr *p = scr_str_new((const char *)path->v.bytes->data, path->v.bytes->len);
370
+ ans = scr_fs_exists(p);
371
+ scr_str_release(p);
372
+ }
373
+ ScrDyn *arg = scr_dyn_new_bool(ans);
374
+ ScrDyn *r = scr_dyn_call(cb, &arg, 1, "the fs.exists callback");
375
+ scr_dyn_release(arg);
376
+ scr_dyn_release(r);
377
+ scr_dyn_release(path);
378
+ scr_dyn_release(cb);
379
+ }
380
+ #endif
381
+
382
+ ScrDyn *scr_fs_exists_async(const ScrDyn *path, const ScrDyn *cb) {
383
+ if (!scr_fs_cb_chk(cb, "cb")) return NULL;
384
+ if (path->kind != SCR_DYN_STR && path->kind != SCR_DYN_BYTES) {
385
+ /* Node's wart, kept exactly: a path getValidatedPath rejects answers
386
+ * false through the callback SYNCHRONOUSLY (`return callback(false)`
387
+ * in lib/fs.js exists). */
388
+ ScrDyn *arg = scr_dyn_new_bool(false);
389
+ ScrDyn *r = scr_dyn_call(cb, &arg, 1, "the fs.exists callback");
390
+ scr_dyn_release(arg);
391
+ scr_dyn_release(r);
392
+ if (scr_exc_pending()) return NULL;
393
+ return scr_dyn_retain(scr_dyn_undefined());
394
+ }
395
+ #ifdef SCR_LIB
396
+ /* Library links exclude the event-loop unit, and the compile-time scan
397
+ * refuses the fs.exists surface (SC4005's family) — this arm exists
398
+ * only so the TU links without scr_async. Unreachable by construction. */
399
+ scr_trap("scriptc: internal error: fs.exists async fire reached in a library build — please report this");
400
+ return NULL;
401
+ #else
402
+ ScrClosure *clo = scr_closure_new((void *)scr_fs_exists_fire, 2);
403
+ clo->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
404
+ scr_box_set_ref(clo->caps[0], scr_dyn_retain((ScrDyn *)path));
405
+ clo->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
406
+ scr_box_set_ref(clo->caps[1], scr_dyn_retain((ScrDyn *)cb));
407
+ scr_set_timeout(clo, 0);
408
+ return scr_dyn_retain(scr_dyn_undefined());
409
+ #endif
410
+ }
411
+
412
+ /* fs.mkdtemp(prefix, options?, cb): callback first (makeCallback), then
413
+ * the prefix (getValidatedPath's 'prefix' slot); the async op fences. */
414
+ void scr_fs_mkdtemp_chk(const ScrDyn *prefix, const ScrDyn *cb, const ScrStr *fence) {
415
+ if (!scr_fs_cb_chk(cb, "cb")) return;
416
+ if (!scr_fs_path_chk(prefix, "prefix")) return;
417
+ scr_throw_lowering_fence(fence);
418
+ }
419
+
420
+ /* fs.mkdtempSync(prefix, options?): prefix validates (Node's 'prefix'
421
+ * slot), the options walk accepts only shapes that leave utf8 semantics
422
+ * (absent/empty records, utf8 spellings — invalid encodings throw the
423
+ * assertEncoding ladder, other effectful options fence), then the REAL
424
+ * mkdtemp runs. +1 result or NULL with the exception pending. */
425
+ ScrStr *scr_fs_mkdtemp_sync_chk(const ScrDyn *prefix, const ScrDyn *opts, const ScrStr *fence) {
426
+ if (!scr_fs_path_chk(prefix, "prefix")) return NULL;
427
+ if (!scr_fs_encoding_chk(opts)) return NULL;
428
+ bool utf8 = true;
429
+ if (opts->kind == SCR_DYN_OBJ) {
430
+ for (size_t i = 0; i < opts->v.obj.len; i++) {
431
+ const ScrDynEntry *e = &opts->v.obj.entries[i];
432
+ if (scr_fs_dyn_absent(e->value)) continue;
433
+ if (strcmp(e->key, "encoding") == 0) {
434
+ const ScrDyn *enc = e->value;
435
+ if (!(enc->kind == SCR_DYN_STR &&
436
+ (scr_fs_str_is(enc->v.str, "utf8") || scr_fs_str_is(enc->v.str, "utf-8")))) {
437
+ utf8 = false;
438
+ }
439
+ continue;
440
+ }
441
+ utf8 = false; /* an unmodeled effectful option */
442
+ }
443
+ } else if (opts->kind == SCR_DYN_STR) {
444
+ utf8 = scr_fs_str_is(opts->v.str, "utf8") || scr_fs_str_is(opts->v.str, "utf-8");
445
+ } else if (!scr_fs_dyn_absent(opts)) {
446
+ utf8 = false;
447
+ }
448
+ if (!utf8 || prefix->kind != SCR_DYN_STR) {
449
+ scr_throw_lowering_fence(fence);
450
+ return NULL;
451
+ }
452
+ ScrStr *p = scr_str_retain(prefix->v.str);
453
+ ScrStr *r = scr_fs_mkdtemp(p);
454
+ scr_str_release(p);
455
+ return r;
456
+ }
457
+
458
+ /* fs.readFile(path, options?, cb): Node's order — the callback
459
+ * (maybeCallback), the options walk's assertEncoding, then the path;
460
+ * the async read itself fences. */
461
+ void scr_fs_read_file_chk(const ScrDyn *path, const ScrDyn *opts, const ScrDyn *cb,
462
+ const ScrStr *fence) {
463
+ if (!scr_fs_cb_chk(cb, "cb")) return;
464
+ if (!scr_fs_encoding_chk(opts)) return;
465
+ if (!scr_fs_path_chk(path, "path")) return;
466
+ scr_throw_lowering_fence(fence);
467
+ }
468
+
469
+ /* fs.opendirSync(path, options?): getValidatedPath, then getOptions'
470
+ * assertEncoding; the Dir machinery fences. */
471
+ void scr_fs_opendir_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence) {
472
+ if (!scr_fs_path_chk(path, "path")) return;
473
+ if (!scr_fs_encoding_chk(opts)) return;
474
+ scr_throw_lowering_fence(fence);
475
+ }
476
+
477
+ /* fs.watchFile(path, options?, listener): the path first, the listener's
478
+ * function check second (Node's watchFile order); real watching fences. */
479
+ void scr_fs_watch_file_chk(const ScrDyn *path, const ScrDyn *listener, const ScrStr *fence) {
480
+ if (!scr_fs_path_chk(path, "path")) return;
481
+ if (listener->kind != SCR_DYN_FUNC) {
482
+ scr_dyn_arg_type_fail("listener", "of type function", listener);
483
+ return;
484
+ }
485
+ scr_throw_lowering_fence(fence);
486
+ }
487
+
488
+ /* fs.lchmod / lchmodSync / fs.promises.lchmod — macOS-only in Node (the
489
+ * callback/sync pair is not even exported elsewhere, so non-APPLE builds
490
+ * answer the not-a-function TypeError; the promise form rejects
491
+ * ERR_METHOD_NOT_IMPLEMENTED, Node's own linux shape). On macOS the
492
+ * validation ladder runs in Node's order and the real lchmod(2) applies
493
+ * where an operation survives it. */
494
+ static bool scr_fs_lchmod_defined(const char *api) {
495
+ #ifdef __APPLE__
496
+ (void)api;
497
+ return true;
498
+ #else
499
+ char msg[64];
500
+ int len = snprintf(msg, sizeof msg, "%s is not a function", api);
501
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
502
+ return false;
503
+ #endif
504
+ }
505
+
506
+ void scr_fs_lchmod_chk(const ScrDyn *path, const ScrDyn *mode, const ScrDyn *cb,
507
+ const ScrStr *fence) {
508
+ if (!scr_fs_lchmod_defined("fs.lchmod")) return;
509
+ if (!scr_fs_cb_chk(cb, "cb")) return;
510
+ if (!scr_fs_path_chk(path, "path")) return;
511
+ if (!scr_fs_mode_chk(mode, "mode")) return;
512
+ scr_throw_lowering_fence(fence); /* the async op + callback dispatch */
513
+ }
514
+
515
+ #ifdef __APPLE__
516
+ static void scr_fs_lchmod_apply(const ScrDyn *path, const ScrDyn *mode) {
517
+ ScrStr *p = path->kind == SCR_DYN_STR ? scr_str_retain(path->v.str)
518
+ : scr_str_new((const char *)path->v.bytes->data, path->v.bytes->len);
519
+ double m = mode->kind == SCR_DYN_NUM ? mode->v.num : (double)strtol(mode->v.str->data, NULL, 8);
520
+ if (lchmod(p->data, (mode_t)m) != 0) scr_fs_throw(errno, "lchmod", p);
521
+ scr_str_release(p);
522
+ }
523
+ #endif
524
+
525
+ /* Answers the DOM undefined (+1) on success — lchmodSync's JS value, so
526
+ * return-position uses lower; NULL with the exception pending. */
527
+ ScrDyn *scr_fs_lchmod_sync_chk(const ScrDyn *path, const ScrDyn *mode) {
528
+ if (!scr_fs_lchmod_defined("fs.lchmodSync")) return NULL;
529
+ if (!scr_fs_path_chk(path, "path")) return NULL;
530
+ if (!scr_fs_mode_chk(mode, "mode")) return NULL;
531
+ #ifdef __APPLE__
532
+ scr_fs_lchmod_apply(path, mode);
533
+ if (scr_exc_pending()) return NULL;
534
+ #endif
535
+ return scr_dyn_retain(scr_dyn_undefined());
536
+ }
537
+
538
+ ScrPromise *scr_fsp_lchmod_chk(const ScrDyn *path, const ScrDyn *mode) {
539
+ #ifndef __APPLE__
540
+ (void)path;
541
+ (void)mode;
542
+ static const char ni[] = "The lchmod() method is not implemented";
543
+ scr_throw_error_msg_code(SCR_ERR_ERROR, ni, sizeof ni - 1, "ERR_METHOD_NOT_IMPLEMENTED");
544
+ return scr_promise_settled_void();
545
+ #else
546
+ if (scr_fs_path_chk(path, "path") && scr_fs_mode_chk(mode, "mode")) {
547
+ scr_fs_lchmod_apply(path, mode);
548
+ }
549
+ return scr_promise_settled_void();
550
+ #endif
551
+ }
552
+
553
+ /* fs.read(fd, buffer, offset, length, position, cb) — the full argument
554
+ * ladder in Node's order (buffer, fd, offset, length, position); the
555
+ * async read fences. Bounds follow lib/fs.js read(): offset within the
556
+ * buffer, length within buffer - offset. */
557
+ static bool scr_fs_int_range_chk(const ScrDyn *v, const char *name, double min, double max,
558
+ const char *range) {
559
+ if (v->kind != SCR_DYN_NUM) {
560
+ scr_dyn_arg_type_fail(name, "of type number", v);
561
+ return false;
562
+ }
563
+ double n = v->v.num;
564
+ char recv[48], msg[192];
565
+ if (!(isfinite(n) && trunc(n) == n)) {
566
+ scr_num_received(n, recv);
567
+ int len = snprintf(msg, sizeof msg,
568
+ "The value of \"%s\" is out of range. It must be an integer. Received %s",
569
+ name, recv);
570
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
571
+ return false;
572
+ }
573
+ if (n < min || n > max) {
574
+ scr_num_received(n, recv);
575
+ int len = snprintf(msg, sizeof msg,
576
+ "The value of \"%s\" is out of range. It must be %s. Received %s",
577
+ name, range, recv);
578
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
579
+ return false;
580
+ }
581
+ return true;
582
+ }
583
+
584
+ void scr_fs_read_chk(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset,
585
+ const ScrDyn *length, const ScrDyn *position, const ScrStr *fence) {
586
+ if (buffer->kind != SCR_DYN_BYTES) {
587
+ scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer);
588
+ return;
589
+ }
590
+ if (fd->kind != SCR_DYN_NUM) {
591
+ scr_dyn_arg_type_fail("fd", "of type number", fd);
592
+ return;
593
+ }
594
+ double buflen = (double)buffer->v.bytes->len;
595
+ if (!scr_fs_dyn_absent(offset)) {
596
+ /* validateInteger's MAX_SAFE range first, the buffer bound second —
597
+ * Node renders each with its own max. */
598
+ if (!scr_fs_int_range_chk(offset, "offset", 0, 9007199254740991.0,
599
+ ">= 0 && <= 9007199254740991")) {
600
+ return;
601
+ }
602
+ if (offset->v.num > buflen) {
603
+ char range[64];
604
+ snprintf(range, sizeof range, ">= 0 && <= %.0f", buflen);
605
+ if (!scr_fs_int_range_chk(offset, "offset", 0, buflen, range)) return;
606
+ }
607
+ }
608
+ double off = offset->kind == SCR_DYN_NUM ? offset->v.num : 0;
609
+ if (!scr_fs_dyn_absent(length)) {
610
+ if (length->kind != SCR_DYN_NUM || !(isfinite(length->v.num) && trunc(length->v.num) == length->v.num) ||
611
+ length->v.num < 0) {
612
+ /* Node renders the bare ">= 0" form here (checkPosition's cousin). */
613
+ if (length->kind == SCR_DYN_NUM) {
614
+ char recv[48], msg[160];
615
+ scr_num_received(length->v.num, recv);
616
+ const char *shape = (isfinite(length->v.num) && trunc(length->v.num) == length->v.num)
617
+ ? "It must be >= 0."
618
+ : "It must be an integer.";
619
+ int len = snprintf(msg, sizeof msg,
620
+ "The value of \"length\" is out of range. %s Received %s", shape, recv);
621
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
622
+ return;
623
+ }
624
+ scr_dyn_arg_type_fail("length", "of type number", length);
625
+ return;
626
+ }
627
+ if (length->v.num > buflen - off) {
628
+ char recv[48], msg[160];
629
+ scr_num_received(length->v.num, recv);
630
+ int len = snprintf(msg, sizeof msg,
631
+ "The value of \"length\" is out of range. It must be <= %.0f. Received %s",
632
+ buflen - off, recv);
633
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
634
+ return;
635
+ }
636
+ }
637
+ if (!scr_fs_dyn_absent(position)) {
638
+ if (position->kind != SCR_DYN_NUM) {
639
+ scr_dyn_arg_type_fail("position", "of type bigint or integer", position);
640
+ return;
641
+ }
642
+ if (!scr_fs_int_range_chk(position, "position", -1, 9007199254740991.0,
643
+ ">= -1 && <= 9007199254740991")) {
644
+ return;
645
+ }
646
+ }
647
+ scr_throw_lowering_fence(fence);
648
+ }
649
+
650
+ /* createReadStream/createWriteStream(path, options?): getOptions'
651
+ * assertEncoding, the fd member's FileHandle-or-integer contract when
652
+ * present, the path contract otherwise; the stream machinery fences. */
653
+ void scr_fs_stream_opts_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence) {
654
+ if (!scr_fs_encoding_chk(opts)) return;
655
+ const ScrDyn *fd = opts->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(opts, "fd", 2) : NULL;
656
+ if (fd != NULL && !scr_fs_dyn_absent(fd)) {
657
+ if (fd->kind != SCR_DYN_NUM) {
658
+ scr_dyn_prop_type_fail("options.fd", "of type number or an instance of FileHandle", fd);
659
+ return;
660
+ }
661
+ if (!scr_fs_int_range_chk(fd, "fd", 0, 2147483647.0, ">= 0 && <= 2147483647")) return;
662
+ } else if (!scr_fs_path_chk(path, "path")) {
663
+ return;
664
+ }
665
+ scr_throw_lowering_fence(fence);
666
+ }
package/src/scr_dc.c CHANGED
@@ -132,6 +132,12 @@ static void dc_throw_bad_fn_arg(const ScrDyn *cb, const char *arg_name) {
132
132
  (int)(cb->v.str->len < 28 ? cb->v.str->len : 28), cb->v.str->data);
133
133
  received = detail;
134
134
  break;
135
+ case SCR_DYN_JSVAL:
136
+ /* An engine callback may well BE a function — subscribing island
137
+ * values is unarmed (lane dyn-routing-ops); fence loudly rather
138
+ * than claim "Received an instance of Object". */
139
+ scr_dyn_isl_fence(cb, "subscribing");
140
+ return;
135
141
  default: break;
136
142
  }
137
143
  char buf[160];