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
@@ -14,6 +14,11 @@ import {
14
14
  import { isSpace, scanStringEnd } from "../../util";
15
15
  import { deserializeFloat } from "../index/float";
16
16
  import { deserializeString } from "../index/string";
17
+ import {
18
+ failProductionParse,
19
+ markProductionParseError,
20
+ takeProductionParseError,
21
+ } from "../error";
17
22
 
18
23
  // "true" as a u64 of UTF-16 code units (LE).
19
24
  const TRUE_WORD: u64 = 28429475166421108;
@@ -42,6 +47,7 @@ export function setParseSrc(s: string): void {
42
47
  export function getParseSrc(): string {
43
48
  return parseSrc;
44
49
  }
50
+ export { markProductionParseError, takeProductionParseError };
45
51
 
46
52
  export function deserializeObject(
47
53
  srcStart: usize,
@@ -58,21 +64,19 @@ export function deserializeObject(
58
64
 
59
65
  while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
60
66
 
61
- if (srcEnd == srcStart)
62
- throw new Error("Input string had zero length or was all whitespace");
63
- if (load<u16>(srcStart) != BRACE_LEFT)
64
- throw new Error(
65
- "Expected '{' at start of object at position " +
66
- (srcEnd - srcStart).toString(),
67
- );
68
- if (load<u16>(srcEnd - 2) != BRACE_RIGHT)
69
- throw new Error(
70
- "Expected '}' at end of object at position " +
71
- (srcEnd - srcStart).toString(),
72
- );
67
+ if (
68
+ srcEnd == srcStart ||
69
+ load<u16>(srcStart) != BRACE_LEFT ||
70
+ load<u16>(srcEnd - 2) != BRACE_RIGHT
71
+ ) {
72
+ failProductionParse();
73
+ return changetype<JSON.Obj>(0);
74
+ }
73
75
 
74
- parseObjectBody(out, srcStart + 2, srcEnd);
75
- return out;
76
+ out.reserveForParse((srcEnd - srcStart) >> 1);
77
+ return parseObjectBody(out, srcStart + 2, srcEnd) != 0
78
+ ? out
79
+ : changetype<JSON.Obj>(0);
76
80
  }
77
81
 
78
82
  export function deserializeJsonArray(
@@ -88,15 +92,18 @@ export function deserializeJsonArray(
88
92
 
89
93
  while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
90
94
 
91
- if (srcEnd == srcStart)
92
- throw new Error("Input string had zero length or was all whitespace");
93
- if (load<u16>(srcStart) != BRACKET_LEFT)
94
- throw new Error("Expected '[' at start of array");
95
- if (load<u16>(srcEnd - 2) != BRACKET_RIGHT)
96
- throw new Error("Expected ']' at end of array");
95
+ if (
96
+ srcEnd == srcStart ||
97
+ load<u16>(srcStart) != BRACKET_LEFT ||
98
+ load<u16>(srcEnd - 2) != BRACKET_RIGHT
99
+ ) {
100
+ failProductionParse();
101
+ return changetype<JSON.Arr>(0);
102
+ }
97
103
 
98
- parseArrayBodySlots(out, srcStart + 2, srcEnd);
99
- return out;
104
+ return parseArrayBodySlots(out, srcStart + 2, srcEnd) != 0
105
+ ? out
106
+ : changetype<JSON.Arr>(0);
100
107
  }
101
108
 
102
109
  /**
@@ -120,13 +127,14 @@ export function parseArrayBodySlots(
120
127
  if (parseSrc.length != 0) {
121
128
  out.pushRawSlot(parseSlotBits(srcStart, srcEnd));
122
129
  } else {
123
- out.pushRawSlot(
124
- JSON.Value.bitsFrom<JSON.Value>(parseValue(srcStart, srcEnd)),
125
- );
130
+ const value = parseValue(srcStart, srcEnd);
131
+ if (parseValueEnd == 0) return 0;
132
+ out.pushRawSlot(JSON.Value.bitsFrom<JSON.Value>(value));
126
133
  }
134
+ if (parseValueEnd == 0) return 0;
127
135
  srcStart = parseValueEnd;
128
136
  }
129
- return srcEnd;
137
+ return 0;
130
138
  }
131
139
 
132
140
  /**
@@ -137,9 +145,19 @@ export function parseArrayBodySlots(
137
145
  * to be set (the caller guards), since a lazy slot points into it.
138
146
  */
139
147
  function parseSlotBits(srcStart: usize, srcEnd: usize): u64 {
148
+ if (srcStart >= srcEnd) {
149
+ parseValueEnd = 0;
150
+ markProductionParseError();
151
+ return JSON.Value.nullBits();
152
+ }
140
153
  const code = load<u16>(srcStart);
141
154
  if (code == QUOTE || code == BRACE_LEFT || code == BRACKET_LEFT) {
142
155
  const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
156
+ if (end == 0) {
157
+ parseValueEnd = 0;
158
+ markProductionParseError();
159
+ return JSON.Value.nullBits();
160
+ }
143
161
  parseValueEnd = end;
144
162
  return JSON.Value.lazyBits(changetype<usize>(parseSrc), srcStart, end);
145
163
  } else if (code - 48 <= 9 || code == 45) {
@@ -153,24 +171,33 @@ function parseSlotBits(srcStart: usize, srcEnd: usize): u64 {
153
171
  parseValueEnd = p;
154
172
  return JSON.Value.f64Bits(deserializeFloat<f64>(srcStart, p));
155
173
  } else if (code == CHAR_T) {
156
- if (load<u64>(srcStart) != TRUE_WORD)
157
- throw new Error("Expected 'true' in JSON");
174
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != TRUE_WORD) {
175
+ parseValueEnd = 0;
176
+ markProductionParseError();
177
+ return JSON.Value.nullBits();
178
+ }
158
179
  parseValueEnd = srcStart + 8;
159
180
  return JSON.Value.boolBits(true);
160
181
  } else if (code == CHAR_F) {
161
- if (load<u64>(srcStart, 2) != ALSE_WORD)
162
- throw new Error("Expected 'false' in JSON");
182
+ if (srcEnd - srcStart < 10 || load<u64>(srcStart, 2) != ALSE_WORD) {
183
+ parseValueEnd = 0;
184
+ markProductionParseError();
185
+ return JSON.Value.nullBits();
186
+ }
163
187
  parseValueEnd = srcStart + 10;
164
188
  return JSON.Value.boolBits(false);
165
189
  } else if (code == CHAR_N) {
166
- if (load<u64>(srcStart) != NULL_WORD)
167
- throw new Error("Expected 'null' in JSON");
190
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != NULL_WORD) {
191
+ parseValueEnd = 0;
192
+ markProductionParseError();
193
+ return JSON.Value.nullBits();
194
+ }
168
195
  parseValueEnd = srcStart + 8;
169
196
  return JSON.Value.nullBits();
170
197
  }
171
- throw new Error(
172
- "Unexpected character in JSON '" + String.fromCharCode(code) + "'",
173
- );
198
+ parseValueEnd = 0;
199
+ markProductionParseError();
200
+ return JSON.Value.nullBits();
174
201
  }
175
202
 
176
203
  /**
@@ -185,12 +212,27 @@ export function lastValueEnd(): usize {
185
212
  }
186
213
 
187
214
  export function parseValue(srcStart: usize, srcEnd: usize): JSON.Value {
215
+ if (srcStart >= srcEnd) {
216
+ parseValueEnd = 0;
217
+ markProductionParseError();
218
+ return changetype<JSON.Value>(0);
219
+ }
188
220
  const code = load<u16>(srcStart);
189
221
  if (code == QUOTE) {
190
222
  const end = scanStringEnd(srcStart, srcEnd);
191
- if (end >= srcEnd) throw new Error("Unterminated string in JSON");
223
+ if (end >= srcEnd) {
224
+ parseValueEnd = 0;
225
+ markProductionParseError();
226
+ return changetype<JSON.Value>(0);
227
+ }
192
228
  parseValueEnd = end + 2;
193
- return JSON.Value.from(deserializeString(srcStart, end + 2));
229
+ const value = deserializeString(srcStart, end + 2);
230
+ if (changetype<usize>(value) == 0) {
231
+ parseValueEnd = 0;
232
+ markProductionParseError();
233
+ return changetype<JSON.Value>(0);
234
+ }
235
+ return JSON.Value.from(value);
194
236
  } else if (code == BRACE_LEFT) {
195
237
  const obj = new JSON.Obj();
196
238
  parseValueEnd = parseObjectBody(obj, srcStart + 2, srcEnd);
@@ -210,24 +252,33 @@ export function parseValue(srcStart: usize, srcEnd: usize): JSON.Value {
210
252
  parseValueEnd = p;
211
253
  return JSON.Value.from(deserializeFloat<f64>(srcStart, p));
212
254
  } else if (code == CHAR_T) {
213
- if (load<u64>(srcStart) != TRUE_WORD)
214
- throw new Error("Expected 'true' in JSON");
255
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != TRUE_WORD) {
256
+ parseValueEnd = 0;
257
+ markProductionParseError();
258
+ return changetype<JSON.Value>(0);
259
+ }
215
260
  parseValueEnd = srcStart + 8;
216
261
  return JSON.Value.from(true);
217
262
  } else if (code == CHAR_F) {
218
- if (load<u64>(srcStart, 2) != ALSE_WORD)
219
- throw new Error("Expected 'false' in JSON");
263
+ if (srcEnd - srcStart < 10 || load<u64>(srcStart, 2) != ALSE_WORD) {
264
+ parseValueEnd = 0;
265
+ markProductionParseError();
266
+ return changetype<JSON.Value>(0);
267
+ }
220
268
  parseValueEnd = srcStart + 10;
221
269
  return JSON.Value.from(false);
222
270
  } else if (code == CHAR_N) {
223
- if (load<u64>(srcStart) != NULL_WORD)
224
- throw new Error("Expected 'null' in JSON");
271
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != NULL_WORD) {
272
+ parseValueEnd = 0;
273
+ markProductionParseError();
274
+ return changetype<JSON.Value>(0);
275
+ }
225
276
  parseValueEnd = srcStart + 8;
226
277
  return JSON.Value.from<usize>(0);
227
278
  }
228
- throw new Error(
229
- "Unexpected character in JSON '" + String.fromCharCode(code) + "'",
230
- );
279
+ parseValueEnd = 0;
280
+ markProductionParseError();
281
+ return changetype<JSON.Value>(0);
231
282
  }
232
283
 
233
284
  /**
@@ -254,14 +305,20 @@ export function parseArrayBody(
254
305
  // Defer strings and composites (the allocating shapes): store the raw
255
306
  // slice, parse on first access. Cheap primitives stay eager below.
256
307
  const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
308
+ if (end == 0) {
309
+ markProductionParseError();
310
+ return 0;
311
+ }
257
312
  out.push(JSON.Value.fromSlice(srcStart, end, parseSrc));
258
313
  srcStart = end;
259
314
  } else {
260
- out.push(parseValue(srcStart, srcEnd));
315
+ const value = parseValue(srcStart, srcEnd);
316
+ if (parseValueEnd == 0) return 0;
317
+ out.push(value);
261
318
  srcStart = parseValueEnd;
262
319
  }
263
320
  }
264
- return srcEnd;
321
+ return 0;
265
322
  }
266
323
 
267
324
  /**
@@ -274,6 +331,7 @@ export function parseObjectBody(
274
331
  srcStart: usize,
275
332
  srcEnd: usize,
276
333
  ): usize {
334
+ const objectStart = srcStart - 2;
277
335
  // Anchor the source for this object's deferred value slots (start pointers
278
336
  // into it). Set here so every caller - deserializeObject, the JSON.Obj[]
279
337
  // array path, parseValue, the map path - gets it. Empty off the parse path,
@@ -287,44 +345,43 @@ export function parseObjectBody(
287
345
  srcStart += 2;
288
346
  continue;
289
347
  }
290
- if (code == BRACE_RIGHT) return srcStart + 2;
348
+ if (code == BRACE_RIGHT) {
349
+ const objectEnd = srcStart + 2;
350
+ out._rawStart = objectStart;
351
+ out._rawEnd = objectEnd;
352
+ return objectEnd;
353
+ }
291
354
 
292
355
  // --- key ---
293
- if (code != QUOTE)
294
- throw new Error(
295
- "Unexpected character in JSON object '" +
296
- String.fromCharCode(code) +
297
- "' at position " +
298
- (srcEnd - srcStart).toString(),
299
- );
356
+ if (code != QUOTE) return failProductionParse();
300
357
  const keyStart = srcStart + 2;
301
358
  srcStart = scanStringEnd(srcStart, srcEnd);
302
- if (srcStart >= srcEnd)
303
- throw new Error("Unterminated string in JSON object");
359
+ if (srcStart >= srcEnd) return failProductionParse();
304
360
  const keyEnd = srcStart;
305
361
  srcStart += 2;
306
362
 
307
363
  // --- colon ---
308
364
  while (srcStart < srcEnd && isSpace((code = load<u16>(srcStart))))
309
365
  srcStart += 2;
310
- if (srcStart >= srcEnd || code != COLON)
311
- throw new Error(
312
- "Expected ':' after key at position " + (srcEnd - srcStart).toString(),
313
- );
366
+ if (srcStart >= srcEnd || code != COLON) return failProductionParse();
314
367
  srcStart += 2;
315
368
 
316
369
  // --- value ---
317
370
  while (srcStart < srcEnd && isSpace((code = load<u16>(srcStart))))
318
371
  srcStart += 2;
372
+ if (srcStart >= srcEnd) return failProductionParse();
319
373
  if (parseSrc.length != 0) {
320
374
  // Parsing: store a NaN-boxed slot directly (strings/composites deferred,
321
375
  // scalars eager) - no per-value JSON.Value object.
322
- out.appendRawSlot(keyStart, keyEnd, parseSlotBits(srcStart, srcEnd));
376
+ out.appendParsedSlot(keyStart, keyEnd, parseSlotBits(srcStart, srcEnd));
377
+ if (parseValueEnd == 0) return 0;
323
378
  } else {
324
379
  // Off the parse path (no source anchor): box eagerly.
325
- out.appendRaw(keyStart, keyEnd, parseValue(srcStart, srcEnd));
380
+ const value = parseValue(srcStart, srcEnd);
381
+ if (parseValueEnd == 0) return 0;
382
+ out.appendRaw(keyStart, keyEnd, value);
326
383
  }
327
384
  srcStart = parseValueEnd;
328
385
  }
329
- return srcEnd;
386
+ return 0;
330
387
  }
@@ -1,6 +1,18 @@
1
1
  import { JSON } from "../..";
2
2
  import { ptrToStr } from "../../util/ptrToStr";
3
3
 
4
- export function deserializeRaw(srcStart: usize, srcEnd: usize): JSON.Raw {
4
+ export function deserializeRaw(
5
+ srcStart: usize,
6
+ srcEnd: usize,
7
+ dst: usize = 0,
8
+ ): JSON.Raw {
9
+ const size = srcEnd - srcStart;
10
+ if (dst) {
11
+ const out = changetype<JSON.Raw>(dst);
12
+ const data = changetype<string>(__renew(changetype<usize>(out.data), size));
13
+ memory.copy(changetype<usize>(data), srcStart, size);
14
+ out.data = data;
15
+ return out;
16
+ }
5
17
  return JSON.Raw.from(ptrToStr(srcStart, srcEnd));
6
18
  }
@@ -63,7 +63,6 @@ function deserializeSetDirect<T extends Set<any>>(
63
63
  if (load<u16>(srcStart) == BRACKET_RIGHT) return srcStart + 2;
64
64
 
65
65
  while (srcStart < srcEnd) {
66
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
67
66
  const code = load<u16>(srcStart);
68
67
 
69
68
  // @ts-ignore: type
@@ -21,7 +21,8 @@ function materializeStaticArray<T extends StaticArray<any>>(
21
21
  src: valueof<T>[],
22
22
  dst: usize,
23
23
  ): T {
24
- const byteLength = <usize>src.length * sizeof<valueof<T>>();
24
+ const len = src.length;
25
+ const byteLength = <usize>len * sizeof<valueof<T>>();
25
26
  let out = dst;
26
27
 
27
28
  if (!out) {
@@ -31,7 +32,7 @@ function materializeStaticArray<T extends StaticArray<any>>(
31
32
  }
32
33
 
33
34
  const typed = changetype<T>(out);
34
- for (let i = 0; i < src.length; i++) {
35
+ for (let i = 0; i < len; i++) {
35
36
  unchecked((typed[i] = unchecked(src[i])));
36
37
  }
37
38
  return typed;
@@ -3,6 +3,8 @@ import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
3
3
  import { __heap_base } from "memory";
4
4
  import { BACK_SLASH, QUOTE } from "../../custom/chars";
5
5
  import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
6
+ import { markProductionParseError } from "../error";
7
+ import { isValidStringEscape } from "../string-validation";
6
8
 
7
9
  function hexDigit(c: u16): u32 {
8
10
  if (c <= 0x39) return c - 0x30; // '0'-'9'
@@ -19,46 +21,10 @@ function hex4ToU16(srcStart: usize): u16 {
19
21
  );
20
22
  }
21
23
 
22
- function isHexDigit(c: u16): bool {
23
- return (
24
- (c >= 0x30 && c <= 0x39) ||
25
- (c >= 0x41 && c <= 0x46) ||
26
- (c >= 0x61 && c <= 0x66)
27
- );
28
- }
29
-
30
- // Strict RFC 8259 check for the char following a backslash, at [escPtr, srcEnd).
31
- // Legal escapes: " \ / b f n r t and \uXXXX (4 hex digits). Throws otherwise:
32
- // unknown escape letter, a trailing backslash, or a short / non-hex \u.
33
- function validateEscape(escPtr: usize, srcEnd: usize): void {
34
- if (escPtr >= srcEnd)
35
- throw new Error("Invalid JSON string: incomplete escape");
36
- const code = load<u16>(escPtr);
37
- if (code == 0x75) {
38
- // \uXXXX
39
- if (escPtr + 10 > srcEnd)
40
- throw new Error("Invalid JSON string: incomplete \\u escape");
41
- if (
42
- !isHexDigit(load<u16>(escPtr, 2)) ||
43
- !isHexDigit(load<u16>(escPtr, 4)) ||
44
- !isHexDigit(load<u16>(escPtr, 6)) ||
45
- !isHexDigit(load<u16>(escPtr, 8))
46
- )
47
- throw new Error("Invalid JSON string: \\u escape needs 4 hex digits");
48
- return;
49
- }
50
- // short escapes: " \ / b f n r t
51
- if (
52
- code != 0x22 &&
53
- code != 0x5c &&
54
- code != 0x2f &&
55
- code != 0x62 &&
56
- code != 0x66 &&
57
- code != 0x6e &&
58
- code != 0x72 &&
59
- code != 0x74
60
- )
61
- throw new Error("Invalid JSON string: illegal escape");
24
+ // RFC 8259 check for the char following a backslash, at [escPtr, srcEnd).
25
+ // Legal escapes: " \ / b f n r t and \uXXXX (4 hex digits).
26
+ function validateEscape(escPtr: usize, srcEnd: usize): bool {
27
+ return isValidStringEscape(escPtr - 2, srcEnd);
62
28
  }
63
29
 
64
30
  export function deserializeString_NAIVE(
@@ -72,8 +38,10 @@ export function deserializeString_NAIVE(
72
38
  srcEnd - srcStart < 4 ||
73
39
  load<u16>(srcStart) != QUOTE ||
74
40
  load<u16>(srcEnd - 2) != QUOTE
75
- )
76
- throw new Error("Invalid JSON string: missing surrounding quotes");
41
+ ) {
42
+ markProductionParseError();
43
+ return changetype<string>(0);
44
+ }
77
45
  // Strip quotes
78
46
  srcStart += 2;
79
47
  srcEnd -= 2;
@@ -88,13 +56,20 @@ export function deserializeString_NAIVE(
88
56
  // Early exit
89
57
  if (block !== 0x5c) {
90
58
  // RFC 8259: literal control chars (U+0000..U+001F) must be escaped.
91
- if (block < 0x20)
92
- throw new Error("Invalid JSON string: unescaped control character");
59
+ if (block < 0x20) {
60
+ bs.offset = bs.buffer + outStart;
61
+ markProductionParseError();
62
+ return changetype<string>(0);
63
+ }
93
64
  bs.offset += 2;
94
65
  continue;
95
66
  }
96
67
 
97
- validateEscape(srcStart, srcEnd);
68
+ if (!validateEscape(srcStart, srcEnd)) {
69
+ bs.offset = bs.buffer + outStart;
70
+ markProductionParseError();
71
+ return changetype<string>(0);
72
+ }
98
73
  const code = load<u16>(srcStart);
99
74
  if (code !== 0x75) {
100
75
  // Short escapes (\n \t \" \\)
@@ -191,7 +166,7 @@ function deserializeEscapedStringField_NAIVE(
191
166
  bs.offset += 2;
192
167
  }
193
168
 
194
- abort("Expected closing quote");
169
+ markProductionParseError();
195
170
  return 0;
196
171
  }
197
172
 
@@ -206,12 +181,26 @@ export function deserializeStringField_NAIVE<T extends string | null>(
206
181
  dstObj: usize,
207
182
  dstOffset: usize = 0,
208
183
  ): usize {
209
- const dstFieldPtr = dstObj + dstOffset;
210
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
211
- abort("Expected leading quote");
184
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
185
+ markProductionParseError();
186
+ return 0;
187
+ }
188
+ return deserializeStringFieldTrusted_NAIVE(
189
+ srcStart + 2,
190
+ srcEnd,
191
+ dstObj,
192
+ dstOffset,
193
+ );
194
+ }
212
195
 
213
- const payloadStart = srcStart + 2;
214
- srcStart = payloadStart;
196
+ export function deserializeStringFieldTrusted_NAIVE(
197
+ payloadStart: usize,
198
+ srcEnd: usize,
199
+ dstObj: usize,
200
+ dstOffset: usize = 0,
201
+ ): usize {
202
+ const dstFieldPtr = dstObj + dstOffset;
203
+ let srcStart = payloadStart;
215
204
 
216
205
  // Scan for the closing quote without touching the scratch buffer. For the
217
206
  // common escape-free case the bytes are a verbatim slice of the source, so we
@@ -238,6 +227,6 @@ export function deserializeStringField_NAIVE<T extends string | null>(
238
227
  srcStart += 2;
239
228
  }
240
229
 
241
- abort("Expected closing quote");
230
+ markProductionParseError();
242
231
  return 0;
243
232
  }