object-input-stream 0.1.0 → 0.2.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.
package/src/ois-ast.ts CHANGED
@@ -1,147 +1,941 @@
1
- import ObjectInputStream, { type OisOptions } from ".";
2
- import type * as ast from './ast';
1
+ /**
2
+ * Forgive me father, for I have sinned.
3
+ */
4
+
5
+ import ObjectInputStream, { ast, Externalizable, FieldDesc, ObjectStreamClass, Serializable, type OisOptions, internal } from ".";
3
6
  import * as exc from "./exceptions";
4
7
 
8
+ type CSTNode = {
9
+ type: string,
10
+ parent: CSTNode | null,
11
+ span: {start: number, end: number},
12
+ value: unknown,
13
+ error?: any,
14
+ handle?: {epoch: number, handle: number},
15
+ children: CSTNode[],
16
+ }
17
+
5
18
 
6
19
  export class ObjectInputStreamAST extends ObjectInputStream {
7
- protected ast: ast.Ast
8
- protected nodeStack: ast.Node[];
20
+ protected cst: CSTNode;
21
+ protected cstStack: CSTNode[];
22
+ protected finalized = false;
23
+ protected ast: ast.Ast | null = null;
24
+ // How many resets happened so far
25
+ protected epoch: number = 0;
9
26
 
10
27
  constructor(data: Uint8Array, options?: OisOptions) {
11
28
  super(data, options);
12
29
 
13
- this.ast = {data, root: {
30
+ const rootNode: CSTNode = {
14
31
  type: "root",
15
- span: {start: 0, end: this.data.length},
16
- children: [
17
- {type: "magic", value: this.STREAM_MAGIC, span: {start: 0, end: 2}, children: null},
18
- {type: "version", value: this.STREAM_VERSION, span: {start: 2, end: 4}, children: null},
19
- {type: "contents", span: {start: 4, end: this.data.length}, children:[]},
20
- ]
21
- }}
22
- this.nodeStack = [this.ast.root, this.ast.root.children[2]];
23
- }
32
+ span: {start: 0, end: data.length},
33
+ value: null,
34
+ parent: null,
35
+ children: [],
36
+ };
37
+
38
+ rootNode.children = [{
39
+ type: "magic",
40
+ span: {start: 0, end: 2},
41
+ value: this.STREAM_MAGIC,
42
+ parent: rootNode,
43
+ children: [],
44
+ }, {
45
+ type: "version",
46
+ span: {start: 2, end: 4},
47
+ value: this.STREAM_VERSION,
48
+ parent: rootNode,
49
+ children: [],
50
+ }, {
51
+ type: "contents",
52
+ span: {start: 4, end: data.length},
53
+ value: null,
54
+ parent: rootNode,
55
+ children: [],
56
+ }]
24
57
 
25
- protected get curNode() {
26
- return this.nodeStack[this.nodeStack.length-1];
58
+ this.cst = rootNode;
59
+ this.cstStack = [this.cst, this.cst.children[2]];
27
60
  }
28
61
 
29
- getAST(): ast.Ast {
30
- return this.ast;
62
+ getAST() {
63
+ if (this.offset < this.data.length) {
64
+ throw new Error("Not done reading");
65
+ }
66
+ if (this.finalized) {
67
+ assert(this.ast !== null);
68
+ return this.ast;
69
+ }
70
+ this.finalized = true;
71
+ return cstToAst(this.cst, this.data);
31
72
  }
32
73
 
33
- @primitiveAST("boolean")
74
+ @traceMethod("primitive/boolean", {keep: true})
34
75
  readBoolean(): boolean { return super.readBoolean(); }
35
- @primitiveAST("byte")
76
+ @traceMethod("primitive/byte", {keep: true})
36
77
  readByte(): number { return super.readByte(); }
37
- @primitiveAST("unsigned-byte")
78
+ @traceMethod("primitive/unsigned-byte", {keep: true})
38
79
  readUnsignedByte(): number { return super.readUnsignedByte(); }
39
- @primitiveAST("char")
80
+ @traceMethod("primitive/char", {keep: true})
40
81
  readChar(): string { return super.readChar(); }
41
- @primitiveAST("short")
82
+ @traceMethod("primitive/short", {keep: true})
42
83
  readShort(): number { return super.readShort(); }
43
- @primitiveAST("unsigned-short")
84
+ @traceMethod("primitive/unsigned-short", {keep: true})
44
85
  readUnsignedShort(): number { return super.readUnsignedShort(); }
45
- @primitiveAST("int")
86
+ @traceMethod("primitive/int", {keep: true})
46
87
  readInt(): number { return super.readInt(); }
47
- @primitiveAST("long")
88
+ @traceMethod("primitive/long", {keep: true})
48
89
  readLong(): bigint { return super.readLong(); }
49
- @primitiveAST("float")
90
+ @traceMethod("primitive/float", {keep: true})
50
91
  readFloat(): number { return super.readFloat(); }
51
- @primitiveAST("double")
92
+ @traceMethod("primitive/double", {keep: true})
52
93
  readDouble(): number { return super.readDouble(); }
53
94
 
54
- @nodeAST("tc")
95
+ @traceMethod("tc", {keep: true})
55
96
  protected readTC(): number { return super.readTC(); }
97
+
98
+ @traceMethod("read1", {keep: true})
99
+ read1() { return super.read1(); }
100
+ @traceMethod("read")
101
+ read(len: number) { return super.read(len); }
102
+ @traceMethod("block-header", {keep: true})
103
+ protected readBlockHeader(): number { return super.readBlockHeader(); }
104
+
105
+ @traceMethod("utf", {keep: true})
106
+ readUTF() { return super.readUTF(); }
107
+ @traceMethod("long-utf", {keep: true})
108
+ protected readLongUTF() { return super.readLongUTF(); }
109
+ @traceMethod("utf-body", {keep: true})
110
+ protected readUTFBody(byteLength: number) { return super.readUTFBody(byteLength); }
111
+
112
+ protected reset(): void {
113
+ assert(this.cstStack.length > 0);
114
+ const currNode = this.cstStack[this.cstStack.length-1];
115
+ assert(currNode.type === "object/reset" || currNode.type === "object/exception");
116
+ currNode.value = ++this.epoch;
117
+ return super.reset();
118
+ }
119
+
120
+ protected newHandle(obj: any): number {
121
+ assert(this.cstStack.length > 0);
122
+ const currNode = this.cstStack[this.cstStack.length-1];
123
+ assert(
124
+ currNode.type === "object/class"
125
+ || currNode.type === "proxy-desc"
126
+ || currNode.type === "non-proxy-desc"
127
+ || currNode.type === "object/string"
128
+ || currNode.type === "object/array"
129
+ || currNode.type === "object/enum"
130
+ || currNode.type === "object/instance"
131
+ );
132
+ const handle = super.newHandle(obj);
133
+ currNode.handle = {epoch: this.epoch, handle: handle};
134
+ return handle;
135
+ }
136
+
137
+ @traceMethod("object/reset")
138
+ protected readReset() { return super.readReset(); }
139
+ @traceMethod("object/null")
140
+ protected readNull() { return super.readNull(); }
141
+ @traceMethod("object/handle")
142
+ protected readHandle() {
143
+ assert(this.cstStack.length > 0);
144
+ const currNode = this.cstStack[this.cstStack.length-1];
145
+ assert(currNode.type === "object/handle");
146
+ currNode.value = this.epoch;
147
+ return super.readHandle();
148
+ }
149
+ @traceMethod("object/class")
150
+ protected readClass() { return super.readClass(); }
151
+ @traceMethod("object/class-desc")
152
+ protected readClassDesc() { return super.readClassDesc(); }
153
+ @traceMethod("proxy-desc")
154
+ protected readProxyDesc() { return super.readProxyDesc(); }
155
+ @traceMethod("non-proxy-desc")
156
+ protected readNonProxyDesc() { return super.readNonProxyDesc(); }
157
+ @traceMethod("object/string", {keep: true})
158
+ protected readString() { return super.readString(); }
159
+ @traceMethod("object/array")
160
+ protected readArray() { return super.readArray(); }
161
+ @traceMethod("object/enum")
162
+ protected readEnum() { return super.readEnum(); }
163
+ @traceMethod("object/instance")
164
+ protected readOrdinaryObject() { return super.readOrdinaryObject(); }
165
+ @traceMethod("external-data")
166
+ protected readExternalData(obj: Externalizable, desc: ObjectStreamClass) { return super.readExternalData(obj, desc); }
167
+ @traceMethod("serial-data")
168
+ protected readSerialData(obj: Serializable, desc: ObjectStreamClass) { return super.readSerialData(obj, desc); }
169
+ @traceMethod("class-data-wr")
170
+ protected readClassDataWr(obj: Serializable, desc: ObjectStreamClass, readMethod: internal.ReadMethodT): void {
171
+ return super.readClassDataWr(obj, desc, readMethod);
172
+ }
173
+ @traceMethod("class-data-no-wr")
174
+ protected readClassDataNoWr(obj: Serializable, desc: ObjectStreamClass, readMethod: internal.ReadMethodT): void { return super.readClassDataNoWr(obj, desc, readMethod); }
175
+ @traceMethod("object/exception")
176
+ protected readFatalException() { return super.readFatalException(); }
177
+ @traceMethod("annotation")
178
+ protected readAnnotation() { return super.readAnnotation(); }
179
+
180
+ @traceMethod("values")
181
+ readFields() { return super.readFields(); }
182
+ @traceMethod("fields")
183
+ protected readFieldDescs(className: string): FieldDesc[] { return super.readFieldDescs(className); }
184
+ @traceMethod("field")
185
+ protected readFieldDesc(className: string): FieldDesc { return super.readFieldDesc(className); }
56
186
  }
57
187
 
58
- function nodeAST<T>(type: string) {return (fun: () => T) => {
59
- return function decorated(this: ObjectInputStreamAST) {
188
+
189
+ function traceMethod<T, ARGS extends any[]>(type: string, options?: {
190
+ keep?: boolean,
191
+ }) {return (method: (this: ObjectInputStreamAST, ...args: ARGS) => T) => {
192
+ return function decorated(this: ObjectInputStreamAST, ...args: ARGS): T {
60
193
  if (!(this instanceof ObjectInputStreamAST))
61
194
  throw new exc.InternalError();
62
195
 
63
- const start = this.offset;
64
- const result = fun.apply(this);
65
- const end = this.offset;
196
+ if (this.finalized)
197
+ throw new exc.NotActiveException();
198
+
199
+ const parent = (this.cstStack?.length > 0) ? this.cstStack[this.cstStack.length-1] : null;
200
+
201
+ const node: CSTNode = {
202
+ type: type,
203
+ span: {start: this.offset, end: this.offset},
204
+ value: null,
205
+ parent: parent,
206
+ children: [],
207
+ }
208
+
209
+ if (parent !== null) {
210
+ parent.children.push(node);
211
+ this.cstStack.push(node);
212
+ }
66
213
 
67
- if (this.nodeStack === undefined)
214
+ let result: T;
215
+ try {
216
+ result = method.apply(this, args);
217
+ if (options?.keep)
218
+ node.value = result;
68
219
  return result;
220
+ } catch (e) {
221
+ node.error = e;
222
+ throw e;
223
+ } finally {
224
+ node.span.end = this.offset;
225
+ if (parent !== null) {
226
+ const popped = this.cstStack.pop();
227
+ assert(popped === node);
228
+ }
229
+ }
230
+ }
231
+ }}
232
+
233
+ function assert(predicate: boolean, message="assertion error"): asserts predicate {
234
+ if (!predicate)
235
+ throw new exc.InternalError(message);
236
+ }
237
+
238
+ /**
239
+ * Convert a concrete-syntax-tree to an abstract-syntax-tree.
240
+ * This function is destructive to the CST.
241
+ */
242
+ function cstToAst(cst: CSTNode, data: Uint8Array): ast.Ast {
243
+ hoistBlockHeaders(cst);
244
+ hoistResets(cst);
69
245
 
70
- const span = {start, end};
246
+ // Remove calls to "read" from primitives and strings
247
+ removeNodesWhere(cst, node => node.type === "read" && node.parent !== null && (
248
+ node.parent.type.startsWith("primitive/") || node.parent.type === "utf-body"
249
+ ))
250
+ // Remove calls to "readByte" from tc
251
+ removeNodesWhere(cst, node => node.type === "primitive/byte" && node.parent?.type === "tc");
252
+ // Remove empty nodes that shouldn't be empty
253
+ removeNodesWhere(cst, node => {
254
+ if (node.span.start < node.span.end) return false;
255
+ return (
256
+ node.type !== "contents"
257
+ && node.type !== "serial-data"
258
+ && node.type !== "external-data"
259
+ && node.type !== "class-data-no-wr"
260
+ )
261
+ }, {recursive: true});
71
262
 
72
- const node: ast.Node = {
73
- type: type as any,
74
- span: span,
75
- children: null,
263
+ handleBlocks(cst);
264
+
265
+ const root = trasformCSTBottomUp(cst, cstToAstNode);
266
+ assert(root.type === "root");
267
+ return {root, data};
268
+ }
269
+
270
+ /**
271
+ * Instead of headers being children of primitives, make them their siblings.
272
+ * They will later become their parents.
273
+ */
274
+ function hoistBlockHeaders(cst: CSTNode): void {
275
+ const headers = [...traverseCST(cst)].filter(node => node.type === "block-header");
276
+ for (const header of headers) {
277
+ while (header.parent !== null && !canContainContent(header.parent)) {
278
+ assert(header.parent.children[0] === header);
279
+ hoistFirstChild(header);
76
280
  }
281
+ assert(header.parent !== null);
282
+ }
283
+ }
77
284
 
78
- if (this.curNode.children === null)
79
- throw new exc.InternalError();
285
+ function pp(node: ast.Node, indent=0) {
286
+ let res = " ".repeat(indent) + node.type;
287
+ if (node.type === "object") res += "/" + node.objectType;
288
+ if (node.type === "primitive") res += "/" + node.dataType;
289
+ if ((node as any).value !== null) res += ": " + (node as any).value;
290
+ if (node.children !== null && node.children.length > 0)
291
+ res += "\n" + node.children.map(c => pp(c,indent+1)).join("\n")
292
+ return res;
293
+ }
294
+
295
+ /**
296
+ * Instead of resets being children of objects / block headers, make them their siblings/
297
+ */
298
+ function hoistResets(cst: CSTNode): void {
299
+ const resets = [...traverseCST(cst)].filter(node => node.type === "object/reset");
300
+ for (const reset of resets) {
301
+ while (reset.parent !== null && !canContainContent(reset.parent)) {
302
+ assert(reset.parent.children[0] === reset);
303
+ hoistFirstChild(reset);
304
+ }
305
+ assert(reset.parent !== null);
306
+ }
307
+ }
308
+
309
+ function canContainContent(node: CSTNode) {
310
+ if (node === null) return false;
311
+ const type = node.type;
312
+ return type === "class-data-no-wr" || type === "class-data-wr" || type === "contents" || type === "external-data";
313
+ }
314
+
315
+ /**
316
+ * Manipulate the CST s.t `child` becomes its parent's sibling.
317
+ * `child` must be its parent's first child.
318
+ */
319
+ function hoistFirstChild(child: CSTNode) {
320
+ const parent = child.parent;
321
+ assert(parent !== null);
322
+ assert(parent.children.indexOf(child) === 0);
323
+ const grandParent = parent.parent;
324
+ assert(grandParent !== null);
325
+ const parentIndex = grandParent.children.indexOf(parent);
326
+ assert(parentIndex !== -1);
327
+
328
+ parent.children.shift();
329
+ parent.span.start = child.span.end;
330
+
331
+ grandParent.children.splice(parentIndex, 0, child);
332
+ child.parent = grandParent;
333
+ }
334
+
335
+ function handleBlocks(cst: CSTNode) {
336
+ // resets that are in between block headers
337
+ const blockResets = removeNodesWhere(cst, node => {
338
+ if (node.type !== "object/reset") return false;
339
+ assert(node.parent !== null);
340
+ const siblings = node.parent.children;
341
+ const index = siblings.indexOf(node);
342
+ assert(0 <= index);
343
+
344
+ const nextRight = siblings.slice(index+1).find(node => node.type !== "object/reset");
345
+ const nextLeft = siblings.slice(0,index).reverse().find(node => node.type !== "object/reset");
346
+
347
+ return (
348
+ nextRight !== undefined && nextRight.type === "block-header"
349
+ && nextLeft !== undefined && nextLeft.type === "block-header"
350
+ )
351
+ }, {recursive: true})
352
+
353
+ const blocks =
354
+ removeNodesWhere(cst, node => node.type === "block-header", {recursive: true})
355
+ .filter(header => header.span.start < header.span.end)
356
+ .map(header => {
357
+ assert(header.value !== -1);
358
+ let parent = header.parent;
359
+ assert(parent !== null);
360
+ assert(typeof header.value === "number");
361
+ const span = {start: header.span.start, end: header.span.end + header.value};
362
+ return {header, parent, span};
363
+ });
80
364
 
81
- // @ts-expect-error
82
- this.curNode.children.push(node);
365
+ if (blocks.length === 0)
366
+ return;
83
367
 
84
- return result;
368
+ // Group blocks into contiguous sequences
369
+ let currSequence: (typeof blocks[0] | typeof blockResets[0])[] = [blocks[0]];
370
+ const blockSequences: (typeof currSequence)[] = [currSequence];
371
+ for (let i=1; i<blocks.length; i++) {
372
+ let prev = currSequence[currSequence.length-1];
373
+
374
+ while (blockResets.length > 0 && blockResets[0].span.start === prev.span.end) {
375
+ const reset = blockResets.shift();
376
+ assert(reset !== undefined);
377
+ currSequence.push(reset);
378
+ prev = reset;
379
+ }
380
+
381
+ const curr = blocks[i];
382
+ if (prev.span.end === curr.span.start) {
383
+ currSequence.push(curr);
384
+ } else {
385
+ currSequence = [curr];
386
+ blockSequences.push(currSequence);
387
+ }
85
388
  }
86
- }}
87
389
 
88
- function primitiveAST<T extends number | string | boolean | bigint>(dataType: string) {return (fun: () => T) => {
89
- return function decorated(this: ObjectInputStreamAST) {
90
- if (!(this instanceof ObjectInputStreamAST))
91
- throw new exc.InternalError();
390
+ assert(blockResets.length === 0);
391
+
392
+ for (const sequence of blockSequences) {
393
+ assert(sequence.length > 0);
394
+ assert(sequence.every(block => block.parent === sequence[0].parent));
92
395
 
93
- const start = this.offset;
94
- const result = fun.apply(this);
95
- const end = this.offset;
396
+ const parent = sequence[0].parent;
397
+ assert(parent !== null);
398
+ const start = sequence[0].span.start;
399
+ const end = sequence[sequence.length-1].span.end;
400
+
401
+ // Get the values stored in each sequence
402
+ const values = removeNodesWhere(cst, node => (
403
+ node.parent === parent && start <= node.span.start && node.span.end <= end
404
+ ), {recursive: true});
405
+
406
+ assert(values.every(v => v.type.startsWith("primitive/") || v.type === "utf" || v.type === "read"));
407
+ values.forEach(v => removeNodesWhere(v, node => node.type === "block-header", {recursive: true}))
408
+
409
+ const sequenceNode: CSTNode = {
410
+ type: "blockdata-sequence",
411
+ span: {start, end},
412
+ parent: null,
413
+ value: values,
414
+ children: [],
415
+ }
416
+
417
+ sequenceNode.children = sequence.map((seqItem) => {
418
+ if ("type" in seqItem && seqItem.type === "object/reset") {
419
+ return seqItem;
420
+ }
421
+
422
+ const {parent, header, span} = seqItem as typeof blocks[0];
423
+ const result = {
424
+ type: "blockdata",
425
+ span: span,
426
+ parent: sequenceNode,
427
+ value: null,
428
+ children: [...header.children],
429
+ };
430
+ result.children.push({
431
+ type: "read",
432
+ span: {start: header.span.end, end: span.end},
433
+ value: null,
434
+ parent: result,
435
+ children: [],
436
+ });
96
437
 
97
- if (this.nodeStack === undefined)
98
438
  return result;
439
+ });
440
+
441
+ insertNode(parent, sequenceNode);
442
+ }
443
+ }
99
444
 
100
- const span = {start, end};
445
+ function removeNodesWhere(cst: CSTNode, condition: (node: CSTNode) => boolean, options?: {recursive?: boolean}): CSTNode[] {
446
+ const toRemove: CSTNode[] = [];
101
447
 
102
- const node: ast.PrimitiveNode = {
103
- type: "primitive",
104
- // @ts-expect-error
105
- dataType: dataType,
106
- value: result,
107
- span: span,
108
- children: null,
448
+ for (const node of traverseCST(cst)) {
449
+ if (condition(node)) {
450
+ toRemove.push(node);
109
451
  }
452
+ }
110
453
 
111
- if (this.curNode.type === "blockdata") {
112
- const sequence = this.nodeStack[this.nodeStack.length-2];
113
- if (sequence.type !== "blockdata-sequence")
114
- throw new exc.InternalError();
115
- sequence.values.push(node);
116
- } else {
117
- if (this.curNode.children === null)
118
- throw new exc.InternalError();
119
- // @ts-expect-error
120
- this.curNode.children.push(node);
454
+ for (const node of toRemove)
455
+ removeNode(node, options);
456
+
457
+ return toRemove;
458
+ }
459
+
460
+ function removeNode(node: CSTNode, options?: {recursive?: boolean}): void {
461
+ assert(node.parent !== null);
462
+ const nodeIndex = node.parent.children.indexOf(node);
463
+ assert(nodeIndex !== -1);
464
+ if (options?.recursive) {
465
+ node.parent.children.splice(nodeIndex, 1);
466
+ } else {
467
+ for (const child of node.children) {
468
+ child.parent = node.parent;
121
469
  }
470
+ node.parent.children.splice(nodeIndex, 1, ...node.children);
471
+ }
472
+ }
473
+
474
+ function insertNode(parent: CSTNode, child: CSTNode): void {
475
+ assert(parent.span.start <= child.span.start && child.span.end <= parent.span.end);
122
476
 
123
- return result;
477
+ child.parent = parent
478
+
479
+ if (parent.children.length === 0) {
480
+ parent.children.push(child);
481
+ return;
124
482
  }
125
- }}
126
483
 
484
+ let inserted = false;
485
+ for (let i=0; i<=parent.children.length; i++) {
486
+ const left = parent.children[i-1] as CSTNode | undefined;
487
+ const right = parent.children[i] as CSTNode | undefined;
127
488
 
128
- const ois_ast = new ObjectInputStreamAST(new Uint8Array([
129
- 172, 237, 0, 5, 119, 30, 69, 39, 20,
130
- 78, 206, 109, 85, 58, 3, 21, 127, 242,
131
- 227, 49, 35, 0, 0, 113, 52, 105, 0,
132
- 0, 0, 0, 0, 0, 0, 207, 199, 1
133
- ]));
489
+ // Not overlapping
490
+ if (left !== undefined)
491
+ assert(child.span.end <= left.span.start || left.span.end <= child.span.start);
492
+ if (right !== undefined)
493
+ assert(child.span.end <= right.span.start || right.span.end <= child.span.start);
134
494
 
135
- ois_ast.readByte()
136
- ois_ast.readChar()
137
- ois_ast.readDouble()
138
- ois_ast.readFloat()
139
- ois_ast.readInt()
140
- ois_ast.readLong()
141
- ois_ast.readShort()
142
- ois_ast.readBoolean()
495
+ const leftGood = (left === undefined) || (left.span.end <= child.span.start);
496
+ const rightGood = (right === undefined) || (child.span.end <= right.span.start);
497
+ if (leftGood && rightGood) {
498
+ parent.children.splice(i, 0, child);
499
+ inserted = true;
500
+ break;
501
+ }
502
+ }
503
+ assert(inserted);
504
+ }
143
505
 
506
+ function *traverseCST(cst: CSTNode): Generator<CSTNode, undefined, undefined> {
507
+ yield cst;
508
+ for (const child of cst.children)
509
+ yield* traverseCST(child);
510
+ }
144
511
 
145
- const myAst: any = ois_ast.getAST();
146
- myAst.data = [...myAst.data];
147
- console.log(JSON.stringify(myAst, (key, value) => typeof value === "bigint" ? ''+value : value, 2));
512
+
513
+ function trasformCSTBottomUp<T>(cst: CSTNode, transform: (node: CSTNode, transformedChildren: T[]) => T): T {
514
+ const children = cst.children.map(c => trasformCSTBottomUp(c, transform));
515
+ return transform(cst, children);
516
+ }
517
+
518
+ function cstToAstNode(node: CSTNode, children: ast.Node[]): ast.Node {
519
+ if (node.type.startsWith("primitive/")) {
520
+ assert(children.length === 0);
521
+ const dataType = node.type.split("/", 2)[1];
522
+ const value = node.value;
523
+ const span = node.span;
524
+ switch (dataType) {
525
+ case "boolean":
526
+ assert(typeof value === "boolean");
527
+ return {type: "primitive", dataType, value, span, children: null};
528
+ case "char":
529
+ assert(typeof value === "string");
530
+ return {type: "primitive", dataType, value, span, children: null};
531
+ case "long":
532
+ assert(typeof value === "bigint");
533
+ return {type: "primitive", dataType, value, span, children: null};
534
+ case "byte":
535
+ case "unsigned-byte":
536
+ case "short":
537
+ case "unsigned-short":
538
+ case "int":
539
+ case "float":
540
+ case "double":
541
+ assert(typeof value === "number");
542
+ return {type: "primitive", dataType, value, span, children: null};
543
+ default:
544
+ throw new exc.InternalError(dataType);
545
+ }
546
+ }
547
+
548
+ switch (node.type) {
549
+ case "root": {
550
+ assert(children.length === 3);
551
+ const magic = children[0];
552
+ const version = children[1];
553
+ const contents = children[2];
554
+ assert(magic.type === "magic");
555
+ assert(version.type === "version");
556
+ assert(contents.type === "contents");
557
+ return {type: "root", span: node.span, children: [magic, version, contents]};
558
+ }
559
+ case "magic": {
560
+ assert(children.length === 0);
561
+ return {type: "magic", span: node.span, value: ObjectInputStream.STREAM_MAGIC, children: null}
562
+ }
563
+ case "version": {
564
+ assert(children.length === 0);
565
+ return {type: "version", span: node.span, value: ObjectInputStream.STREAM_VERSION, children: null}
566
+ }
567
+ case "contents": {
568
+ assert(children.every(c => c.type === "blockdata-sequence" || c.type === "object"));
569
+ return {type: "contents", span: node.span, children: children}
570
+ }
571
+ case "blockdata-sequence": {
572
+ assert(children.every(c => c.type === "blockdata" || (c.type === "object" && c.objectType === "reset")));
573
+ const rawValues = node.value as CSTNode[];
574
+ const values: ast.Node[] = rawValues.map(v => trasformCSTBottomUp(v, cstToAstNode));
575
+ assert(values.every(v => v.type === "primitive" || v.type === "utf"));
576
+ return {type: "blockdata-sequence", span: node.span, values, children}
577
+ }
578
+ case "blockdata": {
579
+ assert(children.length === 3);
580
+ const tc = children[0];
581
+ const length = children[1];
582
+ const bytes = children[2];
583
+ assert(tc.type === "tc")
584
+ assert(length.type === "primitive");
585
+ assert(bytes.type === "primitive" && bytes.dataType === "bytes");
586
+ if (tc.value === ObjectInputStream.TC_BLOCKDATA && length.dataType === "unsigned-byte") {
587
+ return {type: "blockdata", span: node.span, children: [tc, length, bytes]}
588
+ } else if (tc.value === ObjectInputStream.TC_BLOCKDATALONG && length.dataType === "long") {
589
+ return {type: "blockdata", span: node.span, children: [tc, length, bytes]}
590
+ } else {
591
+ throw new exc.InternalError();
592
+ }
593
+ }
594
+ case "tc": {
595
+ assert(children.length === 0);
596
+ assert(typeof node.value === "number" && ObjectInputStream.TC_BASE <= node.value && node.value <= ObjectInputStream.TC_MAX);
597
+ return {type: "tc", span: node.span, value: node.value as ast.TCNode["value"], children: null}
598
+ }
599
+ case "read1": {
600
+ assert(children.length === 0);
601
+ assert(typeof node.value === "number");
602
+ return {type: "primitive", span: node.span, dataType: "unsigned-byte", value: node.value, children: null}
603
+ }
604
+ case "read": {
605
+ assert(children.length === 0);
606
+ return {type: "primitive", dataType: "bytes", span: node.span, value: null, children: null}
607
+ }
608
+ case "utf": {
609
+ assert(children.length === 2);
610
+ const length = children[0];
611
+ const body = children[1];
612
+ assert(length.type === "primitive" && length.dataType === "unsigned-short");
613
+ assert(body.type === "utf-body");
614
+ return {type: "utf", value: body.value, span: node.span, children: [length, body]}
615
+ }
616
+ case "long-utf": {
617
+ assert(children.length === 2);
618
+ const length = children[0];
619
+ const body = children[1];
620
+ assert(length.type === "primitive" && length.dataType === "long");
621
+ assert(body.type === "utf-body");
622
+ return {type: "long-utf", value: body.value, span: node.span, children: [length, body]}
623
+ }
624
+ case "utf-body": {
625
+ assert(children.length === 0);
626
+ assert(typeof node.value === "string");
627
+ return {type: "utf-body", value: node.value, span: node.span, children: null}
628
+ }
629
+ case "object/reset": {
630
+ assert(children.length === 1);
631
+ const tc = children[0];
632
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_RESET);
633
+ assert(typeof node.value === "number");
634
+ return {type: "object", objectType: "reset", newEpoch: node.value, span: node.span, children: [tc]}
635
+ }
636
+ case "object/null": {
637
+ assert(children.length === 1);
638
+ const tc = children[0];
639
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_NULL);
640
+ return {type: "object", objectType: "null", span: node.span, children: [tc]}
641
+ }
642
+ case "object/handle": {
643
+ assert(children.length === 2);
644
+ const tc = children[0];
645
+ const ref = children[1];
646
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_REFERENCE);
647
+ assert(ref.type === "primitive" && ref.dataType === "int");
648
+ assert(typeof node.value === "number")
649
+ return {type: "object", objectType: "prev-object", span: node.span, value: {epoch: node.value, handle: ref.value}, children: [tc, ref]}
650
+ }
651
+ case "object/class": {
652
+ assert(children.length === 1);
653
+ const tc = children[0];
654
+ const desc = children[0];
655
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_CLASS);
656
+ assertDescNode(desc);
657
+ assert(node.handle !== undefined);
658
+ return {type: "object", objectType: "new-class", span: node.span, handle: node.handle, children: [tc, desc]}
659
+ }
660
+ case "object/class-desc": {
661
+ assert(children.length === 1);
662
+ const first = children[0];
663
+ assertDescNode(first);
664
+ return first;
665
+ }
666
+ case "proxy-desc": {
667
+ assert(children.length >= 2);
668
+ const tc = children[0];
669
+ const numIfaces = children[1];
670
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_PROXYCLASSDESC);
671
+ assert(numIfaces.type === "primitive" && numIfaces.dataType === "int");
672
+ assert(children.length >= 2 + numIfaces.value);
673
+ const ifaces = children.slice(2, 2 + numIfaces.value);
674
+ assert(ifaces.every(x => x.type === "utf"));
675
+ assert(children.length === 2 + numIfaces.value + 2);
676
+ const annotation = children[2 + numIfaces.value];
677
+ const superDesc = children[2 + numIfaces.value + 1];
678
+ assert(annotation.type === "annotation");
679
+ assertDescNode(superDesc);
680
+ assert(node.handle !== undefined);
681
+ return {type: "object", objectType: "new-class-desc", handle: node.handle, span: node.span, children: [tc, {
682
+ type: "proxy-class-desc-info",
683
+ span: {start: node.span.start+1, end: node.span.end},
684
+ children: [numIfaces, ...ifaces, annotation, superDesc],
685
+ }]}
686
+ }
687
+ case "non-proxy-desc": {
688
+ assert(children.length === 7);
689
+ const tc = children[0];
690
+ const name = children[1];
691
+ const suid = children[2];
692
+ const flags = children[3];
693
+ const fields = children[4];
694
+ const annotation = children[5];
695
+ const superDesc = children[6];
696
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_CLASSDESC);
697
+ assert(name.type === "utf");
698
+ assert(suid.type === "primitive" && suid.dataType === "long");
699
+ assert(flags.type === "primitive" && flags.dataType === "unsigned-byte");
700
+ assert(fields.type === "fields");
701
+ assert(annotation.type === "annotation");
702
+ assertDescNode(superDesc);
703
+ assert(node.handle !== undefined);
704
+ return {type: "object", objectType: "new-class-desc", handle: node.handle, span: node.span, children: [
705
+ tc, name, suid, {
706
+ type: "class-desc-info",
707
+ span: {start: suid.span.end, end: node.span.end},
708
+ children: [flags, fields, annotation, superDesc],
709
+ }
710
+ ]}
711
+ }
712
+ case "object/string": {
713
+ assert(children.length > 0);
714
+ const first = children[0];
715
+ if (first.type === "object" && first.objectType === "prev-object")
716
+ return first;
717
+
718
+ assert(children.length === 2);
719
+ const tc = children[0];
720
+ const utf = children[1];
721
+ assert(tc.type === "tc");
722
+ if (tc.value === ObjectInputStream.TC_STRING) {
723
+ assert(utf.type === "utf");
724
+ assert(node.handle !== undefined);
725
+ return {type: "object", objectType: "new-string", value: utf.value, handle: node.handle, span: node.span, children: [tc, utf]};
726
+ } else if (tc.value === ObjectInputStream.TC_LONGSTRING) {
727
+ assert(utf.type === "long-utf");
728
+ assert(node.handle !== undefined);
729
+ return {type: "object", objectType: "new-string", value: utf.value, handle: node.handle, span: node.span, children: [tc, utf]};
730
+ } else {
731
+ throw new exc.InternalError();
732
+ }
733
+ }
734
+ case "object/array": {
735
+ assert(children.length >= 3);
736
+ const tc = children[0];
737
+ const desc = children[1];
738
+ const len = children[2];
739
+ const values = children.slice(3);
740
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_ARRAY);
741
+ assertDescNode(desc);
742
+ assert(len.type === "primitive" && len.dataType === "int");
743
+ assert(len.value === values.length);
744
+ assert(values.every(v => v.type === "primitive" || v.type === "object"));
745
+ const firstItem = values.length > 0 ? values[0] : null;
746
+ if (firstItem?.type === "primitive") {
747
+ assert(values.every(v => v.type === "primitive" && v.dataType === firstItem.dataType));
748
+ } else {
749
+ assert(values.every(v => v.type === "object"));
750
+ }
751
+ assert(node.handle !== undefined);
752
+ return {type: "object", objectType: "new-array", handle: node.handle, span: node.span, children: [tc, desc, len, {
753
+ type: "values", span: {start: len.span.end, end: node.span.end}, children: values
754
+ }]}
755
+ }
756
+ case "object/enum": {
757
+ assert(children.length === 2);
758
+ const tc = children[0];
759
+ const desc = children[1];
760
+ const name = children[2];
761
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_ENUM);
762
+ assertDescNode(desc);
763
+ assertStringNode(name);
764
+ assert(node.handle !== undefined);
765
+ return {type: "object", objectType: "new-enum", span: node.span, handle: node.handle, children: [tc, desc, name]}
766
+ }
767
+ case "object/instance": {
768
+ assert(children.length === 3);
769
+ const tc = children[0];
770
+ const desc = children[1];
771
+ const data = children[2];
772
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_OBJECT);
773
+ assertDescNode(desc);
774
+ assert(data.type === "external-data" || data.type === "serial-data");
775
+ assert(node.handle !== undefined);
776
+ return {type: "object", objectType: "new-object", handle: node.handle, span: node.span, children: [tc, desc, data]}
777
+ }
778
+ case "external-data": {
779
+ // PROTOCOL_VERSION_1
780
+ if (children.every(c => c.type === "primitive" || c.type === "object" || c.type === "utf")) {
781
+ return {type: "external-data", protocolVersion: 1, span: node.span, children: children}
782
+ }
783
+ // PROTOCOL_VERSION_2
784
+ else {
785
+ assert(children.length > 0);
786
+ const annotation = children[children.length-1];
787
+ assert(annotation.type === "annotation");
788
+ const before = children.slice(0, children.length-1);
789
+ assert(before.every(c => c.type === "object" || c.type === "blockdata-sequence"));
790
+ const contents = annotation.children[0];
791
+ const endBlock = annotation.children[1];
792
+ const newContents: ast.ContentsNode = {
793
+ type: "contents",
794
+ span: {start: node.span.start, end: contents.span.end},
795
+ children: [...before, ...contents.children],
796
+ }
797
+ return {type: "external-data", protocolVersion: 2, span: node.span, children: [{
798
+ type: "annotation",
799
+ span: node.span,
800
+ children: [newContents, endBlock],
801
+ }]}
802
+ }
803
+ }
804
+ case "serial-data": {
805
+ assert(children.every(c => c.type === "class-data"));
806
+ return {type: "serial-data", span: node.span, children: children}
807
+ }
808
+ case "class-data-wr": {
809
+ assert(children.length > 0);
810
+ const annotation = children[children.length-1];
811
+ assert(annotation.type === "annotation");
812
+ let beforeValues: (ast.BlockDataSequenceNode | ast.ObjectNode)[];
813
+ let values: ast.ValuesNode | null;
814
+ let afterValues: (ast.BlockDataSequenceNode | ast.ObjectNode)[];
815
+ const valuesIdx = children.findIndex(c => c.type === "values");
816
+ if (valuesIdx === -1) {
817
+ beforeValues = [];
818
+ values = null;
819
+ const tempAfter = children.slice(0, -1);
820
+ assert(tempAfter.every(c => c.type === "blockdata-sequence" || c.type === "object"));
821
+ afterValues = tempAfter;
822
+ } else {
823
+ const tempBefore = children.slice(0, valuesIdx);
824
+ const tempValues = children[valuesIdx];
825
+ const tempAfter = children.slice(valuesIdx+1, -1);
826
+ assert(tempBefore.every(c => c.type === "blockdata-sequence" || c.type === "object"));
827
+ assert(tempValues.type === "values");
828
+ assert(tempAfter.every(c => c.type === "blockdata-sequence" || c.type === "object"));
829
+ beforeValues = tempBefore;
830
+ values = tempValues;
831
+ afterValues = tempAfter;
832
+ }
833
+ const annotationSpan = {
834
+ start: afterValues.length > 0 ? afterValues[0].span.start : annotation.span.start,
835
+ end: annotation.span.end,
836
+ };
837
+ const annotationContentsSpan = {start: annotationSpan.start, end: annotationSpan.end-1};
838
+ const newAnnotation: ast.AnnotationNode = {
839
+ type: "annotation",
840
+ span: annotationSpan,
841
+ children: [{
842
+ type: "contents",
843
+ span: annotationContentsSpan,
844
+ children: [...afterValues, ...annotation.children[0].children],
845
+ }, annotation.children[1]],
846
+ }
847
+ const constentsSpan = {start: node.span.start, end: values !== null ? values.span.start : -1};
848
+ const contents: ast.ContentsNode = {
849
+ type: "contents",
850
+ span: constentsSpan,
851
+ children: [...beforeValues],
852
+ }
853
+ const beforeAnnotation = values !== null ? [contents, values] as const : [] as const;
854
+ return {type: "class-data", writeMethod: true, span: node.span, children: [...beforeAnnotation, newAnnotation]}
855
+ }
856
+ case "class-data-no-wr": {
857
+ const hasValues = children.length > 0 && children[children.length-1].type === "values";
858
+ const values = hasValues ? children[children.length-1] : null;
859
+ assert(values === null || values.type === "values");
860
+ const before = children.slice(0, hasValues ? -1 : undefined);
861
+ assert(before.every(c => c.type === "blockdata-sequence" || c.type === "object"));
862
+ const contentsStart = node.span.start;
863
+ const contentsEnd = before.length > 0 ? before[before.length-1].span.end : values !== null ? values.span.start : node.span.end;
864
+ const contents: ast.ContentsNode = {
865
+ type: "contents",
866
+ span: {start: contentsStart, end: contentsEnd},
867
+ children: before,
868
+ }
869
+ const valuesNodes = values !== null ? [values] as const : [] as const;
870
+ return {type: "class-data", writeMethod: false, span: node.span, children: [
871
+ contents, ...valuesNodes,
872
+ ]}
873
+ }
874
+ case "object/exception": {
875
+ assert(node.error !== undefined);
876
+ assert(children.length === 2);
877
+ const tc = children[0];
878
+ const throwable = children[1];
879
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_EXCEPTION);
880
+ assert(throwable.type === "object" && (throwable.objectType === "new-object" || throwable.objectType === "prev-object"));
881
+ assert(typeof node.value === "number");
882
+ const newEpoch = node.value;
883
+ return {type: "object", objectType: "exception", exceptionEpoch: newEpoch-1, newEpoch: newEpoch, span: node.span, children: [tc, throwable]}
884
+ }
885
+ case "annotation": {
886
+ assert(children.length > 0);
887
+ const contents = children.slice(0, -1);
888
+ const tc = children[children.length-1];
889
+ assert(contents.every(c => c.type === "blockdata-sequence" || c.type === "object"));
890
+ assert(tc.type === "tc" && tc.value === ObjectInputStream.TC_ENDBLOCKDATA);
891
+ return {type: "annotation", span: node.span, children: [{
892
+ type: "contents", span: {start: node.span.start, end: tc.span.start}, children: contents,
893
+ }, tc]}
894
+ }
895
+ case "fields": {
896
+ assert(children.length > 0);
897
+ const length = children[0];
898
+ const fields = children.slice(1);
899
+ assert(length.type === "primitive" && length.dataType === "short");
900
+ assert(fields.every(f => f.type === "field-desc"));
901
+ return {type: "fields", span: node.span, children: [length, ...fields]}
902
+ }
903
+ case "field": {
904
+ assert(children.length >= 2);
905
+ const typecode = children[0];
906
+ const name = children[1];
907
+ assert(typecode.type === "primitive" && typecode.dataType === "unsigned-byte");
908
+ assert(name.type === "utf");
909
+ const tcStr = String.fromCharCode(typecode.value);
910
+ if (tcStr === "[" || tcStr === "L") {
911
+ assert(children.length === 3);
912
+ const className = children[2];
913
+ assertStringNode(className);
914
+ return {type: "field-desc", fieldType: "object", span: node.span, children: [typecode, name, className]}
915
+ } else {
916
+ assert(children.length === 2);
917
+ return {type: "field-desc", fieldType: "primitive", span: node.span, children: [typecode, name]}
918
+ }
919
+ }
920
+ case "values": {
921
+ assert(children.every(c => c.type === "primitive" || c.type === "object"));
922
+ return {type: "values", span: node.span, children: children}
923
+ }
924
+ default:
925
+ throw new exc.InternalError();
926
+ }
927
+ }
928
+
929
+ function assertDescNode(node: ast.Node): asserts node is ast.ClassDescNode {
930
+ assert(
931
+ node.type === "object" &&
932
+ (node.objectType === "new-class-desc" || node.objectType === "null" || node.objectType === "prev-object")
933
+ )
934
+ }
935
+
936
+ function assertStringNode(node: ast.Node): asserts node is ast.StringNode {
937
+ assert(
938
+ node.type === "object" &&
939
+ (node.objectType === "new-string" || node.objectType === "prev-object")
940
+ )
941
+ }