1use jdc_core::emit::{escape_string, format_float, Printer};
5use jdc_core::types::JavaType;
6use jdc_core::Ctx;
7
8use crate::access::*;
9use crate::ctx::{java_type_to_generic, DexCtx};
10use crate::method::decompile_method;
11use crate::{desc_type, DexPool, PoolClass, PoolMethod, StaticValue};
12
13#[derive(Debug, Clone)]
14pub struct ClassOptions {
15 pub provenance: bool,
17}
18
19impl Default for ClassOptions {
20 fn default() -> Self {
21 ClassOptions { provenance: true }
22 }
23}
24
25pub type PendingMonitor = (
35 std::sync::mpsc::Receiver<Result<String, String>>,
36 String,
37 std::time::Instant,
38);
39
40pub fn decompile_class(
41 pool: &std::sync::Arc<DexPool>,
42 class: &PoolClass,
43 opts: &ClassOptions,
44 pending: &std::sync::Mutex<Vec<PendingMonitor>>,
45) -> anyhow::Result<String> {
46 if class_is_risky(pool, class) {
47 let pool2 = pool.clone();
51 let cls = class.clone();
52 let opts2 = opts.clone();
53 let (tx, rx) = std::sync::mpsc::channel::<Result<String, String>>();
54 let name = class.name.clone();
55 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
56 let _ = std::thread::Builder::new()
57 .stack_size(64 * 1024 * 1024)
58 .spawn(move || {
59 let r = decompile_class_impl(&pool2, &cls, &opts2).map_err(|e| format!("{:#}", e));
60 let _ = tx.send(r);
61 });
62 pending.lock().unwrap().push((rx, name, deadline));
63 return Err(anyhow::anyhow!(
64 "deferred to monitored thread (pathological-CFG guard)"
65 ));
66 }
67 decompile_class_impl(pool, class, opts)
68}
69
70fn class_is_risky(pool: &DexPool, class: &PoolClass) -> bool {
75 for m in class.all_methods() {
78 if m.code_off == 0 {
79 continue;
80 }
81 if let Some(dex) = pool.dex(m.dex_idx) {
82 if let Some((_regs, insns)) = dex.code_stats(m.code_off) {
83 if insns > 16_000 {
84 return true;
85 }
86 }
87 }
88 }
89 false
90}
91
92fn decompile_class_impl(
93 pool: &DexPool,
94 class: &PoolClass,
95 opts: &ClassOptions,
96) -> anyhow::Result<String> {
97 let ctx = DexCtx::new(pool, class);
98 let mut out = String::new();
99 if opts.provenance {
100 out.push_str(&format!(
101 "// Decompiled by https://github.com/ejfkdev/ddc {}\n",
102 env!("CARGO_PKG_VERSION")
103 ));
104 let label = pool
108 .dex_labels
109 .get(class.dex_idx)
110 .cloned()
111 .unwrap_or_default();
112 if let Some(dex) = pool.dex(class.dex_idx) {
113 out.push_str(&format!("// From: {} (DEX {})\n", label, dex.version));
114 }
115 }
116 if let Some(src) = &class.source_file {
117 out.push_str(&format!("// Source file: {}\n", src));
118 }
119 if class.is_synthetic() {
120 out.push_str("// synthetic\n");
121 }
122 let (pkg, _) = split_name(&class.name);
123 if !pkg.is_empty() {
124 out.push('\n');
125 out.push_str(&format!(
126 "package {};\n",
127 sanitize_fq(&pkg.replace('/', "."))
128 ));
129 }
130 out.push('\n');
131 let mut body = String::new();
132 emit_class_body(pool, class, &ctx, opts, &mut body, 0)?;
133 out.push_str(&body);
134 Ok(out)
135}
136
137#[allow(clippy::only_used_in_recursion)]
142fn emit_class_body(
143 pool: &DexPool,
144 class: &PoolClass,
145 ctx: &DexCtx<'_>,
146 opts: &ClassOptions,
147 out: &mut String,
148 depth: usize,
149) -> anyhow::Result<()> {
150 let ind = indent(depth);
151 let cname = crate::apply_class_rename(&class.name);
157 let (_, simple) = split_name(&cname);
158 let simple = if depth == 0 {
163 simple.to_string()
164 } else {
165 let seg = simple.rsplit('$').next().unwrap_or(&simple);
168 if seg.is_empty() {
169 simple.to_string()
170 } else {
171 seg.to_string()
172 }
173 };
174 let simple = if is_restricted_type_name(&simple) {
178 format!("_{simple}")
179 } else {
180 simple
181 };
182 let is_iface = class.is_interface();
183 let is_enum = class.is_enum();
184
185 let mut head = String::new();
186 let a = class.access;
187 if a & ACC_PUBLIC != 0 {
188 head.push_str("public ");
189 }
190 if a & ACC_FINAL != 0 && !is_enum {
191 head.push_str("final ");
192 }
193 if a & ACC_ABSTRACT != 0 && !is_iface {
194 head.push_str("abstract ");
195 }
196 if a & ACC_ANNOTATION != 0 {
197 head.push('@');
198 }
199 if is_enum {
200 let abstract_ = a & ACC_ABSTRACT != 0;
204 head.push_str(if abstract_ {
205 "/* enum */ class "
206 } else {
207 "/* enum */ final class "
208 });
209 head.push_str(&java_ident(&simple));
210 } else if is_iface {
211 head.push_str("interface ");
212 head.push_str(&java_ident(&simple));
213 } else {
214 head.push_str("class ");
215 head.push_str(&java_ident(&simple));
216 }
217 if is_iface {
218 if !class.interfaces.is_empty() {
219 head.push_str(" extends ");
220 head.push_str(&join_dotted(pool, &class.interfaces));
221 }
222 } else {
223 if let Some(sup) = &class.super_name {
224 if sup != "java/lang/Object" && !is_enum {
225 head.push_str(" extends ");
226 head.push_str(&print_class_name(pool, sup));
227 }
228 }
229 if !class.interfaces.is_empty() {
230 head.push_str(if is_iface {
231 " extends "
232 } else {
233 " implements "
234 });
235 head.push_str(&join_dotted(pool, &class.interfaces));
236 }
237 }
238
239 out.push_str(&ind);
240 out.push_str(&head);
241 out.push_str(" {\n");
242
243 for (i, f) in class.static_fields.iter().enumerate() {
245 if i > 0 || !class.instance_fields.is_empty() {
246 out.push('\n');
247 }
248 emit_field(
249 pool,
250 f,
251 class.static_values.get(i),
252 &class.name,
253 out,
254 depth + 1,
255 true,
256 class.is_interface(),
257 );
258 }
259 if !class.instance_fields.is_empty() && !class.static_fields.is_empty() {
260 out.push('\n');
261 }
262 for (i, f) in class.instance_fields.iter().enumerate() {
263 if i > 0 {
264 out.push('\n');
265 }
266 emit_field(pool, f, None, &class.name, out, depth + 1, false, false);
267 }
268
269 let mut emitted_any = !class.static_fields.is_empty() || !class.instance_fields.is_empty();
271 for m in class.all_methods() {
272 if &*m.name == "<clinit>" {
273 continue; }
275 let text = emit_method(pool, class, ctx, m, depth + 1)?;
276 if let Some(text) = text {
277 if emitted_any {
278 out.push('\n');
279 }
280 out.push_str(&text);
281 emitted_any = true;
282 }
283 }
284 let skip_clinit = class.is_interface();
289 if let Some(clinit) = (!skip_clinit)
290 .then(|| class.all_methods().find(|m| &*m.name == "<clinit>"))
291 .flatten()
292 {
293 if let Some(text) = emit_method(pool, class, ctx, clinit, depth + 1)? {
294 if emitted_any {
295 out.push('\n');
296 }
297 out.push_str(&text);
298 emitted_any = true;
299 }
300 }
301
302 for nested in nested_members(pool, class, ctx) {
305 if emitted_any {
306 out.push('\n');
307 }
308 let nested_ctx = DexCtx::new(pool, nested);
309 out.push('\n');
310 emit_class_body(pool, nested, &nested_ctx, opts, out, depth + 1)?;
311 emitted_any = true;
312 }
313
314 out.push_str(&ind);
315 out.push_str("}\n");
316 Ok(())
317}
318
319fn nested_members<'a>(
322 pool: &'a DexPool,
323 class: &'a PoolClass,
324 ctx: &DexCtx<'_>,
325) -> Vec<&'a PoolClass> {
326 let mut out = Vec::new();
327 for name in pool.children_of(&class.name) {
328 let Some(pc) = pool.get(name) else { continue };
329 let rest = &name[class.name.len() + 1..];
330 if rest.is_empty() || rest.starts_with('-') {
331 continue;
332 }
333 if ctx.find_outer(name).as_deref() != Some(class.name.as_str()) {
334 continue;
335 }
336 let tail = rest.rsplit('$').next().unwrap_or(rest);
337 if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
338 continue; }
340 if tail.starts_with(|c: char| c.is_ascii_digit()) {
341 continue; }
343 out.push(pc);
344 }
345 out
346}
347
348#[allow(clippy::too_many_arguments)]
352fn emit_field(
353 pool: &DexPool,
354 f: &crate::PoolField,
355 init: Option<&StaticValue>,
356 f_class: &str,
357 out: &mut String,
358 depth: usize,
359 is_static: bool,
360 require_init: bool,
361) {
362 let ind = indent(depth);
363 let mut line = String::new();
364 let a = f.access;
365 if a & ACC_PUBLIC != 0 {
366 line.push_str("public ");
367 } else if a & ACC_PRIVATE != 0 {
368 line.push_str("private ");
369 } else if a & ACC_PROTECTED != 0 {
370 line.push_str("protected ");
371 }
372 if is_static {
373 line.push_str("static ");
374 }
375 if a & ACC_FINAL != 0 {
376 line.push_str("final ");
377 }
378 if a & ACC_SYNTHETIC != 0 {
379 line.push_str("/* synthetic */ ");
380 }
381 if a & ACC_TRANSIENT_HINT != 0 {
382 line.push_str("transient ");
383 }
384 if a & ACC_VOLATILE_HINT != 0 {
385 line.push_str("volatile ");
386 }
387 let ty = type_name(pool, &desc_type(&f.desc));
388 line.push_str(&ty);
389 line.push(' ');
390 line.push_str(&java_ident(&f.name));
391 let mut rendered = None;
392 if let Some(v) = init {
393 rendered = render_static_value(pool, v, f_class);
394 }
395 if rendered.is_none() && require_init {
396 let default = match desc_type(&f.desc) {
400 JavaType::Boolean => "false",
401 JavaType::Byte | JavaType::Short | JavaType::Char | JavaType::Int => "0",
402 JavaType::Long => "0L",
403 JavaType::Float => "0.0F",
404 JavaType::Double => "0.0",
405 _ => "null",
406 };
407 rendered = Some(default.to_string());
408 }
409 if let Some(text) = rendered {
410 let text = if matches!(desc_type(&f.desc), JavaType::Long)
414 && text.chars().all(|c| c.is_ascii_digit() || c == '-')
415 {
416 format!("{text}L")
417 } else {
418 text
419 };
420 line.push_str(" = ");
421 line.push_str(&text);
422 }
423 line.push(';');
424 out.push_str(&ind);
425 out.push_str(&line);
426 out.push('\n');
427}
428
429pub const ACC_VOLATILE_HINT: u32 = 0x40;
432pub const ACC_TRANSIENT_HINT: u32 = 0x80;
433
434fn render_static_value(pool: &DexPool, v: &StaticValue, owner: &str) -> Option<String> {
435 Some(match v {
436 StaticValue::Int(i) => i.to_string(),
437 StaticValue::Float(f) => format_float(*f as f64, true),
438 StaticValue::Double(d) => format_float(*d, false),
439 StaticValue::Str(s) => format!("\"{}\"", escape_string(s)),
440 StaticValue::Type(t) => format!("{}.class", dotted(t)),
441 StaticValue::Boolean(b) => b.to_string(),
442 StaticValue::Null => "null".into(),
443 StaticValue::Field(cls, name) => {
444 let n = java_ident(name);
445 if cls == owner {
446 n
447 } else {
448 format!("{}.{}", print_class_name(pool, cls), n)
449 }
450 }
451 StaticValue::Other => return None,
452 })
453}
454
455fn emit_method(
456 pool: &DexPool,
457 class: &PoolClass,
458 ctx: &DexCtx<'_>,
459 m: &PoolMethod,
460 depth: usize,
461) -> anyhow::Result<Option<String>> {
462 let ind = indent(depth);
463 let desc = m.parsed_desc();
464
465 let mut sig = String::new();
467 let a = m.access;
468 if a & ACC_PUBLIC != 0 {
469 sig.push_str("public ");
470 } else if a & ACC_PRIVATE != 0 {
471 sig.push_str("private ");
472 } else if a & ACC_PROTECTED != 0 {
473 sig.push_str("protected ");
474 }
475 let is_clinit = &*m.name == "<clinit>";
476 let is_init = &*m.name == "<init>";
477 if a & ACC_STATIC != 0 || is_clinit {
478 sig.push_str("static ");
479 }
480 if a & ACC_FINAL != 0 {
481 sig.push_str("final ");
482 }
483 if a & (ACC_SYNCHRONIZED | ACC_DECLARED_SYNCHRONIZED) != 0 && a & ACC_ABSTRACT == 0 {
487 sig.push_str("synchronized ");
488 }
489 if a & ACC_NATIVE != 0 {
490 sig.push_str("native ");
491 }
492 if a & ACC_ABSTRACT != 0 {
493 sig.push_str("abstract ");
494 }
495 if a & ACC_SYNTHETIC != 0 {
496 sig.push_str("/* synthetic */ ");
497 }
498
499 let body = decompile_method(pool, class, m).ok().flatten();
501 let param_names: Vec<String> = body
502 .as_ref()
503 .map(|b| {
504 let mut ps: Vec<(u16, String)> =
505 b.vt.vars
506 .iter()
507 .filter(|v| v.is_param && v.name != "this")
508 .map(|v| (v.slot, v.name.clone()))
509 .collect();
510 ps.sort_by_key(|(s, _)| *s);
511 ps.into_iter().map(|(_, n)| n).collect()
512 })
513 .unwrap_or_else(|| {
514 (0..desc.as_ref().map(|d| d.args.len()).unwrap_or(0))
515 .map(|i| format!("p{}", i + 1))
516 .collect()
517 });
518
519 if is_clinit {
520 } else {
522 let Some(d) = &desc else { return Ok(None) };
523 if is_init {
524 let dname = crate::apply_class_rename(&class.name);
529 let (_, simple) = split_name(&dname);
530 let base = if depth <= 1 {
534 simple.to_string()
535 } else {
536 let seg = simple.rsplit('$').next().unwrap_or(&simple);
539 if seg.is_empty() {
540 simple.to_string()
541 } else {
542 seg.to_string()
543 }
544 };
545 let name = if is_restricted_type_name(&base) {
546 format!("_{base}")
547 } else {
548 base
549 };
550 sig.push_str(&java_ident(&name));
551 } else {
552 sig.push_str(&type_name(pool, &d.ret));
553 sig.push(' ');
554 sig.push_str(&java_ident(&m.name));
555 }
556 sig.push('(');
557 let n = d.args.len();
558 let varargs = a & ACC_VARARGS != 0 && n > 0;
559 for (i, arg) in d.args.iter().enumerate() {
560 if i > 0 {
561 sig.push_str(", ");
562 }
563 let name = param_names
564 .get(i)
565 .cloned()
566 .unwrap_or_else(|| format!("p{}", i));
567 if varargs && i + 1 == n {
568 if let JavaType::Array(inner) = arg {
569 sig.push_str(&type_name(pool, inner));
570 sig.push_str("...");
571 } else {
572 sig.push_str(&type_name(pool, arg));
573 }
574 } else {
575 sig.push_str(&type_name(pool, arg));
576 }
577 sig.push(' ');
578 sig.push_str(&java_ident(&name));
581 }
582 sig.push(')');
583 }
584
585 if is_clinit {
586 let Some(b) = body else { return Ok(None) };
587 let printer = Printer::new(ctx, &b.vt);
588 let t_print = std::time::Instant::now();
589 let body_text = printer.into_string(&b.body);
590 crate::method::phase_hit(3, t_print);
591 let mut out = String::new();
592 out.push_str(&ind);
593 out.push_str("static {\n");
594 for line in body_text.lines() {
595 if line.is_empty() {
596 out.push('\n');
597 } else {
598 out.push_str(&ind);
599 out.push_str(" ");
600 out.push_str(line);
601 out.push('\n');
602 }
603 }
604 out.push_str(&ind);
605 out.push_str("}\n");
606 return Ok(Some(out));
607 }
608 if a & (ACC_ABSTRACT | ACC_NATIVE) != 0 || body.is_none() {
609 return Ok(Some(format!("{}{};\n", ind, sig)));
610 }
611 let Some(b) = body else { return Ok(None) };
612
613 let mut printer = Printer::new(ctx, &b.vt);
616 match &b.desc.ret {
617 JavaType::Boolean => {
618 printer = printer.with_ret_bool(true);
619 }
620 JavaType::Char => {
621 printer = printer.with_ret_char(true);
622 }
623 JavaType::Byte => {
624 printer = printer.with_ret_narrow(true, false);
625 }
626 JavaType::Short => {
627 printer = printer.with_ret_narrow(false, true);
628 }
629 _ => {}
630 }
631 let t_print = std::time::Instant::now();
632 let body_text = printer.into_string(&b.body);
633 crate::method::phase_hit(3, t_print);
634
635 let mut out = String::new();
636 out.push_str(&ind);
637 out.push_str(&sig);
638 out.push_str(" {\n");
639 if !body_text.trim().is_empty() {
640 for line in body_text.lines() {
641 if line.is_empty() {
642 out.push('\n');
643 } else {
644 out.push_str(&ind);
645 out.push_str(" ");
646 out.push_str(line);
647 out.push('\n');
648 }
649 }
650 }
651 out.push_str(&ind);
652 out.push_str("}\n");
653 Ok(Some(out))
654}
655
656fn indent(depth: usize) -> String {
661 " ".repeat(depth)
662}
663
664pub(crate) fn java_ident(name: &str) -> String {
674 if std::env::var_os("DDC_DBG_IDENT").is_some() {
675 eprintln!("[ident] {name:?}");
676 }
677 let clean = name
678 .chars()
679 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
680 let keyword = matches!(
684 name,
685 "abstract"
686 | "assert"
687 | "boolean"
688 | "break"
689 | "byte"
690 | "case"
691 | "catch"
692 | "char"
693 | "class"
694 | "const"
695 | "continue"
696 | "default"
697 | "do"
698 | "double"
699 | "else"
700 | "enum"
701 | "extends"
702 | "final"
703 | "finally"
704 | "float"
705 | "for"
706 | "goto"
707 | "if"
708 | "implements"
709 | "import"
710 | "instanceof"
711 | "int"
712 | "interface"
713 | "long"
714 | "native"
715 | "new"
716 | "package"
717 | "private"
718 | "protected"
719 | "public"
720 | "return"
721 | "short"
722 | "static"
723 | "strictfp"
724 | "super"
725 | "switch"
726 | "synchronized"
727 | "this"
728 | "throw"
729 | "throws"
730 | "transient"
731 | "try"
732 | "void"
733 | "volatile"
734 | "while"
735 | "true"
736 | "false"
737 | "null"
738 | "_"
742 );
743 let digit_start = name.chars().next().is_some_and(|c| c.is_ascii_digit());
747 if clean && !keyword && !digit_start {
748 name.to_string()
749 } else if keyword || digit_start {
750 format!("_{name}")
751 } else {
752 let mapped: String = name
755 .chars()
756 .map(|c| {
757 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
758 c
759 } else {
760 '_'
761 }
762 })
763 .collect();
764 if mapped == "_" {
765 "__".to_string()
766 } else {
767 mapped
768 }
769 }
770}
771
772fn split_name(internal: &str) -> (String, String) {
773 match internal.rfind('/') {
774 Some(i) => (internal[..i].to_string(), internal[i + 1..].to_string()),
775 None => (String::new(), internal.to_string()),
776 }
777}
778
779pub fn dotted(internal: &str) -> String {
781 let mut out = internal.replace('/', ".");
782 let mut i = 0;
785 while let Some(p) = out[i..].find('$') {
786 let at = i + p;
787 let head_ok = at > 0
790 && out[..at]
791 .chars()
792 .next_back()
793 .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_');
794 let tail_ok = out[at + 1..]
795 .chars()
796 .next()
797 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
798 if tail_ok && head_ok {
799 out.replace_range(at..at + 1, ".");
800 i = at + 1;
801 } else {
802 i = at + 1;
803 }
804 }
805 sanitize_fq(&out)
808}
809
810fn join_dotted(pool: &DexPool, names: &[String]) -> String {
811 names
812 .iter()
813 .map(|n| print_class_name(pool, n))
814 .collect::<Vec<_>>()
815 .join(", ")
816}
817
818pub fn type_name(pool: &DexPool, t: &JavaType) -> String {
821 match t {
822 JavaType::Void => "void".into(),
823 JavaType::Object(n) => print_class_name(pool, n),
824 JavaType::Array(inner) => format!("{}[]", type_name(pool, inner)),
825 other => other.to_java(false),
826 }
827}
828
829pub fn print_class_name(pool: &DexPool, internal: &str) -> String {
832 let cow = crate::apply_class_rename(internal);
833 let internal: &str = &cow;
834 let mut out = String::new();
835 let mut rest: &str = internal;
836 loop {
837 match rest.find('$') {
838 Some(i) => {
839 let cand = &rest[..i];
840 let known = pool.get(cand).is_some() || cand == internal;
841 let tail_ok = rest[i + 1..]
848 .chars()
849 .next()
850 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
851 out.push_str(&cand.replace('/', "."));
852 out.push_str(if known && tail_ok { "." } else { "$" });
853 rest = &rest[i + 1..];
854 }
855 None => {
856 let seg = rest.replace('/', ".");
857 out.push_str(&seg);
858 return sanitize_ref(&out);
859 }
860 }
861 }
862}
863
864pub(crate) fn sanitize_fq(dotted: &str) -> String {
870 dotted
871 .split('.')
872 .map(|seg| {
873 if is_java_keyword_name(seg)
874 || is_restricted_type_name(seg)
875 || seg.chars().next().is_some_and(|c| c.is_ascii_digit())
876 {
877 format!("_{seg}")
878 } else if seg
879 .chars()
880 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
881 {
882 seg.to_string()
883 } else {
884 let mapped: String = seg
887 .chars()
888 .map(|c| {
889 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
890 c
891 } else {
892 '_'
893 }
894 })
895 .collect();
896 if mapped == "_" {
897 "__".to_string()
898 } else {
899 mapped
900 }
901 }
902 })
903 .collect::<Vec<_>>()
904 .join(".")
905}
906
907pub(crate) fn is_restricted_type_name(s: &str) -> bool {
911 matches!(s, "var" | "yield" | "record" | "sealed" | "permits")
912}
913
914fn is_java_keyword_name(s: &str) -> bool {
915 matches!(
916 s,
917 "abstract"
918 | "assert"
919 | "boolean"
920 | "break"
921 | "byte"
922 | "case"
923 | "catch"
924 | "char"
925 | "class"
926 | "const"
927 | "continue"
928 | "default"
929 | "do"
930 | "double"
931 | "else"
932 | "enum"
933 | "extends"
934 | "final"
935 | "finally"
936 | "float"
937 | "for"
938 | "goto"
939 | "if"
940 | "implements"
941 | "import"
942 | "instanceof"
943 | "int"
944 | "interface"
945 | "long"
946 | "native"
947 | "new"
948 | "package"
949 | "private"
950 | "protected"
951 | "public"
952 | "return"
953 | "short"
954 | "static"
955 | "strictfp"
956 | "super"
957 | "switch"
958 | "synchronized"
959 | "this"
960 | "throw"
961 | "throws"
962 | "transient"
963 | "try"
964 | "void"
965 | "volatile"
966 | "while"
967 | "true"
968 | "false"
969 | "null"
970 | "_"
974 )
975}
976
977fn sanitize_ref(name: &str) -> String {
978 sanitize_fq(name)
979}
980
981pub fn dotted_pool(pool: &DexPool, internal: &str) -> String {
983 print_class_name(pool, internal)
984}
985
986#[allow(dead_code)]
988fn _unused(_: &dyn Fn(&JavaType) -> jdc_core::types::GenericType) {
989 let _ = java_type_to_generic;
990}