-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclass_generator.js
More file actions
402 lines (326 loc) · 12.8 KB
/
Copy pathclass_generator.js
File metadata and controls
402 lines (326 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import fs from 'node:fs';
// The base structure of this file was written by Claude - it was very useful. I could then focus on the implementation
// of the JVM instructions.
// of course, I had already understood the structure of the ClassFile format, while writing the read_jvm_bytecode.js
// file.
export class ClassFileGenerator {
constructor() {
this.buffer = [];
this.constantPool = [];
this.constantPoolMap = new Map(); // For deduplication
}
// Write unsigned integers of different sizes
writeU1(value) {
this.buffer.push(value & 0xFF);
}
writeU2(value) {
this.buffer.push((value >> 8) & 0xFF);
this.buffer.push(value & 0xFF);
}
writeU4(value) {
this.buffer.push((value >> 24) & 0xFF);
this.buffer.push((value >> 16) & 0xFF);
this.buffer.push((value >> 8) & 0xFF);
this.buffer.push(value & 0xFF);
}
writeBytes(bytes) {
this.buffer.push(...bytes);
}
// Constant pool management
addUtf8Constant(str) {
const key = `utf8:${str}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const bytes = new TextEncoder().encode(str);
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 1, // CONSTANT_Utf8
length: bytes.length,
bytes: bytes
});
this.constantPoolMap.set(key, index);
return index;
}
addClassConstant(nameIndex) {
const key = `class:${nameIndex}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 7, // CONSTANT_Class
nameIndex: nameIndex
});
this.constantPoolMap.set(key, index);
return index;
}
addNameAndTypeConstant(nameIndex, descriptorIndex) {
const key = `nameandtype:${nameIndex}:${descriptorIndex}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 12, // CONSTANT_NameAndType
nameIndex: nameIndex,
descriptorIndex: descriptorIndex
});
this.constantPoolMap.set(key, index);
return index;
}
addMethodrefConstant(classIndex, nameAndTypeIndex) {
const key = `methodref:${classIndex}:${nameAndTypeIndex}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 10, // CONSTANT_Methodref
classIndex: classIndex,
nameAndTypeIndex: nameAndTypeIndex
});
this.constantPoolMap.set(key, index);
return index;
}
addStringConstant(stringIndex) {
const key = `string:${stringIndex}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 8, // CONSTANT_String
stringIndex: stringIndex
});
this.constantPoolMap.set(key, index);
return index;
}
addFieldrefConstant(classIndex, nameAndTypeIndex) {
const key = `fieldref:${classIndex}:${nameAndTypeIndex}`;
if (this.constantPoolMap.has(key)) {
return this.constantPoolMap.get(key);
}
const index = this.constantPool.length + 1;
this.constantPool.push({
tag: 9, // CONSTANT_Fieldref
classIndex: classIndex,
nameAndTypeIndex: nameAndTypeIndex
});
this.constantPoolMap.set(key, index);
return index;
}
// Write constant pool to buffer
writeConstantPool() {
this.writeU2(this.constantPool.length + 1); // constant_pool_count
for (const constant of this.constantPool) {
this.writeU1(constant.tag);
switch (constant.tag) {
case 1: // CONSTANT_Utf8
this.writeU2(constant.length);
this.writeBytes(Array.from(constant.bytes));
break;
case 7: // CONSTANT_Class
this.writeU2(constant.nameIndex);
break;
case 8: // CONSTANT_String
this.writeU2(constant.stringIndex);
break;
case 9: // CONSTANT_Fieldref
case 10: // CONSTANT_Methodref
this.writeU2(constant.classIndex);
this.writeU2(constant.nameAndTypeIndex);
break;
case 12: // CONSTANT_NameAndType
this.writeU2(constant.nameIndex);
this.writeU2(constant.descriptorIndex);
break;
}
}
}
generateHelloWorldClass(className = 'HelloWorld', makeInstructions = () => []) {
// Reset buffer and constants
this.buffer = [];
this.constantPool = [];
this.constantPoolMap.clear();
// Add required constants
const objectClassNameIndex = this.addUtf8Constant("java/lang/Object");
const objectClassIndex = this.addClassConstant(objectClassNameIndex);
const helloWorldClassNameIndex = this.addUtf8Constant(className);
const helloWorldClassIndex = this.addClassConstant(helloWorldClassNameIndex);
const systemClassNameIndex = this.addUtf8Constant("java/lang/System");
const systemClassIndex = this.addClassConstant(systemClassNameIndex);
const helloStringIndex = this.addUtf8Constant("Hello, World!");
const helloStringConstantIndex = this.addStringConstant(helloStringIndex);
const printStreamClassNameIndex = this.addUtf8Constant("java/io/PrintStream");
const printStreamClassIndex = this.addClassConstant(printStreamClassNameIndex);
const outFieldNameIndex = this.addUtf8Constant("out");
const printStreamDescriptorIndex = this.addUtf8Constant("Ljava/io/PrintStream;");
const outNameAndTypeIndex = this.addNameAndTypeConstant(outFieldNameIndex, printStreamDescriptorIndex);
const outFieldrefIndex = this.addFieldrefConstant(systemClassIndex, outNameAndTypeIndex);
const printlnNameIndex = this.addUtf8Constant("print");
const printlnDescriptorIndex = this.addUtf8Constant("(C)V");
const printlnNameAndTypeIndex = this.addNameAndTypeConstant(printlnNameIndex, printlnDescriptorIndex);
const printlnMethodrefIndex = this.addMethodrefConstant(printStreamClassIndex, printlnNameAndTypeIndex);
// StackMapTable (for Java 8 compatibility)
const stackMapTableConstantIndex = this.addUtf8Constant("StackMapTable");
const cellsDescriptorIndex = this.addUtf8Constant("[B");
this.addClassConstant(cellsDescriptorIndex)
// end of StackMapTable
// INPUT
const inputStreamClassNameIndex = this.addUtf8Constant("java/io/InputStream");
const inputStreamClassIndex = this.addClassConstant(inputStreamClassNameIndex);
const inFieldNameIndex = this.addUtf8Constant("in");
const inputStreamDescriptorIndex = this.addUtf8Constant("Ljava/io/InputStream;");
const inNameAndTypeIndex = this.addNameAndTypeConstant(inFieldNameIndex, inputStreamDescriptorIndex);
const inFieldrefIndex = this.addFieldrefConstant(systemClassIndex, inNameAndTypeIndex);
const readNameIndex = this.addUtf8Constant("read");
const readDescriptorIndex = this.addUtf8Constant("()I");
const readNameAndTypeIndex = this.addNameAndTypeConstant(readNameIndex, readDescriptorIndex);
const readMethodrefIndex = this.addMethodrefConstant(inputStreamClassIndex, readNameAndTypeIndex);
// INPUT
const mainNameIndex = this.addUtf8Constant("main");
const mainDescriptorIndex = this.addUtf8Constant("([Ljava/lang/String;)V");
const codeNameIndex = this.addUtf8Constant("Code");
const constructorNameIndex = this.addUtf8Constant("<init>");
const constructorDescriptorIndex = this.addUtf8Constant("()V");
const constructorNameAndTypeIndex = this.addNameAndTypeConstant(constructorNameIndex, constructorDescriptorIndex);
const constructorMethodrefIndex = this.addMethodrefConstant(objectClassIndex, constructorNameAndTypeIndex);
const symbolicConstantPool = {
output: {
fieldRef: outFieldrefIndex,
printlnDescriptorIndex,
printlnNameAndTypeIndex,
printlnMethodrefIndex
},
input: {
classNameIndex: inputStreamClassNameIndex,
classIndex: inputStreamClassIndex,
fieldNameIndex: inFieldNameIndex,
descriptorIndex: inputStreamDescriptorIndex,
nameAndTypeIndex: inNameAndTypeIndex,
fieldRef: inFieldrefIndex,
readNameIndex,
readDescriptorIndex,
readNameAndTypeIndex,
readMethodrefIndex
}
};
const { code: jvmInstructions, stackMapTable } = makeInstructions({ constantPool: this.constantPool, symbolicConstantPool });
// Write ClassFile structure
// Magic number
this.writeU4(0xCAFEBABE);
// Version (Java 8 = 52.0)
this.writeU2(0); // minor_version
this.writeU2(52); // major_version
// Constant pool
this.writeConstantPool();
// Access flags (public)
this.writeU2(0x0021); // ACC_PUBLIC | ACC_SUPER
// This class
this.writeU2(helloWorldClassIndex);
// Super class
this.writeU2(objectClassIndex);
// Interfaces
this.writeU2(0); // interfaces_count
// Fields
this.writeU2(0); // fields_count
// Methods (constructor + main)
this.writeU2(2); // methods_count
// Constructor method
this.writeU2(0x0001); // ACC_PUBLIC
this.writeU2(constructorNameIndex); // name_index
this.writeU2(constructorDescriptorIndex); // descriptor_index
this.writeU2(1); // attributes_count
// Constructor Code attribute
this.writeU2(codeNameIndex); // attribute_name_index
this.writeU4(17); // attribute_length
this.writeU2(1); // max_stack
this.writeU2(1); // max_locals
this.writeU4(5); // code_length
// Constructor bytecode: aload_0, invokespecial Object.<init>, return
this.writeU1(0x2A); // aload_0
this.writeU1(0xB7); // invokespecial
this.writeU2(constructorMethodrefIndex);
this.writeU1(0xB1); // return
this.writeU2(0); // exception_table_length
this.writeU2(0); // attributes_count
// Main method
this.writeU2(0x0009); // ACC_PUBLIC | ACC_STATIC
this.writeU2(mainNameIndex); // name_index
this.writeU2(mainDescriptorIndex); // descriptor_index
this.writeU2(1); // attributes_count
// Main Code attribute
// Code attribute base size is 12 (u2 + u2 + u4 + u2 + u2)
// without the code
// u2 max_stack
// u2 max_locals
// u4 code_length
// u2 exception_table_length
// u2 attributes_count
const CODE_ATTR_BASE_SIZE = 12;
// const STACK_MAP_TABLE_ATTR_BASE_SIZE = 6; // u2 attribute_name_index + u4 attribute_length
this.writeU2(codeNameIndex); // attribute_name_index
const computedStackMapTable = this.computeStackMapTable({
stackMapTable,
stackMapTableConstantIndex
});
this.writeU4(CODE_ATTR_BASE_SIZE + jvmInstructions.length + computedStackMapTable.length); // attribute_length
this.writeU2(4); // max_stack
this.writeU2(3); // max_locals
this.writeU4(jvmInstructions.length); // code_length
this.writeBytes(jvmInstructions);
this.writeU2(0); // exception_table_length
const codeAttributesCount = stackMapTable.length > 0 ? 1 : 0;
this.writeU2(codeAttributesCount); // attributes_count
if (codeAttributesCount > 0) {
this.writeBytes(computedStackMapTable);
}
// Class attributes
this.writeU2(0); // attributes_count
return new Uint8Array(this.buffer);
}
computeStackMapTable({
stackMapTable,
stackMapTableConstantIndex,
}) {
const gen = new ClassFileGenerator();
gen.writeU2(stackMapTableConstantIndex); // attribute_name_index
const entriesBuf = new ClassFileGenerator();
for (const entry of stackMapTable) {
let entrySize = 0;
const frameType = entry.frameType || entry.offsetDelta;
const isAppendFrame = frameType >= 252 && frameType <= 254;
const isSameFrame = frameType >= 0 && frameType <= 63;
const isSameFrameExtended = frameType === 251;
if ([isSameFrame, isSameFrameExtended, isAppendFrame].every(v => !v)) {
throw new Error(`Invalid frame type: ${frameType}`);
}
entriesBuf.writeU1(frameType); // frame type
entrySize += 1;
if (isAppendFrame || isSameFrameExtended) {
entriesBuf.writeU2(entry.offsetDelta); // offset delta
entrySize += 2;
}
if (entry.locals) {
for (const local of entry.locals) {
entriesBuf.writeU1(local.type); // write local type
entrySize += 1;
if (local.type === 7) { // Object type
entriesBuf.writeU2(local.cpoolIndex); // write constant pool index for object type
entrySize += 2;
}
}
}
}
const numberEntries = stackMapTable.length;
// 2 is the size of number_of_entries (u2)
gen.writeU4(2 + entriesBuf.buffer.length); // attribute_length
gen.writeU2(numberEntries); // number of entries in StackMapTable
gen.writeBytes(entriesBuf.buffer);
return gen.buffer;
}
static saveToFile(filename, classData) {
fs.writeFileSync(filename, classData);
}
}