json-as 1.5.0 → 1.6.1

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 (66) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +42 -0
  3. package/assembly/deserialize/error.ts +21 -0
  4. package/assembly/deserialize/index/arbitrary.ts +29 -8
  5. package/assembly/deserialize/index/array.ts +2 -1
  6. package/assembly/deserialize/index/bool.ts +1 -1
  7. package/assembly/deserialize/index/object.ts +3 -0
  8. package/assembly/deserialize/index/string.ts +37 -0
  9. package/assembly/deserialize/index.ts +0 -1
  10. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  11. package/assembly/deserialize/naive/array/array.ts +1 -0
  12. package/assembly/deserialize/naive/array/bool.ts +21 -11
  13. package/assembly/deserialize/naive/array/box.ts +13 -33
  14. package/assembly/deserialize/naive/array/float.ts +23 -13
  15. package/assembly/deserialize/naive/array/integer.ts +33 -21
  16. package/assembly/deserialize/naive/array/object.ts +1 -0
  17. package/assembly/deserialize/naive/array/string.ts +29 -16
  18. package/assembly/deserialize/naive/bool.ts +17 -5
  19. package/assembly/deserialize/naive/float.ts +1 -2
  20. package/assembly/deserialize/naive/map.ts +339 -39
  21. package/assembly/deserialize/naive/object.ts +122 -65
  22. package/assembly/deserialize/naive/raw.ts +13 -1
  23. package/assembly/deserialize/naive/set.ts +0 -1
  24. package/assembly/deserialize/naive/staticarray.ts +3 -2
  25. package/assembly/deserialize/naive/string.ts +41 -52
  26. package/assembly/deserialize/parseMode.ts +216 -0
  27. package/assembly/deserialize/simd/array/integer.ts +6 -3
  28. package/assembly/deserialize/simd/float.ts +35 -29
  29. package/assembly/deserialize/simd/string.ts +79 -17
  30. package/assembly/deserialize/string-validation.ts +34 -0
  31. package/assembly/deserialize/swar/array/array.ts +63 -13
  32. package/assembly/deserialize/swar/array/bool.ts +14 -3
  33. package/assembly/deserialize/swar/array/float.ts +394 -13
  34. package/assembly/deserialize/swar/array/generic.ts +12 -2
  35. package/assembly/deserialize/swar/array/integer.ts +22 -62
  36. package/assembly/deserialize/swar/array/object.ts +11 -2
  37. package/assembly/deserialize/swar/array/shared.ts +1 -1
  38. package/assembly/deserialize/swar/array/string.ts +34 -5
  39. package/assembly/deserialize/swar/array/struct.ts +109 -15
  40. package/assembly/deserialize/swar/float.ts +19 -12
  41. package/assembly/deserialize/swar/string.ts +70 -111
  42. package/assembly/index.d.ts +6 -0
  43. package/assembly/index.ts +411 -33
  44. package/assembly/serialize/index/bool.ts +1 -1
  45. package/assembly/serialize/index/float.ts +1 -5
  46. package/assembly/serialize/index/integer.ts +1 -1
  47. package/assembly/serialize/index/object.ts +17 -0
  48. package/assembly/serialize/naive/array.ts +64 -0
  49. package/assembly/serialize/naive/float.ts +0 -5
  50. package/assembly/serialize/naive/map.ts +37 -0
  51. package/assembly/serialize/naive/set.ts +0 -1
  52. package/assembly/serialize/naive/staticarray.ts +0 -1
  53. package/assembly/serialize/naive/string.ts +0 -6
  54. package/assembly/serialize/naive/typedarray.ts +0 -1
  55. package/assembly/util/eisel-lemire.ts +179 -0
  56. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  57. package/assembly/util/scanValueEndSimd.ts +59 -36
  58. package/assembly/util/validateJson.ts +338 -0
  59. package/lib/as-bs.ts +28 -6
  60. package/package.json +17 -7
  61. package/transform/lib/index.d.ts +6 -0
  62. package/transform/lib/index.js +773 -112
  63. package/transform/lib/types.d.ts +1 -0
  64. package/transform/lib/types.js +3 -0
  65. package/assembly/deserialize/index/struct.ts +0 -1
  66. package/assembly/deserialize/naive/struct.ts +0 -21
@@ -4,6 +4,8 @@ import { __heap_base } from "memory";
4
4
  import { BACK_SLASH, QUOTE } from "../../custom/chars";
5
5
  import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
6
6
  import { hex4_to_u16_swar } from "../../util/swar";
7
+ import { markProductionParseError } from "../error";
8
+ import { isValidStringEscape } from "../string-validation";
7
9
 
8
10
  // Overflow Pattern for Unicode Escapes (READ)
9
11
  // \u0001 0 \u00|01__ + 4
@@ -71,7 +73,7 @@ function deserializeEscapedString_SWAR(
71
73
  }
72
74
 
73
75
  let srcStart = escapeStart;
74
- const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
76
+ const srcEnd8 = srcEnd - 8;
75
77
 
76
78
  while (srcStart <= srcEnd8) {
77
79
  const block = load<u64>(srcStart);
@@ -107,6 +109,11 @@ function deserializeEscapedString_SWAR(
107
109
  const srcIdx = srcStart + laneIdx;
108
110
  if ((load<u32>(srcIdx) & 0xffff) !== 0x5c) continue; // false positive
109
111
  bs.offset += laneIdx;
112
+ if (!isValidStringEscape(srcIdx, srcEnd)) {
113
+ bs.offset = bs.buffer + outStart;
114
+ markProductionParseError();
115
+ return changetype<string>(0);
116
+ }
110
117
  const code = load<u16>(srcIdx, 2);
111
118
  if (code !== 0x75) {
112
119
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -134,6 +141,11 @@ function deserializeEscapedString_SWAR(
134
141
  srcStart += 2;
135
142
  continue;
136
143
  }
144
+ if (!isValidStringEscape(srcStart, srcEnd)) {
145
+ bs.offset = bs.buffer + outStart;
146
+ markProductionParseError();
147
+ return changetype<string>(0);
148
+ }
137
149
  const code = load<u16>(srcStart, 2);
138
150
  if (code !== 0x75) {
139
151
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -256,16 +268,25 @@ function deserializeEscapedStringField_SWAR(
256
268
  ): usize {
257
269
  const prefixLen = <u32>(escapeStart - payloadStart);
258
270
  bs.offset = bs.buffer;
259
- bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
271
+ // `srcEnd` may be the end of a containing map/document when a generated
272
+ // parser is cursor-driven. Reserving that entire remainder for one escaped
273
+ // field retained multi-megabyte scratch buffers. Grow with the decoded
274
+ // string instead; escapes only shrink relative to the source.
275
+ bs.ensureSize(prefixLen + 1024);
260
276
  if (prefixLen != 0) {
261
277
  memory.copy(bs.buffer, payloadStart, prefixLen);
262
278
  bs.offset += prefixLen;
263
279
  }
264
280
 
265
281
  let srcStart = escapeStart;
266
- const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
282
+ const srcEnd8 = srcEnd - 8;
283
+ let scratchEnd = bs.buffer + bs.bufferSize - 8;
267
284
 
268
285
  while (srcStart <= srcEnd8) {
286
+ if (bs.offset > scratchEnd) {
287
+ bs.ensureSize(1024);
288
+ scratchEnd = bs.buffer + bs.bufferSize - 8;
289
+ }
269
290
  const block = load<u64>(srcStart);
270
291
  let mask = backslash_or_quote_mask(block);
271
292
  if (mask == 0) {
@@ -285,6 +306,10 @@ function deserializeEscapedStringField_SWAR(
285
306
  srcStart += 8;
286
307
  }
287
308
  const runLen = <u32>(srcStart - runStart);
309
+ if (bs.offset + runLen > scratchEnd) {
310
+ bs.ensureSize(runLen + 1024);
311
+ scratchEnd = bs.buffer + bs.bufferSize - 8;
312
+ }
288
313
  memory.copy(bs.offset, runStart, runLen);
289
314
  bs.offset += runLen;
290
315
  }
@@ -310,6 +335,11 @@ function deserializeEscapedStringField_SWAR(
310
335
  bs.offset = bs.buffer;
311
336
  return srcIdx + 2;
312
337
  }
338
+ if (!isValidStringEscape(srcIdx, srcEnd)) {
339
+ bs.offset = bs.buffer;
340
+ markProductionParseError();
341
+ return 0;
342
+ }
313
343
  const code = load<u16>(srcIdx, 2);
314
344
  if (code !== 0x75) {
315
345
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -330,6 +360,7 @@ function deserializeEscapedStringField_SWAR(
330
360
  }
331
361
 
332
362
  // scalar tail (< 8 bytes remaining)
363
+ bs.ensureSize(<u32>(srcEnd - srcStart) + 2);
333
364
  while (srcStart < srcEnd) {
334
365
  const char = load<u16>(srcStart);
335
366
  if (char == QUOTE) {
@@ -343,6 +374,11 @@ function deserializeEscapedStringField_SWAR(
343
374
  srcStart += 2;
344
375
  continue;
345
376
  }
377
+ if (!isValidStringEscape(srcStart, srcEnd)) {
378
+ bs.offset = bs.buffer;
379
+ markProductionParseError();
380
+ return 0;
381
+ }
346
382
  const code = load<u16>(srcStart, 2);
347
383
  if (code !== 0x75) {
348
384
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -356,122 +392,37 @@ function deserializeEscapedStringField_SWAR(
356
392
  }
357
393
 
358
394
  bs.offset = bs.buffer;
359
- abort("Unterminated string literal");
360
- return srcStart;
395
+ markProductionParseError();
396
+ return 0;
361
397
  }
362
398
 
363
- function deserializeEscapedStringContinuation_SWAR_MergedTuned(
364
- lastPtr: usize,
399
+ export function deserializeStringField_SWAR<T extends string | null>(
365
400
  srcStart: usize,
366
401
  srcEnd: usize,
367
- dstFieldPtr: usize,
402
+ dstObj: usize,
403
+ dstOffset: usize = 0,
368
404
  ): usize {
369
- const srcEnd8 = srcEnd - 8;
370
-
371
- while (srcStart <= srcEnd8) {
372
- const blockStart = srcStart;
373
- let mask = backslash_or_quote_mask(load<u64>(srcStart));
374
- if (mask === 0) {
375
- srcStart += 8;
376
- continue;
377
- }
378
-
379
- do {
380
- const laneIdx = usize(ctz(mask) >> 3);
381
- mask &= mask - 1;
382
- const srcIdx = srcStart + laneIdx;
383
- const char = load<u16>(srcIdx);
384
- if (char == QUOTE) {
385
- const runLen = <u32>(srcIdx - lastPtr);
386
- if (runLen != 0) {
387
- memory.copy(bs.offset, lastPtr, runLen);
388
- bs.offset += runLen;
389
- }
390
- writeStringToField(
391
- dstFieldPtr,
392
- bs.buffer,
393
- <u32>(bs.offset - bs.buffer),
394
- );
395
- bs.offset = bs.buffer;
396
- return srcIdx + 2;
397
- }
398
- if (char != BACK_SLASH) continue;
399
-
400
- const runLen = <u32>(srcIdx - lastPtr);
401
- if (runLen != 0) {
402
- memory.copy(bs.offset, lastPtr, runLen);
403
- bs.offset += runLen;
404
- }
405
-
406
- const chunk = load<u32>(srcIdx);
407
- const code = <u16>(chunk >> 16);
408
- if (code !== 0x75) {
409
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
410
- bs.offset += 2;
411
- lastPtr = srcIdx + 4;
412
- } else {
413
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
414
- bs.offset += 2;
415
- lastPtr = srcIdx + 12;
416
- }
417
- srcStart = lastPtr;
418
- break;
419
- } while (mask !== 0);
420
-
421
- if (srcStart == blockStart) srcStart += 8;
422
- }
423
-
424
- while (srcStart < srcEnd) {
425
- const tailChar = load<u16>(srcStart);
426
- if (tailChar == QUOTE) {
427
- const runLen = <u32>(srcStart - lastPtr);
428
- if (runLen != 0) {
429
- memory.copy(bs.offset, lastPtr, runLen);
430
- bs.offset += runLen;
431
- }
432
- writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
433
- bs.offset = bs.buffer;
434
- return srcStart + 2;
435
- }
436
- if (tailChar != BACK_SLASH) {
437
- srcStart += 2;
438
- continue;
439
- }
440
-
441
- const runLen = <u32>(srcStart - lastPtr);
442
- if (runLen != 0) {
443
- memory.copy(bs.offset, lastPtr, runLen);
444
- bs.offset += runLen;
445
- }
446
- const tailCode = load<u16>(srcStart, 2);
447
- if (tailCode !== 0x75) {
448
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + tailCode));
449
- bs.offset += 2;
450
- srcStart += 4;
451
- } else {
452
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
453
- bs.offset += 2;
454
- srcStart += 12;
455
- }
456
- lastPtr = srcStart;
405
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
406
+ markProductionParseError();
407
+ return 0;
457
408
  }
458
-
459
- bs.offset = bs.buffer;
460
- return srcStart;
409
+ return deserializeStringFieldTrusted_SWAR(
410
+ srcStart + 2,
411
+ srcEnd,
412
+ dstObj,
413
+ dstOffset,
414
+ );
461
415
  }
462
416
 
463
- export function deserializeStringField_SWAR<T extends string | null>(
464
- srcStart: usize,
417
+ export function deserializeStringFieldTrusted_SWAR(
418
+ payloadStart: usize,
465
419
  srcEnd: usize,
466
420
  dstObj: usize,
467
421
  dstOffset: usize = 0,
468
422
  ): usize {
469
423
  const dstFieldPtr = dstObj + dstOffset;
470
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
471
- abort("Expected leading quote");
472
-
473
- const payloadStart = srcStart + 2;
474
- srcStart = payloadStart;
424
+ let srcStart = payloadStart;
425
+ let pendingMask: u64 = 0;
475
426
 
476
427
  // Wide pre-scan: skip 16 bytes per iter while both halves are clean. The
477
428
  // common case (plain ASCII payloads, no escape) hits this loop exclusively
@@ -482,9 +433,15 @@ export function deserializeStringField_SWAR<T extends string | null>(
482
433
  // Test the first word before loading the second: short values and keys
483
434
  // close (or escape) within the first 8 bytes, so this skips the second
484
435
  // load on the common case while still skipping 16 bytes when both clean.
485
- if (backslash_or_quote_mask(load<u64>(srcStart)) != 0) break;
486
- if (backslash_or_quote_mask(load<u64>(srcStart, 8)) != 0) {
436
+ const firstMask = backslash_or_quote_mask(load<u64>(srcStart));
437
+ if (firstMask != 0) {
438
+ pendingMask = firstMask;
439
+ break;
440
+ }
441
+ const secondMask = backslash_or_quote_mask(load<u64>(srcStart, 8));
442
+ if (secondMask != 0) {
487
443
  srcStart += 8;
444
+ pendingMask = secondMask;
488
445
  break;
489
446
  }
490
447
  srcStart += 16;
@@ -493,7 +450,9 @@ export function deserializeStringField_SWAR<T extends string | null>(
493
450
 
494
451
  const srcEnd8 = srcEnd - 8;
495
452
  while (srcStart <= srcEnd8) {
496
- let mask = backslash_or_quote_mask(load<u64>(srcStart));
453
+ let mask = pendingMask;
454
+ if (mask == 0) mask = backslash_or_quote_mask(load<u64>(srcStart));
455
+ pendingMask = 0;
497
456
  if (mask === 0) {
498
457
  srcStart += 8;
499
458
  continue;
@@ -545,8 +504,8 @@ export function deserializeStringField_SWAR<T extends string | null>(
545
504
  srcStart += 2;
546
505
  }
547
506
 
548
- abort("Unterminated string literal");
549
- return srcStart;
507
+ markProductionParseError();
508
+ return 0;
550
509
  }
551
510
 
552
511
  /**
@@ -267,6 +267,12 @@ declare function deserializer(
267
267
  */
268
268
  declare const JSON_MODE: JSONMode;
269
269
 
270
+ /**
271
+ * Whether strict RFC 8259 validation is enabled. Injected by the transform from
272
+ * the `JSON_STRICT` build-time environment variable (default `false`).
273
+ */
274
+ declare const JSON_STRICT: bool;
275
+
270
276
  /**
271
277
  * Whether the string cache is enabled (default off). Injected from the
272
278
  * `JSON_CACHE` build-time environment variable. When on, repeated strings are