1use rajac_base::shared_string::SharedString;
2use ristretto_classfile::attributes::Attribute;
3use ristretto_classfile::{ClassFile, ConstantPool, Field, Method};
4
5pub fn pretty_print_classfile(class_file: &ClassFile) -> SharedString {
6 let mut out = String::new();
7
8 let class_name_internal = class_file
9 .constant_pool
10 .try_get_class(class_file.this_class)
11 .unwrap_or("<invalid:this_class>");
12 let super_name_internal = class_file
13 .constant_pool
14 .try_get_class(class_file.super_class)
15 .unwrap_or("<invalid:super_class>");
16
17 let class_name = internal_to_java_name(class_name_internal);
18 let super_name = internal_to_java_name(super_name_internal);
19
20 out.push_str(&format!(
21 "// version: {}.{} ({})\n",
22 class_file.version.major(),
23 class_file.version.minor(),
24 class_file.version
25 ));
26
27 out.push_str(&class_file.access_flags.as_code());
28 out.push(' ');
29 out.push_str(&class_name);
30 if super_name_internal != "java/lang/Object" {
31 out.push_str(" extends ");
32 out.push_str(&super_name);
33 }
34 out.push_str(" {\n");
35
36 if !class_file.interfaces.is_empty() {
37 out.push_str(" // implements\n");
38 for iface in &class_file.interfaces {
39 let iface_name = class_file
40 .constant_pool
41 .try_get_class(*iface)
42 .map(internal_to_java_name)
43 .unwrap_or_else(|_| "<invalid:interface>".to_string());
44 out.push_str(&format!(" // - {}\n", iface_name));
45 }
46 }
47
48 if !class_file.fields.is_empty() {
49 out.push_str("\n // fields\n");
50 let mut sorted_fields: Vec<_> = class_file.fields.iter().collect();
51 sorted_fields.sort_by(|a, b| {
52 let name_a = class_file
53 .constant_pool
54 .try_get_utf8(a.name_index)
55 .unwrap_or("");
56 let name_b = class_file
57 .constant_pool
58 .try_get_utf8(b.name_index)
59 .unwrap_or("");
60 name_a.cmp(name_b)
61 });
62 for field in sorted_fields {
63 pretty_print_field(&mut out, &class_file.constant_pool, field);
64 }
65 }
66
67 out.push_str("\n // methods\n");
68 let mut sorted_methods: Vec<_> = class_file.methods.iter().collect();
70 sorted_methods.sort_by(|a, b| {
71 let name_a = class_file
72 .constant_pool
73 .try_get_utf8(a.name_index)
74 .unwrap_or("");
75 let name_b = class_file
76 .constant_pool
77 .try_get_utf8(b.name_index)
78 .unwrap_or("");
79 name_a.cmp(name_b)
80 });
81 for method in sorted_methods {
82 pretty_print_method(&mut out, &class_file.constant_pool, method);
83 }
84
85 if !class_file.attributes.is_empty() {
86 out.push_str("\n // class attributes\n");
87 let mut rendered_attributes = class_file
88 .attributes
89 .iter()
90 .filter_map(|attribute| render_class_attribute(attribute, &class_file.constant_pool))
91 .collect::<Vec<_>>();
92 rendered_attributes.sort();
93 for rendered in rendered_attributes {
94 out.push_str(&rendered);
95 }
96 }
97
98 out.push_str("}\n");
99
100 SharedString::from(out)
101}
102
103fn internal_to_java_name(internal: &str) -> String {
104 internal.replace('/', ".")
105}
106
107fn resolve_class_name(constant_pool: &ConstantPool, index: u16) -> String {
108 constant_pool
109 .try_get_class(index)
110 .map(internal_to_java_name)
111 .unwrap_or_else(|_| "<invalid:class>".to_string())
112}
113
114fn resolve_optional_class_name(
115 constant_pool: &ConstantPool,
116 index: u16,
117 empty_value: &str,
118) -> String {
119 if index == 0 {
120 return empty_value.to_string();
121 }
122 resolve_class_name(constant_pool, index)
123}
124
125fn resolve_optional_utf8(constant_pool: &ConstantPool, index: u16, empty_value: &str) -> String {
126 if index == 0 {
127 return empty_value.to_string();
128 }
129 constant_pool
130 .try_get_utf8(index)
131 .map(|value| value.to_string())
132 .unwrap_or_else(|_| "<invalid:utf8>".to_string())
133}
134
135fn format_ldc_constant(constant_pool: &ConstantPool, index: u16) -> String {
141 if let Ok(value) = constant_pool.try_get_utf8(index) {
142 return format!("ldc \"{value}\"");
143 }
144
145 if let Ok(value) = constant_pool.try_get_string(index) {
146 return format!("ldc \"{value}\"");
147 }
148
149 if let Ok(value) = constant_pool.try_get_integer(index) {
150 return format!("ldc {value}");
151 }
152
153 if let Ok(value) = constant_pool.try_get_float(index) {
154 return format!("ldc {value}");
155 }
156
157 if let Ok(value) = constant_pool.try_get_long(index) {
158 return format!("ldc {value}");
159 }
160
161 if let Ok(value) = constant_pool.try_get_double(index) {
162 return format!("ldc {value}");
163 }
164
165 "ldc <invalid:constant>".to_string()
166}
167
168fn pretty_print_field(out: &mut String, constant_pool: &ConstantPool, field: &Field) {
169 let name = constant_pool
170 .try_get_utf8(field.name_index)
171 .unwrap_or("<invalid:name>");
172 let descriptor = constant_pool
173 .try_get_utf8(field.descriptor_index)
174 .unwrap_or("<invalid:descriptor>");
175
176 out.push_str(&format!(
177 " {} {} /* {} */;\n",
178 field.access_flags.as_code(),
179 name,
180 descriptor
181 ));
182}
183
184fn pretty_print_method(out: &mut String, constant_pool: &ConstantPool, method: &Method) {
185 let name = constant_pool
186 .try_get_utf8(method.name_index)
187 .unwrap_or("<invalid:name>");
188 let descriptor = constant_pool
189 .try_get_utf8(method.descriptor_index)
190 .unwrap_or("<invalid:descriptor>");
191
192 out.push_str(&format!(
193 " {}{}{} /* {} */;\n",
194 method.access_flags.as_code(),
195 if name == "<init>" { "" } else { " " },
196 name,
197 descriptor
198 ));
199
200 for attribute in &method.attributes {
202 if let Attribute::Code {
203 max_stack,
204 max_locals,
205 code,
206 exception_table,
207 ..
208 } = attribute
209 {
210 out.push_str(" Code:\n");
211 out.push_str(&format!(" max_stack = {}\n", max_stack));
212 out.push_str(&format!(" max_locals = {}\n", max_locals));
213
214 if !code.is_empty() {
215 out.push_str(" Code:\n");
216 for (i, instruction) in code.iter().enumerate() {
217 let offset = i;
218 let instruction_str = format_instruction(instruction, constant_pool);
219 out.push_str(&format!(" {}: {}\n", offset, instruction_str));
220 }
221 }
222
223 if !exception_table.is_empty() {
224 out.push_str(" ExceptionTable:\n");
225 for (i, exception) in exception_table.iter().enumerate() {
226 out.push_str(&format!(
227 " {} {} {} {} {}\n",
228 i,
229 exception.range_pc.start,
230 exception.range_pc.end,
231 exception.handler_pc,
232 exception.catch_type
233 ));
234 }
235 }
236
237 }
241 if let Attribute::Exceptions {
242 exception_indexes, ..
243 } = attribute
244 {
245 out.push_str(" Exceptions:\n");
246 for exception_index in exception_indexes {
247 let exception_name = constant_pool
248 .try_get_class(*exception_index)
249 .unwrap_or("<invalid:class>");
250 out.push_str(&format!(" {}\n", exception_name));
251 }
252 }
253 }
254}
255
256fn format_instruction(
257 instruction: &ristretto_classfile::attributes::Instruction,
258 constant_pool: &ConstantPool,
259) -> String {
260 use ristretto_classfile::attributes::Instruction;
261
262 match instruction {
263 Instruction::Nop => "nop".to_string(),
264 Instruction::Aconst_null => "aconst_null".to_string(),
265 Instruction::Iconst_m1 => "iconst_m1".to_string(),
266 Instruction::Iconst_0 => "iconst_0".to_string(),
267 Instruction::Iconst_1 => "iconst_1".to_string(),
268 Instruction::Iconst_2 => "iconst_2".to_string(),
269 Instruction::Iconst_3 => "iconst_3".to_string(),
270 Instruction::Iconst_4 => "iconst_4".to_string(),
271 Instruction::Iconst_5 => "iconst_5".to_string(),
272 Instruction::Lconst_0 => "lconst_0".to_string(),
273 Instruction::Lconst_1 => "lconst_1".to_string(),
274 Instruction::Fconst_0 => "fconst_0".to_string(),
275 Instruction::Fconst_1 => "fconst_1".to_string(),
276 Instruction::Fconst_2 => "fconst_2".to_string(),
277 Instruction::Dconst_0 => "dconst_0".to_string(),
278 Instruction::Dconst_1 => "dconst_1".to_string(),
279 Instruction::Bipush(byte) => format!("bipush {}", byte),
280 Instruction::Sipush(short) => format!("sipush {}", short),
281 Instruction::Ldc(index) => format_ldc_constant(constant_pool, u16::from(*index)),
282 Instruction::Ldc_w(index) => format_ldc_constant(constant_pool, *index),
283 Instruction::Ldc2_w(index) => format_ldc_constant(constant_pool, *index),
284 Instruction::Iload(index) => format!("iload {}", index),
285 Instruction::Lload(index) => format!("lload {}", index),
286 Instruction::Fload(index) => format!("fload {}", index),
287 Instruction::Dload(index) => format!("dload {}", index),
288 Instruction::Aload(index) => format!("aload {}", index),
289 Instruction::Iload_0 => "iload_0".to_string(),
290 Instruction::Iload_1 => "iload_1".to_string(),
291 Instruction::Iload_2 => "iload_2".to_string(),
292 Instruction::Iload_3 => "iload_3".to_string(),
293 Instruction::Lload_0 => "lload_0".to_string(),
294 Instruction::Lload_1 => "lload_1".to_string(),
295 Instruction::Lload_2 => "lload_2".to_string(),
296 Instruction::Lload_3 => "lload_3".to_string(),
297 Instruction::Fload_0 => "fload_0".to_string(),
298 Instruction::Fload_1 => "fload_1".to_string(),
299 Instruction::Fload_2 => "fload_2".to_string(),
300 Instruction::Fload_3 => "fload_3".to_string(),
301 Instruction::Dload_0 => "dload_0".to_string(),
302 Instruction::Dload_1 => "dload_1".to_string(),
303 Instruction::Dload_2 => "dload_2".to_string(),
304 Instruction::Dload_3 => "dload_3".to_string(),
305 Instruction::Aload_0 => "aload_0".to_string(),
306 Instruction::Aload_1 => "aload_1".to_string(),
307 Instruction::Aload_2 => "aload_2".to_string(),
308 Instruction::Aload_3 => "aload_3".to_string(),
309 Instruction::Iaload => "iaload".to_string(),
310 Instruction::Laload => "laload".to_string(),
311 Instruction::Faload => "faload".to_string(),
312 Instruction::Daload => "daload".to_string(),
313 Instruction::Aaload => "aaload".to_string(),
314 Instruction::Baload => "baload".to_string(),
315 Instruction::Caload => "caload".to_string(),
316 Instruction::Saload => "saload".to_string(),
317 Instruction::Istore(index) => format!("istore {}", index),
318 Instruction::Lstore(index) => format!("lstore {}", index),
319 Instruction::Fstore(index) => format!("fstore {}", index),
320 Instruction::Dstore(index) => format!("dstore {}", index),
321 Instruction::Astore(index) => format!("astore {}", index),
322 Instruction::Istore_0 => "istore_0".to_string(),
323 Instruction::Istore_1 => "istore_1".to_string(),
324 Instruction::Istore_2 => "istore_2".to_string(),
325 Instruction::Istore_3 => "istore_3".to_string(),
326 Instruction::Lstore_0 => "lstore_0".to_string(),
327 Instruction::Lstore_1 => "lstore_1".to_string(),
328 Instruction::Lstore_2 => "lstore_2".to_string(),
329 Instruction::Lstore_3 => "lstore_3".to_string(),
330 Instruction::Fstore_0 => "fstore_0".to_string(),
331 Instruction::Fstore_1 => "fstore_1".to_string(),
332 Instruction::Fstore_2 => "fstore_2".to_string(),
333 Instruction::Fstore_3 => "fstore_3".to_string(),
334 Instruction::Dstore_0 => "dstore_0".to_string(),
335 Instruction::Dstore_1 => "dstore_1".to_string(),
336 Instruction::Dstore_2 => "dstore_2".to_string(),
337 Instruction::Dstore_3 => "dstore_3".to_string(),
338 Instruction::Astore_0 => "astore_0".to_string(),
339 Instruction::Astore_1 => "astore_1".to_string(),
340 Instruction::Astore_2 => "astore_2".to_string(),
341 Instruction::Astore_3 => "astore_3".to_string(),
342 Instruction::Iastore => "iastore".to_string(),
343 Instruction::Lastore => "lastore".to_string(),
344 Instruction::Fastore => "fastore".to_string(),
345 Instruction::Dastore => "dastore".to_string(),
346 Instruction::Aastore => "aastore".to_string(),
347 Instruction::Bastore => "bastore".to_string(),
348 Instruction::Castore => "castore".to_string(),
349 Instruction::Sastore => "sastore".to_string(),
350 Instruction::Pop => "pop".to_string(),
351 Instruction::Pop2 => "pop2".to_string(),
352 Instruction::Dup => "dup".to_string(),
353 Instruction::Dup_x1 => "dup_x1".to_string(),
354 Instruction::Dup_x2 => "dup_x2".to_string(),
355 Instruction::Dup2 => "dup2".to_string(),
356 Instruction::Dup2_x1 => "dup2_x1".to_string(),
357 Instruction::Dup2_x2 => "dup2_x2".to_string(),
358 Instruction::Swap => "swap".to_string(),
359 Instruction::Iadd => "iadd".to_string(),
360 Instruction::Ladd => "ladd".to_string(),
361 Instruction::Fadd => "fadd".to_string(),
362 Instruction::Dadd => "dadd".to_string(),
363 Instruction::Isub => "isub".to_string(),
364 Instruction::Lsub => "lsub".to_string(),
365 Instruction::Fsub => "fsub".to_string(),
366 Instruction::Dsub => "dsub".to_string(),
367 Instruction::Imul => "imul".to_string(),
368 Instruction::Lmul => "lmul".to_string(),
369 Instruction::Fmul => "fmul".to_string(),
370 Instruction::Dmul => "dmul".to_string(),
371 Instruction::Idiv => "idiv".to_string(),
372 Instruction::Ldiv => "ldiv".to_string(),
373 Instruction::Fdiv => "fdiv".to_string(),
374 Instruction::Ddiv => "ddiv".to_string(),
375 Instruction::Irem => "irem".to_string(),
376 Instruction::Lrem => "lrem".to_string(),
377 Instruction::Frem => "frem".to_string(),
378 Instruction::Drem => "drem".to_string(),
379 Instruction::Ineg => "ineg".to_string(),
380 Instruction::Lneg => "lneg".to_string(),
381 Instruction::Fneg => "fneg".to_string(),
382 Instruction::Dneg => "dneg".to_string(),
383 Instruction::Ishl => "ishl".to_string(),
384 Instruction::Lshl => "lshl".to_string(),
385 Instruction::Ishr => "ishr".to_string(),
386 Instruction::Lshr => "lshr".to_string(),
387 Instruction::Iushr => "iushr".to_string(),
388 Instruction::Lushr => "lushr".to_string(),
389 Instruction::Iand => "iand".to_string(),
390 Instruction::Land => "land".to_string(),
391 Instruction::Ior => "ior".to_string(),
392 Instruction::Lor => "lor".to_string(),
393 Instruction::Ixor => "ixor".to_string(),
394 Instruction::Lxor => "lxor".to_string(),
395 Instruction::Iinc(index, value) => format!("iinc {} {}", index, value),
396 Instruction::I2l => "i2l".to_string(),
397 Instruction::I2f => "i2f".to_string(),
398 Instruction::I2d => "i2d".to_string(),
399 Instruction::L2i => "l2i".to_string(),
400 Instruction::L2f => "l2f".to_string(),
401 Instruction::L2d => "l2d".to_string(),
402 Instruction::F2i => "f2i".to_string(),
403 Instruction::F2l => "f2l".to_string(),
404 Instruction::F2d => "f2d".to_string(),
405 Instruction::D2i => "d2i".to_string(),
406 Instruction::D2l => "d2l".to_string(),
407 Instruction::D2f => "d2f".to_string(),
408 Instruction::I2b => "i2b".to_string(),
409 Instruction::I2c => "i2c".to_string(),
410 Instruction::I2s => "i2s".to_string(),
411 Instruction::Lcmp => "lcmp".to_string(),
412 Instruction::Fcmpl => "fcmpl".to_string(),
413 Instruction::Fcmpg => "fcmpg".to_string(),
414 Instruction::Dcmpl => "dcmpl".to_string(),
415 Instruction::Dcmpg => "dcmpg".to_string(),
416 Instruction::Ifeq(branch) => format!("ifeq {}", branch),
417 Instruction::Ifne(branch) => format!("ifne {}", branch),
418 Instruction::Iflt(branch) => format!("iflt {}", branch),
419 Instruction::Ifge(branch) => format!("ifge {}", branch),
420 Instruction::Ifgt(branch) => format!("ifgt {}", branch),
421 Instruction::Ifle(branch) => format!("ifle {}", branch),
422 Instruction::If_icmpeq(branch) => format!("if_icmpeq {}", branch),
423 Instruction::If_icmpne(branch) => format!("if_icmpne {}", branch),
424 Instruction::If_icmplt(branch) => format!("if_icmplt {}", branch),
425 Instruction::If_icmpge(branch) => format!("if_icmpge {}", branch),
426 Instruction::If_icmpgt(branch) => format!("if_icmpgt {}", branch),
427 Instruction::If_icmple(branch) => format!("if_icmple {}", branch),
428 Instruction::If_acmpeq(branch) => format!("if_acmpeq {}", branch),
429 Instruction::If_acmpne(branch) => format!("if_acmpne {}", branch),
430 Instruction::Goto(branch) => format!("goto {}", branch),
431 Instruction::Jsr(branch) => format!("jsr {}", branch),
432 Instruction::Ret(index) => format!("ret {}", index),
433 Instruction::Tableswitch(table_switch) => {
434 format!(
435 "tableswitch {{ {} to {} }}",
436 table_switch.low, table_switch.high
437 )
438 }
439 Instruction::Lookupswitch(lookup_switch) => {
440 format!("lookupswitch {{ {} entries }}", lookup_switch.pairs.len())
441 }
442 Instruction::Ireturn => "ireturn".to_string(),
443 Instruction::Lreturn => "lreturn".to_string(),
444 Instruction::Freturn => "freturn".to_string(),
445 Instruction::Dreturn => "dreturn".to_string(),
446 Instruction::Areturn => "areturn".to_string(),
447 Instruction::Return => "return".to_string(),
448 Instruction::Getstatic(index) => match constant_pool.try_get_field_ref(*index) {
449 Ok((class_index, name_and_type_index)) => {
450 let class = constant_pool
451 .try_get_class(*class_index)
452 .unwrap_or("<invalid:class>");
453 let (name_index, descriptor_index) = constant_pool
454 .try_get_name_and_type(*name_and_type_index)
455 .unwrap_or((&0, &0));
456 let name = constant_pool
457 .try_get_utf8(*name_index)
458 .unwrap_or("<invalid:name>");
459 let descriptor = constant_pool
460 .try_get_utf8(*descriptor_index)
461 .unwrap_or("<invalid:descriptor>");
462 format!(
463 "getstatic {}.{}:{}",
464 internal_to_java_name(class),
465 name,
466 descriptor
467 )
468 }
469 Err(_) => format!("getstatic #{}", index),
470 },
471 Instruction::Putstatic(index) => match constant_pool.try_get_field_ref(*index) {
472 Ok((class_index, name_and_type_index)) => {
473 let class = constant_pool
474 .try_get_class(*class_index)
475 .unwrap_or("<invalid:class>");
476 let (name_index, descriptor_index) = constant_pool
477 .try_get_name_and_type(*name_and_type_index)
478 .unwrap_or((&0, &0));
479 let name = constant_pool
480 .try_get_utf8(*name_index)
481 .unwrap_or("<invalid:name>");
482 let descriptor = constant_pool
483 .try_get_utf8(*descriptor_index)
484 .unwrap_or("<invalid:descriptor>");
485 format!(
486 "putstatic {}.{}:{}",
487 internal_to_java_name(class),
488 name,
489 descriptor
490 )
491 }
492 Err(_) => format!("putstatic #{}", index),
493 },
494 Instruction::Getfield(index) => match constant_pool.try_get_field_ref(*index) {
495 Ok((class_index, name_and_type_index)) => {
496 let class = constant_pool
497 .try_get_class(*class_index)
498 .unwrap_or("<invalid:class>");
499 let (name_index, descriptor_index) = constant_pool
500 .try_get_name_and_type(*name_and_type_index)
501 .unwrap_or((&0, &0));
502 let name = constant_pool
503 .try_get_utf8(*name_index)
504 .unwrap_or("<invalid:name>");
505 let descriptor = constant_pool
506 .try_get_utf8(*descriptor_index)
507 .unwrap_or("<invalid:descriptor>");
508 format!(
509 "getfield {}.{}:{}",
510 internal_to_java_name(class),
511 name,
512 descriptor
513 )
514 }
515 Err(_) => format!("getfield #{}", index),
516 },
517 Instruction::Putfield(index) => match constant_pool.try_get_field_ref(*index) {
518 Ok((class_index, name_and_type_index)) => {
519 let class = constant_pool
520 .try_get_class(*class_index)
521 .unwrap_or("<invalid:class>");
522 let (name_index, descriptor_index) = constant_pool
523 .try_get_name_and_type(*name_and_type_index)
524 .unwrap_or((&0, &0));
525 let name = constant_pool
526 .try_get_utf8(*name_index)
527 .unwrap_or("<invalid:name>");
528 let descriptor = constant_pool
529 .try_get_utf8(*descriptor_index)
530 .unwrap_or("<invalid:descriptor>");
531 format!(
532 "putfield {}.{}:{}",
533 internal_to_java_name(class),
534 name,
535 descriptor
536 )
537 }
538 Err(_) => format!("putfield #{}", index),
539 },
540 Instruction::Invokevirtual(index) => match constant_pool.try_get_method_ref(*index) {
541 Ok((class_index, name_and_type_index)) => {
542 let class = constant_pool
543 .try_get_class(*class_index)
544 .unwrap_or("<invalid:class>");
545 let (name_index, descriptor_index) = constant_pool
546 .try_get_name_and_type(*name_and_type_index)
547 .unwrap_or((&0, &0));
548 let name = constant_pool
549 .try_get_utf8(*name_index)
550 .unwrap_or("<invalid:name>");
551 let descriptor = constant_pool
552 .try_get_utf8(*descriptor_index)
553 .unwrap_or("<invalid:descriptor>");
554 format!(
555 "invokevirtual {}.{}:{}",
556 internal_to_java_name(class),
557 name,
558 descriptor
559 )
560 }
561 Err(_) => format!("invokevirtual #{}", index),
562 },
563 Instruction::Invokespecial(index) => match constant_pool.try_get_method_ref(*index) {
564 Ok((class_index, name_and_type_index)) => {
565 let class = constant_pool
566 .try_get_class(*class_index)
567 .unwrap_or("<invalid:class>");
568 let (name_index, descriptor_index) = constant_pool
569 .try_get_name_and_type(*name_and_type_index)
570 .unwrap_or((&0, &0));
571 let name = constant_pool
572 .try_get_utf8(*name_index)
573 .unwrap_or("<invalid:name>");
574 let descriptor = constant_pool
575 .try_get_utf8(*descriptor_index)
576 .unwrap_or("<invalid:descriptor>");
577 format!(
578 "invokespecial {}.{}:{}",
579 internal_to_java_name(class),
580 name,
581 descriptor
582 )
583 }
584 Err(_) => format!("invokespecial #{}", index),
585 },
586 Instruction::Invokestatic(index) => match constant_pool.try_get_method_ref(*index) {
587 Ok((class_index, name_and_type_index)) => {
588 let class = constant_pool
589 .try_get_class(*class_index)
590 .unwrap_or("<invalid:class>");
591 let (name_index, descriptor_index) = constant_pool
592 .try_get_name_and_type(*name_and_type_index)
593 .unwrap_or((&0, &0));
594 let name = constant_pool
595 .try_get_utf8(*name_index)
596 .unwrap_or("<invalid:name>");
597 let descriptor = constant_pool
598 .try_get_utf8(*descriptor_index)
599 .unwrap_or("<invalid:descriptor>");
600 format!(
601 "invokestatic {}.{}:{}",
602 internal_to_java_name(class),
603 name,
604 descriptor
605 )
606 }
607 Err(_) => format!("invokestatic #{}", index),
608 },
609 Instruction::Invokeinterface(index, count) => {
610 match constant_pool.try_get_interface_method_ref(*index) {
611 Ok((class_index, name_and_type_index)) => {
612 let class = constant_pool
613 .try_get_class(*class_index)
614 .unwrap_or("<invalid:class>");
615 let (name_index, descriptor_index) = constant_pool
616 .try_get_name_and_type(*name_and_type_index)
617 .unwrap_or((&0, &0));
618 let name = constant_pool
619 .try_get_utf8(*name_index)
620 .unwrap_or("<invalid:name>");
621 let descriptor = constant_pool
622 .try_get_utf8(*descriptor_index)
623 .unwrap_or("<invalid:descriptor>");
624 format!(
625 "invokeinterface {}.{}:{} {}",
626 internal_to_java_name(class),
627 name,
628 descriptor,
629 count
630 )
631 }
632 Err(_) => format!("invokeinterface #{} {}", index, count),
633 }
634 }
635 Instruction::Invokedynamic(index) => match constant_pool.try_get_invoke_dynamic(*index) {
636 Ok((name, descriptor)) => {
637 format!("invokedynamic {}:{}", name, descriptor)
638 }
639 Err(_) => format!("invokedynamic #{}", index),
640 },
641 Instruction::New(index) => match constant_pool.try_get_class(*index) {
642 Ok(class) => {
643 format!("new {}", internal_to_java_name(class))
644 }
645 Err(_) => format!("new #{}", index),
646 },
647 Instruction::Newarray(array_type) => {
648 let type_name = match array_type {
649 ristretto_classfile::attributes::ArrayType::Boolean => "boolean",
650 ristretto_classfile::attributes::ArrayType::Char => "char",
651 ristretto_classfile::attributes::ArrayType::Float => "float",
652 ristretto_classfile::attributes::ArrayType::Double => "double",
653 ristretto_classfile::attributes::ArrayType::Byte => "byte",
654 ristretto_classfile::attributes::ArrayType::Short => "short",
655 ristretto_classfile::attributes::ArrayType::Int => "int",
656 ristretto_classfile::attributes::ArrayType::Long => "long",
657 };
658 format!("newarray {}", type_name)
659 }
660 Instruction::Anewarray(index) => match constant_pool.try_get_class(*index) {
661 Ok(class) => {
662 format!("anewarray {}", internal_to_java_name(class))
663 }
664 Err(_) => format!("anewarray #{}", index),
665 },
666 Instruction::Arraylength => "arraylength".to_string(),
667 Instruction::Athrow => "athrow".to_string(),
668 Instruction::Checkcast(index) => match constant_pool.try_get_class(*index) {
669 Ok(class) => {
670 format!("checkcast {}", internal_to_java_name(class))
671 }
672 Err(_) => format!("checkcast #{}", index),
673 },
674 Instruction::Instanceof(index) => match constant_pool.try_get_class(*index) {
675 Ok(class) => {
676 format!("instanceof {}", internal_to_java_name(class))
677 }
678 Err(_) => format!("instanceof #{}", index),
679 },
680 Instruction::Monitorenter => "monitorenter".to_string(),
681 Instruction::Monitorexit => "monitorexit".to_string(),
682 Instruction::Wide => "wide".to_string(),
683 Instruction::Multianewarray(index, dimensions) => {
684 match constant_pool.try_get_class(*index) {
685 Ok(class) => {
686 format!(
687 "multianewarray {} {}",
688 internal_to_java_name(class),
689 dimensions
690 )
691 }
692 Err(_) => format!("multianewarray #{} {}", index, dimensions),
693 }
694 }
695 Instruction::Ifnull(branch) => format!("ifnull {}", branch),
696 Instruction::Ifnonnull(branch) => format!("ifnonnull {}", branch),
697 Instruction::Goto_w(branch) => format!("goto_w {}", branch),
698 Instruction::Jsr_w(branch) => format!("jsr_w {}", branch),
699 Instruction::Breakpoint => "breakpoint".to_string(),
700 Instruction::Impdep1 => "impdep1".to_string(),
701 Instruction::Impdep2 => "impdep2".to_string(),
702 _ => format!("unknown instruction: {:?}", instruction),
703 }
704}
705
706fn render_class_attribute(attribute: &Attribute, constant_pool: &ConstantPool) -> Option<String> {
707 match attribute {
708 Attribute::SourceFile {
709 source_file_index, ..
710 } => {
711 let source_file_name = constant_pool
712 .try_get_utf8(*source_file_index)
713 .unwrap_or("<invalid:source_file>");
714 Some(format!(" // SourceFile: {}\n", source_file_name))
715 }
716 Attribute::InnerClasses { classes, .. } => {
717 let mut rendered = String::from(" // InnerClasses:\n");
718 let mut entries = classes
719 .iter()
720 .map(|entry| {
721 let inner_name = resolve_class_name(constant_pool, entry.class_info_index);
722 let outer_name = resolve_optional_class_name(
723 constant_pool,
724 entry.outer_class_info_index,
725 "<none>",
726 );
727 let inner_simple =
728 resolve_optional_utf8(constant_pool, entry.name_index, "<anonymous>");
729 format!(
730 " // - inner: {} outer: {} name: {} flags: {}\n",
731 inner_name, outer_name, inner_simple, entry.access_flags
732 )
733 })
734 .collect::<Vec<_>>();
735 entries.sort();
736 for entry in entries {
737 rendered.push_str(&entry);
738 }
739 Some(rendered)
740 }
741 Attribute::NestHost {
742 host_class_index, ..
743 } => {
744 let host_name = resolve_class_name(constant_pool, *host_class_index);
745 Some(format!(" // NestHost: {}\n", host_name))
746 }
747 Attribute::NestMembers { class_indexes, .. } => {
748 let mut rendered = String::from(" // NestMembers:\n");
749 let mut members = class_indexes
750 .iter()
751 .map(|class_index| {
752 format!(
753 " // - {}\n",
754 resolve_class_name(constant_pool, *class_index)
755 )
756 })
757 .collect::<Vec<_>>();
758 members.sort();
759 for member in members {
760 rendered.push_str(&member);
761 }
762 Some(rendered)
763 }
764 Attribute::Signature { .. } => None,
767 _ => None,
768 }
769}
770
771#[cfg(test)]
772mod tests {
773 use super::*;
774 use expect_test::expect;
775 use rajac_ast::{
776 Ast, AstArena, AstType, ClassDecl, ClassKind, ClassMember, Field, Method, Modifiers,
777 PrimitiveType,
778 };
779 use rajac_symbols::SymbolTable;
780 use rajac_types::Ident;
781 use ristretto_classfile::attributes::{InnerClass, NestedClassAccessFlags};
782 use ristretto_classfile::{ClassAccessFlags, ConstantPool, JAVA_21};
783
784 #[test]
785 fn pretty_print_is_java_like_and_includes_details() {
786 let mut arena = AstArena::new();
787 let mut ast = Ast::new(SharedString::new("test"));
788 let type_arena = rajac_types::TypeArena::new();
789 let symbol_table = SymbolTable::new();
790
791 let int_ty = arena.alloc_type(AstType::Primitive {
792 kind: PrimitiveType::Int,
793 ty: rajac_types::TypeId::INVALID,
794 });
795 let void_ty = arena.alloc_type(AstType::Primitive {
796 kind: PrimitiveType::Void,
797 ty: rajac_types::TypeId::INVALID,
798 });
799
800 let field = Field {
801 name: Ident::new(SharedString::new("x")),
802 ty: int_ty,
803 initializer: None,
804 modifiers: Modifiers(Modifiers::PUBLIC | Modifiers::STATIC | Modifiers::FINAL),
805 };
806 let method = Method {
807 name: Ident::new(SharedString::new("f")),
808 params: vec![],
809 return_ty: void_ty,
810 body: None,
811 throws: vec![],
812 modifiers: Modifiers(Modifiers::PUBLIC),
813 };
814
815 let field_member_id = arena.alloc_class_member(ClassMember::Field(field));
816 let method_member_id = arena.alloc_class_member(ClassMember::Method(method));
817 let class_id = arena.alloc_class_decl(ClassDecl {
818 kind: ClassKind::Interface,
819 name: Ident::new(SharedString::new("Foo")),
820 type_params: vec![],
821 extends: None,
822 implements: vec![],
823 permits: vec![],
824 enum_entries: vec![],
825 members: vec![field_member_id, method_member_id],
826 modifiers: Modifiers(Modifiers::PUBLIC),
827 });
828 ast.classes.push(class_id);
829
830 let class_file = crate::classfile::classfile_from_class_decl(
831 &ast,
832 &arena,
833 class_id,
834 &type_arena,
835 &symbol_table,
836 )
837 .unwrap();
838 class_file.verify().unwrap();
839
840 let printed = pretty_print_classfile(&class_file);
841 let printed = printed.as_str();
842
843 expect![[r#"
844 // version: 65.0 (Java 21)
845 public abstract interface Foo {
846
847 // fields
848 public static final x /* I */;
849
850 // methods
851 public abstract f /* ()V */;
852 }
853 "#]]
854 .assert_eq(printed);
855 }
856
857 #[test]
858 fn pretty_prints_inner_classes_and_nesthost_details() {
859 let mut constant_pool = ConstantPool::default();
860 let outer_class = constant_pool.add_class("p/Outer").unwrap();
861 let inner_class = constant_pool.add_class("p/Outer$Inner").unwrap();
862 let super_class = constant_pool.add_class("java/lang/Object").unwrap();
863 let inner_name = constant_pool.add_utf8("Inner").unwrap();
864 let inner_classes_name = constant_pool.add_utf8("InnerClasses").unwrap();
865 let nest_host_name = constant_pool.add_utf8("NestHost").unwrap();
866
867 let class_file = ClassFile {
868 version: JAVA_21,
869 access_flags: ClassAccessFlags::PUBLIC,
870 constant_pool,
871 this_class: inner_class,
872 super_class,
873 interfaces: vec![],
874 fields: vec![],
875 methods: vec![],
876 attributes: vec![
877 Attribute::InnerClasses {
878 name_index: inner_classes_name,
879 classes: vec![InnerClass {
880 class_info_index: inner_class,
881 outer_class_info_index: outer_class,
882 name_index: inner_name,
883 access_flags: NestedClassAccessFlags::PRIVATE,
884 }],
885 },
886 Attribute::NestHost {
887 name_index: nest_host_name,
888 host_class_index: outer_class,
889 },
890 ],
891 };
892
893 let printed = pretty_print_classfile(&class_file);
894 let printed = printed.as_str();
895
896 expect![[r#"
897 // version: 65.0 (Java 21)
898 public class p.Outer$Inner {
899
900 // methods
901
902 // class attributes
903 // InnerClasses:
904 // - inner: p.Outer$Inner outer: p.Outer name: Inner flags: (0x0002) ACC_PRIVATE
905 // NestHost: p.Outer
906 }
907 "#]]
908 .assert_eq(printed);
909 }
910
911 #[test]
912 fn pretty_prints_ldc_numeric_constants_by_value() {
913 let mut constant_pool = ConstantPool::default();
914 let int_index = constant_pool.add_integer(2147483647).unwrap();
915 let long_index = constant_pool.add_long(9223372036854775807).unwrap();
916
917 assert_eq!(
918 format_instruction(
919 &ristretto_classfile::attributes::Instruction::Ldc(int_index as u8),
920 &constant_pool
921 ),
922 "ldc 2147483647"
923 );
924 assert_eq!(
925 format_instruction(
926 &ristretto_classfile::attributes::Instruction::Ldc2_w(long_index),
927 &constant_pool
928 ),
929 "ldc 9223372036854775807"
930 );
931 }
932}