@scriptc/cli-darwin-x64 0.2.3 → 0.2.5

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.
Files changed (141) hide show
  1. package/dist/THIRD_PARTY_NOTICES +1 -1
  2. package/dist/bin/scriptc +0 -0
  3. package/dist/bin/scriptc.json +1 -1
  4. package/dist/lib/declarations/scriptc-node-fallback.d.ts +915 -215
  5. package/dist/lib/declarations/scriptc-overrides.d.ts +5 -1
  6. package/dist/lib/declarations/scriptc.d.ts +22 -2
  7. package/dist/lib/llvm/bin/scriptc-llvm-codegen +0 -0
  8. package/dist/lib/llvm/package.json +1 -1
  9. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_array.o +0 -0
  10. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bigint.o +0 -0
  11. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bytes.o +0 -0
  12. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bytes_io.o +0 -0
  13. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_copying.o +0 -0
  14. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_cycle.o +0 -0
  15. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_json.o +0 -0
  16. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_lib.o +0 -0
  17. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_map.o +0 -0
  18. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_number.o +0 -0
  19. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_regex.o +0 -0
  20. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_string.o +0 -0
  21. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_util_compare.o +0 -0
  22. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_array.o +0 -0
  23. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bigint.o +0 -0
  24. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bytes.o +0 -0
  25. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bytes_io.o +0 -0
  26. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_copying.o +0 -0
  27. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_cycle.o +0 -0
  28. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_json.o +0 -0
  29. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_lib.o +0 -0
  30. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_map.o +0 -0
  31. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_number.o +0 -0
  32. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_regex.o +0 -0
  33. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_string.o +0 -0
  34. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_util_compare.o +0 -0
  35. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  36. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  37. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_array.o +0 -0
  38. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bigint.o +0 -0
  39. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bytes.o +0 -0
  40. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bytes_io.o +0 -0
  41. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_copying.o +0 -0
  42. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_cycle.o +0 -0
  43. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_json.o +0 -0
  44. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_lib.o +0 -0
  45. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_map.o +0 -0
  46. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_number.o +0 -0
  47. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_regex.o +0 -0
  48. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_string.o +0 -0
  49. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  50. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_array.o +0 -0
  51. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bigint.o +0 -0
  52. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bytes.o +0 -0
  53. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bytes_io.o +0 -0
  54. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_copying.o +0 -0
  55. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_cycle.o +0 -0
  56. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_json.o +0 -0
  57. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_lib.o +0 -0
  58. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_map.o +0 -0
  59. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_number.o +0 -0
  60. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_regex.o +0 -0
  61. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_string.o +0 -0
  62. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  63. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_array.o +0 -0
  64. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bigint.o +0 -0
  65. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bytes.o +0 -0
  66. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bytes_io.o +0 -0
  67. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_copying.o +0 -0
  68. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_cycle.o +0 -0
  69. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_json.o +0 -0
  70. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_lib.o +0 -0
  71. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_map.o +0 -0
  72. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_number.o +0 -0
  73. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_regex.o +0 -0
  74. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_string.o +0 -0
  75. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  76. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_array.o +0 -0
  77. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bigint.o +0 -0
  78. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bytes.o +0 -0
  79. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bytes_io.o +0 -0
  80. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_copying.o +0 -0
  81. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_cycle.o +0 -0
  82. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_json.o +0 -0
  83. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_lib.o +0 -0
  84. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_map.o +0 -0
  85. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_number.o +0 -0
  86. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_regex.o +0 -0
  87. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_string.o +0 -0
  88. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  89. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_array.o +0 -0
  90. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bigint.o +0 -0
  91. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bytes.o +0 -0
  92. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bytes_io.o +0 -0
  93. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_copying.o +0 -0
  94. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_cycle.o +0 -0
  95. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_json.o +0 -0
  96. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_lib.o +0 -0
  97. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_map.o +0 -0
  98. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_number.o +0 -0
  99. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_regex.o +0 -0
  100. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_string.o +0 -0
  101. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_util_compare.o +0 -0
  102. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_array.o +0 -0
  103. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bigint.o +0 -0
  104. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bytes.o +0 -0
  105. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bytes_io.o +0 -0
  106. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_copying.o +0 -0
  107. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_cycle.o +0 -0
  108. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_json.o +0 -0
  109. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_lib.o +0 -0
  110. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_map.o +0 -0
  111. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_number.o +0 -0
  112. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_regex.o +0 -0
  113. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_string.o +0 -0
  114. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_util_compare.o +0 -0
  115. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  116. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  117. package/dist/lib/runtime-darwin-x64/package.json +1 -1
  118. package/dist/lib/runtime-darwin-x64/runtime-pack.json +215 -215
  119. package/dist/lib/runtime-sources/package.json +1 -1
  120. package/dist/lib/runtime-sources/src/scr_array.c +317 -40
  121. package/dist/lib/runtime-sources/src/scr_async.test.ts +16 -6
  122. package/dist/lib/runtime-sources/src/scr_bigint.c +15 -0
  123. package/dist/lib/runtime-sources/src/scr_bytes.c +437 -216
  124. package/dist/lib/runtime-sources/src/scr_bytes.test.c +85 -0
  125. package/dist/lib/runtime-sources/src/scr_bytes_io.c +0 -34
  126. package/dist/lib/runtime-sources/src/scr_copying.c +12 -2
  127. package/dist/lib/runtime-sources/src/scr_cycle.c +84 -60
  128. package/dist/lib/runtime-sources/src/scr_json.c +494 -161
  129. package/dist/lib/runtime-sources/src/scr_json.test.c +323 -1
  130. package/dist/lib/runtime-sources/src/scr_json.test.ts +58 -25
  131. package/dist/lib/runtime-sources/src/scr_key.h +68 -0
  132. package/dist/lib/runtime-sources/src/scr_lib.c +177 -89
  133. package/dist/lib/runtime-sources/src/scr_map.c +268 -96
  134. package/dist/lib/runtime-sources/src/scr_number.c +2 -4
  135. package/dist/lib/runtime-sources/src/scr_numeric.h +25 -0
  136. package/dist/lib/runtime-sources/src/scr_numeric.test.c +37 -0
  137. package/dist/lib/runtime-sources/src/scr_numeric.test.ts +33 -0
  138. package/dist/lib/runtime-sources/src/scr_regex.c +255 -145
  139. package/dist/lib/runtime-sources/src/scr_runtime.h +76 -19
  140. package/dist/lib/runtime-sources/src/scr_string.c +184 -42
  141. package/package.json +1 -1
@@ -22,6 +22,7 @@
22
22
  * serializers (and the error messages here).
23
23
  */
24
24
  #include "scr_runtime.h"
25
+ #include "scr_key.h"
25
26
 
26
27
  static SCR_TL ScrDyn *scr_builtin_object_prototype;
27
28
 
@@ -159,7 +160,9 @@ void scr_jb_init(ScrJsonBuf *b) {
159
160
  }
160
161
 
161
162
  static void scr_jb_grow(ScrJsonBuf *b, size_t need) {
162
- if (b->len + need <= b->cap) return;
163
+ if (need > SIZE_MAX - sizeof(ScrStr) - 1 - b->len) scr_json_oom();
164
+ size_t required = b->len + need;
165
+ if (required <= b->cap) return;
163
166
  if (!b->data) { /* first allocation: len == 0 */
164
167
  size_t cap = scr_jb_hint >= need ? scr_jb_hint : need;
165
168
  ScrStr *s = scr_str_alloc_raw(0, cap);
@@ -168,7 +171,13 @@ static void scr_jb_grow(ScrJsonBuf *b, size_t need) {
168
171
  return;
169
172
  }
170
173
  size_t cap = b->cap;
171
- while (cap < b->len + need) cap *= 2;
174
+ while (cap < required) {
175
+ if (cap > (SIZE_MAX - sizeof(ScrStr) - 1) / 2) {
176
+ cap = required;
177
+ break;
178
+ }
179
+ cap *= 2;
180
+ }
172
181
  ScrStr *s = scr_str_regrow(SCR_JB_STR(b), cap);
173
182
  b->data = s->data;
174
183
  b->cap = cap;
@@ -186,7 +195,8 @@ void scr_jb_putc(ScrJsonBuf *b, char c) {
186
195
  b->data[b->len++] = c;
187
196
  }
188
197
 
189
- static void scr_jb_write(ScrJsonBuf *b, const char *s, size_t n) {
198
+ void scr_jb_write(ScrJsonBuf *b, const char *s, size_t n) {
199
+ if (n == 0) return;
190
200
  scr_jb_grow(b, n);
191
201
  memcpy(b->data + b->len, s, n);
192
202
  b->len += n;
@@ -215,18 +225,45 @@ void scr_jb_put_f64(ScrJsonBuf *b, double v) {
215
225
  scr_jb_write(b, buf, n);
216
226
  }
217
227
 
218
- void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s) {
228
+ /* Find the next quote, backslash or control byte. memcpy keeps the word
229
+ * loads unaligned-safe, and the length guard never reads past the input.
230
+ * The masks only decide whether to inspect a word byte by byte: borrow
231
+ * propagation may mark extra lanes, so it must not determine a byte index.
232
+ * High UTF-8 bytes are ordinary data, independent of host byte order. */
233
+ static size_t scr_json_plain_bytes(const char *data, size_t len) {
234
+ const uint64_t ones = UINT64_C(0x0101010101010101);
235
+ const uint64_t high = UINT64_C(0x8080808080808080);
236
+ size_t i = 0;
237
+ while (len - i >= sizeof(uint64_t)) {
238
+ uint64_t word;
239
+ memcpy(&word, data + i, sizeof word);
240
+ uint64_t quotes = word ^ (ones * '"');
241
+ uint64_t slashes = word ^ (ones * '\\');
242
+ uint64_t special = ((quotes - ones) & ~quotes) |
243
+ ((slashes - ones) & ~slashes) |
244
+ ((word - ones * 0x20) & ~word);
245
+ if (special & high) break;
246
+ i += sizeof word;
247
+ }
248
+ while (i < len) {
249
+ unsigned char c = (unsigned char)data[i];
250
+ if (c < 0x20 || c == '"' || c == '\\') break;
251
+ i++;
252
+ }
253
+ return i;
254
+ }
255
+
256
+ static void scr_jb_put_json_span(ScrJsonBuf *b, const char *data, size_t len) {
219
257
  scr_jb_putc(b, '"');
220
258
  /* Bulk-copy runs of unescaped bytes (UTF-8 passes through verbatim,
221
259
  * like JS); escapes interrupt the run. */
222
- size_t i = 0, run = 0;
223
- while (i + run < s->len) {
224
- unsigned char c = (unsigned char)s->data[i + run];
225
- if (c != '"' && c != '\\' && c >= 0x20) {
226
- run++;
227
- continue;
228
- }
229
- scr_jb_write(b, s->data + i, run);
260
+ size_t i = 0;
261
+ while (i < len) {
262
+ size_t run = scr_json_plain_bytes(data + i, len - i);
263
+ scr_jb_write(b, data + i, run);
264
+ i += run;
265
+ if (i == len) break;
266
+ unsigned char c = (unsigned char)data[i++];
230
267
  switch (c) {
231
268
  case '"': scr_jb_puts(b, "\\\""); break;
232
269
  case '\\': scr_jb_puts(b, "\\\\"); break;
@@ -241,13 +278,14 @@ void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s) {
241
278
  scr_jb_puts(b, esc);
242
279
  }
243
280
  }
244
- i += run + 1;
245
- run = 0;
246
281
  }
247
- scr_jb_write(b, s->data + i, run);
248
282
  scr_jb_putc(b, '"');
249
283
  }
250
284
 
285
+ void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s) {
286
+ scr_jb_put_json_span(b, s->data, s->len);
287
+ }
288
+
251
289
  ScrStr *scr_jb_finish(ScrJsonBuf *b) {
252
290
  free(b->seen);
253
291
  b->seen = NULL;
@@ -413,11 +451,110 @@ void scr_dyn_from_leave(void) {
413
451
  * strict alloc/free balance.
414
452
  */
415
453
  static SCR_TL ScrDyn *scr_builtin_array_prototype;
454
+
455
+ /* Property names are immutable and often repeated across simultaneously live
456
+ * objects. Entries own references to their bytes; this fixed weak table owns
457
+ * none. Collisions only reduce sharing, and the final release removes a weak
458
+ * entry before freeing storage. Long keys bypass lookup entirely. */
459
+ #define SCR_DYN_KEY_SHARE_SLOTS 256
460
+ #define SCR_DYN_KEY_SHARE_LENGTH 64
461
+ typedef struct {
462
+ size_t rc;
463
+ char bytes[];
464
+ } ScrDynKey;
465
+ typedef struct {
466
+ ScrDynKey *key;
467
+ size_t length;
468
+ } ScrDynKeySlot;
469
+ static SCR_TL ScrDynKeySlot scr_dyn_keys[SCR_DYN_KEY_SHARE_SLOTS];
470
+ #ifdef SCR_RC_AUDIT
471
+ static SCR_TL long scr_live_dyn_keys;
472
+ long scr_dyn_key_live_count(void) { return scr_live_dyn_keys; }
473
+ #endif
474
+
475
+ static size_t scr_dyn_key_slot(const char *key, size_t length) {
476
+ size_t hash = length;
477
+ for (size_t i = 0; i < length; i++) hash = hash * 31 + (unsigned char)key[i];
478
+ return hash % SCR_DYN_KEY_SHARE_SLOTS;
479
+ }
480
+
481
+ static char *scr_dyn_key_copy(const char *key, size_t length) {
482
+ ScrDynKeySlot *slot = length <= SCR_DYN_KEY_SHARE_LENGTH
483
+ ? &scr_dyn_keys[scr_dyn_key_slot(key, length)] : NULL;
484
+ if (slot && slot->key && slot->length == length &&
485
+ !memcmp(slot->key->bytes, key, length)) {
486
+ slot->key->rc++;
487
+ return slot->key->bytes;
488
+ }
489
+ if (length > SIZE_MAX - sizeof(ScrDynKey) - 1) scr_json_oom();
490
+ ScrDynKey *owned = malloc(sizeof *owned + length + 1);
491
+ if (!owned) scr_json_oom();
492
+ #ifdef SCR_RC_AUDIT
493
+ scr_live_dyn_keys++;
494
+ #endif
495
+ owned->rc = 1;
496
+ memcpy(owned->bytes, key, length);
497
+ owned->bytes[length] = '\0';
498
+ if (slot) *slot = (ScrDynKeySlot){owned, length};
499
+ return owned->bytes;
500
+ }
501
+
502
+ static void scr_dyn_key_release(char *key, size_t length) {
503
+ if (!key) return;
504
+ ScrDynKey *owned = (ScrDynKey *)(key - offsetof(ScrDynKey, bytes));
505
+ if (--owned->rc) return;
506
+ if (length <= SCR_DYN_KEY_SHARE_LENGTH) {
507
+ ScrDynKeySlot *slot = &scr_dyn_keys[scr_dyn_key_slot(key, length)];
508
+ if (slot->key == owned) slot->key = NULL;
509
+ }
510
+ #ifdef SCR_RC_AUDIT
511
+ scr_live_dyn_keys--;
512
+ #endif
513
+ free(owned);
514
+ }
515
+
416
516
  #ifndef SCR_RC_AUDIT
417
517
  static SCR_TL ScrDyn *scr_dyn_free_arr, *scr_dyn_free_obj, *scr_dyn_free_misc;
418
518
  static SCR_TL size_t scr_dyn_free_count;
419
519
  #define SCR_DYN_FREE_MAX 8192
520
+ /* Recycled small objects may retain key bytes, but never values or an
521
+ * unbounded document's keys. This budget applies across the whole pool. */
522
+ static SCR_TL size_t scr_dyn_cached_key_bytes;
523
+ #define SCR_DYN_KEY_CACHE_MAX ((size_t)64 * 1024)
524
+ #define SCR_DYN_KEY_CACHE_SLOTS 16
525
+ #define SCR_DYN_KEY_CACHE_LENGTH 64
526
+ #endif
527
+
528
+ static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) {
529
+ for (size_t i = 0; i < d->v.obj.cap; i++) {
530
+ ScrDynEntry *entry = &d->v.obj.entries[i];
531
+ if (!entry->key) continue;
532
+ #ifndef SCR_RC_AUDIT
533
+ if (cache && d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS &&
534
+ entry->key_len <= SCR_DYN_KEY_CACHE_LENGTH &&
535
+ sizeof(ScrDynKey) + entry->key_len + 1 <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) {
536
+ scr_dyn_cached_key_bytes += sizeof(ScrDynKey) + entry->key_len + 1;
537
+ continue;
538
+ }
539
+ #else
540
+ (void)cache;
420
541
  #endif
542
+ scr_dyn_key_release(entry->key, entry->key_len);
543
+ entry->key = NULL;
544
+ entry->key_len = 0;
545
+ }
546
+ }
547
+
548
+ /* Primitive kinds cannot acquire properties or change kind while live, so
549
+ * an object property edge to one cannot participate in a cycle. Keep its RC edge
550
+ * out of trial deletion and release it in collected teardown instead. Other
551
+ * owners may still trace the same primitive through the uniform header ABI;
552
+ * our untraced reference keeps it alive until that teardown releases it.
553
+ * Entry metadata snapshots this decision during trace, before any white node
554
+ * can be freed. Teardown must never inspect the child's kind again. */
555
+ static bool scr_dyn_member_is_traced(const ScrDyn *value) {
556
+ return value && value->kind > SCR_DYN_STR && value->kind != SCR_DYN_UNDEF;
557
+ }
421
558
 
422
559
  /* Checked objects and functions can close over one another. All heap dyn
423
560
  * nodes carry a collector header so a captured unknown value has one uniform
@@ -425,19 +562,21 @@ static SCR_TL size_t scr_dyn_free_count;
425
562
  * capsules keep their conservative ownership edges. */
426
563
  void scr_dyn_trace_v(void *ptr, ScrTraceVisit visit, void *ctx) {
427
564
  ScrDyn *d = ptr;
428
- visit(d->prototype, ctx);
429
- visit(d->symbol_properties, ctx);
430
- visit(d->symbol_keys, ctx);
565
+ if (d->prototype) visit(d->prototype, ctx);
566
+ if (d->symbol_properties) visit(d->symbol_properties, ctx);
567
+ if (d->symbol_keys) visit(d->symbol_keys, ctx);
431
568
  switch (d->kind) {
432
569
  case SCR_DYN_ARR:
433
570
  for (size_t i = 0; i < d->v.arr.len; i++) visit(d->v.arr.items[i], ctx);
434
- visit(d->v.arr.properties, ctx);
571
+ if (d->v.arr.properties) visit(d->v.arr.properties, ctx);
435
572
  break;
436
573
  case SCR_DYN_OBJ:
437
574
  for (size_t i = 0; i < d->v.obj.len; i++) {
438
- visit(d->v.obj.entries[i].value, ctx);
439
- visit(d->v.obj.entries[i].getter, ctx);
440
- visit(d->v.obj.entries[i].setter, ctx);
575
+ ScrDynEntry *entry = &d->v.obj.entries[i];
576
+ entry->value_traced = scr_dyn_member_is_traced(entry->value);
577
+ if (entry->value_traced) visit(entry->value, ctx);
578
+ if (entry->getter) visit(entry->getter, ctx);
579
+ if (entry->setter) visit(entry->setter, ctx);
441
580
  }
442
581
  break;
443
582
  case SCR_DYN_FUNC: visit(d->v.fn.clo, ctx); break;
@@ -539,6 +678,12 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
539
678
  d->v.arr.presence = NULL;
540
679
  } else if (kind == SCR_DYN_OBJ) {
541
680
  d->v.obj.len = 0; /* cap/entries preserved */
681
+ if (d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS) {
682
+ for (size_t i = 0; i < d->v.obj.cap; i++) {
683
+ ScrDynEntry *entry = &d->v.obj.entries[i];
684
+ if (entry->key) scr_dyn_cached_key_bytes -= sizeof(ScrDynKey) + entry->key_len + 1;
685
+ }
686
+ }
542
687
  d->v.obj.source_identity = NULL;
543
688
  d->v.obj.source_access = NULL;
544
689
  } else {
@@ -565,6 +710,10 @@ static void scr_dyn_handle_release(void *h, ScrDynHandleTag tag);
565
710
  static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d);
566
711
 
567
712
  static void scr_dyn_dispose(ScrDyn *d, bool collected) {
713
+ if (d->kind == SCR_DYN_OBJ) {
714
+ free(d->v.obj.index);
715
+ d->v.obj.index = NULL;
716
+ }
568
717
  if (!collected) scr_dyn_release(d->prototype);
569
718
  if (!collected) scr_dyn_release(d->symbol_properties);
570
719
  if (!collected) scr_dyn_release(d->symbol_keys);
@@ -592,12 +741,15 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
592
741
  break;
593
742
  case SCR_DYN_OBJ:
594
743
  for (size_t i = 0; i < d->v.obj.len; i++) {
595
- free(d->v.obj.entries[i].key);
744
+ ScrDyn *value = d->v.obj.entries[i].value;
745
+ if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release(value);
596
746
  if (!collected) {
597
- scr_dyn_release(d->v.obj.entries[i].value);
598
747
  scr_dyn_release(d->v.obj.entries[i].getter);
599
748
  scr_dyn_release(d->v.obj.entries[i].setter);
600
749
  }
750
+ d->v.obj.entries[i].value = NULL;
751
+ d->v.obj.entries[i].getter = NULL;
752
+ d->v.obj.entries[i].setter = NULL;
601
753
  }
602
754
  if (d->v.obj.source_identity) {
603
755
  d->v.obj.source_access(d->v.obj.source_identity, false);
@@ -646,6 +798,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
646
798
  #endif
647
799
  #ifndef SCR_RC_AUDIT
648
800
  if (!collected && scr_dyn_free_count < SCR_DYN_FREE_MAX) {
801
+ if (d->kind == SCR_DYN_OBJ) scr_dyn_obj_drop_keys(d, true);
649
802
  /* Pooled blocks retain their headers and count toward the bounded
650
803
  * allocated heap, but never remain in a candidate buffer. */
651
804
  scr_weak_dispose(d);
@@ -659,7 +812,10 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
659
812
  }
660
813
  #endif
661
814
  if (d->kind == SCR_DYN_ARR) free(d->v.arr.items);
662
- else if (d->kind == SCR_DYN_OBJ) free(d->v.obj.entries);
815
+ else if (d->kind == SCR_DYN_OBJ) {
816
+ scr_dyn_obj_drop_keys(d, false);
817
+ free(d->v.obj.entries);
818
+ }
663
819
  scr_cyc_free(d);
664
820
  }
665
821
 
@@ -678,25 +834,95 @@ void scr_dyn_release(ScrDyn *d) {
678
834
  scr_dyn_dispose(d, false);
679
835
  }
680
836
 
837
+ /* Small objects retain their compact linear representation. A wide object
838
+ * owns a bucket index of entry positions; entries remain insertion ordered.
839
+ * No key/value ownership lives here, and callbacks never retain its slots. */
840
+ #define SCR_DYN_OBJECT_INDEX_MIN 32
841
+ typedef struct ScrDynObjectIndex {
842
+ size_t buckets;
843
+ size_t slots[]; /* zero is empty; occupied slots store entry + 1 */
844
+ } ScrDynObjectIndex;
845
+
846
+ static void scr_dyn_index_insert(ScrDynObjectIndex *index, const ScrDynEntry *entry, size_t position) {
847
+ size_t slot = scr_key_hash(entry->key, entry->key_len) & (index->buckets - 1);
848
+ while (index->slots[slot]) slot = (slot + 1) & (index->buckets - 1);
849
+ index->slots[slot] = position + 1;
850
+ }
851
+
852
+ /* Keep index allocation/probing outside compact object insertion and reads. */
853
+ static __attribute__((noinline)) void scr_dyn_index_append(ScrDyn *object) {
854
+ size_t count = object->v.obj.len;
855
+ ScrDynObjectIndex *index = object->v.obj.index;
856
+ if (!index || count > index->buckets / 2) {
857
+ size_t buckets = index ? index->buckets : 64;
858
+ while (count > buckets / 2) {
859
+ if (buckets > (SIZE_MAX - sizeof *index) / sizeof(size_t) / 2) scr_json_oom();
860
+ buckets *= 2;
861
+ }
862
+ free(index);
863
+ index = calloc(1, sizeof *index + buckets * sizeof(size_t));
864
+ if (!index) scr_json_oom();
865
+ index->buckets = buckets;
866
+ object->v.obj.index = index;
867
+ for (size_t i = 0; i < count; i++) scr_dyn_index_insert(index, &object->v.obj.entries[i], i);
868
+ } else {
869
+ scr_dyn_index_insert(index, &object->v.obj.entries[count - 1], count - 1);
870
+ }
871
+ }
872
+
873
+ /* A checked hint avoids repeating a linear walk for recurring property
874
+ * names. Slots store indices only, never object/key pointers. Every hit
875
+ * checks the current entry, so different shapes, deletion and reordering
876
+ * need no invalidation and collisions fall back to the ordinary search. */
877
+ #define SCR_DYN_PROPERTY_HINTS 64
878
+ static SCR_TL size_t scr_dyn_property_hints[SCR_DYN_PROPERTY_HINTS];
879
+
880
+ static ScrDynEntry *scr_dyn_find_linear_entry(const ScrDyn *object, const char *key, size_t length) {
881
+ for (size_t i = 0; i < object->v.obj.len; i++) {
882
+ ScrDynEntry *entry = &object->v.obj.entries[i];
883
+ if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
884
+ }
885
+ return NULL;
886
+ }
887
+
888
+ static __attribute__((noinline)) ScrDynEntry *scr_dyn_find_indexed_entry(
889
+ const ScrDyn *object, const char *key, size_t length) {
890
+ const ScrDynObjectIndex *index = object->v.obj.index;
891
+ size_t slot = scr_key_hash(key, length) & (index->buckets - 1);
892
+ while (index->slots[slot]) {
893
+ ScrDynEntry *entry = &object->v.obj.entries[index->slots[slot] - 1];
894
+ if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
895
+ slot = (slot + 1) & (index->buckets - 1);
896
+ }
897
+ return NULL;
898
+ }
899
+
900
+ static ScrDynEntry *scr_dyn_find_entry(const ScrDyn *object, const char *key, size_t length) {
901
+ if (object->kind != SCR_DYN_OBJ) return NULL;
902
+ if (object->v.obj.index) return scr_dyn_find_indexed_entry(object, key, length);
903
+ size_t slot = (length + (length ? (unsigned char)key[0] * 7u : 0)) % SCR_DYN_PROPERTY_HINTS;
904
+ size_t hint = scr_dyn_property_hints[slot];
905
+ if (hint < object->v.obj.len) {
906
+ ScrDynEntry *entry = &object->v.obj.entries[hint];
907
+ if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
908
+ }
909
+ ScrDynEntry *entry = scr_dyn_find_linear_entry(object, key, length);
910
+ if (entry) scr_dyn_property_hints[slot] = (size_t)(entry - object->v.obj.entries);
911
+ return entry;
912
+ }
913
+
681
914
  ScrDyn *scr_dyn_obj_get(const ScrDyn *d, const char *key, size_t key_len) {
682
915
  if (d->kind == SCR_DYN_PROXY) {
683
916
  scr_dyn_proxy_unsupported("raw object inspection");
684
917
  return NULL;
685
918
  }
686
- if (d->kind != SCR_DYN_OBJ) return NULL;
687
- for (size_t i = 0; i < d->v.obj.len; i++) {
688
- const ScrDynEntry *e = &d->v.obj.entries[i];
689
- if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) return e->value;
690
- }
691
- return NULL;
919
+ ScrDynEntry *entry = scr_dyn_find_entry(d, key, key_len);
920
+ return entry ? entry->value : NULL;
692
921
  }
693
922
 
694
923
  bool scr_dyn_obj_enumerable(const ScrDyn *d, const char *key, size_t key_len) {
695
- for (size_t i = 0; i < d->v.obj.len; i++) {
696
- const ScrDynEntry *e = &d->v.obj.entries[i];
697
- if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) return e->enumerable;
698
- }
699
- return false;
924
+ ScrDynEntry *entry = scr_dyn_find_entry(d, key, key_len);
925
+ return entry && entry->enumerable;
700
926
  }
701
927
 
702
928
  static const ScrDyn *scr_dyn_property_owner(const ScrDyn *object, const char *key, size_t length);
@@ -983,29 +1209,27 @@ ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key) {
983
1209
  return value;
984
1210
  }
985
1211
 
986
- /* Takes ownership of key (malloc'd) and value. Duplicate keys: the LATER
987
- * value wins (like JS JSON.parse) — the old value is released and the new
988
- * key buffer freed (the surviving entry keeps its original, equal key). */
989
- static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *value) {
990
- for (size_t i = 0; i < obj->v.obj.len; i++) {
991
- ScrDynEntry *e = &obj->v.obj.entries[i];
992
- if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) {
993
- if (e->accessor || !e->writable) {
994
- free(key);
995
- scr_dyn_release(value);
996
- static const char message[] = "Cannot assign to read only property";
997
- scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
998
- return;
999
- }
1000
- ScrDyn *old = e->value;
1001
- e->value = value;
1002
- scr_dyn_release(old);
1003
- free(key);
1212
+ /* Borrows key and takes ownership of value. Duplicate keys retain their
1213
+ * original spelling/storage and insertion position; the later value wins.
1214
+ * A recycled entry can reuse equal key bytes without a malloc/free pair. */
1215
+ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) {
1216
+ /* Construction usually appends an absent name. Avoid a read hint that
1217
+ * repeats comparisons on compact objects; wide objects use their index. */
1218
+ ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len)
1219
+ : scr_dyn_find_linear_entry(obj, key, key_len);
1220
+ if (existing) {
1221
+ if (existing->accessor || !existing->writable) {
1222
+ scr_dyn_release(value);
1223
+ static const char message[] = "Cannot assign to read only property";
1224
+ scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
1004
1225
  return;
1005
1226
  }
1227
+ ScrDyn *old = existing->value;
1228
+ existing->value = value;
1229
+ scr_dyn_release(old);
1230
+ return;
1006
1231
  }
1007
1232
  if (obj->non_extensible) {
1008
- free(key);
1009
1233
  scr_dyn_release(value);
1010
1234
  static const char message[] = "Cannot add property, object is not extensible";
1011
1235
  scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
@@ -1015,11 +1239,16 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
1015
1239
  size_t cap = obj->v.obj.cap ? obj->v.obj.cap * 2 : 4;
1016
1240
  ScrDynEntry *entries = realloc(obj->v.obj.entries, cap * sizeof *entries);
1017
1241
  if (!entries) scr_json_oom();
1242
+ memset(entries + obj->v.obj.cap, 0, (cap - obj->v.obj.cap) * sizeof *entries);
1018
1243
  obj->v.obj.entries = entries;
1019
1244
  obj->v.obj.cap = cap;
1020
1245
  }
1021
1246
  ScrDynEntry *e = &obj->v.obj.entries[obj->v.obj.len++];
1022
- e->key = key;
1247
+ if (!e->key || e->key_len != key_len || memcmp(e->key, key, key_len)) {
1248
+ char *owned = scr_dyn_key_copy(key, key_len);
1249
+ scr_dyn_key_release(e->key, e->key_len);
1250
+ e->key = owned;
1251
+ }
1023
1252
  e->key_len = key_len;
1024
1253
  e->value = value;
1025
1254
  e->getter = NULL;
@@ -1028,6 +1257,7 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
1028
1257
  e->writable = true;
1029
1258
  e->enumerable = true;
1030
1259
  e->configurable = true;
1260
+ if (obj->v.obj.len >= SCR_DYN_OBJECT_INDEX_MIN) scr_dyn_index_append(obj);
1031
1261
  }
1032
1262
 
1033
1263
  /* ── dyn construction (compiler-emitted converters & overflow reads) ───── */
@@ -1039,12 +1269,20 @@ ScrDyn *scr_dyn_undefined(void) {
1039
1269
  return &undef;
1040
1270
  }
1041
1271
 
1042
- ScrDyn *scr_dyn_new_null(void) { return scr_dyn_alloc(SCR_DYN_NULL); }
1272
+ /* Primitive nodes have no mutable properties or observable box identity.
1273
+ * As with undefined, SIZE_MAX keeps them out of RC and cycle bookkeeping;
1274
+ * callers retain the ordinary owned-result and ownership-transfer contracts. */
1275
+ ScrDyn *scr_dyn_new_null(void) {
1276
+ static ScrDyn value = { .rc = SIZE_MAX, .kind = SCR_DYN_NULL };
1277
+ return &value;
1278
+ }
1043
1279
 
1044
1280
  ScrDyn *scr_dyn_new_bool(bool b) {
1045
- ScrDyn *d = scr_dyn_alloc(SCR_DYN_BOOL);
1046
- d->v.b = b;
1047
- return d;
1281
+ static ScrDyn values[2] = {
1282
+ { .rc = SIZE_MAX, .kind = SCR_DYN_BOOL, .v.b = false },
1283
+ { .rc = SIZE_MAX, .kind = SCR_DYN_BOOL, .v.b = true },
1284
+ };
1285
+ return &values[b ? 1 : 0];
1048
1286
  }
1049
1287
 
1050
1288
  ScrDyn *scr_dyn_new_bigint(ScrBigInt *value) {
@@ -1080,6 +1318,22 @@ ScrDyn *scr_dyn_symbol_get(const ScrDyn *value, const ScrStr *key) {
1080
1318
  }
1081
1319
 
1082
1320
  ScrDyn *scr_dyn_new_num(double n) {
1321
+ /* Small integers are immutable primitives, just like the boolean and null
1322
+ * singletons. Initialize only touched slots; the table neither grows nor
1323
+ * owns heap references. Preserve negative zero as a distinct payload. */
1324
+ if (n >= -128 && n <= 255) {
1325
+ int value = (int)n;
1326
+ if ((double)value == n && (value != 0 || !signbit(n))) {
1327
+ static SCR_TL ScrDyn values[384];
1328
+ ScrDyn *cached = &values[value + 128];
1329
+ if (cached->rc == 0) {
1330
+ cached->rc = SIZE_MAX;
1331
+ cached->kind = SCR_DYN_NUM;
1332
+ cached->v.num = n;
1333
+ }
1334
+ return cached;
1335
+ }
1336
+ }
1083
1337
  ScrDyn *d = scr_dyn_alloc(SCR_DYN_NUM);
1084
1338
  d->v.num = n;
1085
1339
  return d;
@@ -2819,14 +3073,10 @@ static void scr_dyn_handle_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
2819
3073
  scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
2820
3074
  }
2821
3075
 
2822
- /* Public obj insertion: COPIES the key bytes (the internal put takes a
2823
- * malloc'd buffer), owns the value, later duplicate keys win. */
3076
+ /* Public obj insertion copies a new key only when needed, owns the value,
3077
+ * and preserves the existing entry for duplicate keys. */
2824
3078
  void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) {
2825
- char *copy = malloc(key_len + 1);
2826
- if (!copy) scr_json_oom();
2827
- memcpy(copy, key, key_len);
2828
- copy[key_len] = '\0';
2829
- scr_dyn_obj_put(obj, copy, key_len, value);
3079
+ scr_dyn_obj_put(obj, key, key_len, value);
2830
3080
  }
2831
3081
 
2832
3082
  static ScrStr *scr_dyn_property_key(const ScrDyn *key) {
@@ -2834,12 +3084,7 @@ static ScrStr *scr_dyn_property_key(const ScrDyn *key) {
2834
3084
  }
2835
3085
 
2836
3086
  static ScrDynEntry *scr_dyn_entry(ScrDyn *obj, const ScrStr *key) {
2837
- if (obj->kind != SCR_DYN_OBJ) return NULL;
2838
- for (size_t i = 0; i < obj->v.obj.len; i++) {
2839
- ScrDynEntry *entry = &obj->v.obj.entries[i];
2840
- if (entry->key_len == key->len && memcmp(entry->key, key->data, key->len) == 0) return entry;
2841
- }
2842
- return NULL;
3087
+ return scr_dyn_find_entry(obj, key->data, key->len);
2843
3088
  }
2844
3089
 
2845
3090
  static bool scr_dyn_property_same_value(const ScrDyn *a, const ScrDyn *b) {
@@ -3022,6 +3267,7 @@ typedef struct {
3022
3267
  unsigned kind; /* 0: array, 1: string, 2: Map, 3: Set */
3023
3268
  unsigned selection; /* 0: values, 1: keys, 2: entries */
3024
3269
  bool array_like;
3270
+ bool step_done; /* completion of the last compiler-consumed step */
3025
3271
  double limit;
3026
3272
  } ScrNativeIterator;
3027
3273
 
@@ -3063,21 +3309,18 @@ static void scr_native_iterator_release(void *ptr) {
3063
3309
  free(iterator);
3064
3310
  }
3065
3311
 
3066
- static ScrDyn *scr_native_iterator_next(ScrNativeIterator *iterator) {
3312
+ /* Produce an owned value without allocating the protocol result. The
3313
+ * compiler uses this only after checking the captured next function; public
3314
+ * next() calls still receive a fresh, independently mutable result object. */
3315
+ static ScrDyn *scr_native_iterator_value(ScrNativeIterator *iterator, bool *done_out) {
3067
3316
  ScrDyn *source = iterator->source;
3068
- if (source && iterator->kind == 4) {
3069
- ScrDyn *result = scr_dyn_handle_ops_of(source)->iter_step(source->v.handle.ptr, &iterator->index, iterator->selection);
3070
- if (!result) return NULL;
3071
- if (scr_dyn_truthy(scr_dyn_obj_get(result, "done", 4))) {
3072
- iterator->source = NULL;
3073
- scr_dyn_release(source);
3074
- }
3075
- return result;
3076
- }
3317
+ /* An inherited indexed getter can advance this same iterator recursively.
3318
+ * Keep the source alive even if that nested call exhausts the iterator. */
3319
+ if (source) scr_dyn_retain(source);
3077
3320
  /* Native array capsules retain the live array, so refresh its view at each
3078
3321
  * step rather than iterating a snapshot taken when the iterator opened. */
3079
3322
  ScrDyn *view = source && source->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_materialize(source) : NULL;
3080
- if (scr_exc_pending()) { scr_dyn_release(view); return NULL; }
3323
+ if (scr_exc_pending()) { scr_dyn_release(view); scr_dyn_release(source); return NULL; }
3081
3324
  ScrDyn *items = view ? view : source;
3082
3325
  ScrMap *map = source && (iterator->kind == 2 || iterator->kind == 3) ? source->v.handle.ptr : NULL;
3083
3326
  if (map) while (iterator->index < scr_map_iter_count(map) && !scr_map_iter_live(map, iterator->index)) iterator->index++;
@@ -3123,6 +3366,24 @@ static ScrDyn *scr_native_iterator_next(ScrNativeIterator *iterator) {
3123
3366
  scr_str_release(point);
3124
3367
  }
3125
3368
  scr_dyn_release(view);
3369
+ scr_dyn_release(source);
3370
+ *done_out = done;
3371
+ return value;
3372
+ }
3373
+
3374
+ static ScrDyn *scr_native_iterator_next(ScrNativeIterator *iterator) {
3375
+ ScrDyn *source = iterator->source;
3376
+ if (source && iterator->kind == 4) {
3377
+ ScrDyn *result = scr_dyn_handle_ops_of(source)->iter_step(source->v.handle.ptr, &iterator->index, iterator->selection);
3378
+ if (!result) return NULL;
3379
+ if (scr_dyn_truthy(scr_dyn_obj_get(result, "done", 4))) {
3380
+ iterator->source = NULL;
3381
+ scr_dyn_release(source);
3382
+ }
3383
+ return result;
3384
+ }
3385
+ bool done;
3386
+ ScrDyn *value = scr_native_iterator_value(iterator, &done);
3126
3387
  if (!value) return NULL;
3127
3388
  ScrDyn *result = scr_dyn_new_obj();
3128
3389
  scr_dyn_obj_set(result, "value", 5, value);
@@ -3172,6 +3433,28 @@ static ScrDyn *scr_native_handle_iterator_next_call(ScrClosure *closure, ScrDyn
3172
3433
  return scr_native_iterator_next_checked(4);
3173
3434
  }
3174
3435
 
3436
+ bool scr_dyn_iterator_can_step(const ScrDyn *iterator, const ScrDyn *next) {
3437
+ if (iterator->kind != SCR_DYN_HANDLE || iterator->v.handle.tag != SCR_DYNH_ITERATOR) return false;
3438
+ unsigned kind = ((ScrNativeIterator *)iterator->v.handle.ptr)->kind;
3439
+ if (kind > 3) return false;
3440
+ size_t index = kind == 3 ? 8 : kind == 2 ? 7 : kind == 1 ? 5 : 0;
3441
+ return next == scr_iterator_functions[index];
3442
+ }
3443
+
3444
+ ScrDyn *scr_dyn_iterator_step(const ScrDyn *value) {
3445
+ ScrNativeIterator *iterator = value->v.handle.ptr;
3446
+ bool done = false;
3447
+ ScrDyn *item = scr_native_iterator_value(iterator, &done);
3448
+ /* Publish after getters finish, so recursive steps cannot overwrite this
3449
+ * step's completion. No user code runs between step and step_done. */
3450
+ iterator->step_done = done;
3451
+ return item;
3452
+ }
3453
+
3454
+ bool scr_dyn_iterator_step_done(const ScrDyn *value) {
3455
+ return ((ScrNativeIterator *)value->v.handle.ptr)->step_done;
3456
+ }
3457
+
3175
3458
  static ScrDyn *scr_native_iterator_get(void *ptr, const char *key, size_t len) {
3176
3459
  if (len != 4 || memcmp(key, "next", 4)) return NULL;
3177
3460
  unsigned kind = ((ScrNativeIterator *)ptr)->kind;
@@ -5475,10 +5758,7 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
5475
5758
  if (e->value->kind == SCR_DYN_JSVAL && scr_dyn_isl_typeof_is(e->value, "function")) continue; /* engine functions drop too */
5476
5759
  if (!first) scr_jb_putc(b, ',');
5477
5760
  first = false;
5478
- /* Keys escape exactly like string values (put_json_str quotes). */
5479
- ScrStr *k = scr_str_new(e->key, e->key_len);
5480
- scr_jb_put_json_str(b, k);
5481
- scr_str_release(k);
5761
+ scr_jb_put_json_span(b, e->key, e->key_len);
5482
5762
  scr_jb_putc(b, ':');
5483
5763
  scr_jb_put_dyn(b, e->value);
5484
5764
  }
@@ -5639,8 +5919,10 @@ static int32_t scr_json_hex4(ScrJsonP *p) {
5639
5919
  * slow path would produce) and returns -1. */
5640
5920
  static int scr_json_string_span(ScrJsonP *p, const char **span,
5641
5921
  size_t *span_len) {
5642
- size_t i = p->pos + 1;
5643
- while (i < p->len) {
5922
+ *span = p->s + p->pos + 1;
5923
+ *span_len = scr_json_plain_bytes(*span, p->len - p->pos - 1);
5924
+ size_t i = p->pos + 1 + *span_len;
5925
+ if (i < p->len) {
5644
5926
  unsigned char c = (unsigned char)p->s[i];
5645
5927
  if (c == '"') {
5646
5928
  *span = p->s + p->pos + 1;
@@ -5653,7 +5935,6 @@ static int scr_json_string_span(ScrJsonP *p, const char **span,
5653
5935
  scr_json_throw_pos("Bad control character in string literal", i);
5654
5936
  return -1;
5655
5937
  }
5656
- i++;
5657
5938
  }
5658
5939
  scr_json_throw_pos("Unterminated string", p->pos);
5659
5940
  return -1;
@@ -5661,11 +5942,13 @@ static int scr_json_string_span(ScrJsonP *p, const char **span,
5661
5942
 
5662
5943
  /* Slow path: parses the string literal at p->pos (the opening quote),
5663
5944
  * decoding escapes, into a +1 ScrStr. NULL on error (thrown). */
5664
- static ScrStr *scr_json_string_slow(ScrJsonP *p) {
5945
+ static ScrStr *scr_json_string_slow(ScrJsonP *p, size_t prefix) {
5665
5946
  size_t open = p->pos;
5666
5947
  p->pos++; /* opening quote */
5667
5948
  ScrJsonBuf b;
5668
5949
  scr_jb_init(&b);
5950
+ scr_jb_write(&b, p->s + p->pos, prefix);
5951
+ p->pos += prefix;
5669
5952
  for (;;) {
5670
5953
  if (p->pos >= p->len) {
5671
5954
  scr_jb_dispose(&b);
@@ -5683,8 +5966,9 @@ static ScrStr *scr_json_string_slow(ScrJsonP *p) {
5683
5966
  return NULL;
5684
5967
  }
5685
5968
  if (c != '\\') {
5686
- scr_jb_putc(&b, (char)c); /* raw UTF-8 passes through */
5687
- p->pos++;
5969
+ size_t run = scr_json_plain_bytes(p->s + p->pos, p->len - p->pos);
5970
+ scr_jb_write(&b, p->s + p->pos, run);
5971
+ p->pos += run;
5688
5972
  continue;
5689
5973
  }
5690
5974
  p->pos++; /* backslash */
@@ -5759,7 +6043,7 @@ static ScrStr *scr_json_string_scr(ScrJsonP *p) {
5759
6043
  int r = scr_json_string_span(p, &span, &span_len);
5760
6044
  if (r < 0) return NULL;
5761
6045
  if (r > 0) return scr_str_new(span, span_len);
5762
- return scr_json_string_slow(p);
6046
+ return scr_json_string_slow(p, span_len);
5763
6047
  }
5764
6048
 
5765
6049
  static ScrDyn *scr_json_value(ScrJsonP *p);
@@ -5838,20 +6122,17 @@ static ScrDyn *scr_json_number(ScrJsonP *p) {
5838
6122
  }
5839
6123
  exp10 += eneg ? -ev : ev;
5840
6124
  }
5841
- ScrDyn *d = scr_dyn_alloc(SCR_DYN_NUM);
5842
6125
  if (precise && exp10 >= -22 && exp10 <= 22) {
5843
6126
  double v = (double)mant; /* exact: mant < 10^15 < 2^53 */
5844
6127
  if (exp10 > 0) v *= scr_json_pow10[exp10];
5845
6128
  else if (exp10 < 0) v /= scr_json_pow10[-exp10];
5846
- d->v.num = neg ? -v : v;
5847
- return d;
6129
+ return scr_dyn_new_num(neg ? -v : v);
5848
6130
  }
5849
6131
  /* The validated span re-parses with strtod (correctly rounded, and the
5850
6132
  * grammar above is a strict subset of what strtod accepts). The ScrStr
5851
6133
  * data is NUL-terminated, and strtod stops at the first non-number char,
5852
6134
  * so parsing from `start` reads exactly the validated token. */
5853
- d->v.num = strtod(p->s + start, NULL);
5854
- return d;
6135
+ return scr_dyn_new_num(strtod(p->s + start, NULL));
5855
6136
  }
5856
6137
 
5857
6138
  static bool scr_json_lit(ScrJsonP *p, const char *word, size_t n) {
@@ -5918,10 +6199,12 @@ static ScrDyn *scr_json_object(ScrJsonP *p) {
5918
6199
  scr_json_throw_pos("Expected property name or '}'", p->pos);
5919
6200
  return NULL;
5920
6201
  }
5921
- /* Key: fast span path (no escapes) copies straight into the malloc'd
5922
- * key buffer; the slow path decodes into a ScrStr first. */
6202
+ /* Keep the key borrowed until insertion determines whether it already
6203
+ * exists or can reuse a recycled slot. Escaped keys own their decoded
6204
+ * string until the value has been parsed and inserted. */
5923
6205
  size_t key_len = 0;
5924
- char *key;
6206
+ const char *key;
6207
+ ScrStr *decoded_key = NULL;
5925
6208
  {
5926
6209
  const char *span;
5927
6210
  size_t span_len;
@@ -5931,27 +6214,21 @@ static ScrDyn *scr_json_object(ScrJsonP *p) {
5931
6214
  return NULL;
5932
6215
  }
5933
6216
  if (r > 0) {
5934
- key = malloc(span_len + 1);
5935
- if (!key) scr_json_oom();
5936
- memcpy(key, span, span_len);
5937
- key[span_len] = '\0';
6217
+ key = span;
5938
6218
  key_len = span_len;
5939
6219
  } else {
5940
- ScrStr *ks = scr_json_string_slow(p);
5941
- if (!ks) {
6220
+ decoded_key = scr_json_string_slow(p, span_len);
6221
+ if (!decoded_key) {
5942
6222
  scr_dyn_release(obj);
5943
6223
  return NULL;
5944
6224
  }
5945
- key = malloc(ks->len + 1);
5946
- if (!key) scr_json_oom();
5947
- memcpy(key, ks->data, ks->len + 1);
5948
- key_len = ks->len;
5949
- scr_str_release(ks);
6225
+ key = decoded_key->data;
6226
+ key_len = decoded_key->len;
5950
6227
  }
5951
6228
  }
5952
6229
  scr_json_ws(p);
5953
6230
  if (p->pos >= p->len || p->s[p->pos] != ':') {
5954
- free(key);
6231
+ scr_str_release(decoded_key);
5955
6232
  scr_dyn_release(obj);
5956
6233
  if (p->pos >= p->len) scr_json_throw("Unexpected end of JSON input");
5957
6234
  else scr_json_throw_pos("Expected ':' after property name", p->pos);
@@ -5960,11 +6237,12 @@ static ScrDyn *scr_json_object(ScrJsonP *p) {
5960
6237
  p->pos++; /* ':' */
5961
6238
  ScrDyn *value = scr_json_value(p);
5962
6239
  if (!value) {
5963
- free(key);
6240
+ scr_str_release(decoded_key);
5964
6241
  scr_dyn_release(obj);
5965
6242
  return NULL;
5966
6243
  }
5967
6244
  scr_dyn_obj_put(obj, key, key_len, value); /* later duplicate keys win */
6245
+ scr_str_release(decoded_key);
5968
6246
  scr_json_ws(p);
5969
6247
  if (p->pos >= p->len) {
5970
6248
  scr_dyn_release(obj);
@@ -6013,19 +6291,15 @@ static ScrDyn *scr_json_value(ScrJsonP *p) {
6013
6291
  }
6014
6292
  if (c == 't') {
6015
6293
  if (!scr_json_lit(p, "true", 4)) return NULL;
6016
- ScrDyn *d = scr_dyn_alloc(SCR_DYN_BOOL);
6017
- d->v.b = true;
6018
- return d;
6294
+ return scr_dyn_new_bool(true);
6019
6295
  }
6020
6296
  if (c == 'f') {
6021
6297
  if (!scr_json_lit(p, "false", 5)) return NULL;
6022
- ScrDyn *d = scr_dyn_alloc(SCR_DYN_BOOL);
6023
- d->v.b = false;
6024
- return d;
6298
+ return scr_dyn_new_bool(false);
6025
6299
  }
6026
6300
  if (c == 'n') {
6027
6301
  if (!scr_json_lit(p, "null", 4)) return NULL;
6028
- return scr_dyn_alloc(SCR_DYN_NULL);
6302
+ return scr_dyn_new_null();
6029
6303
  }
6030
6304
  if (c == '-' || (c >= '0' && c <= '9')) return scr_json_number(p);
6031
6305
  scr_json_throw_token(p);
@@ -6117,7 +6391,12 @@ static void scr_json_delete_member(ScrDyn *object, const ScrStr *key) {
6117
6391
  ScrDynEntry removed = *entry;
6118
6392
  memmove(entry, entry + 1, (object->v.obj.len - i - 1) * sizeof *entry);
6119
6393
  object->v.obj.len--;
6120
- free(removed.key);
6394
+ free(object->v.obj.index);
6395
+ object->v.obj.index = NULL;
6396
+ /* The moved tail aliases a live entry; spare slots own only their own
6397
+ * retained key bytes, never a second pointer to a surviving property. */
6398
+ memset(&object->v.obj.entries[object->v.obj.len], 0, sizeof *entry);
6399
+ scr_dyn_key_release(removed.key, removed.key_len);
6121
6400
  scr_dyn_release(removed.value);
6122
6401
  scr_dyn_release(removed.getter);
6123
6402
  scr_dyn_release(removed.setter);
@@ -6324,12 +6603,76 @@ static void scr_json_gap(ScrJsonBuf *buffer, const ScrStr *gap, size_t depth) {
6324
6603
  for (size_t i = 0; i < depth; i++) scr_jb_write(buffer, gap->data, gap->len);
6325
6604
  }
6326
6605
 
6606
+ /* These primitive values cannot invoke toJSON or mutate a containing frame.
6607
+ * BigInts, functions and every reference kind keep the protocol walk. */
6608
+ static bool scr_json_plain_value(const ScrDyn *value) {
6609
+ switch (value->kind) {
6610
+ case SCR_DYN_NULL: case SCR_DYN_UNDEF: case SCR_DYN_SYMBOL:
6611
+ case SCR_DYN_BOOL: case SCR_DYN_NUM: case SCR_DYN_STR: return true;
6612
+ default: return false;
6613
+ }
6614
+ }
6615
+
6616
+ /* Prove the whole immediate container before writing any of it. Without a
6617
+ * replacer or executable children, entries cannot change and borrowed names
6618
+ * need neither a key snapshot nor repeated lookups. Numeric property names
6619
+ * keep the ordinary sorted-key walk; sparse/accessor arrays keep Get. This
6620
+ * proof is repeated per call, so later mutations never leave a stale fact. */
6621
+ static bool scr_json_plain_container(const ScrDyn *value) {
6622
+ if (value->kind == SCR_DYN_ARR) {
6623
+ if (value->v.arr.presence || value->v.arr.properties) return false;
6624
+ for (size_t i = 0; i < value->v.arr.len; i++)
6625
+ if (!scr_json_plain_value(value->v.arr.items[i])) return false;
6626
+ return true;
6627
+ }
6628
+ if (value->kind != SCR_DYN_OBJ) return false;
6629
+ for (size_t i = 0; i < value->v.obj.len; i++) {
6630
+ const ScrDynEntry *entry = &value->v.obj.entries[i];
6631
+ if (!entry->enumerable) continue;
6632
+ double index;
6633
+ if (entry->accessor || !scr_json_plain_value(entry->value) ||
6634
+ scr_dyn_key_is_index(entry->key, entry->key_len, &index)) return false;
6635
+ }
6636
+ return true;
6637
+ }
6638
+
6639
+ static bool scr_json_plain_write(ScrJsonBuf *buffer, const ScrDyn *value,
6640
+ const ScrStr *gap, size_t depth) {
6641
+ bool array = value->kind == SCR_DYN_ARR;
6642
+ size_t length = array ? value->v.arr.len : value->v.obj.len;
6643
+ scr_jb_putc(buffer, array ? '[' : '{');
6644
+ bool first = true;
6645
+ for (size_t i = 0; i < length; i++) {
6646
+ const ScrDynEntry *entry = array ? NULL : &value->v.obj.entries[i];
6647
+ if (entry && !entry->enumerable) continue;
6648
+ const ScrDyn *child = array ? value->v.arr.items[i] : entry->value;
6649
+ bool omitted = scr_json_omitted(child);
6650
+ if (!array && omitted) continue;
6651
+ if (!omitted && !scr_json_callback_depth(depth + 1)) return false;
6652
+ if (!first) scr_jb_putc(buffer, ',');
6653
+ first = false;
6654
+ scr_json_gap(buffer, gap, depth + 1);
6655
+ if (entry) {
6656
+ scr_jb_put_json_span(buffer, entry->key, entry->key_len);
6657
+ scr_jb_putc(buffer, ':');
6658
+ if (gap->len) scr_jb_putc(buffer, ' ');
6659
+ }
6660
+ if (omitted) scr_jb_puts(buffer, "null");
6661
+ else scr_dyn_json_write(buffer, child);
6662
+ }
6663
+ if (!first) scr_json_gap(buffer, gap, depth);
6664
+ scr_jb_putc(buffer, array ? ']' : '}');
6665
+ return true;
6666
+ }
6667
+
6327
6668
  static bool scr_json_replaced_write(ScrJsonBuf *buffer, const ScrDyn *value,
6328
6669
  const ScrDyn *replacer, const ScrStr *gap, size_t depth) {
6329
6670
  if (!scr_json_callback_depth(depth)) return false;
6330
6671
  ScrDyn *view = value->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_materialize(value) : NULL;
6331
6672
  const ScrDyn *observed = view ? view : value;
6332
6673
  if (scr_exc_pending()) { scr_dyn_release(view); return false; }
6674
+ if (!view && replacer->kind == SCR_DYN_UNDEF && scr_json_plain_container(value))
6675
+ return scr_json_plain_write(buffer, value, gap, depth);
6333
6676
  if (observed->kind != SCR_DYN_OBJ && observed->kind != SCR_DYN_ARR && observed->kind != SCR_DYN_BYTES) {
6334
6677
  /* Engine objects need an engine callback bridge, not an opaque JSON
6335
6678
  * splice: that would silently skip all their children. */
@@ -6820,9 +7163,8 @@ static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_
6820
7163
  continue;
6821
7164
  }
6822
7165
  if (current->kind != SCR_DYN_OBJ) continue;
6823
- for (size_t i = 0; i < current->v.obj.len; i++) {
6824
- ScrDynEntry *entry = &current->v.obj.entries[i];
6825
- if (entry->key_len != key_len || memcmp(entry->key, key, key_len) != 0) continue;
7166
+ ScrDynEntry *entry = scr_dyn_find_entry(current, key, key_len);
7167
+ if (entry) {
6826
7168
  if (!entry->accessor) return scr_dyn_retain(entry->value);
6827
7169
  if (!entry->getter) return scr_dyn_retain(scr_dyn_undefined());
6828
7170
  ScrDyn *getter = scr_dyn_retain(entry->getter);
@@ -7090,15 +7432,9 @@ bool scr_dyn_reflect_set(ScrDyn *target, ScrDyn *raw_key, ScrDyn *value, ScrDyn
7090
7432
 
7091
7433
  /* The array-index test (ECMA: a canonical numeric string < 2^32-1). */
7092
7434
  static bool scr_dyn_key_is_index(const char *key, size_t len, double *out) {
7093
- if (len == 0 || len > 10) return false;
7094
- if (key[0] == '0' && len > 1) return false;
7095
- double v = 0;
7096
- for (size_t i = 0; i < len; i++) {
7097
- if (key[i] < '0' || key[i] > '9') return false;
7098
- v = v * 10 + (key[i] - '0');
7099
- }
7100
- if (v > 4294967294.0) return false;
7101
- *out = v;
7435
+ uint32_t index;
7436
+ if (!scr_key_array_index(key, len, &index)) return false;
7437
+ *out = index;
7102
7438
  return true;
7103
7439
  }
7104
7440
 
@@ -7116,34 +7452,31 @@ static ScrDyn *scr_dyn_objwalk_key(const char *key, size_t key_len) {
7116
7452
  ScrDyn *scr_dyn_obj_own_keys(const ScrDyn *v) {
7117
7453
  size_t n = v->v.obj.len;
7118
7454
  ScrDyn *keys = scr_dyn_new_arr();
7119
- bool *is_index = malloc(n ? n * sizeof *is_index : 1);
7120
- double *idx = malloc(n ? n * sizeof *idx : 1);
7121
- if (!is_index || !idx) scr_trap("scriptc: out of memory\n");
7122
- size_t index_count = 0;
7455
+ ScrKeyIndex *indices = NULL;
7456
+ size_t count = 0;
7123
7457
  for (size_t i = 0; i < n; i++) {
7124
- const ScrDynEntry *e = &v->v.obj.entries[i];
7125
- is_index[i] = scr_dyn_key_is_index(e->key, e->key_len, &idx[i]);
7126
- if (is_index[i]) index_count++;
7127
- }
7128
- double last = -1;
7129
- for (size_t done = 0; done < index_count; done++) {
7130
- size_t best = (size_t)-1;
7131
- for (size_t i = 0; i < n; i++) {
7132
- if (!is_index[i] || idx[i] <= last) continue;
7133
- if (best == (size_t)-1 || idx[i] < idx[best]) best = i;
7134
- }
7135
- if (best == (size_t)-1) break;
7136
- last = idx[best];
7137
- scr_dyn_arr_push(keys, scr_dyn_objwalk_key(v->v.obj.entries[best].key,
7138
- v->v.obj.entries[best].key_len));
7139
- }
7458
+ const ScrDynEntry *entry = &v->v.obj.entries[i];
7459
+ uint32_t index;
7460
+ if (!scr_key_array_index(entry->key, entry->key_len, &index)) continue;
7461
+ if (!indices) {
7462
+ if (n > SIZE_MAX / sizeof *indices) scr_json_oom();
7463
+ indices = malloc(n * sizeof *indices);
7464
+ if (!indices) scr_json_oom();
7465
+ }
7466
+ indices[count++] = (ScrKeyIndex){index, i};
7467
+ }
7468
+ scr_key_index_sort(indices, count);
7469
+ for (size_t i = 0; i < count; i++) {
7470
+ const ScrDynEntry *entry = &v->v.obj.entries[indices[i].entry];
7471
+ scr_dyn_arr_push(keys, scr_dyn_objwalk_key(entry->key, entry->key_len));
7472
+ }
7473
+ free(indices);
7140
7474
  for (size_t i = 0; i < n; i++) {
7141
- if (is_index[i]) continue;
7142
- scr_dyn_arr_push(keys, scr_dyn_objwalk_key(v->v.obj.entries[i].key,
7143
- v->v.obj.entries[i].key_len));
7475
+ const ScrDynEntry *entry = &v->v.obj.entries[i];
7476
+ uint32_t index;
7477
+ if (!scr_key_array_index(entry->key, entry->key_len, &index))
7478
+ scr_dyn_arr_push(keys, scr_dyn_objwalk_key(entry->key, entry->key_len));
7144
7479
  }
7145
- free(is_index);
7146
- free(idx);
7147
7480
  return keys;
7148
7481
  }
7149
7482