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::with_capacity(
107 (class.all_methods().count() * 1024).min(256 * 1024) + 512,
108 );
109 if opts.provenance {
110 static BANNER: std::sync::OnceLock<String> = std::sync::OnceLock::new();
111 out.push_str(BANNER.get_or_init(|| {
112 format!(
113 "// Decompiled by https://github.com/ejfkdev/ddc {}\n",
114 env!("CARGO_PKG_VERSION")
115 )
116 }));
117 if let Some(dex) = pool.dex(class.dex_idx) {
123 if let Some(label) = pool.dex_labels.get(class.dex_idx) {
124 out.push_str("// From: ");
125 out.push_str(label);
126 out.push_str(" (DEX ");
127 out.push_str(&dex.version);
128 out.push_str(")\n");
129 }
130 }
131 }
132 if let Some(src) = &class.source_file {
133 out.push_str("// Source file: ");
134 out.push_str(src);
135 out.push('\n');
136 }
137 if class.is_synthetic() {
138 out.push_str("// synthetic\n");
139 }
140 let (pkg, _) = split_name(&class.name);
141 if !pkg.is_empty() {
142 out.push('\n');
143 out.push_str("package ");
144 let dotted = pkg.replace('/', ".");
145 out.push_str(&sanitize_fq(&dotted));
146 out.push_str(";\n");
147 }
148 out.push('\n');
149 emit_class_body(pool, class, &ctx, opts, &mut out, 0)?;
153 Ok(out)
154}
155
156#[allow(clippy::only_used_in_recursion)]
161fn emit_class_body(
162 pool: &DexPool,
163 class: &PoolClass,
164 ctx: &DexCtx<'_>,
165 opts: &ClassOptions,
166 out: &mut String,
167 depth: usize,
168) -> anyhow::Result<()> {
169 let ind = indent(depth);
170 let cname = crate::apply_class_rename(&class.name);
176 let (_, simple) = split_name(&cname);
177 let simple = if depth == 0 {
182 simple.to_string()
183 } else {
184 let seg = simple.rsplit('$').next().unwrap_or(&simple);
187 if seg.is_empty() {
188 simple.to_string()
189 } else {
190 seg.to_string()
191 }
192 };
193 let simple = if is_restricted_type_name(&simple) {
197 format!("_{simple}")
198 } else {
199 simple
200 };
201 let is_iface = class.is_interface();
202 let is_enum = class.is_enum();
203
204 let mut head = String::new();
205 let a = class.access;
206 if a & ACC_PUBLIC != 0 {
207 head.push_str("public ");
208 }
209 if depth > 0 && !is_iface && ctx.nested_is_static(&class.name) {
213 head.push_str("static ");
214 }
215 if a & ACC_FINAL != 0 && !is_enum {
216 head.push_str("final ");
217 }
218 if a & ACC_ABSTRACT != 0 && !is_iface {
219 head.push_str("abstract ");
220 }
221 if a & ACC_ANNOTATION != 0 {
222 head.push('@');
223 }
224 if is_enum {
225 let abstract_ = a & ACC_ABSTRACT != 0;
229 head.push_str(if abstract_ {
230 "/* enum */ class "
231 } else {
232 "/* enum */ final class "
233 });
234 head.push_str(&java_ident(&simple));
235 } else if is_iface {
236 head.push_str("interface ");
237 head.push_str(&java_ident(&simple));
238 } else {
239 head.push_str("class ");
240 head.push_str(&java_ident(&simple));
241 }
242 if is_iface {
243 if !class.interfaces.is_empty() {
244 head.push_str(" extends ");
245 head.push_str(&join_dotted(pool, &class.interfaces));
246 }
247 } else {
248 if let Some(sup) = &class.super_name {
249 if sup != "java/lang/Object" && !is_enum {
250 head.push_str(" extends ");
251 head.push_str(&print_class_name(pool, sup));
252 }
253 }
254 if !class.interfaces.is_empty() {
255 head.push_str(if is_iface {
256 " extends "
257 } else {
258 " implements "
259 });
260 head.push_str(&join_dotted(pool, &class.interfaces));
261 }
262 }
263
264 out.push_str(&ind);
265 out.push_str(&head);
266 out.push_str(" {\n");
267
268 for (i, f) in class.static_fields.iter().enumerate() {
270 if i > 0 || !class.instance_fields.is_empty() {
271 out.push('\n');
272 }
273 emit_field(
274 pool,
275 f,
276 class.static_values.get(i),
277 &class.name,
278 out,
279 depth + 1,
280 true,
281 class.is_interface(),
282 );
283 }
284 if !class.instance_fields.is_empty() && !class.static_fields.is_empty() {
285 out.push('\n');
286 }
287 for (i, f) in class.instance_fields.iter().enumerate() {
288 if i > 0 {
289 out.push('\n');
290 }
291 emit_field(pool, f, None, &class.name, out, depth + 1, false, false);
292 }
293
294 fn sig_key(m: &PoolMethod) -> (&str, &str) {
306 let d: &str = &m.desc;
307 let lo = d.find('(').map(|i| i + 1).unwrap_or(0);
308 let hi = d.find(')').unwrap_or(d.len());
309 (&*m.name, &d[lo..hi])
310 }
311 let methods: Vec<&PoolMethod> = class.all_methods().collect();
312 let mut claim: jdc_core::FxHashMap<(&str, &str), usize> = jdc_core::FxHashMap::default();
313 for (i, m) in methods.iter().enumerate() {
314 let key = sig_key(m);
315 match claim.get(&key) {
316 Some(&j)
317 if m.access & crate::access::ACC_BRIDGE == 0
318 && methods[j].access & crate::access::ACC_BRIDGE != 0 =>
319 {
320 claim.insert(key, i);
321 }
322 None => {
323 claim.insert(key, i);
324 }
325 _ => {}
326 }
327 }
328 let mut emitted_any = !class.static_fields.is_empty() || !class.instance_fields.is_empty();
329 for (i, m) in methods.iter().enumerate() {
330 if &*m.name == "<clinit>" {
331 continue; }
333 if claim.get(&sig_key(m)) != Some(&i) {
334 continue;
335 }
336 let mark = out.len();
337 if emitted_any {
338 out.push('\n');
339 }
340 if emit_method(pool, class, ctx, m, depth + 1, out)? {
341 emitted_any = true;
342 } else {
343 out.truncate(mark);
344 }
345 }
346 let skip_clinit = class.is_interface();
351 if let Some(clinit) = (!skip_clinit)
352 .then(|| class.all_methods().find(|m| &*m.name == "<clinit>"))
353 .flatten()
354 {
355 let mark = out.len();
356 if emitted_any {
357 out.push('\n');
358 }
359 if emit_method(pool, class, ctx, clinit, depth + 1, out)? {
360 emitted_any = true;
361 } else {
362 out.truncate(mark);
363 }
364 }
365
366 for nested in nested_members(pool, class, ctx) {
369 if emitted_any {
370 out.push('\n');
371 }
372 let nested_ctx = DexCtx::new(pool, nested);
373 out.push('\n');
374 emit_class_body(pool, nested, &nested_ctx, opts, out, depth + 1)?;
375 emitted_any = true;
376 }
377
378 out.push_str(&ind);
379 out.push_str("}\n");
380 Ok(())
381}
382
383fn nested_members<'a>(
386 pool: &'a DexPool,
387 class: &'a PoolClass,
388 ctx: &DexCtx<'_>,
389) -> Vec<&'a PoolClass> {
390 let mut out = Vec::new();
391 for name in pool.children_of(&class.name) {
392 let Some(pc) = pool.get(name) else { continue };
393 let Some(rest) = name
400 .strip_prefix(class.name.as_str())
401 .and_then(|t| t.strip_prefix('$'))
402 else {
403 continue;
404 };
405 if rest.is_empty() || rest.starts_with('-') {
406 continue;
407 }
408 if ctx.find_outer(name).as_deref() != Some(class.name.as_str()) {
409 continue;
410 }
411 let tail = rest.rsplit('$').next().unwrap_or(rest);
412 if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
413 continue; }
415 if tail.starts_with(|c: char| c.is_ascii_digit()) {
416 continue; }
418 out.push(pc);
419 }
420 out
421}
422
423#[allow(clippy::too_many_arguments)]
427fn emit_field(
428 pool: &DexPool,
429 f: &crate::PoolField,
430 init: Option<&StaticValue>,
431 f_class: &str,
432 out: &mut String,
433 depth: usize,
434 is_static: bool,
435 require_init: bool,
436) {
437 let ind = indent(depth);
438 let mut line = String::new();
439 let a = f.access;
440 if a & ACC_PUBLIC != 0 {
441 line.push_str("public ");
442 } else if a & ACC_PRIVATE != 0 {
443 line.push_str("private ");
444 } else if a & ACC_PROTECTED != 0 {
445 line.push_str("protected ");
446 }
447 if is_static {
448 line.push_str("static ");
449 }
450 if a & ACC_FINAL != 0 {
451 line.push_str("final ");
452 }
453 if a & ACC_SYNTHETIC != 0 {
454 line.push_str("/* synthetic */ ");
455 }
456 if a & ACC_TRANSIENT_HINT != 0 {
457 line.push_str("transient ");
458 }
459 if a & ACC_VOLATILE_HINT != 0 {
460 line.push_str("volatile ");
461 }
462 let ty = type_name(pool, &desc_type(&f.desc));
463 line.push_str(&ty);
464 line.push(' ');
465 line.push_str(&java_ident(&f.name));
466 let mut rendered = None;
467 if let Some(v) = init {
468 rendered = render_static_value(pool, v, f_class);
469 }
470 if rendered.is_none() && require_init {
471 let default = match desc_type(&f.desc) {
475 JavaType::Boolean => "false",
476 JavaType::Byte | JavaType::Short | JavaType::Char | JavaType::Int => "0",
477 JavaType::Long => "0L",
478 JavaType::Float => "0.0F",
479 JavaType::Double => "0.0",
480 _ => "null",
481 };
482 rendered = Some(default.to_string());
483 }
484 if let Some(text) = rendered {
485 let text = if matches!(desc_type(&f.desc), JavaType::Long)
489 && text.chars().all(|c| c.is_ascii_digit() || c == '-')
490 {
491 format!("{text}L")
492 } else {
493 text
494 };
495 line.push_str(" = ");
496 line.push_str(&text);
497 }
498 line.push(';');
499 out.push_str(&ind);
500 out.push_str(&line);
501 out.push('\n');
502}
503
504pub const ACC_VOLATILE_HINT: u32 = 0x40;
507pub const ACC_TRANSIENT_HINT: u32 = 0x80;
508
509fn render_static_value(pool: &DexPool, v: &StaticValue, owner: &str) -> Option<String> {
510 Some(match v {
511 StaticValue::Int(i) => i.to_string(),
512 StaticValue::Float(f) => format_float(*f as f64, true),
513 StaticValue::Double(d) => format_float(*d, false),
514 StaticValue::Str(s) => format!("\"{}\"", escape_string(s)),
515 StaticValue::Type(t) => format!("{}.class", dotted(t)),
516 StaticValue::Boolean(b) => b.to_string(),
517 StaticValue::Null => "null".into(),
518 StaticValue::Field(cls, name) => {
519 let n = java_ident(name);
520 if cls == owner {
521 n.into_owned()
522 } else {
523 format!("{}.{}", print_class_name(pool, cls), n)
524 }
525 }
526 StaticValue::Other => return None,
527 })
528}
529
530fn emit_method(
535 pool: &DexPool,
536 class: &PoolClass,
537 ctx: &DexCtx<'_>,
538 m: &PoolMethod,
539 depth: usize,
540 out: &mut String,
541) -> anyhow::Result<bool> {
542 let ind = indent(depth);
543 let desc = m.parsed_desc();
544
545 let mut sig = String::with_capacity(192);
547 let a = m.access;
548 if a & ACC_PUBLIC != 0 {
549 sig.push_str("public ");
550 } else if a & ACC_PRIVATE != 0 {
551 sig.push_str("private ");
552 } else if a & ACC_PROTECTED != 0 {
553 sig.push_str("protected ");
554 }
555 let is_clinit = &*m.name == "<clinit>";
556 let is_init = &*m.name == "<init>";
557 if a & ACC_STATIC != 0 || is_clinit {
558 sig.push_str("static ");
559 }
560 if a & ACC_FINAL != 0 {
561 sig.push_str("final ");
562 }
563 if a & (ACC_SYNCHRONIZED | ACC_DECLARED_SYNCHRONIZED) != 0 && a & ACC_ABSTRACT == 0 {
567 sig.push_str("synchronized ");
568 }
569 if a & ACC_NATIVE != 0 {
570 sig.push_str("native ");
571 }
572 if a & ACC_ABSTRACT != 0 {
573 sig.push_str("abstract ");
574 }
575 if a & ACC_SYNTHETIC != 0 {
576 sig.push_str("/* synthetic */ ");
577 }
578
579 let body = decompile_method(pool, class, m).ok().flatten();
581 let param_names: Vec<String> = body
582 .as_ref()
583 .map(|b| {
584 let mut ps: Vec<(u16, String)> =
585 b.vt.vars
586 .iter()
587 .filter(|v| v.is_param && v.name != "this")
588 .map(|v| (v.slot, v.name.clone()))
589 .collect();
590 ps.sort_by_key(|(s, _)| *s);
591 ps.into_iter().map(|(_, n)| n).collect()
592 })
593 .unwrap_or_else(|| {
594 (0..desc.as_ref().map(|d| d.args.len()).unwrap_or(0))
595 .map(|i| format!("p{}", i + 1))
596 .collect()
597 });
598
599 if is_clinit {
600 } else {
602 let Some(d) = &desc else { return Ok(false) };
603 if is_init {
604 let dname = crate::apply_class_rename(&class.name);
609 let (_, simple) = split_name(&dname);
610 let base = if depth <= 1 {
614 simple.to_string()
615 } else {
616 let seg = simple.rsplit('$').next().unwrap_or(&simple);
619 if seg.is_empty() {
620 simple.to_string()
621 } else {
622 seg.to_string()
623 }
624 };
625 let name = if is_restricted_type_name(&base) {
626 format!("_{base}")
627 } else {
628 base
629 };
630 sig.push_str(&java_ident(&name));
631 } else {
632 sig.push_str(&type_name(pool, &d.ret));
633 sig.push(' ');
634 sig.push_str(&java_ident(&m.name));
635 }
636 sig.push('(');
637 let n = d.args.len();
638 let varargs = a & ACC_VARARGS != 0 && n > 0;
639 for (i, arg) in d.args.iter().enumerate() {
640 if i > 0 {
641 sig.push_str(", ");
642 }
643 let name = param_names
644 .get(i)
645 .cloned()
646 .unwrap_or_else(|| format!("p{}", i));
647 if varargs && i + 1 == n {
648 if let JavaType::Array(inner) = arg {
649 sig.push_str(&type_name(pool, inner));
650 sig.push_str("...");
651 } else {
652 sig.push_str(&type_name(pool, arg));
653 }
654 } else {
655 sig.push_str(&type_name(pool, arg));
656 }
657 sig.push(' ');
658 sig.push_str(&java_ident(&name));
661 }
662 sig.push(')');
663 }
664
665 if is_clinit {
666 let Some(b) = body else { return Ok(false) };
667 out.push_str(&ind);
672 out.push_str("static {\n");
673 let hdr = out.len();
674 let printer = Printer::new(ctx, &b.vt)
675 .with_indent(depth + 1)
676 .with_output(std::mem::take(out));
677 let t_print = std::time::Instant::now();
678 let mut rendered = printer.into_string(&b.body);
679 crate::method::phase_hit(3, t_print);
680 if rendered[hdr..].trim().is_empty() {
681 rendered.truncate(hdr);
682 }
683 rendered.push_str(&ind);
684 rendered.push_str("}\n");
685 *out = rendered;
686 return Ok(true);
687 }
688 if a & (ACC_ABSTRACT | ACC_NATIVE) != 0 || body.is_none() {
689 out.push_str(&ind);
690 out.push_str(&sig);
691 out.push_str(";\n");
692 return Ok(true);
693 }
694 let Some(b) = body else { return Ok(false) };
695
696 let mut printer = Printer::new(ctx, &b.vt).with_indent(depth + 1);
698 match &b.desc.ret {
699 JavaType::Boolean => {
700 printer = printer.with_ret_bool(true);
701 }
702 JavaType::Char => {
703 printer = printer.with_ret_char(true);
704 }
705 JavaType::Byte => {
706 printer = printer.with_ret_narrow(true, false);
707 }
708 JavaType::Short => {
709 printer = printer.with_ret_narrow(false, true);
710 }
711 _ => {}
712 }
713 out.push_str(&ind);
714 out.push_str(&sig);
715 out.push_str(" {\n");
716 let hdr = out.len();
717 let printer = printer.with_output(std::mem::take(out));
718 let t_print = std::time::Instant::now();
719 let mut rendered = printer.into_string(&b.body);
720 crate::method::phase_hit(3, t_print);
721 if rendered[hdr..].trim().is_empty() {
722 rendered.truncate(hdr);
723 }
724 rendered.push_str(&ind);
725 rendered.push_str("}\n");
726 *out = rendered;
727 Ok(true)
728}
729
730fn indent(depth: usize) -> String {
735 " ".repeat(depth)
736}
737
738pub(crate) fn java_ident(name: &str) -> std::borrow::Cow<'_, str> {
748 static DBG_IDENT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
751 if *DBG_IDENT.get_or_init(|| std::env::var_os("DDC_DBG_IDENT").is_some()) {
752 eprintln!("[ident] {name:?}");
753 }
754 let clean = name
755 .chars()
756 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
757 let keyword = matches!(
761 name,
762 "abstract"
763 | "assert"
764 | "boolean"
765 | "break"
766 | "byte"
767 | "case"
768 | "catch"
769 | "char"
770 | "class"
771 | "const"
772 | "continue"
773 | "default"
774 | "do"
775 | "double"
776 | "else"
777 | "enum"
778 | "extends"
779 | "final"
780 | "finally"
781 | "float"
782 | "for"
783 | "goto"
784 | "if"
785 | "implements"
786 | "import"
787 | "instanceof"
788 | "int"
789 | "interface"
790 | "long"
791 | "native"
792 | "new"
793 | "package"
794 | "private"
795 | "protected"
796 | "public"
797 | "return"
798 | "short"
799 | "static"
800 | "strictfp"
801 | "super"
802 | "switch"
803 | "synchronized"
804 | "this"
805 | "throw"
806 | "throws"
807 | "transient"
808 | "try"
809 | "void"
810 | "volatile"
811 | "while"
812 | "true"
813 | "false"
814 | "null"
815 | "_"
819 );
820 let digit_start = name.chars().next().is_some_and(|c| c.is_ascii_digit());
824 if clean && !keyword && !digit_start {
825 std::borrow::Cow::Borrowed(name)
826 } else if keyword || digit_start {
827 std::borrow::Cow::Owned(format!("_{name}"))
828 } else {
829 let mapped: String = name
832 .chars()
833 .map(|c| {
834 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
835 c
836 } else {
837 '_'
838 }
839 })
840 .collect();
841 if mapped == "_" {
842 std::borrow::Cow::Owned("__".to_string())
843 } else {
844 std::borrow::Cow::Owned(mapped)
845 }
846 }
847}
848
849fn split_name(internal: &str) -> (String, String) {
850 match internal.rfind('/') {
851 Some(i) => (internal[..i].to_string(), internal[i + 1..].to_string()),
852 None => (String::new(), internal.to_string()),
853 }
854}
855
856pub fn dotted(internal: &str) -> String {
858 let mut out = internal.replace('/', ".");
859 let mut i = 0;
862 while let Some(p) = out[i..].find('$') {
863 let at = i + p;
864 let head_ok = at > 0
867 && out[..at]
868 .chars()
869 .next_back()
870 .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_');
871 let tail_ok = out[at + 1..]
872 .chars()
873 .next()
874 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
875 if tail_ok && head_ok {
876 out.replace_range(at..at + 1, ".");
877 i = at + 1;
878 } else {
879 i = at + 1;
880 }
881 }
882 sanitize_fq(&out)
885}
886
887fn join_dotted(pool: &DexPool, names: &[String]) -> String {
888 names
889 .iter()
890 .map(|n| print_class_name(pool, n))
891 .collect::<Vec<_>>()
892 .join(", ")
893}
894
895pub fn type_name(pool: &DexPool, t: &JavaType) -> String {
898 match t {
899 JavaType::Void => "void".into(),
900 JavaType::Object(n) => print_class_name(pool, n),
901 JavaType::Array(inner) => format!("{}[]", type_name(pool, inner)),
902 other => other.to_java(false),
903 }
904}
905
906pub fn print_class_name(pool: &DexPool, internal: &str) -> String {
909 let cow = crate::apply_class_rename(internal);
910 let internal: &str = &cow;
911 let mut out = String::new();
912 let mut off = 0usize;
918 loop {
919 let rest = &internal[off..];
920 match rest.find('$') {
921 Some(i) => {
922 let seg = &rest[..i];
923 let prefix = &internal[..off + i];
924 let known = pool.get(prefix).is_some()
925 || jdc_core::rename::is_renamed_display(prefix);
926 let tail_ok = rest[i + 1..]
933 .chars()
934 .next()
935 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
936 out.push_str(&seg.replace('/', "."));
937 out.push_str(if known && tail_ok { "." } else { "$" });
938 off += i + 1;
939 }
940 None => {
941 out.push_str(&rest.replace('/', "."));
942 return sanitize_ref(&out);
943 }
944 }
945 }
946}
947
948pub(crate) fn sanitize_fq(dotted: &str) -> String {
954 dotted
955 .split('.')
956 .map(|seg| {
957 if is_java_keyword_name(seg)
958 || is_restricted_type_name(seg)
959 || seg.chars().next().is_some_and(|c| c.is_ascii_digit())
960 {
961 format!("_{seg}")
962 } else if seg
963 .chars()
964 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
965 {
966 seg.to_string()
967 } else {
968 let mapped: String = seg
971 .chars()
972 .map(|c| {
973 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
974 c
975 } else {
976 '_'
977 }
978 })
979 .collect();
980 if mapped == "_" {
981 "__".to_string()
982 } else {
983 mapped
984 }
985 }
986 })
987 .collect::<Vec<_>>()
988 .join(".")
989}
990
991pub(crate) fn is_restricted_type_name(s: &str) -> bool {
995 matches!(s, "var" | "yield" | "record" | "sealed" | "permits")
996}
997
998fn is_java_keyword_name(s: &str) -> bool {
999 matches!(
1000 s,
1001 "abstract"
1002 | "assert"
1003 | "boolean"
1004 | "break"
1005 | "byte"
1006 | "case"
1007 | "catch"
1008 | "char"
1009 | "class"
1010 | "const"
1011 | "continue"
1012 | "default"
1013 | "do"
1014 | "double"
1015 | "else"
1016 | "enum"
1017 | "extends"
1018 | "final"
1019 | "finally"
1020 | "float"
1021 | "for"
1022 | "goto"
1023 | "if"
1024 | "implements"
1025 | "import"
1026 | "instanceof"
1027 | "int"
1028 | "interface"
1029 | "long"
1030 | "native"
1031 | "new"
1032 | "package"
1033 | "private"
1034 | "protected"
1035 | "public"
1036 | "return"
1037 | "short"
1038 | "static"
1039 | "strictfp"
1040 | "super"
1041 | "switch"
1042 | "synchronized"
1043 | "this"
1044 | "throw"
1045 | "throws"
1046 | "transient"
1047 | "try"
1048 | "void"
1049 | "volatile"
1050 | "while"
1051 | "true"
1052 | "false"
1053 | "null"
1054 | "_"
1058 )
1059}
1060
1061fn sanitize_ref(name: &str) -> String {
1062 sanitize_fq(name)
1063}
1064
1065pub fn dotted_pool(pool: &DexPool, internal: &str) -> String {
1067 print_class_name(pool, internal)
1068}
1069
1070#[allow(dead_code)]
1072fn _unused(_: &dyn Fn(&JavaType) -> jdc_core::types::GenericType) {
1073 let _ = java_type_to_generic;
1074}