jseption 0.0.2 → 0.0.4

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.
@@ -0,0 +1,1071 @@
1
+ /*
2
+ * Bytecode serialization: JS_WriteObject / JS_ReadObject.
3
+ *
4
+ * Writes the function tree produced by JS_EVAL_FLAG_COMPILE_ONLY to bytes and
5
+ * reads it back into any runtime of the same jseption version, so that a
6
+ * script can be compiled ahead of time and loaded without parsing. The design
7
+ * follows quickjs.c (bc_write_* / bc_read_*): atoms are runtime specific, so
8
+ * the writer emits an atom table and renumbers every atom (in the function
9
+ * fields and in the atom operands of the byte code, found by walking the
10
+ * opcodes by their format), and the reader interns the strings in its own
11
+ * runtime and patches the operands back.
12
+ *
13
+ * Only load bytecode you produced yourself: like in QuickJS, the reader checks
14
+ * the format (header, bounds, tags, indices, opcode boundaries, checksum), not
15
+ * the semantics of the byte code, and hand-made byte code can reach states the
16
+ * parser never emits.
17
+ *
18
+ * Format (all integers are unsigned LEB128 unless stated otherwise):
19
+ *
20
+ * magic 4 bytes "JSBC"
21
+ * format u8 (BC_FORMAT)
22
+ * version length + ASCII bytes: the jseption package version
23
+ * flags BC_FLAG_*
24
+ * strings count, then per string (length << 1 | wide), then the
25
+ * byte length and bytes of the Latin-1 blob, then the
26
+ * byte length and bytes of the UTF-16LE blob
27
+ * atoms count, then the string index of each atom
28
+ * root a tagged value (BC_TAG_FUNCTION_BYTECODE)
29
+ * checksum 8 bytes: two 32-bit little-endian sums (Fletcher style, over
30
+ * little-endian 32-bit words) of everything before them
31
+ *
32
+ * Atom references (function fields and byte code operands) use the encoding of
33
+ * bc_atom_to_idx: predefined atoms (< JS_ATOM_END) and tagged integer atoms are
34
+ * kept, other atoms become JS_ATOM_END + their index in the atom table.
35
+ */
36
+ import { JS_ATOM_END, JS_ATOM_raw, JS_ATOM_TAG_INT, JS_ATOM_TYPE_STRING, __JS_AtomIsTaggedInt } from "./atoms.js";
37
+ import { JS_GetFunctionData, JS_ThrowSyntaxError, JS_ThrowTypeError, JS_NewArray, JS_DefinePropertyValue, JS_DefinePropertyValueUint32, JS_IsException, JS_PROP_ENUMERABLE, hooks } from "./quickjs.js";
38
+ import { JSClosureVar, JSFunctionBytecode, JSVarDef, JSModuleDef, JSReqModuleEntry, JSExportEntry, JSStarExportEntry, JSImportEntry } from "./values.js";
39
+ import { js_module_set_function, hooks_bytecode } from "./module.js";
40
+ import { short_opcode_info } from "./opcodes.js";
41
+ import { JSEPTION_VERSION } from "./version.js";
42
+ /* keep each function's source text (Function.prototype.toString); off by default */
43
+ export const JS_WRITE_OBJ_SOURCE = 1 << 0;
44
+ export const BC_MAGIC = [0x4a, 0x53, 0x42, 0x43]; /* "JSBC" */
45
+ export const BC_FORMAT = 1;
46
+ const BC_FLAG_SOURCE = 1 << 0;
47
+ /* JSFunctionBytecode flags */
48
+ const F_HAS_PROTOTYPE = 1 << 0;
49
+ const F_SIMPLE_PARAMS = 1 << 1;
50
+ const F_DERIVED_CTOR = 1 << 2;
51
+ const F_HOME_OBJECT = 1 << 3;
52
+ const F_NEW_TARGET = 1 << 4;
53
+ const F_SUPER_CALL = 1 << 5;
54
+ const F_SUPER = 1 << 6;
55
+ const F_ARGUMENTS = 1 << 7;
56
+ const F_HAS_DEBUG = 1 << 8;
57
+ const F_EVAL = 1 << 9;
58
+ const F_HAS_SOURCE = 1 << 10;
59
+ /* JSVarDef / JSClosureVar flags */
60
+ const V_CONST = 1 << 0;
61
+ const V_LEXICAL = 1 << 1;
62
+ const V_CAPTURED = 1 << 2;
63
+ const V_STATIC_PRIVATE = 1 << 3;
64
+ const V_LOCAL = 1 << 4;
65
+ const V_ARG = 1 << 5;
66
+ const VAR_KIND_MAX = 9 /* JSVarKind.PRIVATE_GETTER_SETTER */;
67
+ /* the size of each opcode of the final byte code (0: not an opcode) and what its operand
68
+ holds, indexed by the opcode byte, so that walking the byte code costs a few array reads
69
+ per instruction */
70
+ const OPK_ATOM = 1, OPK_CONST = 2, OPK_CONST8 = 3, OPK_FCLOSURE = 4, OPK_FCLOSURE8 = 5;
71
+ const OP_SIZE = new Uint8Array(256);
72
+ const OP_KIND = new Uint8Array(256);
73
+ for (let op = 0; op < 256; op++) {
74
+ const info = short_opcode_info(op);
75
+ if (info === undefined)
76
+ continue;
77
+ OP_SIZE[op] = info.size;
78
+ switch (info.fmt) {
79
+ case 23 /* OpFmt.atom */:
80
+ case 24 /* OpFmt.atom_u8 */:
81
+ case 25 /* OpFmt.atom_u16 */:
82
+ case 26 /* OpFmt.atom_label_u8 */:
83
+ case 27 /* OpFmt.atom_label_u16 */:
84
+ OP_KIND[op] = OPK_ATOM;
85
+ break;
86
+ case 21 /* OpFmt.const */:
87
+ OP_KIND[op] = OPK_CONST;
88
+ break;
89
+ case 8 /* OpFmt.const8 */:
90
+ OP_KIND[op] = OPK_CONST8;
91
+ break;
92
+ }
93
+ }
94
+ OP_KIND[3 /* OP.fclosure */] = OPK_FCLOSURE;
95
+ OP_KIND[194 /* OP.fclosure8 */] = OPK_FCLOSURE8;
96
+ /* ---------------------------------------------------------------------
97
+ Writer
98
+ --------------------------------------------------------------------- */
99
+ class BCWriteError extends Error {
100
+ }
101
+ class ByteBuf {
102
+ buf = new Uint8Array(4096);
103
+ len = 0;
104
+ f64 = new Float64Array(1);
105
+ f64b = new Uint8Array(this.f64.buffer);
106
+ grow(n) {
107
+ let size = this.buf.length * 2;
108
+ while (size < this.len + n)
109
+ size *= 2;
110
+ const b = new Uint8Array(size);
111
+ b.set(this.buf.subarray(0, this.len));
112
+ this.buf = b;
113
+ }
114
+ u8(v) {
115
+ if (this.len >= this.buf.length)
116
+ this.grow(1);
117
+ this.buf[this.len++] = v;
118
+ }
119
+ leb(v) {
120
+ if (this.len + 5 > this.buf.length)
121
+ this.grow(5);
122
+ const buf = this.buf;
123
+ while (v >= 0x80) {
124
+ buf[this.len++] = (v % 0x80) | 0x80;
125
+ v = Math.floor(v / 0x80);
126
+ }
127
+ buf[this.len++] = v;
128
+ }
129
+ sleb(v) {
130
+ this.leb(v < 0 ? -2 * v - 1 : 2 * v);
131
+ }
132
+ f64v(v) {
133
+ if (this.len + 8 > this.buf.length)
134
+ this.grow(8);
135
+ this.f64[0] = v;
136
+ const b = this.f64b;
137
+ /* little endian on the wire */
138
+ if (LITTLE_ENDIAN)
139
+ for (let i = 0; i < 8; i++)
140
+ this.buf[this.len++] = b[i];
141
+ else
142
+ for (let i = 7; i >= 0; i--)
143
+ this.buf[this.len++] = b[i];
144
+ }
145
+ bytes(src) {
146
+ if (this.len + src.length > this.buf.length)
147
+ this.grow(src.length);
148
+ this.buf.set(src, this.len);
149
+ this.len += src.length;
150
+ }
151
+ result() { return this.buf.slice(0, this.len); }
152
+ }
153
+ const LITTLE_ENDIAN = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
154
+ class BCWriter {
155
+ ctx;
156
+ flags;
157
+ out = new ByteBuf();
158
+ strings = [];
159
+ string_idx = new Map();
160
+ atoms = []; /* string index of each atom table entry */
161
+ atom_idx = new Map();
162
+ constructor(ctx, flags) {
163
+ this.ctx = ctx;
164
+ this.flags = flags;
165
+ }
166
+ string_ref(s) {
167
+ let i = this.string_idx.get(s);
168
+ if (i === undefined) {
169
+ i = this.strings.length;
170
+ this.strings.push(s);
171
+ this.string_idx.set(s, i);
172
+ }
173
+ return i;
174
+ }
175
+ /* bc_atom_to_idx */
176
+ atom_to_idx(atom) {
177
+ if (atom < JS_ATOM_END || __JS_AtomIsTaggedInt(atom))
178
+ return atom;
179
+ let i = this.atom_idx.get(atom);
180
+ if (i === undefined) {
181
+ const atoms = this.ctx.rt.atoms;
182
+ if (atoms.getType(atom) !== JS_ATOM_TYPE_STRING)
183
+ throw new BCWriteError('cannot serialize a symbol atom: ' + atoms.toDisplayStr(atom));
184
+ i = this.atoms.length;
185
+ this.atoms.push(this.string_ref(atoms.toStr(atom)));
186
+ this.atom_idx.set(atom, i);
187
+ }
188
+ return JS_ATOM_END + i;
189
+ }
190
+ atom(atom) { this.out.leb(this.atom_to_idx(atom)); }
191
+ value(v) {
192
+ const out = this.out;
193
+ switch (typeof v) {
194
+ case 'undefined':
195
+ out.u8(2 /* BCTag.UNDEFINED */);
196
+ return;
197
+ case 'boolean':
198
+ out.u8(v ? 4 /* BCTag.TRUE */ : 3 /* BCTag.FALSE */);
199
+ return;
200
+ case 'number':
201
+ if ((v | 0) === v && !(v === 0 && 1 / v < 0)) {
202
+ out.u8(5 /* BCTag.INT32 */);
203
+ out.sleb(v);
204
+ }
205
+ else {
206
+ out.u8(6 /* BCTag.FLOAT64 */);
207
+ out.f64v(v);
208
+ }
209
+ return;
210
+ case 'string':
211
+ out.u8(7 /* BCTag.STRING */);
212
+ out.leb(this.string_ref(v));
213
+ return;
214
+ case 'bigint':
215
+ out.u8(8 /* BCTag.BIG_INT */);
216
+ out.leb(this.string_ref(v.toString()));
217
+ return;
218
+ case 'object':
219
+ if (v === null) {
220
+ out.u8(1 /* BCTag.NULL */);
221
+ return;
222
+ }
223
+ if (v instanceof JSFunctionBytecode) {
224
+ this.function(v);
225
+ return;
226
+ }
227
+ if (Array.isArray(v) && Object.isFrozen(v) && Object.prototype.hasOwnProperty.call(v, 'raw')) {
228
+ this.template(v);
229
+ return;
230
+ }
231
+ break;
232
+ }
233
+ throw new BCWriteError('unsupported constant in the function tree: ' + describe(v));
234
+ }
235
+ /* a template object (js_parse_template): frozen array of cooked strings with a frozen
236
+ 'raw' array of raw strings */
237
+ template(arr) {
238
+ const out = this.out;
239
+ const raw = arr.raw;
240
+ if (!Array.isArray(raw) || raw.length !== arr.length)
241
+ throw new BCWriteError('malformed template object');
242
+ out.u8(10 /* BCTag.TEMPLATE_OBJECT */);
243
+ out.leb(arr.length);
244
+ for (let i = 0; i < arr.length; i++) {
245
+ const c = arr[i], r = raw[i];
246
+ if ((c !== undefined && typeof c !== 'string') || typeof r !== 'string')
247
+ throw new BCWriteError('malformed template object');
248
+ out.leb(c === undefined ? 0 : this.string_ref(c) + 1);
249
+ out.leb(this.string_ref(r));
250
+ }
251
+ }
252
+ function(b) {
253
+ const out = this.out;
254
+ const keep_source = (this.flags & JS_WRITE_OBJ_SOURCE) !== 0 && b.has_debug && b.debug.source !== null;
255
+ let flags = 0;
256
+ if (b.has_prototype)
257
+ flags |= F_HAS_PROTOTYPE;
258
+ if (b.has_simple_parameter_list)
259
+ flags |= F_SIMPLE_PARAMS;
260
+ if (b.is_derived_class_constructor)
261
+ flags |= F_DERIVED_CTOR;
262
+ if (b.need_home_object)
263
+ flags |= F_HOME_OBJECT;
264
+ if (b.new_target_allowed)
265
+ flags |= F_NEW_TARGET;
266
+ if (b.super_call_allowed)
267
+ flags |= F_SUPER_CALL;
268
+ if (b.super_allowed)
269
+ flags |= F_SUPER;
270
+ if (b.arguments_allowed)
271
+ flags |= F_ARGUMENTS;
272
+ if (b.has_debug)
273
+ flags |= F_HAS_DEBUG;
274
+ if (b.is_direct_or_indirect_eval)
275
+ flags |= F_EVAL;
276
+ if (keep_source)
277
+ flags |= F_HAS_SOURCE;
278
+ if (b.vardefs.length !== b.arg_count + b.var_count)
279
+ throw new BCWriteError('inconsistent variable count');
280
+ out.u8(9 /* BCTag.FUNCTION_BYTECODE */);
281
+ out.leb(flags);
282
+ out.u8(b.js_mode);
283
+ out.u8(b.func_kind);
284
+ this.atom(b.func_name);
285
+ out.leb(b.arg_count);
286
+ out.leb(b.var_count);
287
+ out.leb(b.defined_arg_count);
288
+ out.leb(b.stack_size);
289
+ out.leb(b.closure_var.length);
290
+ out.leb(b.cpool.length);
291
+ out.leb(b.byte_code_len);
292
+ for (const vd of b.vardefs) {
293
+ this.atom(vd.var_name);
294
+ out.leb(vd.scope_level);
295
+ out.sleb(vd.scope_next);
296
+ out.u8((vd.is_const ? V_CONST : 0) | (vd.is_lexical ? V_LEXICAL : 0) | (vd.is_captured ? V_CAPTURED : 0) |
297
+ (vd.is_static_private ? V_STATIC_PRIVATE : 0));
298
+ out.u8(vd.var_kind);
299
+ out.sleb(vd.func_pool_idx);
300
+ }
301
+ for (const cv of b.closure_var) {
302
+ this.atom(cv.var_name);
303
+ out.leb(cv.var_idx);
304
+ out.u8((cv.is_const ? V_CONST : 0) | (cv.is_lexical ? V_LEXICAL : 0) | (cv.is_local ? V_LOCAL : 0) |
305
+ (cv.is_arg ? V_ARG : 0));
306
+ out.u8(cv.var_kind);
307
+ }
308
+ out.bytes(this.byte_code(b));
309
+ if (b.has_debug) {
310
+ this.atom(b.debug.filename);
311
+ const pc2line = b.debug.pc2line;
312
+ if (pc2line === null)
313
+ out.leb(0);
314
+ else {
315
+ out.leb(pc2line.length);
316
+ out.bytes(pc2line);
317
+ }
318
+ if (keep_source)
319
+ out.leb(this.string_ref(b.debug.source));
320
+ }
321
+ for (const v of b.cpool)
322
+ this.value(v);
323
+ }
324
+ /* a compiled module (JS_WriteModule): its requests, entries and body function */
325
+ module(m) {
326
+ const out = this.out;
327
+ out.u8(11 /* BCTag.MODULE */);
328
+ this.atom(m.module_name);
329
+ out.leb(m.req_module_entries.length);
330
+ for (const rme of m.req_module_entries) {
331
+ this.atom(rme.module_name);
332
+ if (rme.attributes === null) {
333
+ out.leb(0);
334
+ }
335
+ else {
336
+ const keys = Object.keys(rme.attributes);
337
+ out.leb(keys.length + 1);
338
+ for (const k of keys) {
339
+ out.leb(this.string_ref(k));
340
+ out.leb(this.string_ref(rme.attributes[k]));
341
+ }
342
+ }
343
+ }
344
+ out.leb(m.export_entries.length);
345
+ for (const me of m.export_entries) {
346
+ out.u8(me.export_type);
347
+ if (me.export_type === 0 /* JSExportType.LOCAL */) {
348
+ out.leb(me.var_idx);
349
+ }
350
+ else {
351
+ out.leb(me.req_module_idx);
352
+ this.atom(me.local_name);
353
+ }
354
+ this.atom(me.export_name);
355
+ }
356
+ out.leb(m.star_export_entries.length);
357
+ for (const se of m.star_export_entries)
358
+ out.leb(se.req_module_idx);
359
+ out.leb(m.import_entries.length);
360
+ for (const mi of m.import_entries) {
361
+ out.leb(mi.var_idx);
362
+ out.u8(mi.is_star ? 1 : 0);
363
+ this.atom(mi.import_name);
364
+ out.leb(mi.req_module_idx);
365
+ }
366
+ out.u8(m.has_tla ? 1 : 0);
367
+ this.function(m.func_bytecode);
368
+ }
369
+ /* the byte code with its atom operands renumbered (JS_WriteFunctionBytecode) */
370
+ byte_code(b) {
371
+ const len = b.byte_code_len;
372
+ const bc = b.byte_code_buf.slice(0, len);
373
+ let pos = 0;
374
+ while (pos < len) {
375
+ const op = bc[pos], size = OP_SIZE[op];
376
+ if (size === 0)
377
+ throw new BCWriteError(`invalid opcode ${op} at pc ${pos}`);
378
+ if (OP_KIND[op] === OPK_ATOM && pos + 5 <= len) {
379
+ const atom = (bc[pos + 1] | (bc[pos + 2] << 8) | (bc[pos + 3] << 16) | (bc[pos + 4] << 24)) >>> 0;
380
+ const v = this.atom_to_idx(atom);
381
+ bc[pos + 1] = v;
382
+ bc[pos + 2] = v >>> 8;
383
+ bc[pos + 3] = v >>> 16;
384
+ bc[pos + 4] = v >>> 24;
385
+ }
386
+ pos += size;
387
+ }
388
+ if (pos !== len)
389
+ throw new BCWriteError('truncated instruction at the end of the byte code');
390
+ return bc;
391
+ }
392
+ finish(body) {
393
+ const out = new ByteBuf();
394
+ for (const c of BC_MAGIC)
395
+ out.u8(c);
396
+ out.u8(BC_FORMAT);
397
+ out.leb(JSEPTION_VERSION.length);
398
+ for (let i = 0; i < JSEPTION_VERSION.length; i++)
399
+ out.u8(JSEPTION_VERSION.charCodeAt(i));
400
+ out.leb(this.flags & JS_WRITE_OBJ_SOURCE ? BC_FLAG_SOURCE : 0);
401
+ /* string table */
402
+ const strings = this.strings;
403
+ out.leb(strings.length);
404
+ let narrow_len = 0, wide_len = 0;
405
+ const wide = [];
406
+ for (const s of strings) {
407
+ let w = false;
408
+ for (let i = 0; i < s.length; i++)
409
+ if (s.charCodeAt(i) > 0xff) {
410
+ w = true;
411
+ break;
412
+ }
413
+ wide.push(w);
414
+ out.leb(s.length * 2 + (w ? 1 : 0));
415
+ if (w)
416
+ wide_len += s.length;
417
+ else
418
+ narrow_len += s.length;
419
+ }
420
+ out.leb(narrow_len);
421
+ const narrow = new Uint8Array(narrow_len);
422
+ let p = 0;
423
+ for (let k = 0; k < strings.length; k++) {
424
+ if (wide[k])
425
+ continue;
426
+ const s = strings[k];
427
+ for (let i = 0; i < s.length; i++)
428
+ narrow[p++] = s.charCodeAt(i);
429
+ }
430
+ out.bytes(narrow);
431
+ out.leb(wide_len * 2);
432
+ const wbuf = new Uint8Array(wide_len * 2);
433
+ p = 0;
434
+ for (let k = 0; k < strings.length; k++) {
435
+ if (!wide[k])
436
+ continue;
437
+ const s = strings[k];
438
+ for (let i = 0; i < s.length; i++) {
439
+ const c = s.charCodeAt(i);
440
+ wbuf[p++] = c;
441
+ wbuf[p++] = c >>> 8;
442
+ }
443
+ }
444
+ out.bytes(wbuf);
445
+ /* atom table */
446
+ out.leb(this.atoms.length);
447
+ for (const i of this.atoms)
448
+ out.leb(i);
449
+ out.bytes(body.buf.subarray(0, body.len));
450
+ const [a, b] = checksum(out.buf, out.len);
451
+ for (const v of [a, b]) {
452
+ out.u8(v & 0xff);
453
+ out.u8((v >>> 8) & 0xff);
454
+ out.u8((v >>> 16) & 0xff);
455
+ out.u8(v >>> 24);
456
+ }
457
+ return out.result();
458
+ }
459
+ }
460
+ function describe(v) {
461
+ if (typeof v === 'function')
462
+ return 'function object';
463
+ if (Array.isArray(v))
464
+ return 'array';
465
+ if (typeof v === 'object' && v !== null)
466
+ return 'object';
467
+ return typeof v;
468
+ }
469
+ const CHECKSUM_SIZE = 8;
470
+ /* Fletcher-style sums of buf[0, end) taken as little-endian 32-bit words (the tail bytewise):
471
+ detects truncation and corrupted bytes, at a quarter of the cost of a bytewise checksum
472
+ (it is not a defence against deliberate tampering) */
473
+ function checksum(buf, end) {
474
+ const n = end >>> 2;
475
+ let a = 1, b = 0;
476
+ if (LITTLE_ENDIAN) {
477
+ if (buf.byteOffset & 3)
478
+ buf = buf.slice(0, end);
479
+ const w = new Int32Array(buf.buffer, buf.byteOffset, n);
480
+ for (let i = 0; i < n; i++) {
481
+ a = (a + w[i]) | 0;
482
+ b = (b + a) | 0;
483
+ }
484
+ }
485
+ else {
486
+ for (let i = 0; i < n; i++) {
487
+ const p = i * 4;
488
+ a = (a + (buf[p] | (buf[p + 1] << 8) | (buf[p + 2] << 16) | (buf[p + 3] << 24))) | 0;
489
+ b = (b + a) | 0;
490
+ }
491
+ }
492
+ for (let i = n * 4; i < end; i++) {
493
+ a = (a + buf[i]) | 0;
494
+ b = (b + a) | 0;
495
+ }
496
+ return [a >>> 0, b >>> 0];
497
+ }
498
+ /* Serialize the function returned by JS_Eval(..., JS_EVAL_FLAG_COMPILE_ONLY) (or any
499
+ bytecode function that does not close over variables). Returns null with a pending
500
+ exception on failure. */
501
+ export function JS_WriteObject(ctx, obj, flags = 0) {
502
+ if (obj instanceof JSModuleDef) {
503
+ /* a module compiled with JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY */
504
+ if (obj.kind !== 0 /* JSModuleKind.JS */ || obj.func_bytecode === null) {
505
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: only JavaScript modules can be written');
506
+ return null;
507
+ }
508
+ const w = new BCWriter(ctx, flags);
509
+ try {
510
+ w.module(obj);
511
+ return w.finish(w.out);
512
+ }
513
+ catch (e) {
514
+ if (e instanceof BCWriteError) {
515
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: ' + e.message);
516
+ return null;
517
+ }
518
+ throw e;
519
+ }
520
+ }
521
+ const fd = JS_GetFunctionData(obj);
522
+ if (fd === null) {
523
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: not a bytecode function (expected the result of JS_EVAL_FLAG_COMPILE_ONLY)');
524
+ return null;
525
+ }
526
+ const b = fd.function_bytecode;
527
+ if (b.closure_var.length !== 0) {
528
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: the function closes over variables of an enclosing scope');
529
+ return null;
530
+ }
531
+ const w = new BCWriter(ctx, flags);
532
+ try {
533
+ w.function(b);
534
+ const body = w.out;
535
+ return w.finish(body);
536
+ }
537
+ catch (e) {
538
+ if (e instanceof BCWriteError) {
539
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: ' + e.message);
540
+ return null;
541
+ }
542
+ throw e;
543
+ }
544
+ }
545
+ /* ---------------------------------------------------------------------
546
+ Reader
547
+ --------------------------------------------------------------------- */
548
+ class BCReadError extends Error {
549
+ }
550
+ const MAX_DEPTH = 2000;
551
+ /* parser limits (define_var / add_arg: at most 65534 each; compute_stack_size: JS_STACK_SIZE_MAX) */
552
+ const MAX_LOCALS = 65535;
553
+ const MAX_STACK_SIZE = 65534;
554
+ class BCReader {
555
+ ctx;
556
+ pos = 0;
557
+ end;
558
+ strings = [];
559
+ atoms = [];
560
+ keep_source = false;
561
+ depth = 0;
562
+ f64 = new Float64Array(1);
563
+ f64b = new Uint8Array(this.f64.buffer);
564
+ buf;
565
+ /* the reader works on a private copy of the input: the byte code is patched in place and
566
+ the functions keep views of it (one copy instead of two per function) */
567
+ constructor(ctx, input) {
568
+ this.ctx = ctx;
569
+ this.buf = input.slice();
570
+ this.end = this.buf.length;
571
+ }
572
+ fail(msg) { throw new BCReadError(msg); }
573
+ truncated() { throw new BCReadError('read after the end of the buffer'); }
574
+ u8() {
575
+ if (this.pos >= this.end)
576
+ this.truncated();
577
+ return this.buf[this.pos++];
578
+ }
579
+ leb() {
580
+ const buf = this.buf;
581
+ let pos = this.pos;
582
+ if (pos >= this.end)
583
+ this.truncated();
584
+ let a = buf[pos++];
585
+ if (a < 0x80) {
586
+ this.pos = pos;
587
+ return a;
588
+ }
589
+ let v = a & 0x7f, mul = 0x80;
590
+ for (let i = 1;; i++) {
591
+ if (pos >= this.end)
592
+ this.truncated();
593
+ if (i >= 5)
594
+ this.fail('invalid LEB128 number');
595
+ a = buf[pos++];
596
+ v += (a & 0x7f) * mul;
597
+ if (a < 0x80)
598
+ break;
599
+ mul *= 0x80;
600
+ }
601
+ if (v > 0xffffffff)
602
+ this.fail('invalid LEB128 number');
603
+ this.pos = pos;
604
+ return v;
605
+ }
606
+ sleb() {
607
+ const v = this.leb();
608
+ return (v % 2 === 1) ? -((v + 1) / 2) : v / 2;
609
+ }
610
+ f64v() {
611
+ if (this.pos + 8 > this.end)
612
+ this.truncated();
613
+ const b = this.f64b, buf = this.buf;
614
+ if (LITTLE_ENDIAN)
615
+ for (let i = 0; i < 8; i++)
616
+ b[i] = buf[this.pos++];
617
+ else
618
+ for (let i = 7; i >= 0; i--)
619
+ b[i] = buf[this.pos++];
620
+ return this.f64[0];
621
+ }
622
+ /* a count whose items each take at least one byte */
623
+ count() {
624
+ const n = this.leb();
625
+ if (n > this.end - this.pos)
626
+ this.truncated();
627
+ return n;
628
+ }
629
+ bytes(n) {
630
+ if (n > this.end - this.pos)
631
+ this.truncated();
632
+ const r = this.buf.subarray(this.pos, this.pos + n);
633
+ this.pos += n;
634
+ return r;
635
+ }
636
+ string() {
637
+ const i = this.leb();
638
+ if (i >= this.strings.length)
639
+ this.fail(`invalid string index ${i}`);
640
+ return this.strings[i];
641
+ }
642
+ /* bc_idx_to_atom */
643
+ idx_to_atom(v) {
644
+ if (v < JS_ATOM_END || v >= JS_ATOM_TAG_INT)
645
+ return v;
646
+ const i = v - JS_ATOM_END;
647
+ if (i >= this.atoms.length)
648
+ this.fail(`invalid atom index ${v}`);
649
+ return this.atoms[i];
650
+ }
651
+ atom() { return this.idx_to_atom(this.leb()); }
652
+ header() {
653
+ const buf = this.buf;
654
+ if (buf.length < BC_MAGIC.length + 2 + CHECKSUM_SIZE)
655
+ this.truncated();
656
+ for (let i = 0; i < BC_MAGIC.length; i++)
657
+ if (buf[i] !== BC_MAGIC[i])
658
+ this.fail('not jseption bytecode (bad magic number)');
659
+ this.pos = BC_MAGIC.length;
660
+ const format = this.u8();
661
+ const vlen = this.leb();
662
+ if (vlen > 64)
663
+ this.fail('not jseption bytecode (bad header)');
664
+ if (vlen > this.end - this.pos)
665
+ this.truncated();
666
+ let version = '';
667
+ for (let i = 0; i < vlen; i++)
668
+ version += String.fromCharCode(buf[this.pos++]);
669
+ if (format !== BC_FORMAT || version !== JSEPTION_VERSION)
670
+ this.fail(`bytecode version mismatch: written by jseption ${version} (format ${format}), ` +
671
+ `this is jseption ${JSEPTION_VERSION} (format ${BC_FORMAT})`);
672
+ /* everything after the version is covered by the checksum */
673
+ if (buf.length - CHECKSUM_SIZE < this.pos)
674
+ this.truncated();
675
+ this.end = buf.length - CHECKSUM_SIZE;
676
+ const [a, b] = checksum(buf, this.end);
677
+ const u32 = (p) => (buf[p] | (buf[p + 1] << 8) | (buf[p + 2] << 16) | (buf[p + 3] << 24)) >>> 0;
678
+ if (u32(this.end) !== a || u32(this.end + 4) !== b)
679
+ this.fail('checksum mismatch (truncated or corrupted bytecode)');
680
+ const flags = this.leb();
681
+ if (flags & ~BC_FLAG_SOURCE)
682
+ this.fail('unknown flags');
683
+ this.keep_source = (flags & BC_FLAG_SOURCE) !== 0;
684
+ }
685
+ string_table() {
686
+ const n = this.count();
687
+ const lens = new Array(n);
688
+ let narrow_len = 0, wide_len = 0;
689
+ for (let i = 0; i < n; i++) {
690
+ const v = this.leb();
691
+ lens[i] = v;
692
+ if (v & 1)
693
+ wide_len += (v - 1) / 2;
694
+ else
695
+ narrow_len += v / 2;
696
+ }
697
+ if (this.leb() !== narrow_len)
698
+ this.fail('inconsistent string table');
699
+ if (narrow_len > this.end - this.pos)
700
+ this.truncated();
701
+ const narrow = latin1_decode(this.buf, this.pos, this.pos + narrow_len);
702
+ this.pos += narrow_len;
703
+ if (this.leb() !== wide_len * 2)
704
+ this.fail('inconsistent string table');
705
+ if (wide_len * 2 > this.end - this.pos)
706
+ this.truncated();
707
+ const wide = utf16_decode(this.buf, this.pos, wide_len);
708
+ this.pos += wide_len * 2;
709
+ const strings = this.strings = new Array(n);
710
+ let np = 0, wp = 0;
711
+ for (let i = 0; i < n; i++) {
712
+ const v = lens[i];
713
+ if (v & 1) {
714
+ const l = (v - 1) / 2;
715
+ strings[i] = wide.slice(wp, wp + l);
716
+ wp += l;
717
+ }
718
+ else {
719
+ const l = v / 2;
720
+ strings[i] = narrow.slice(np, np + l);
721
+ np += l;
722
+ }
723
+ }
724
+ }
725
+ atom_table() {
726
+ const n = this.count();
727
+ const atoms = this.atoms = new Array(n);
728
+ const table = this.ctx.rt.atoms;
729
+ for (let i = 0; i < n; i++)
730
+ atoms[i] = table.newAtomStr(this.string());
731
+ }
732
+ value() {
733
+ const tag = this.u8();
734
+ switch (tag) {
735
+ case 1 /* BCTag.NULL */: return null;
736
+ case 2 /* BCTag.UNDEFINED */: return undefined;
737
+ case 3 /* BCTag.FALSE */: return false;
738
+ case 4 /* BCTag.TRUE */: return true;
739
+ case 5 /* BCTag.INT32 */: {
740
+ const v = this.sleb();
741
+ if ((v | 0) !== v)
742
+ this.fail('invalid int32');
743
+ return v;
744
+ }
745
+ case 6 /* BCTag.FLOAT64 */: return this.f64v();
746
+ case 7 /* BCTag.STRING */: return this.string();
747
+ case 8 /* BCTag.BIG_INT */: {
748
+ const s = this.string();
749
+ if (!/^-?[0-9]+$/.test(s))
750
+ this.fail('invalid BigInt');
751
+ return BigInt(s);
752
+ }
753
+ case 9 /* BCTag.FUNCTION_BYTECODE */: return this.function();
754
+ case 10 /* BCTag.TEMPLATE_OBJECT */: return this.template();
755
+ }
756
+ return this.fail(`invalid tag ${tag} at offset ${this.pos - 1}`);
757
+ }
758
+ template() {
759
+ const ctx = this.ctx;
760
+ const n = this.count();
761
+ const arr = JS_NewArray(ctx), raw = JS_NewArray(ctx);
762
+ if (JS_IsException(arr) || JS_IsException(raw))
763
+ this.fail('out of memory');
764
+ for (let i = 0; i < n; i++) {
765
+ const c = this.leb();
766
+ let cooked;
767
+ if (c !== 0) {
768
+ if (c > this.strings.length)
769
+ this.fail(`invalid string index ${c - 1}`);
770
+ cooked = this.strings[c - 1];
771
+ }
772
+ JS_DefinePropertyValueUint32(ctx, raw, i, this.string(), JS_PROP_ENUMERABLE);
773
+ JS_DefinePropertyValueUint32(ctx, arr, i, cooked, JS_PROP_ENUMERABLE);
774
+ }
775
+ JS_DefinePropertyValue(ctx, arr, JS_ATOM_raw, raw, 0);
776
+ Object.freeze(raw);
777
+ Object.freeze(arr);
778
+ return arr;
779
+ }
780
+ function() {
781
+ if (++this.depth > MAX_DEPTH)
782
+ this.fail('functions nested too deeply');
783
+ const b = new JSFunctionBytecode();
784
+ const flags = this.leb();
785
+ if (flags >= F_HAS_SOURCE << 1)
786
+ this.fail('invalid function flags');
787
+ b.has_prototype = (flags & F_HAS_PROTOTYPE) !== 0;
788
+ b.has_simple_parameter_list = (flags & F_SIMPLE_PARAMS) !== 0;
789
+ b.is_derived_class_constructor = (flags & F_DERIVED_CTOR) !== 0;
790
+ b.need_home_object = (flags & F_HOME_OBJECT) !== 0;
791
+ b.new_target_allowed = (flags & F_NEW_TARGET) !== 0;
792
+ b.super_call_allowed = (flags & F_SUPER_CALL) !== 0;
793
+ b.super_allowed = (flags & F_SUPER) !== 0;
794
+ b.arguments_allowed = (flags & F_ARGUMENTS) !== 0;
795
+ b.has_debug = (flags & F_HAS_DEBUG) !== 0;
796
+ b.is_direct_or_indirect_eval = (flags & F_EVAL) !== 0;
797
+ b.js_mode = this.u8();
798
+ const kind = this.u8();
799
+ if (kind > 3 /* JSFunctionKind.ASYNC_GENERATOR */)
800
+ this.fail('invalid function kind');
801
+ b.func_kind = kind;
802
+ b.func_name = this.atom();
803
+ b.arg_count = this.count();
804
+ b.var_count = this.count();
805
+ b.defined_arg_count = this.leb();
806
+ b.stack_size = this.leb();
807
+ /* the parser's limits: the interpreter sizes each frame from these, so an out-of-range
808
+ value would make it allocate a huge frame instead of failing here */
809
+ if (b.arg_count >= MAX_LOCALS || b.var_count >= MAX_LOCALS)
810
+ this.fail('too many variables');
811
+ if (b.defined_arg_count > b.arg_count)
812
+ this.fail('invalid defined argument count');
813
+ if (b.stack_size > MAX_STACK_SIZE)
814
+ this.fail(`invalid stack size ${b.stack_size}`);
815
+ const closure_count = this.count();
816
+ const cpool_count = this.count();
817
+ const bc_len = this.count();
818
+ const nvars = b.arg_count + b.var_count;
819
+ const vardefs = b.vardefs = new Array(nvars);
820
+ for (let i = 0; i < nvars; i++) {
821
+ const vd = new JSVarDef(this.atom());
822
+ vd.scope_level = this.leb();
823
+ vd.scope_next = this.sleb();
824
+ const f = this.u8();
825
+ vd.is_const = (f & V_CONST) !== 0;
826
+ vd.is_lexical = (f & V_LEXICAL) !== 0;
827
+ vd.is_captured = (f & V_CAPTURED) !== 0;
828
+ vd.is_static_private = (f & V_STATIC_PRIVATE) !== 0;
829
+ const k = this.u8();
830
+ if (k > VAR_KIND_MAX)
831
+ this.fail('invalid variable kind');
832
+ vd.var_kind = k;
833
+ vd.func_pool_idx = this.sleb();
834
+ vardefs[i] = vd;
835
+ }
836
+ const closure_var = b.closure_var = new Array(closure_count);
837
+ for (let i = 0; i < closure_count; i++) {
838
+ const cv = new JSClosureVar();
839
+ cv.var_name = this.atom();
840
+ cv.var_idx = this.leb();
841
+ const f = this.u8();
842
+ cv.is_const = (f & V_CONST) !== 0;
843
+ cv.is_lexical = (f & V_LEXICAL) !== 0;
844
+ cv.is_local = (f & V_LOCAL) !== 0;
845
+ cv.is_arg = (f & V_ARG) !== 0;
846
+ const k = this.u8();
847
+ if (k > VAR_KIND_MAX)
848
+ this.fail('invalid variable kind');
849
+ cv.var_kind = k;
850
+ closure_var[i] = cv;
851
+ }
852
+ const bc = this.bytes(bc_len);
853
+ b.byte_code_buf = bc;
854
+ b.byte_code_len = bc_len;
855
+ if (b.has_debug) {
856
+ b.debug.filename = this.atom();
857
+ const n = this.leb();
858
+ b.debug.pc2line = this.bytes(n);
859
+ if (flags & F_HAS_SOURCE) {
860
+ if (!this.keep_source)
861
+ this.fail('invalid function flags');
862
+ const src = this.string();
863
+ b.debug.source = src;
864
+ b.debug.source_len = src.length;
865
+ }
866
+ }
867
+ else if (flags & F_HAS_SOURCE) {
868
+ this.fail('invalid function flags');
869
+ }
870
+ const cpool = b.cpool = new Array(cpool_count);
871
+ for (let i = 0; i < cpool_count; i++)
872
+ cpool[i] = this.value();
873
+ this.fixup_byte_code(b);
874
+ b.realm = this.ctx;
875
+ this.depth--;
876
+ return b;
877
+ }
878
+ /* a compiled module (JS_ReadModule) */
879
+ module() {
880
+ const m = new JSModuleDef(this.atom());
881
+ m.realm = this.ctx;
882
+ const nreq = this.count();
883
+ for (let i = 0; i < nreq; i++) {
884
+ const name = this.atom();
885
+ const n = this.leb();
886
+ let attrs = null;
887
+ if (n > 0) {
888
+ if (n - 1 > this.end - this.pos)
889
+ this.truncated();
890
+ attrs = Object.create(null);
891
+ for (let j = 0; j < n - 1; j++) {
892
+ const k = this.string();
893
+ attrs[k] = this.string();
894
+ }
895
+ }
896
+ m.req_module_entries.push(new JSReqModuleEntry(name, attrs));
897
+ }
898
+ const req_idx = () => {
899
+ const i = this.leb();
900
+ if (i >= nreq)
901
+ this.fail(`invalid module request index ${i}`);
902
+ return i;
903
+ };
904
+ const nexp = this.count();
905
+ const local_exports = [];
906
+ for (let i = 0; i < nexp; i++) {
907
+ const type = this.u8();
908
+ let me;
909
+ if (type === 0 /* JSExportType.LOCAL */) {
910
+ const var_idx = this.leb();
911
+ local_exports.push(var_idx);
912
+ me = new JSExportEntry(0, this.atom(), 0 /* JSExportType.LOCAL */);
913
+ me.var_idx = var_idx;
914
+ me.local_name = me.export_name;
915
+ }
916
+ else if (type === 1 /* JSExportType.INDIRECT */) {
917
+ const idx = req_idx();
918
+ const local_name = this.atom();
919
+ me = new JSExportEntry(local_name, this.atom(), 1 /* JSExportType.INDIRECT */);
920
+ me.req_module_idx = idx;
921
+ }
922
+ else {
923
+ this.fail('invalid export entry');
924
+ }
925
+ m.export_entries.push(me);
926
+ }
927
+ const nstar = this.count();
928
+ for (let i = 0; i < nstar; i++)
929
+ m.star_export_entries.push(new JSStarExportEntry(req_idx()));
930
+ const nimp = this.count();
931
+ const imports = [];
932
+ for (let i = 0; i < nimp; i++) {
933
+ const var_idx = this.leb();
934
+ const is_star = this.u8();
935
+ if (is_star > 1)
936
+ this.fail('invalid import entry');
937
+ const mi = new JSImportEntry(var_idx, is_star === 1, this.atom());
938
+ mi.req_module_idx = req_idx();
939
+ m.import_entries.push(mi);
940
+ imports.push({ var_idx, is_star: is_star === 1 });
941
+ }
942
+ const tla = this.u8();
943
+ if (tla > 1)
944
+ this.fail('invalid module flags');
945
+ m.has_tla = tla === 1;
946
+ if (this.u8() !== 9 /* BCTag.FUNCTION_BYTECODE */)
947
+ this.fail('the module body is not a function');
948
+ const b = this.function();
949
+ if (b.func_kind !== 2 /* JSFunctionKind.ASYNC */)
950
+ this.fail('the module body is not an async function');
951
+ /* the entries index the closure variables of the body */
952
+ const ncv = b.closure_var.length;
953
+ for (const v of local_exports)
954
+ if (v >= ncv)
955
+ this.fail(`invalid export variable index ${v}`);
956
+ for (const { var_idx, is_star } of imports) {
957
+ if (var_idx >= ncv)
958
+ this.fail(`invalid import variable index ${var_idx}`);
959
+ if (b.closure_var[var_idx].is_local !== is_star)
960
+ this.fail('invalid import variable');
961
+ }
962
+ js_module_set_function(m, b);
963
+ return m;
964
+ }
965
+ /* patch the atom operands and check the instruction boundaries and the constant pool
966
+ indices (JS_ReadFunctionBytecode) */
967
+ fixup_byte_code(b) {
968
+ const bc = b.byte_code_buf, len = b.byte_code_len, cpool = b.cpool;
969
+ let pos = 0;
970
+ const natoms = this.atoms.length, atoms = this.atoms;
971
+ while (pos < len) {
972
+ const op = bc[pos], size = OP_SIZE[op];
973
+ if (size === 0)
974
+ this.fail(`invalid opcode ${op} at pc ${pos}`);
975
+ if (pos + size > len)
976
+ this.fail(`truncated instruction at pc ${pos}`);
977
+ const kind = OP_KIND[op];
978
+ if (kind !== 0) {
979
+ if (kind === OPK_ATOM) {
980
+ const v = (bc[pos + 1] | (bc[pos + 2] << 8) | (bc[pos + 3] << 16) | (bc[pos + 4] << 24)) >>> 0;
981
+ if (v >= JS_ATOM_END && v < JS_ATOM_TAG_INT) {
982
+ const i = v - JS_ATOM_END;
983
+ if (i >= natoms)
984
+ this.fail(`invalid atom index ${v} at pc ${pos}`);
985
+ const atom = atoms[i];
986
+ bc[pos + 1] = atom;
987
+ bc[pos + 2] = atom >>> 8;
988
+ bc[pos + 3] = atom >>> 16;
989
+ bc[pos + 4] = atom >>> 24;
990
+ }
991
+ }
992
+ else {
993
+ const idx = (kind === OPK_CONST8 || kind === OPK_FCLOSURE8) ? bc[pos + 1]
994
+ : (bc[pos + 1] | (bc[pos + 2] << 8) | (bc[pos + 3] << 16) | (bc[pos + 4] << 24)) >>> 0;
995
+ if (idx >= cpool.length)
996
+ this.fail(`invalid constant pool index ${idx} at pc ${pos}`);
997
+ if (kind >= OPK_FCLOSURE && !(cpool[idx] instanceof JSFunctionBytecode))
998
+ this.fail(`closure of a constant that is not a function at pc ${pos}`);
999
+ }
1000
+ }
1001
+ pos += size;
1002
+ }
1003
+ }
1004
+ }
1005
+ /* buf[start, end) as a Latin-1 string */
1006
+ function latin1_decode(buf, start, end) {
1007
+ const CHUNK = 8192;
1008
+ if (end - start <= CHUNK)
1009
+ return String.fromCharCode.apply(null, buf.subarray(start, end));
1010
+ const parts = [];
1011
+ for (let i = start; i < end; i += CHUNK)
1012
+ parts.push(String.fromCharCode.apply(null, buf.subarray(i, Math.min(end, i + CHUNK))));
1013
+ return parts.join('');
1014
+ }
1015
+ /* n UTF-16LE code units at buf[start] as a string (lone surrogates preserved) */
1016
+ function utf16_decode(buf, start, n) {
1017
+ if (n === 0)
1018
+ return '';
1019
+ let units;
1020
+ if (LITTLE_ENDIAN) {
1021
+ units = new Uint16Array(buf.slice(start, start + 2 * n).buffer);
1022
+ }
1023
+ else {
1024
+ units = new Uint16Array(n);
1025
+ for (let i = 0; i < n; i++)
1026
+ units[i] = buf[start + 2 * i] | (buf[start + 2 * i + 1] << 8);
1027
+ }
1028
+ const CHUNK = 8192;
1029
+ const parts = [];
1030
+ for (let i = 0; i < n; i += CHUNK)
1031
+ parts.push(String.fromCharCode.apply(null, units.subarray(i, Math.min(n, i + CHUNK))));
1032
+ return parts.join('');
1033
+ }
1034
+ /* Read bytes produced by JS_WriteObject into this context. Returns a function object to
1035
+ run with JS_EvalFunction, or JS_EXCEPTION with a SyntaxError pending when the input is
1036
+ not valid bytecode of this jseption version. */
1037
+ export function JS_ReadObject(ctx, buf) {
1038
+ if (!(buf instanceof Uint8Array))
1039
+ return JS_ThrowTypeError(ctx, 'JS_ReadObject: expected a Uint8Array');
1040
+ const r = new BCReader(ctx, buf);
1041
+ let b;
1042
+ try {
1043
+ r.header();
1044
+ r.string_table();
1045
+ r.atom_table();
1046
+ const tag = r.u8();
1047
+ if (tag === 11 /* BCTag.MODULE */) {
1048
+ const m = r.module();
1049
+ if (r.pos !== r.end)
1050
+ r.fail('trailing bytes after the root value');
1051
+ return m;
1052
+ }
1053
+ if (tag !== 9 /* BCTag.FUNCTION_BYTECODE */)
1054
+ r.fail('the root value is not a function');
1055
+ b = r.function();
1056
+ if (r.pos !== r.end)
1057
+ r.fail('trailing bytes after the root value');
1058
+ }
1059
+ catch (e) {
1060
+ if (e instanceof BCReadError)
1061
+ return JS_ThrowSyntaxError(ctx, 'JS_ReadObject: ' + e.message);
1062
+ if (e instanceof RangeError)
1063
+ return JS_ThrowSyntaxError(ctx, 'JS_ReadObject: ' + e.message);
1064
+ throw e;
1065
+ }
1066
+ if (b.closure_var.length !== 0)
1067
+ return JS_ThrowSyntaxError(ctx, 'JS_ReadObject: the root function has closure variables');
1068
+ return hooks.js_closure(ctx, b, null, null);
1069
+ }
1070
+ hooks_bytecode.read = (ctx, bytes) => JS_ReadObject(ctx, bytes);
1071
+ //# sourceMappingURL=bytecode.js.map