1mod jdk;
9pub use jdk::set_jdk_home;
10mod vexec;
11
12use cure_engine::kind::{BinOp, UnOp};
13use cure_engine::{Config, Edit, Effect, Lang, LitRef, NodeKind, Report, RewriteCtx, Rule};
14use cure_java_ast::{CompilationUnit, JavaAst, JavaId, JType, Lit, Member, NoopCandidate, NodeData, TypeDecl, TypeKind};
15
16pub struct CastSimplify;
24
25impl Rule<JavaAst> for CastSimplify {
26 fn name(&self) -> &'static str {
27 "cast_simplify"
28 }
29 fn kinds(&self) -> &'static [NodeKind] {
30 &[NodeKind::Cast]
31 }
32 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
33 let lang = ctx.lang;
34 let NodeData::Cast { ty } = lang.data(id) else {
35 return None;
36 };
37 let ty = ty.clone();
38 let inner = lang.children(id)[0];
39
40 if let NodeData::Cast { ty: inner_ty } = lang.data(inner) {
42 if *inner_ty == ty {
43 return Some(Edit::Replace {
44 target: id,
45 with: inner,
46 });
47 }
48 }
49
50 if let NodeData::VarRef { .. } = lang.data(inner) {
52 if lang.var_type(inner) == Some(&ty) {
53 return Some(Edit::Replace {
54 target: id,
55 with: inner,
56 });
57 }
58 }
59 None
60 }
61}
62
63pub struct SelfCompare;
66
67impl Rule<JavaAst> for SelfCompare {
68 fn name(&self) -> &'static str {
69 "self_compare"
70 }
71 fn kinds(&self) -> &'static [NodeKind] {
72 &[NodeKind::Binary]
73 }
74 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
75 use cure_engine::kind::BinOp::{Eq, Ne};
76 let lang = ctx.lang;
77 let NodeData::Binary { op } = lang.data(id) else {
78 return None;
79 };
80 if !matches!(op, Eq | Ne) {
81 return None;
82 }
83 let ch = lang.children(id);
84 let (NodeData::VarRef { name: n1 }, NodeData::VarRef { name: n2 }) =
85 (lang.data(ch[0]), lang.data(ch[1]))
86 else {
87 return None;
88 };
89 if n1 != n2 || !lang.is_exact_int(ch[0]) {
90 return None;
91 }
92 let val = lang.build_bool(*op == Eq);
93 Some(Edit::Replace {
94 target: id,
95 with: val,
96 })
97 }
98}
99
100
101pub struct StringBuilderFold;
112
113impl Rule<JavaAst> for StringBuilderFold {
114 fn name(&self) -> &'static str {
115 "string_builder_fold"
116 }
117 fn kinds(&self) -> &'static [NodeKind] {
118 &[NodeKind::Call]
119 }
120 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
121 let root = ctx.root();
122 let lang = ctx.lang;
123 if lang.kind(id) != NodeKind::Call {
125 return None;
126 }
127 let callee = lang.children(id)[0];
128 if let NodeData::Member { name } = lang.data(callee) {
129 if lang.sn(*name) != "toString" || !lang.children(id)[1..].is_empty() {
130 return None;
131 }
132 } else {
133 return None;
134 }
135 let mut node = lang.children(callee)[0]; let mut parts: Vec<JavaId> = Vec::new();
137 loop {
139 match lang.data(node) {
140 NodeData::Call => {
141 let c = lang.children(node);
142 if let NodeData::Member { name } = lang.data(c[0]) {
143 if lang.sn(*name) == "append" && c.len() == 2 {
144 parts.push(c[1]);
145 node = lang.children(c[0])[0];
146 continue;
147 }
148 }
149 return None;
150 }
151 NodeData::New { ty, .. } => {
152 let is_sb = !lang.unit_shadows_lang
153 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
154 if !is_sb {
155 return None;
156 }
157 let args = lang.children(node).to_vec();
158 match args.len() {
159 0 => {} 1 => {
161 let a = args[0];
166 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
167 || lang
168 .var_type(a)
169 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
170 if stringy {
171 parts.push(a);
172 } else if !matches!(
173 lang.literal(a),
174 Some(LitRef::Int(_) | LitRef::Long(_))
175 ) {
176 return None;
177 }
178 }
179 _ => return None,
180 }
181 break;
182 }
183 _ => return None,
184 }
185 }
186 if parts.iter().all(|&p| lang.literal(p).is_some())
190 && lang.has_string_identity_compare(root)
191 {
192 return None;
193 }
194 if parts.is_empty() {
195 let empty = lang.build_str("");
197 return Some(Edit::Replace {
198 target: id,
199 with: empty,
200 });
201 }
202 parts.reverse();
204 let first = parts[0];
205 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
206 || lang
207 .var_type(first)
208 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
209 let mut acc = if stringy_first {
210 first
211 } else {
212 let empty = lang.build_str("");
213 lang.build_bin(BinOp::Add, empty, first)
214 };
215 for &p in &parts[1..] {
216 acc = lang.build_bin(BinOp::Add, acc, p);
217 }
218 Some(Edit::Replace {
219 target: id,
220 with: acc,
221 })
222 }
223}
224
225pub struct BoxUnboxChain;
233
234const BOXERS: &[(&str, &str)] = &[
235 ("Integer", "intValue"),
236 ("java.lang.Integer", "intValue"),
237 ("Long", "longValue"),
238 ("java.lang.Long", "longValue"),
239 ("Boolean", "booleanValue"),
240 ("java.lang.Boolean", "booleanValue"),
241 ("Double", "doubleValue"),
242 ("java.lang.Double", "doubleValue"),
243 ("Float", "floatValue"),
244 ("java.lang.Float", "floatValue"),
245 ("Character", "charValue"),
246 ("java.lang.Character", "charValue"),
247 ("Short", "shortValue"),
248 ("java.lang.Short", "shortValue"),
249 ("Byte", "byteValue"),
250 ("java.lang.Byte", "byteValue"),
251];
252
253impl Rule<JavaAst> for BoxUnboxChain {
254 fn name(&self) -> &'static str {
255 "box_unbox_chain"
256 }
257 fn kinds(&self) -> &'static [NodeKind] {
258 &[NodeKind::Call]
259 }
260 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
261 let lang = ctx.lang;
262 if lang.kind(id) != NodeKind::Call {
264 return None;
265 }
266 let callee = lang.children(id)[0];
267 let NodeData::Member { name: unbox } = lang.data(callee) else {
268 return None;
269 };
270 if !lang.children(id)[1..].is_empty() {
271 return None;
272 }
273 let inner = lang.children(callee)[0];
274 if lang.kind(inner) != NodeKind::Call {
275 return None;
276 }
277 let ich = lang.children(inner).to_vec();
279 let (box_name, arg): (String, JavaId) = if let NodeData::New { ty, .. } = lang.data(inner)
280 {
281 let n = match ty {
282 JType::Ref(r) => r.clone(),
283 _ => return None,
284 };
285 if ich.len() != 1 {
286 return None;
287 }
288 (n, ich[0])
289 } else {
290 let icallee = ich.first().copied()?;
292 if let NodeData::Member { name } = lang.data(icallee) {
293 let recv = lang.children(icallee)[0];
295 if lang.sn(*name) != "valueOf" {
296 return None;
297 }
298 {
299 let n = lang.var_name(recv)?;
300 (n.to_string(), *ich.get(1)?)
301 }
302 } else if let NodeData::VarRef { name } = lang.data(icallee) {
303 let _ = name;
306 return None;
307 } else {
308 return None;
309 }
310 };
311 let unbox_name = *unbox;
312 let expected = BOXERS
313 .iter()
314 .find(|(b, _)| *b == box_name.as_str())
315 .map(|(_, u)| *u)?;
316 if expected != lang.sn(unbox_name) {
317 return None;
318 }
319 let var_is = |t: fn(&JType) -> bool| lang.var_type(arg).is_some_and(t);
322 let lit_is_num = lang.literal(arg).is_some_and(|l| {
323 matches!(
324 l,
325 LitRef::Int(_) | LitRef::Long(_) | LitRef::Float(_) | LitRef::Double(_)
326 )
327 });
328 let ok = match box_name.trim_start_matches("java.lang.") {
329 "Integer" | "Long" | "Short" | "Byte" => lang.is_exact_int(arg),
330 "Double" | "Float" => {
331 lit_is_num
332 || var_is(|t| matches!(t, JType::Double | JType::Float | JType::Int | JType::Long))
333 }
334 "Boolean" => lang.is_bool(arg),
335 "Character" => {
336 matches!(lang.literal(arg), Some(LitRef::Char(_))) || var_is(|t| *t == JType::Char)
337 }
338 _ => false,
339 };
340 if !ok {
341 return None;
342 }
343 Some(Edit::Replace {
344 target: id,
345 with: arg,
346 })
347 }
348}
349
350pub struct IteratorToForEach;
362
363impl Rule<JavaAst> for IteratorToForEach {
364 fn name(&self) -> &'static str {
365 "iterator_to_for_each"
366 }
367 fn kinds(&self) -> &'static [NodeKind] {
368 &[NodeKind::For]
369 }
370 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
371 let lang = ctx.lang;
372 let NodeData::For {
373 inits,
374 has_cond,
375 steps,
376 } = lang.data(id)
377 else {
378 return None;
379 };
380 if *inits != 1 || !*has_cond || *steps != 0 {
381 return None;
382 }
383 let ch = lang.children(id).to_vec();
384 let (init, cond, body) = (ch[0], ch[1], ch[2]);
386
387 let NodeData::VarDecl { name: it_name, .. } = lang.data(init) else {
389 return None;
390 };
391 let it_name = it_name.clone();
392 let init_call = lang.children(init).first().copied()?;
393 if lang.kind(init_call) != NodeKind::Call {
394 return None;
395 }
396 let ic = lang.children(init_call).to_vec();
397 let icallee = *ic.first()?;
398 let NodeData::Member { name: m0 } = lang.data(icallee) else {
399 return None;
400 };
401 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
402 return None;
403 }
404 let iterable = lang.children(icallee)[0];
405 if subtree_has_var(&*lang, iterable, &it_name) {
407 return None;
408 }
409
410 if lang.kind(cond) != NodeKind::Call {
412 return None;
413 }
414 let cc = lang.children(cond).to_vec();
415 let ccallee = *cc.first()?;
416 let NodeData::Member { name: m1 } = lang.data(ccallee) else {
417 return None;
418 };
419 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
420 return None;
421 }
422 if lang.var_name(lang.children(ccallee)[0]) != Some(it_name.as_str()) {
423 return None;
424 }
425
426 if lang.kind(body) != NodeKind::Block {
428 return None;
429 }
430 let bch = lang.children(body).to_vec();
431 let first = *bch.first()?;
432 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
433 return None;
434 };
435 let (e_name, ty) = (e_name.clone(), ty.clone());
436 let next_call = lang.children(first).first().copied()?;
437 if lang.kind(next_call) != NodeKind::Call {
438 return None;
439 }
440 let nc = lang.children(next_call).to_vec();
441 let ncallee = *nc.first()?;
442 let NodeData::Member { name: m2 } = lang.data(ncallee) else {
443 return None;
444 };
445 if lang.sn(*m2) != "next" || nc.len() != 1 {
446 return None;
447 }
448 if lang.var_name(lang.children(ncallee)[0]) != Some(it_name.as_str()) {
449 return None;
450 }
451 let rest = bch[1..].to_vec();
453 for &r in &rest {
454 if subtree_has_var(&*lang, r, &it_name) {
455 return None;
456 }
457 }
458 let new_body = lang.build_block(rest);
459 let with = lang.for_each(&e_name, ty, iterable, new_body);
460 Some(Edit::Replace {
461 target: id,
462 with,
463 })
464 }
465}
466
467fn unwrap_paren_cast(lang: &JavaAst, mut n: JavaId) -> JavaId {
469 loop {
470 match lang.kind(n) {
471 NodeKind::Paren | NodeKind::Cast => {
472 let ch = lang.children(n);
473 if ch.is_empty() {
474 return n;
475 }
476 n = ch[0];
477 }
478 _ => return n,
479 }
480 }
481}
482
483fn generic_elem_ty(ty: &JType) -> Option<JType> {
485 let name = match ty {
486 JType::Ref(n) => n.as_str(),
487 _ => return None,
488 };
489 let lt = name.find('<')?;
490 let gt = name.rfind('>')?;
491 if gt <= lt {
492 return None;
493 }
494 let inner = name[lt + 1..gt].trim();
495 if inner.is_empty() || inner.contains('<') {
496 return None; }
498 Some(JType::Ref(inner.to_string()))
499}
500
501fn statically_iterable(ty: &JType) -> bool {
508 match ty {
509 JType::Array(_) => true,
510 JType::Ref(name) => {
511 let base = name.split('<').next().unwrap_or(name).trim();
512 let last = base.rsplit('.').next().unwrap_or(base);
513 matches!(
514 last,
515 "Iterable"
516 | "Collection"
517 | "List"
518 | "ArrayList"
519 | "LinkedList"
520 | "Vector"
521 | "Stack"
522 | "AbstractList"
523 | "AbstractCollection"
524 | "Set"
525 | "HashSet"
526 | "LinkedHashSet"
527 | "TreeSet"
528 | "SortedSet"
529 | "NavigableSet"
530 | "AbstractSet"
531 | "Queue"
532 | "Deque"
533 | "ArrayDeque"
534 | "PriorityQueue"
535 | "BlockingQueue"
536 | "BlockingDeque"
537 | "ConcurrentLinkedQueue"
538 | "ConcurrentLinkedDeque"
539 | "ConcurrentSkipListSet"
540 | "CopyOnWriteArrayList"
541 | "CopyOnWriteArraySet"
542 | "EnumSet"
543 | "LinkedBlockingQueue"
544 | "LinkedBlockingDeque"
545 | "ArrayBlockingQueue"
546 | "PriorityBlockingQueue"
547 | "DelayQueue"
548 | "SynchronousQueue"
549 | "LinkedTransferQueue"
550 | "TransferQueue"
551 )
552 }
553 _ => false,
554 }
555}
556
557fn for_each_elem_ok(ty: &JType) -> bool {
561 match ty {
562 JType::Ref(n) => !n.trim_start().starts_with('?'),
563 _ => true,
564 }
565}
566
567fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
569 let mut stack = vec![root];
570 while let Some(n) = stack.pop() {
571 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
572 out.push(n);
573 }
574 for &c in lang.children(n) {
575 stack.push(c);
576 }
577 }
578}
579
580fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
582 let mut stack = vec![root];
583 while let Some(n) = stack.pop() {
584 for &c in lang.children(n) {
585 if c == target {
586 return n;
587 }
588 stack.push(c);
589 }
590 }
591 root
592}
593
594fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
595 let mut stack = vec![id];
596 while let Some(n) = stack.pop() {
597 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
598 return true;
599 }
600 for &c in lang.children(n) {
601 stack.push(c);
602 }
603 }
604 false
605}
606
607fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
609 let mut stack = vec![id];
610 while let Some(n) = stack.pop() {
611 match lang.data(n) {
612 NodeData::Catch { name: cn, .. } if cn == name => return true,
613 NodeData::ForEach { name: vn, .. } if vn == name => return true,
614 NodeData::Lambda { params_raw } => {
617 for part in
618 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
619 {
620 if part == name {
621 return true;
622 }
623 }
624 }
625 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
627 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
630 _ => {}
631 }
632 for &c in lang.children(n) {
633 stack.push(c);
634 }
635 }
636 false
637}
638
639pub struct NewStringFold;
645
646impl Rule<JavaAst> for NewStringFold {
647 fn name(&self) -> &'static str {
648 "new_string_fold"
649 }
650 fn kinds(&self) -> &'static [NodeKind] {
651 &[NodeKind::New]
652 }
653 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
654 let root = ctx.root();
655 let lang = ctx.lang;
656 let NodeData::New { ty, .. } = lang.data(id) else {
657 return None;
658 };
659 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
660 return None;
661 }
662 if lang.unit_shadows_lang {
666 return None;
667 }
668 let _ = root;
671 if lang.unit_string_identity {
672 return None;
673 }
674 let args = lang.children(id).to_vec();
675 match args.len() {
676 0 => {
677 let empty = lang.build_str("");
678 Some(Edit::Replace {
679 target: id,
680 with: empty,
681 })
682 }
683 1 => {
684 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
685 Some(Edit::Replace {
686 target: id,
687 with: args[0],
688 })
689 } else {
690 None
691 }
692 }
693 _ => None,
694 }
695 }
696}
697
698
699pub struct NewStringCharArrayFold;
705
706impl Rule<JavaAst> for NewStringCharArrayFold {
707 fn name(&self) -> &'static str {
708 "new_string_char_array_fold"
709 }
710 fn kinds(&self) -> &'static [NodeKind] {
711 &[NodeKind::New]
712 }
713 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
714 let root = ctx.root();
715 let lang = ctx.lang;
716 let NodeData::New { ty, .. } = lang.data(id) else {
717 return None;
718 };
719 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
720 return None;
721 }
722 let _ = root;
723 if lang.unit_shadows_lang || lang.unit_string_identity {
724 return None;
725 }
726 let args = lang.children(id).to_vec();
728 if args.len() != 1 {
729 return None;
730 }
731 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
732 return None;
733 };
734 if *sized != 0 {
735 return None;
736 }
737 let arr_children = lang.children(args[0]).to_vec();
738 let lit = *arr_children.first()?;
739 if lang.kind(lit) != NodeKind::ArrayLit {
740 return None;
741 }
742 let mut text = String::new();
743 for &c in lang.children(lit) {
744 match lang.literal(c) {
745 Some(LitRef::Char(ch)) => text.push(ch),
746 _ => return None, }
748 }
749 let with = lang.build_str(&text);
750 Some(Edit::Replace {
751 target: id,
752 with,
753 })
754 }
755}
756
757pub struct EmptyFinallyStrip;
765
766impl Rule<JavaAst> for EmptyFinallyStrip {
767 fn name(&self) -> &'static str {
768 "empty_finally_strip"
769 }
770 fn kinds(&self) -> &'static [NodeKind] {
771 &[NodeKind::Try]
772 }
773 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
774 let lang = ctx.lang;
775 if lang.kind(id) != NodeKind::Try {
776 return None;
777 }
778 let ch = lang.children(id).to_vec();
779 let is_empty_block = |n: JavaId| -> bool {
786 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
787 };
788 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
789 if ch.len() < try_idx + 2 {
790 return None; }
792 let last = *ch.last()?;
794 if lang.kind(last) == NodeKind::Catch {
795 return None;
796 }
797 if !is_empty_block(last) {
798 return None;
799 }
800 Some(Edit::Splice {
803 node: id,
804 index: ch.len() - 1,
805 remove: 1,
806 insert: Vec::new(),
807 })
808 }
809}
810
811fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
857 if lang.kind(a) != lang.kind(b) {
858 let (ia, ib) = (lang.kind(a), lang.kind(b));
860 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
861 if k == NodeKind::ExprStmt {
862 lang.children(x).first().copied()
863 } else {
864 Some(x)
865 }
866 };
867 return match (unwrap(ia, a), unwrap(ib, b)) {
868 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
869 _ => false,
870 };
871 }
872 print_equiv_inner(lang, a, b)
873}
874
875fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
876 if lang.kind(a) != lang.kind(b) {
877 return false;
878 }
879 match (lang.literal(a), lang.literal(b)) {
881 (Some(x), Some(y)) => {
882 if x != y {
883 return false;
884 }
885 }
886 (None, None) => {}
887 _ => return false,
888 }
889 let (ca, cb) = (lang.children(a), lang.children(b));
890 if ca.len() != cb.len() {
891 return false;
892 }
893 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
894}
895
896fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
900 for &root in rest {
901 let mut stack = vec![root];
902 while let Some(n) = stack.pop() {
903 match lang.kind(n) {
904 NodeKind::Assign => {
905 let t = lang.children(n)[0];
906 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
907 return true;
908 }
909 }
910 NodeKind::Unary => {
911 if let NodeData::Unary { op } = lang.data(n) {
912 if op.is_incdec() {
913 let t = lang.children(n)[0];
914 if lang.kind(t) == NodeKind::VarRef
915 && lang.var_name(t) == Some(name)
916 {
917 return true;
918 }
919 }
920 }
921 }
922 _ => {}
923 }
924 for &c in lang.children(n) {
925 stack.push(c);
926 }
927 }
928 }
929 false
930}
931
932pub struct StaticExec;
933
934impl Rule<JavaAst> for StaticExec {
935 fn name(&self) -> &'static str {
936 "static_exec"
937 }
938 fn kinds(&self) -> &'static [NodeKind] {
939 &[NodeKind::Block]
940 }
941 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
942 if ctx.parent(id).is_some() {
945 return None;
946 }
947 let lang = ctx.lang;
948 if lang.kind(id) != NodeKind::Block {
949 return None;
950 }
951 let stmts = lang.children(id).to_vec();
952 if stmts.len() < 3 {
953 return None; }
955 let mut ex = vexec::Exec::new(lang);
961 let run = ex.run_prefix(&stmts).ok()?;
962 if std::env::var("CURE_DBG_P1").is_ok() {
963 eprintln!(
964 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
965 run.cuts.len(),
966 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
967 ex.budget_exhausted(),
968 ex.effect_log().len(),
969 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
970 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
971 ex.effect_log().iter().map(|e| match e {
972 vexec::EffectEvent::FieldWrite(_) => "FW",
973 vexec::EffectEvent::ClassLoad { .. } => "CL",
974 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
975 }).collect::<Vec<_>>()
976 );
977 }
978 if ex.budget_exhausted() {
980 return None;
981 }
982 let root_idx = run.root_cut_index();
983 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
985 if total_completed < 3 {
986 return None;
987 }
988 let true_rest = run.true_rest();
992 if !true_rest.is_empty() {
993 for ev in ex.effect_log().iter() {
994 if let vexec::EffectEvent::FieldWrite(k) = ev {
995 if let Some(name) = lang.name_of_key(*k) {
996 if lang.final_fields.contains(&name)
1001 && rest_reassigns_field(lang, &true_rest, &name)
1002 {
1003 return None;
1004 }
1005 }
1006 }
1007 }
1008 }
1009 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1011 let field_finals: Vec<(u32, vexec::VVal)> =
1012 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1013 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1014 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1017 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1018 _ => None,
1019 };
1020 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1023 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1029 for (j, cut) in run.cuts.iter().enumerate() {
1030 let range_end = if j == 0 {
1031 decl_order.len()
1032 } else {
1033 run.cuts[j - 1].decl_start
1034 };
1035 let rest_j = run.rest_for_cut(j);
1036 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1037 let mut live_names: std::collections::HashSet<String> =
1038 std::collections::HashSet::new();
1039 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1040 if ex.var_value(k).is_none() {
1041 continue; }
1043 let name = lang.name_of_key(k)?;
1044 live_names.insert(name.clone());
1045 let referenced = rest_j.iter().any(|&st| {
1046 cure_engine::analysis::subtree_contains(lang, st, |n| {
1047 lang.kind(n) == NodeKind::VarRef
1048 && lang.var_name(n) == Some(name.as_str())
1049 })
1050 });
1051 if referenced {
1052 esc.push((
1053 k,
1054 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1055 ex.var_domain(k),
1056 ex.var_decl_ty(k).unwrap_or(JType::Var),
1059 ));
1060 }
1061 }
1062 escaped_by_cut.push(esc);
1063 cut_live_names.push(live_names);
1064 }
1065 drop(ex);
1066 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1068 for (j, cut) in run.cuts.iter().enumerate() {
1069 let block_id = if j == root_idx {
1070 id
1071 } else {
1072 cut.block_hint?
1073 };
1074 let mut insert: Vec<JavaId> = Vec::new();
1075 for pos in cut.log_start..cut.log_end {
1077 match &log[pos] {
1078 vexec::EffectEvent::FieldWrite(k) => {
1079 let is_last = !log[pos + 1..]
1081 .iter()
1082 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1083 if !is_last {
1084 continue;
1085 }
1086 let name = lang.name_of_key(*k)?;
1087 if lang.is_param_name(&name) {
1089 return None;
1090 }
1091 let v = field_finals
1092 .iter()
1093 .find(|(fk, _)| fk == k)
1094 .map(|(_, v)| v.clone())?;
1095 let v = match (&v, lang.field_types.get(&name)) {
1100 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1101 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1102 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1103 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1104 _ => v,
1105 };
1106 let (val, mut extra) = materialize(lang, &v)?;
1107 let tgt = lang.var(&name);
1108 insert.push(lang.assign(tgt, val));
1109 insert.append(&mut extra);
1110 }
1111 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1112 if std::env::var("CURE_DBG_P1").is_ok() {
1113 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1114 }
1115 let name = lang.name_of_key(*k)?;
1120 if lang.is_param_name(&name) {
1122 return None;
1123 }
1124 let cch = lang.children(*call).to_vec();
1125 let mut new_args: Vec<JavaId> = Vec::new();
1126 let mut kept_original: Vec<bool> = Vec::new();
1127 for (i, &arg) in cch.iter().enumerate().skip(1) {
1128 let val = args.get(i - 1).cloned().flatten();
1129 let replaced = match val {
1130 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1131 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1132 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1133 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1134 _ => None,
1135 };
1136 kept_original.push(replaced.is_none());
1137 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1138 }
1139 let names_j = cut_live_names.get(j)?;
1143 for ((i, &arg), &kept) in
1144 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1145 {
1146 let _ = i;
1147 if kept
1148 && !names_j.is_empty()
1149 && cure_engine::analysis::subtree_contains(
1150 lang,
1151 arg,
1152 |n| {
1153 lang.kind(n) == NodeKind::VarRef
1154 && lang
1155 .var_name(n)
1156 .is_some_and(|nm| names_j.contains(nm))
1157 },
1158 )
1159 {
1160 return None;
1161 }
1162 }
1163 let callee = *lang.children(*call).first()?;
1164 let callee_copy = lang.copy_subtree(callee);
1165 let mut ch = vec![callee_copy];
1166 ch.extend(new_args);
1167 let new_call = lang.clone_node(*call, ch);
1168 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1169 lang.clone_node(*stmt, vec![new_call])
1170 } else {
1171 new_call
1172 };
1173 let tgt = lang.var(&name);
1175 let assign = lang.assign(tgt, new_stmt);
1176 insert.push(assign);
1177 }
1178 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1179 let lit = lang.lit(Lit::Str(name.clone()));
1187 let callee = *lang.children(*call).first()?;
1188 let callee_copy = lang.copy_subtree(callee);
1189 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1190 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1191 lang.clone_node(*stmt, vec![new_call])
1192 } else {
1193 new_call
1194 };
1195 let try_block = lang.block(vec![new_stmt]);
1196 let catch_block = lang.block(vec![]);
1197 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1198 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1199 insert.push(wrapped);
1200 }
1201 }
1202 }
1203 let mut alias_first: std::collections::HashMap<usize, String> =
1208 std::collections::HashMap::new();
1209 for (k, v, dom, dty) in &escaped_by_cut[j] {
1210 let name = lang.name_of_key(*k)?;
1211 let (val, mut extra) = match v {
1212 vexec::VVal::CA(a) => {
1213 let addr = std::rc::Rc::as_ptr(a) as usize;
1214 match alias_first.get(&addr) {
1215 Some(first) => (lang.var(first), vec![]),
1216 None => {
1217 alias_first.insert(addr, name.clone());
1218 materialize(lang, v)?
1219 }
1220 }
1221 }
1222 vexec::VVal::SA(a) => {
1223 let addr = std::rc::Rc::as_ptr(a) as usize;
1224 match alias_first.get(&addr) {
1225 Some(first) => (lang.var(first), vec![]),
1226 None => {
1227 alias_first.insert(addr, name.clone());
1228 materialize(lang, v)?
1229 }
1230 }
1231 }
1232 _ => materialize(lang, v)?,
1233 };
1234 let (ty, val) = match (*dom, val) {
1239 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1240 match lang.literal(lit) {
1243 Some(LitRef::Int(v)) => {
1244 match u32::try_from(v).ok().and_then(char::from_u32) {
1245 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1246 None => (JType::Char, lit),
1247 }
1248 }
1249 Some(LitRef::Long(v)) => {
1250 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1251 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1252 None => (JType::Char, lit),
1253 }
1254 }
1255 _ => (JType::Char, lit),
1256 }
1257 }
1258 (3, lit) => (JType::Byte, lit),
1259 (4, lit) => (JType::Short, lit),
1260 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1261 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1262 (JType::Long, lang.build_int(v, true))
1263 } else {
1264 (JType::Long, lit)
1265 }
1266 }
1267 (_, lit) => {
1272 if matches!(dty, JType::Ref(_)) {
1273 (dty.clone(), lit)
1274 } else {
1275 (JType::Var, lit)
1276 }
1277 }
1278 };
1279 let stmt = lang.var_decl(&name, ty, Some(val));
1280 insert.push(stmt);
1281 insert.append(&mut extra);
1282 }
1283 if j == root_idx {
1285 if let Some(v) = ret_payload.clone().flatten() {
1286 let (val, mut extra) = materialize(lang, &v)?;
1287 let stmt = lang.ret(Some(val));
1288 insert.push(stmt);
1289 insert.append(&mut extra);
1290 }
1291 }
1292 if insert.is_empty() && cut.completed == 0 {
1294 continue;
1295 }
1296 {
1299 let removed = lang.children(block_id).to_vec();
1300 if insert.len() == cut.completed
1301 && cut.completed <= removed.len()
1302 && insert
1303 .iter()
1304 .zip(removed[..cut.completed].iter())
1305 .all(|(&n, &o)| print_equiv(lang, n, o))
1306 {
1307 continue;
1308 }
1309 }
1310 edits.push(Edit::Splice {
1311 node: block_id,
1312 index: 0,
1313 remove: cut.completed,
1314 insert,
1315 });
1316 }
1317 if std::env::var("CURE_DBG_P1").is_ok() {
1318 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1319 }
1320 if edits.is_empty() {
1321 return None;
1322 }
1323 if edits.len() == 1 {
1325 Some(edits.pop().unwrap())
1326 } else {
1327 Some(Edit::Multi(edits))
1328 }
1329 }
1330}
1331
1332fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1335 use vexec::VVal;
1336 match v {
1337 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1338 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1339 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1340 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1341 VVal::CA(a) => {
1342 let chars: Vec<char> = a.borrow().clone();
1344 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1345 let lit_arr = lang.array_lit(lits);
1346 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1347 Some((node, vec![]))
1348 }
1349 VVal::SA(elems) => {
1350 let arr = elems.borrow().clone();
1353 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1354 let lits: Vec<JavaId> = arr
1355 .iter()
1356 .map(|e| match e {
1357 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1358 _ => unreachable!(),
1359 })
1360 .collect();
1361 let lit_arr = lang.array_lit(lits);
1362 let node =
1363 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1364 Some((node, vec![]))
1365 } else {
1366 None
1371 }
1372 }
1373 VVal::Undef => None,
1375 }
1376}
1377
1378pub struct TryUnwrapRethrow;
1379
1380impl Rule<JavaAst> for TryUnwrapRethrow {
1381 fn name(&self) -> &'static str {
1382 "try_unwrap_rethrow"
1383 }
1384 fn kinds(&self) -> &'static [NodeKind] {
1385 &[NodeKind::Try]
1386 }
1387 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1388 let lang = ctx.lang;
1389 if lang.kind(id) != NodeKind::Try {
1390 return None;
1391 }
1392 let ch = lang.children(id).to_vec();
1393 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1395 let catches: Vec<JavaId> =
1396 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1397 if catches.is_empty() {
1398 return None;
1399 }
1400 let rethrow_only = |c: JavaId| -> bool {
1401 let block = match lang.children(c).first() {
1403 Some(&b) => b,
1404 None => return false,
1405 };
1406 let stmts = lang.children(block);
1407 if stmts.len() != 1 {
1408 return false;
1409 }
1410 let stmt = stmts[0];
1411 if lang.kind(stmt) != NodeKind::Throw {
1412 return false;
1413 }
1414 let Some(&thrown) = lang.children(stmt).first() else { return false };
1415 if lang.kind(thrown) != NodeKind::VarRef {
1417 return false;
1418 }
1419 match &lang.node(c).data {
1420 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1421 _ => false,
1422 }
1423 };
1424 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1425 if all_rethrow {
1426 let extras: Vec<JavaId> = ch
1428 .iter()
1429 .copied()
1430 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1431 .collect();
1432 if extras.is_empty() {
1433 return Some(Edit::Replace {
1434 target: id,
1435 with: try_block,
1436 });
1437 }
1438 return None;
1439 }
1440 let catch_slots: Vec<usize> = ch
1446 .iter()
1447 .enumerate()
1448 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1449 .map(|(i, _)| i)
1450 .collect();
1451 for &slot in &catch_slots {
1452 let c = ch[slot];
1453 if !rethrow_only(c) {
1454 continue;
1455 }
1456 let later_all_rethrow = catch_slots
1457 .iter()
1458 .filter(|&&s2| s2 > slot)
1459 .all(|&s2| rethrow_only(ch[s2]));
1460 if !later_all_rethrow {
1461 continue;
1462 }
1463 return Some(Edit::Splice {
1464 node: id,
1465 index: slot,
1466 remove: 1,
1467 insert: vec![],
1468 });
1469 }
1470 None
1471 }
1472}
1473
1474pub struct TryUnwrapNoCatch;
1475
1476impl Rule<JavaAst> for TryUnwrapNoCatch {
1477 fn name(&self) -> &'static str {
1478 "try_unwrap_no_catch"
1479 }
1480 fn kinds(&self) -> &'static [NodeKind] {
1481 &[NodeKind::Try]
1482 }
1483 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1484 let lang = ctx.lang;
1485 if lang.kind(id) != NodeKind::Try {
1486 return None;
1487 }
1488 let ch = lang.children(id).to_vec();
1489 if ch.is_empty() {
1490 return None;
1491 }
1492 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1495 if has_catch {
1496 return None;
1497 }
1498 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1502 let rest: Vec<JavaId> = ch
1503 .iter()
1504 .copied()
1505 .filter(|&c| c != try_block)
1506 .collect();
1507 for &r in &rest {
1510 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1511 if !is_empty_block {
1512 return None; }
1514 }
1515 Some(Edit::Replace {
1516 target: id,
1517 with: try_block,
1518 })
1519 }
1520}
1521
1522pub struct LoopHeadBreak;
1530
1531impl Rule<JavaAst> for LoopHeadBreak {
1532 fn name(&self) -> &'static str {
1533 "loop_head_break"
1534 }
1535 fn kinds(&self) -> &'static [NodeKind] {
1536 &[NodeKind::While, NodeKind::DoWhile]
1537 }
1538 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1539 let lang = ctx.lang;
1540 let body = match lang.kind(id) {
1541 NodeKind::While => {
1542 let ch = lang.children(id).to_vec();
1543 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1544 return None;
1545 }
1546 ch[1]
1547 }
1548 NodeKind::DoWhile => {
1549 let ch = lang.children(id).to_vec();
1550 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1551 return None;
1552 }
1553 ch[0]
1554 }
1555 _ => return None,
1556 };
1557 if lang.kind(body) != NodeKind::Block {
1558 return None;
1559 }
1560 let bch = lang.children(body).to_vec();
1561 let is_do = lang.kind(id) == NodeKind::DoWhile;
1562 if is_do && bch.len() != 1 {
1564 return None;
1565 }
1566 let first = *bch.first()?;
1567 if lang.kind(first) != NodeKind::If {
1568 return None;
1569 }
1570 let ich = lang.children(first).to_vec();
1571 let c = ich[0];
1572
1573 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1574 let is_break = |n: JavaId| match lang.kind(n) {
1575 NodeKind::Break => bare_break(n),
1576 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1577 _ => false,
1578 };
1579 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1580 let ends_with_continue = |n: JavaId| -> bool {
1581 match lang.kind(n) {
1582 NodeKind::Continue => bare_continue(n),
1583 NodeKind::Block => lang
1584 .children(n)
1585 .last()
1586 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1587 _ => false,
1588 }
1589 };
1590 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1591 match lang.kind(n) {
1592 NodeKind::Continue => vec![],
1593 NodeKind::Block => {
1594 let ch = lang.children(n).to_vec();
1595 if ch
1596 .last()
1597 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1598 {
1599 ch[..ch.len() - 1].to_vec()
1600 } else {
1601 ch
1602 }
1603 }
1604 _ => vec![n],
1605 }
1606 };
1607
1608 match ich.len() {
1609 2 => {
1610 if is_do || !is_break(ich[1]) {
1612 return None;
1613 }
1614 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1615 if let Some(&last) = rest.last() {
1616 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1617 rest.pop();
1618 }
1619 }
1620 let nc = lang.build_unary(UnOp::Not, c);
1621 let inner = lang.build_block(rest);
1622 let w = lang.while_(nc, inner);
1623 Some(Edit::Replace {
1624 target: id,
1625 with: w,
1626 })
1627 }
1628 3 => {
1629 let (then, els) = (ich[1], ich[2]);
1630 if ends_with_continue(then) && is_break(els) {
1632 let rest = strip_tail_continue(then);
1633 let inner = lang.build_block(rest);
1634 let w = lang.while_(c, inner);
1635 return Some(Edit::Replace {
1636 target: id,
1637 with: w,
1638 });
1639 }
1640 if is_break(then) && ends_with_continue(els) {
1642 let rest = strip_tail_continue(els);
1643 let inner = lang.build_block(rest);
1644 let nc = lang.build_unary(UnOp::Not, c);
1645 let w = lang.while_(nc, inner);
1646 return Some(Edit::Replace {
1647 target: id,
1648 with: w,
1649 });
1650 }
1651 if !is_do && is_break(then) {
1653 let mut all = strip_tail_continue(els);
1654 all.extend(bch[1..].iter().copied());
1655 let inner = lang.build_block(all);
1656 let nc = lang.build_unary(UnOp::Not, c);
1657 let w = lang.while_(nc, inner);
1658 return Some(Edit::Replace {
1659 target: id,
1660 with: w,
1661 });
1662 }
1663 None
1664 }
1665 _ => None,
1666 }
1667 }
1668}
1669
1670pub struct WhileIteratorToForEach;
1671
1672impl Rule<JavaAst> for WhileIteratorToForEach {
1673 fn name(&self) -> &'static str {
1674 "while_iterator_to_for_each"
1675 }
1676 fn kinds(&self) -> &'static [NodeKind] {
1677 &[NodeKind::While]
1678 }
1679 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1680 let parent = ctx.parent(id)?;
1682 let idx = ctx.index(id)?;
1683 let lang = ctx.lang;
1684 if lang.kind(id) != NodeKind::While {
1685 return None;
1686 }
1687 let wch = lang.children(id).to_vec();
1688 let body = wch[1];
1689 if lang.kind(parent) != NodeKind::Block {
1691 return None;
1692 }
1693 if idx == 0 {
1694 return None;
1695 }
1696 let stmts = lang.children(parent).to_vec();
1697 let cond = wch[0];
1700 let it_hint = match lang.kind(cond) {
1701 NodeKind::Call => {
1702 let cc0 = lang.children(cond).to_vec();
1703 match lang.data(*cc0.first()?) {
1704 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1705 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1706 }
1707 _ => None,
1708 }
1709 }
1710 _ => None,
1711 };
1712 let mut decl = None;
1713 if let Some(hint) = it_hint {
1714 let mut up = idx;
1715 for _ in 0..4 {
1716 if up == 0 {
1717 break;
1718 }
1719 up -= 1;
1720 let st = stmts[up];
1721 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1722 decl = Some(st);
1723 break;
1724 }
1725 if subtree_has_var(lang, st, &hint) {
1726 break;
1727 }
1728 }
1729 }
1730 let decl = decl?;
1731 let it_name = lang.var_name(decl)?.to_string();
1732 if stmts[idx + 1..]
1737 .iter()
1738 .any(|&s| subtree_has_var(lang, s, &it_name))
1739 {
1740 return None;
1741 }
1742 let init_call = lang.children(decl).first().copied()?;
1743 if lang.kind(init_call) != NodeKind::Call {
1744 return None;
1745 }
1746 let ic = lang.children(init_call).to_vec();
1747 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1748 return None;
1749 };
1750 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1751 return None;
1752 }
1753 let iterable = lang.children(ic[0])[0];
1754 if subtree_has_var(&*lang, iterable, &it_name) {
1755 return None;
1756 }
1757 {
1767 let ty = lang.var_type(iterable);
1768 let ok = ty.is_some_and(|t| statically_iterable(t));
1769 if !ok {
1770 return None;
1771 }
1772 }
1773 if lang.kind(cond) != NodeKind::Call {
1775 return None;
1776 }
1777 let cc = lang.children(cond).to_vec();
1778 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1779 return None;
1780 };
1781 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1782 return None;
1783 }
1784 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1785 return None;
1786 }
1787 if lang.kind(body) != NodeKind::Block {
1789 return None;
1790 }
1791 let bch = lang.children(body).to_vec();
1792 let first = *bch.first()?;
1793 if lang.kind(first) != NodeKind::VarDecl {
1794 let it_refs_in_body: Vec<JavaId> = {
1797 let mut v = Vec::new();
1798 collect_var_refs(&*lang, body, &it_name, &mut v);
1799 v
1800 };
1801 if it_refs_in_body.len() != 1 {
1802 return None;
1803 }
1804 let next_recv = it_refs_in_body[0];
1805 let member = parent_of_recv(lang, body, next_recv);
1807 if lang.kind(member) != NodeKind::Member {
1808 return None;
1809 }
1810 let NodeData::Member { name: mn } = lang.data(member) else {
1811 return None;
1812 };
1813 if lang.sn(*mn) != "next" {
1814 return None;
1815 }
1816 let next_call = parent_of_recv(lang, body, member);
1817 if lang.kind(next_call) != NodeKind::Call {
1818 return None;
1819 }
1820 if lang.children(next_call).len() != 1 {
1821 return None;
1822 }
1823 let mut wrapped = next_call;
1825 loop {
1826 let par = parent_of_recv(lang, body, wrapped);
1827 match lang.kind(par) {
1828 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1829 _ => break,
1830 }
1831 }
1832 {
1842 let mut cur = wrapped;
1843 while cur != body {
1844 let par = parent_of_recv(lang, body, cur);
1845 if par == body || par == cur {
1846 break; }
1848 let ch = lang.children(par).to_vec();
1849 let idx = ch.iter().position(|&c| c == cur);
1850 match lang.kind(par) {
1851 NodeKind::Block
1852 | NodeKind::ExprStmt
1853 | NodeKind::Label
1854 | NodeKind::Return
1855 | NodeKind::Throw => {}
1856 NodeKind::Ternary => {
1857 if idx.unwrap_or(0) > 0 {
1859 return None;
1860 }
1861 }
1862 NodeKind::Binary => {
1863 if let Some(op) = lang.bin_op(par) {
1864 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1865 return None; }
1867 }
1868 }
1869 NodeKind::If
1870 | NodeKind::While
1871 | NodeKind::DoWhile
1872 | NodeKind::For
1873 | NodeKind::Switch
1874 | NodeKind::Try
1875 | NodeKind::Catch
1876 | NodeKind::Synchronized
1877 | NodeKind::ForEach => return None,
1878 NodeKind::Lambda | NodeKind::Assert => return None,
1883 _ => {} }
1885 cur = par;
1886 }
1887 }
1888 let it_decl_elem = match lang.data(decl) {
1896 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
1897 _ => None,
1898 };
1899 let elem = match it_decl_elem {
1900 Some(t) => t,
1901 None => match lang.var_type(iterable) {
1902 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
1903 None => JType::Ref("Object".into()),
1904 },
1905 };
1906 let elem = if for_each_elem_ok(&elem) {
1907 elem
1908 } else {
1909 JType::Ref("Object".into())
1910 };
1911 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
1912 let (replace_target, e_ty) = match lang.data(wrapped) {
1913 NodeData::Cast { .. } if is_object => (next_call, elem),
1914 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
1915 _ => (wrapped, elem),
1916 };
1917 let taken = |nm: &str| {
1921 subtree_has_var(&*lang, body, nm)
1922 || subtree_has_binding(&*lang, body, nm)
1923 };
1924 let e_name = if taken("e") {
1925 if taken("e2") { "e3" } else { "e2" }
1926 } else {
1927 "e"
1928 };
1929 let e_ref = lang.var(e_name);
1930 let foreach = lang.for_each(e_name, e_ty, iterable, body);
1931 let wrapped_parent = parent_of_recv(lang, body, wrapped);
1935 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
1936 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
1937 {
1938 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
1939 if let Some(si) = lang
1940 .children(stmt_block)
1941 .iter()
1942 .position(|&c| c == wrapped_parent)
1943 {
1944 return Some(Edit::Multi(vec![
1945 Edit::Splice {
1946 node: stmt_block,
1947 index: si,
1948 remove: 1,
1949 insert: vec![],
1950 },
1951 Edit::Splice {
1952 node: parent,
1953 index: idx,
1954 remove: 1,
1955 insert: vec![foreach],
1956 },
1957 Edit::Delete { node: decl },
1958 ]));
1959 }
1960 }
1961 return Some(Edit::Multi(vec![
1962 Edit::Replace {
1963 target: replace_target,
1964 with: e_ref,
1965 },
1966 Edit::Splice {
1967 node: parent,
1968 index: idx,
1969 remove: 1,
1970 insert: vec![foreach],
1971 },
1972 Edit::Delete { node: decl },
1973 ]));
1974 }
1975 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
1976 return None;
1977 };
1978 let (e_name, ty) = (e_name.clone(), ty.clone());
1979 let next_call = lang.children(first).first().copied()?;
1980 let next_call = unwrap_paren_cast(&*lang, next_call);
1981 if lang.kind(next_call) != NodeKind::Call {
1982 return None;
1983 }
1984 let nc = lang.children(next_call).to_vec();
1985 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
1986 return None;
1987 };
1988 if lang.sn(*m2) != "next" || nc.len() != 1 {
1989 return None;
1990 }
1991 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
1992 return None;
1993 }
1994 let elem_is_object = match lang.var_type(iterable) {
1996 Some(it_t) => generic_elem_ty(it_t).is_none(),
1997 None => true,
1998 };
1999 if elem_is_object && ty != JType::Ref("Object".into()) {
2000 return None;
2001 }
2002 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2004 let rest = bch[1..].to_vec();
2005 for &r in &rest {
2006 if subtree_has_var(&*lang, r, &it_name) {
2007 return None;
2008 }
2009 }
2010 let new_body = lang.build_block(rest);
2011 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2012 Some(Edit::Multi(vec![
2015 Edit::Splice {
2016 node: parent,
2017 index: idx,
2018 remove: 1,
2019 insert: vec![foreach],
2020 },
2021 Edit::Delete { node: decl },
2022 ]))
2023 }
2024}
2025
2026pub struct ConcatValueOfDrop;
2035
2036impl Rule<JavaAst> for ConcatValueOfDrop {
2037 fn name(&self) -> &'static str {
2038 "concat_value_of_drop"
2039 }
2040 fn kinds(&self) -> &'static [NodeKind] {
2041 &[NodeKind::Binary]
2042 }
2043 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2044 let root = ctx.root();
2045 let lang = ctx.lang;
2046 if lang.kind(id) != NodeKind::Binary {
2047 return None;
2048 }
2049 if lang.bin_op(id) != Some(BinOp::Add) {
2050 return None;
2051 }
2052 let ch = lang.children(id).to_vec();
2053 let (l, r) = (ch[0], ch[1]);
2054 let becomes_constant = |x: JavaId, other: JavaId| {
2057 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2058 };
2059 let is_value_of = |n: JavaId| -> Option<JavaId> {
2060 if lang.kind(n) != NodeKind::Call {
2061 return None;
2062 }
2063 let c = lang.children(n).to_vec();
2064 if c.len() != 2 {
2065 return None;
2066 }
2067 let callee = c[0];
2068 if let NodeData::Member { name } = lang.data(callee) {
2069 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2070 return Some(c[1]);
2071 }
2072 }
2073 if let NodeData::VarRef { name } = lang.data(callee) {
2074 if lang.sn(*name) == "valueOf" {
2075 return Some(c[1]);
2076 }
2077 }
2078 None
2079 };
2080 let stringy = |n: JavaId| -> bool {
2081 matches!(lang.literal(n), Some(LitRef::Str(_)))
2082 || lang
2083 .var_type(n)
2084 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2085 };
2086 if let Some(x) = is_value_of(l) {
2088 if stringy(r) {
2089 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2090 return None;
2091 }
2092 let with = lang.build_bin(BinOp::Add, x, r);
2093 return Some(Edit::Replace {
2094 target: id,
2095 with,
2096 });
2097 }
2098 }
2099 if let Some(x) = is_value_of(r) {
2101 if stringy(l) {
2102 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2103 return None;
2104 }
2105 let with = lang.build_bin(BinOp::Add, l, x);
2106 return Some(Edit::Replace {
2107 target: id,
2108 with,
2109 });
2110 }
2111 }
2112 None
2113 }
2114}
2115
2116
2117pub struct StringBuilderStatements;
2130
2131fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2132 let mut n = 0;
2135 let mut stack = vec![root];
2136 while let Some(x) = stack.pop() {
2137 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2138 let ch = lang.children(x).to_vec();
2139 if let Some(&t) = ch.first() {
2140 let is_named_target =
2141 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2142 if !is_named_target {
2143 stack.push(t);
2144 }
2145 for &c in &ch[1..] {
2146 stack.push(c);
2147 }
2148 }
2149 continue;
2150 }
2151 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2152 n += 1;
2153 }
2154 for &c in lang.children(x) {
2155 stack.push(c);
2156 }
2157 }
2158 n
2159}
2160
2161impl Rule<JavaAst> for StringBuilderStatements {
2162 fn name(&self) -> &'static str {
2163 "string_builder_statements"
2164 }
2165 fn kinds(&self) -> &'static [NodeKind] {
2166 &[NodeKind::VarDecl]
2167 }
2168 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2169 let parent = ctx.parent(id)?;
2171 let idx = ctx.index(id)?;
2172 let root = ctx.root();
2173 let lang = ctx.lang;
2174
2175 if lang.kind(id) != NodeKind::VarDecl {
2176 return None;
2177 }
2178 if lang.kind(parent) != NodeKind::Block {
2179 return None;
2180 }
2181 let v0 = lang.var_name(id)?.to_string();
2182 let init = *lang.children(id).first()?;
2184 if lang.kind(init) != NodeKind::Call {
2185 return None;
2186 }
2187 let ic = lang.children(init).to_vec();
2188 if ic.len() != 2 {
2189 return None;
2190 }
2191 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2192 return None;
2193 };
2194 if lang.sn(*m0) != "append" {
2195 return None;
2196 }
2197 let sb_new = lang.children(ic[0])[0];
2198 let (ctor_arg, a0) = match lang.data(sb_new) {
2199 NodeData::New { ty, .. } => {
2200 let is_sb = !lang.unit_shadows_lang
2201 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2202 if !is_sb {
2203 return None;
2204 }
2205 let args = lang.children(sb_new).to_vec();
2206 match args.len() {
2207 0 => (None, ic[1]),
2208 1 => {
2209 let a = args[0];
2210 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2211 || lang
2212 .var_type(a)
2213 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2214 if stringy {
2215 (Some(a), ic[1])
2216 } else if matches!(
2217 lang.literal(a),
2218 Some(LitRef::Int(_) | LitRef::Long(_))
2219 ) {
2220 (None, ic[1])
2222 } else {
2223 return None; }
2225 }
2226 _ => return None,
2227 }
2228 }
2229 _ => return None,
2230 };
2231
2232 let stmts = lang.children(parent).to_vec();
2233 let mut parts: Vec<JavaId> = vec![a0];
2235 let mut expected_reads: std::collections::HashMap<String, usize> =
2236 std::collections::HashMap::new();
2237 let mut chain_len = 1usize; let mut cur = v0.clone();
2239 while idx + chain_len < stmts.len() {
2240 let s = stmts[idx + chain_len];
2241 let (recv, arg, new_var, consumed) = match lang.data(s) {
2242 NodeData::Assign { op: None } => {
2244 let ch = lang.children(s).to_vec();
2245 if ch.len() != 2 {
2246 break;
2247 }
2248 let (t, v) = (ch[0], ch[1]);
2249 if lang.kind(t) != NodeKind::VarRef
2250 || lang.var_name(t) != Some(cur.as_str())
2251 || lang.kind(v) != NodeKind::Call
2252 {
2253 break;
2254 }
2255 let vc = lang.children(v).to_vec();
2256 if vc.len() != 2 {
2257 break;
2258 }
2259 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2260 break;
2261 };
2262 if lang.sn(*mn) != "append" {
2263 break;
2264 }
2265 let recv = lang.children(vc[0])[0];
2266 (recv, vc[1], false, s)
2267 }
2268 NodeData::ExprStmt => {
2269 let ch = lang.children(s).to_vec();
2270 if ch.len() != 1 {
2271 break;
2272 }
2273 let inner = ch[0];
2274 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2275 break;
2276 }
2277 let ach = lang.children(inner).to_vec();
2278 if ach.len() != 2 {
2279 break;
2280 }
2281 let (t, v) = (ach[0], ach[1]);
2282 if lang.kind(t) != NodeKind::VarRef
2283 || lang.var_name(t) != Some(cur.as_str())
2284 || lang.kind(v) != NodeKind::Call
2285 {
2286 break;
2287 }
2288 let vc = lang.children(v).to_vec();
2289 if vc.len() != 2 {
2290 break;
2291 }
2292 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2293 break;
2294 };
2295 if lang.sn(*mn) != "append" {
2296 break;
2297 }
2298 let recv = lang.children(vc[0])[0];
2299 (recv, vc[1], false, s)
2300 }
2301 NodeData::VarDecl { .. } => {
2303 let init = match lang.children(s).first() {
2304 Some(&x) => x,
2305 None => break,
2306 };
2307 if lang.kind(init) != NodeKind::Call {
2308 break;
2309 }
2310 let vc = lang.children(init).to_vec();
2311 if vc.len() != 2 {
2312 break;
2313 }
2314 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2315 break;
2316 };
2317 if lang.sn(*mn) != "append" {
2318 break;
2319 }
2320 let recv = lang.children(vc[0])[0];
2321 if lang.var_name(recv) != Some(cur.as_str()) {
2322 break;
2323 }
2324 (recv, vc[1], true, s)
2325 }
2326 _ => break,
2327 };
2328 if lang.var_name(recv) != Some(cur.as_str()) {
2330 break;
2331 }
2332 parts.push(arg);
2333 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2334 if new_var {
2335 cur = lang.var_name(consumed)?.to_string();
2336 }
2337 chain_len += 1;
2338 let _ = consumed;
2339 }
2340
2341 let final_stmt = *stmts.get(idx + chain_len)?;
2343 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2344 return None;
2345 };
2346 let s_init = *lang.children(final_stmt).first()?;
2347 if lang.kind(s_init) != NodeKind::Call {
2348 return None;
2349 }
2350 let sc = lang.children(s_init).to_vec();
2351 if sc.len() != 1 {
2352 return None;
2353 }
2354 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2355 return None;
2356 };
2357 if lang.sn(*mn) != "toString" {
2358 return None;
2359 }
2360 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2361 return None;
2362 }
2363
2364 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2368 if count_var_uses_in(&*lang, parent, v) != *exp {
2369 return None;
2370 }
2371 }
2372
2373 let mut all_parts: Vec<JavaId> = Vec::new();
2375 if let Some(ca) = ctor_arg {
2376 all_parts.push(ca);
2377 }
2378 all_parts.extend(parts);
2379 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2380 && lang.has_string_identity_compare(root)
2381 {
2382 return None;
2383 }
2384 if all_parts.is_empty() {
2385 let empty = lang.build_str("");
2386 return Some(Edit::Multi(vec![
2387 Edit::Replace {
2388 target: s_init,
2389 with: empty,
2390 },
2391 Edit::Splice {
2392 node: parent,
2393 index: idx,
2394 remove: chain_len,
2395 insert: Vec::new(),
2396 },
2397 ]));
2398 }
2399 let first = all_parts[0];
2400 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2401 || lang
2402 .var_type(first)
2403 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2404 let mut acc = if stringy_first {
2405 first
2406 } else {
2407 let empty = lang.build_str("");
2408 lang.build_bin(BinOp::Add, empty, first)
2409 };
2410 for &p in &all_parts[1..] {
2411 acc = lang.build_bin(BinOp::Add, acc, p);
2412 }
2413 Some(Edit::Multi(vec![
2414 Edit::Replace {
2415 target: s_init,
2416 with: acc,
2417 },
2418 Edit::Splice {
2419 node: parent,
2420 index: idx,
2421 remove: chain_len,
2422 insert: Vec::new(),
2423 },
2424 ]))
2425 }
2426}
2427
2428
2429pub struct TrailingContinueJava;
2436
2437impl Rule<JavaAst> for TrailingContinueJava {
2438 fn name(&self) -> &'static str {
2439 "trailing_continue"
2440 }
2441 fn kinds(&self) -> &'static [NodeKind] {
2442 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2443 }
2444 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2445 let parent = ctx.parent(id)?;
2446 let lang = ctx.lang;
2447 let body = match lang.kind(id) {
2448 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2449 *lang.children(id).last()?
2450 }
2451 _ => return None,
2452 };
2453 if lang.kind(body) != NodeKind::Block {
2454 return None;
2455 }
2456 let ch = lang.children(body).to_vec();
2457 let last = *ch.last()?;
2458 if lang.kind(last) != NodeKind::Continue {
2459 return None;
2460 }
2461 let label = match lang.data(last) {
2462 NodeData::Continue { label } => label.clone(),
2463 _ => return None,
2464 };
2465 match label {
2466 None => Some(Edit::Delete { node: last }),
2467 Some(l) => {
2468 if lang.kind(parent) == NodeKind::Label {
2470 if let NodeData::Label { name } = lang.data(parent) {
2471 if *name == l {
2472 return Some(Edit::Delete { node: last });
2473 }
2474 }
2475 }
2476 None
2477 }
2478 }
2479 }
2480}
2481
2482
2483pub struct XorNoise;
2491
2492impl Rule<JavaAst> for XorNoise {
2493 fn name(&self) -> &'static str {
2494 "xor_noise"
2495 }
2496 fn kinds(&self) -> &'static [NodeKind] {
2497 &[NodeKind::Binary]
2498 }
2499 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2500 let lang = ctx.lang;
2501 if lang.kind(id) != NodeKind::Binary {
2502 return None;
2503 }
2504 let op = lang.bin_op(id)?;
2505 if op != BinOp::BitXor {
2506 return None;
2507 }
2508 let ch = lang.children(id).to_vec();
2509 let (l, r) = (ch[0], ch[1]);
2510 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2512 if int_lit(r) == Some(0) {
2513 return Some(Edit::Replace {
2514 target: id,
2515 with: l,
2516 });
2517 }
2518 if int_lit(l) == Some(0) {
2519 return Some(Edit::Replace {
2520 target: id,
2521 with: r,
2522 });
2523 }
2524 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2526 return None;
2527 }
2528 let ich = lang.children(l).to_vec();
2529 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2530 let k2 = int_lit(r)?;
2531 let x = ich[0];
2532 let k = k1 ^ k2;
2533 let with = if k == 0 {
2534 x
2535 } else {
2536 let lit = lang.build_int(k, false);
2537 lang.build_bin(BinOp::BitXor, x, lit)
2538 };
2539 Some(Edit::Replace {
2540 target: id,
2541 with,
2542 })
2543 }
2544}
2545
2546pub struct StrLenFold;
2551
2552impl Rule<JavaAst> for StrLenFold {
2553 fn name(&self) -> &'static str {
2554 "str_len_fold"
2555 }
2556 fn kinds(&self) -> &'static [NodeKind] {
2557 &[NodeKind::Call]
2558 }
2559 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2560 let _at_stmt_pos = ctx
2562 .parent(id)
2563 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2564 .unwrap_or(false);
2565 let lang = ctx.lang;
2566 if lang.kind(id) != NodeKind::Call {
2567 return None;
2568 }
2569 let ch = lang.children(id).to_vec();
2570 if ch.len() != 1 {
2571 return None;
2572 }
2573 let NodeData::Member { name } = lang.data(ch[0]) else {
2574 return None;
2575 };
2576 if lang.sn(*name) != "length" {
2577 return None;
2578 }
2579 let recv = lang.children(ch[0])[0];
2580 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2581 let with = lang.build_int(s.chars().count() as i64, false);
2582 return Some(Edit::Replace {
2583 target: id,
2584 with,
2585 });
2586 }
2587 None
2588 }
2589}
2590
2591
2592pub struct LiteralEval;
2605
2606impl Rule<JavaAst> for LiteralEval {
2607 fn name(&self) -> &'static str {
2608 "literal_eval"
2609 }
2610 fn kinds(&self) -> &'static [NodeKind] {
2611 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2612 }
2613 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2614 let root = ctx.root();
2615 let lang = ctx.lang;
2616 let edit = match lang.kind(id) {
2617 NodeKind::Call => self.eval_call(&mut *lang, id),
2618 NodeKind::Index => self.eval_index(&mut *lang, id),
2619 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2620 _ => None,
2621 };
2622 if let Some(Edit::Replace { with, .. }) = &edit {
2626 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2627 && lang.has_string_identity_compare(root)
2628 {
2629 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2630 eprintln!("[gate] blocked literal_eval at {id:?}");
2631 }
2632 return None;
2633 }
2634 }
2635 edit
2636 }
2637}
2638
2639impl LiteralEval {
2640 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2641 let ch = lang.children(id).to_vec();
2642 let callee = ch[0];
2643 let NodeData::Member { name: method } = lang.data(callee) else {
2644 return None;
2645 };
2646 let method = *method;
2647 let recv = lang.children(callee)[0];
2648 let args: Vec<Lit> = ch[1..]
2650 .iter()
2651 .map(|&a| litref_to_lit(lang.literal(a)))
2652 .collect::<Option<Vec<_>>>()?;
2653
2654 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2655 eval_str_method(s, lang.sn(method), &args)
2656 } else if let Some(cls) = lang.var_name(recv) {
2657 eval_static_method(cls, lang.sn(method), &args)
2658 } else {
2659 None
2660 };
2661 let lit = result?;
2662 let with = lit_to_node(lang, &lit);
2663 Some(Edit::Replace {
2664 target: id,
2665 with,
2666 })
2667 }
2668
2669 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2670 let ch = lang.children(id).to_vec();
2672 let arr = match lang.kind(ch[0]) {
2674 NodeKind::ArrayLit => ch[0],
2675 NodeKind::NewArray => {
2676 let nc = lang.children(ch[0]);
2677 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2678 return None;
2679 };
2680 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2681 return None;
2682 }
2683 nc[0]
2684 }
2685 _ => return None,
2686 };
2687 let idx = lang.literal(ch[1])?.as_int()?;
2688 let elems = lang.children(arr).to_vec();
2689 if idx < 0 || idx >= elems.len() as i64 {
2690 return None;
2691 }
2692 Some(Edit::Replace {
2693 target: id,
2694 with: elems[idx as usize],
2695 })
2696 }
2697}
2698
2699fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2700 Some(match l? {
2701 LitRef::Str(s) => Lit::Str(s.to_string()),
2702 LitRef::Int(v) => Lit::Int(v),
2703 LitRef::Long(v) => Lit::Long(v),
2704 LitRef::Bool(b) => Lit::Bool(b),
2705 LitRef::Char(c) => Lit::Char(c),
2706 LitRef::Float(v) => Lit::Float(v),
2707 LitRef::Double(v) => Lit::Double(v),
2708 LitRef::Null => return None,
2709 })
2710}
2711
2712fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2713 match lit {
2714 Lit::Str(s) => lang.build_str(s),
2715 Lit::Int(v) => lang.build_int(*v, false),
2716 Lit::Long(v) => lang.build_int(*v, true),
2717 Lit::Bool(b) => lang.build_bool(*b),
2718 Lit::Char(c) => lang.build_char(*c),
2719 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2720 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2721 _ => lang.lit(Lit::Null),
2722 }
2723}
2724
2725fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2727 let NodeData::Cast { ty } = lang.data(id) else {
2728 return None;
2729 };
2730 let ty = ty.clone();
2731 let inner = lang.children(id)[0];
2732 let lit = litref_to_lit(lang.literal(inner))?;
2733 let with = match (&ty, &lit) {
2734 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2735 lang.build_char(char::from_u32(*v as u32)?)
2736 }
2737 (JType::Char, Lit::Char(_)) => inner,
2738 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2739 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2740 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2741 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2742 lang.build_int(*v, false)
2743 }
2744 (JType::Int, Lit::Int(_)) => inner,
2745 _ => return None,
2746 };
2747 Some(Edit::Replace {
2748 target: id,
2749 with,
2750 })
2751}
2752
2753fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2754 let chars: Vec<char> = s.chars().collect();
2755 let n = chars.len() as i64;
2756 Some(match (method, args) {
2757 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2758 ("intern", []) => Lit::Str(s.to_string()),
2760 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2761 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2762 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2763 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2764 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2765 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2766 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2767 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2768 }
2769 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2770 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2771 ),
2772 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2773 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2774 ),
2775 ("length", []) => Lit::Int(n),
2776 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2777 Lit::Char(chars[*i as usize])
2778 }
2779 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2780 Lit::Str(s.chars().skip(*b as usize).collect())
2781 }
2782 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2783 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2784 }
2785 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2786 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2787 }
2788 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2789 Lit::Str(s.replace(a.as_str(), b))
2790 }
2791 ("hashCode", []) => Lit::Int(
2793 s.chars()
2794 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2795 as i64,
2796 ),
2797 _ => return None,
2798 })
2799}
2800
2801fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2802 let cls = cls.rsplit('.').next().unwrap_or(cls);
2803 Some(match (cls, method, args) {
2804 ("Integer", "parseInt", [Lit::Str(s)]) => {
2805 Lit::Int(s.trim().parse::<i64>().ok()?)
2806 }
2807 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2808 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2809 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2810 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2811 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2812 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2813 Lit::Bool(s.eq_ignore_ascii_case("true"))
2814 }
2815 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2816 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2817 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2818 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2819 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2820 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2821 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2822 let fmt = match &fmt_args[0] {
2823 Lit::Str(s) => s.clone(),
2824 _ => return None,
2825 };
2826 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2827 Lit::Str(out)
2828 }
2829 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2830 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2831 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2832 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2833 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2834 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2835 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2836 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2837 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2838 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2839 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2840 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2841 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2842 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2843 _ => return None,
2844 })
2845}
2846
2847fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2849 let mut out = String::new();
2850 let mut it = fmt.chars().peekable();
2851 let mut arg_i = 0usize;
2852 while let Some(c) = it.next() {
2853 if c != '%' {
2854 out.push(c);
2855 continue;
2856 }
2857 match it.next()? {
2858 '%' => out.push('%'),
2859 's' => {
2860 let a = args.get(arg_i)?;
2861 arg_i += 1;
2862 match a {
2863 Lit::Str(s) => out.push_str(s),
2864 Lit::Char(ch) => out.push(*ch),
2865 Lit::Bool(b) => out.push_str(&b.to_string()),
2866 _ => out.push_str(&lit_debug_str(a)),
2867 }
2868 }
2869 'd' => {
2870 let a = args.get(arg_i)?;
2871 arg_i += 1;
2872 match a {
2873 Lit::Int(v) => out.push_str(&v.to_string()),
2874 Lit::Long(v) => out.push_str(&v.to_string()),
2875 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
2876 _ => return None,
2877 }
2878 }
2879 _ => return None,
2881 }
2882 }
2883 if arg_i != args.len() {
2884 return None; }
2886 Some(out)
2887}
2888
2889fn lit_debug_str(l: &Lit) -> String {
2890 match l {
2891 Lit::Int(v) => v.to_string(),
2892 Lit::Long(v) => v.to_string(),
2893 _ => "?".into(),
2894 }
2895}
2896
2897fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
2920 match ast.data(m) {
2921 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
2922 _ => None,
2923 }
2924}
2925
2926pub struct UrlDecodeFold;
2927
2928impl Rule<JavaAst> for UrlDecodeFold {
2929 fn name(&self) -> &'static str {
2930 "url_decode_fold"
2931 }
2932 fn kinds(&self) -> &'static [NodeKind] {
2933 &[NodeKind::Call]
2934 }
2935 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2936 let lang = ctx.lang;
2937 let ch = lang.children(id).to_vec();
2940 if ch.len() != 3 {
2941 return None; }
2943 let callee = ch[0];
2944 if lang.kind(callee) != NodeKind::Member {
2945 return None;
2946 }
2947 if member_name(lang, callee).as_deref() != Some("decode") {
2948 return None;
2949 }
2950 let recv = *lang.children(callee).first()?;
2952 let recv_name = match lang.kind(recv) {
2953 NodeKind::Member => member_name(lang, recv)?,
2954 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
2955 _ => return None,
2956 };
2957 if !recv_name.ends_with("URLDecoder") {
2958 return None;
2959 }
2960 let s_lit = match lang.literal(ch[1])? {
2961 LitRef::Str(s) => s.to_string(),
2962 _ => return None,
2963 };
2964 let cs = match lang.literal(ch[2])? {
2966 LitRef::Str(s) => s.to_string(),
2967 _ => return None,
2968 };
2969 if cs != "UTF-8" {
2970 return None;
2971 }
2972 if s_lit.len() > 8192 {
2974 return None;
2975 }
2976 let decoded = url_decode(&s_lit)?;
2977 if decoded.chars().any(|c| {
2979 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
2980 }) {
2981 return None;
2982 }
2983 let lit = lang.lit(Lit::Str(decoded));
2984 Some(Edit::Replace { target: id, with: lit })
2985 }
2986}
2987
2988fn url_decode(s: &str) -> Option<String> {
2991 let b = s.as_bytes();
2992 let mut out: Vec<u8> = Vec::with_capacity(b.len());
2993 let mut i = 0usize;
2994 while i < b.len() {
2995 match b[i] {
2996 b'+' => {
2997 out.push(b' ');
2998 i += 1;
2999 }
3000 b'%' => {
3001 if i + 3 > b.len() {
3002 return None;
3003 }
3004 let hi = hex_val(b[i + 1])?;
3005 let lo = hex_val(b[i + 2])?;
3006 out.push(hi * 16 + lo);
3007 i += 3;
3008 }
3009 c => {
3010 out.push(c);
3011 i += 1;
3012 }
3013 }
3014 }
3015 String::from_utf8(out).ok()
3016}
3017
3018fn hex_val(c: u8) -> Option<u8> {
3019 match c {
3020 b'0'..=b'9' => Some(c - b'0'),
3021 b'a'..=b'f' => Some(c - b'a' + 10),
3022 b'A'..=b'F' => Some(c - b'A' + 10),
3023 _ => None,
3024 }
3025}
3026
3027pub struct Base64NewStringFold;
3028
3029impl Rule<JavaAst> for Base64NewStringFold {
3030 fn name(&self) -> &'static str {
3031 "base64_new_string_fold"
3032 }
3033 fn kinds(&self) -> &'static [NodeKind] {
3034 &[NodeKind::New]
3035 }
3036 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3037 let root = ctx.root();
3038 let lang = ctx.lang;
3039 let NodeData::New { ty, .. } = lang.data(id) else {
3040 return None;
3041 };
3042 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3043 return None;
3044 }
3045 if lang.has_string_identity_compare(root) {
3047 return None;
3048 }
3049 let args = lang.children(id).to_vec();
3050 if args.len() != 1 {
3051 return None;
3052 }
3053 if lang.kind(args[0]) != NodeKind::Call {
3055 return None;
3056 }
3057 let dc = lang.children(args[0]).to_vec();
3058 if dc.len() != 2 {
3059 return None;
3060 }
3061 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3062 return None;
3063 };
3064 if lang.sn(*dn) != "decode" {
3065 return None;
3066 }
3067 let recv = lang.children(dc[0])[0];
3069 if lang.kind(recv) != NodeKind::Call {
3070 return None;
3071 }
3072 let gc = lang.children(recv).to_vec();
3073 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3074 return None;
3075 };
3076 let url_safe = match lang.sn(*gn) {
3077 "getDecoder" => false,
3078 "getUrlDecoder" => true,
3079 _ => return None,
3080 };
3081 let owner = lang.children(gc[0])[0];
3083 let owner_ok = match lang.data(owner) {
3084 NodeData::Member { name } => lang.sn(*name) == "Base64",
3085 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3086 };
3087 if !owner_ok {
3088 return None;
3089 }
3090 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3091 return None;
3092 };
3093 let bytes = base64_decode(b64, url_safe)?;
3094 let text = String::from_utf8(bytes).ok()?;
3095 let with = lang.build_str(&text);
3096 Some(Edit::Replace {
3097 target: id,
3098 with,
3099 })
3100 }
3101}
3102
3103fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3105 let val = |c: char| -> Option<u32> {
3106 match c {
3107 'A'..='Z' => Some(c as u32 - 'A' as u32),
3108 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3109 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3110 '+' if !url_safe => Some(62),
3111 '/' if !url_safe => Some(63),
3112 '-' if url_safe => Some(62),
3113 '_' if url_safe => Some(63),
3114 _ => None,
3115 }
3116 };
3117 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3118 if clean.len() % 4 == 1 {
3119 return None;
3120 }
3121 let mut out = Vec::new();
3122 let mut buf = 0u32;
3123 let mut bits = 0u32;
3124 for c in clean.chars() {
3125 buf = (buf << 6) | val(c)?;
3126 bits += 6;
3127 if bits >= 8 {
3128 bits -= 8;
3129 out.push((buf >> bits) as u8);
3130 buf &= (1 << bits) - 1;
3131 }
3132 }
3133 Some(out)
3134}
3135
3136
3137pub struct CffRecover;
3159
3160struct CffCase {
3161 body: Vec<JavaId>,
3162 tail: CffTail,
3163}
3164
3165enum CffTail {
3166 Goto(i64),
3167 Cond { cond: JavaId, a: i64, b: i64 },
3168 Term(JavaId),
3169 Exit,
3170}
3171
3172#[derive(Clone, Copy, PartialEq, Debug)]
3173enum Flow {
3174 Done,
3175 Stop,
3176 Cycle(usize),
3177 Cond(i64),
3178 External(i64),
3179}
3180
3181struct CffCtx<'a> {
3182 lang: &'a mut JavaAst,
3183 cases: std::collections::HashMap<i64, CffCase>,
3184 default_term: Option<Vec<JavaId>>,
3186 used_default: bool,
3187 reached: std::collections::HashSet<i64>,
3189 emitted: std::collections::HashSet<i64>,
3191}
3192
3193impl Rule<JavaAst> for CffRecover {
3194 fn name(&self) -> &'static str {
3195 "cff_recover"
3196 }
3197 fn kinds(&self) -> &'static [NodeKind] {
3198 &[NodeKind::While]
3199 }
3200 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3201 let parent = ctx.parent(id)?;
3202 let idx = ctx.index(id)?;
3203 let lang = ctx.lang;
3204 if lang.kind(id) != NodeKind::While {
3205 return None;
3206 }
3207 let wch = lang.children(id).to_vec();
3208 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3209 return None;
3210 }
3211 if lang.kind(wch[1]) != NodeKind::Block {
3212 return None;
3213 }
3214 let body_ch = lang.children(wch[1]).to_vec();
3215 let (switch, exit_if) = match body_ch.len() {
3216 1 => (body_ch[0], None),
3217 2 => (body_ch[0], Some(body_ch[1])),
3218 _ => return None,
3219 };
3220 if lang.kind(switch) != NodeKind::Switch {
3221 return None;
3222 }
3223 let sch = lang.children(switch).to_vec();
3224 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3225 return None;
3226 }
3227 let v = lang.var_name(sch[0])?.to_string();
3228
3229 let sentinel = match exit_if {
3231 None => None,
3232 Some(ei) => {
3233 if lang.kind(ei) != NodeKind::If {
3234 return None;
3235 }
3236 let ich = lang.children(ei).to_vec();
3237 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3238 return None;
3239 }
3240 let cch = lang.children(ich[0]).to_vec();
3241 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3242 && lang.var_name(cch[0]) == Some(v.as_str())
3243 {
3244 lang.literal(cch[1]).and_then(|x| x.as_int())
3245 } else if lang.kind(cch[1]) == NodeKind::VarRef
3246 && lang.var_name(cch[1]) == Some(v.as_str())
3247 {
3248 lang.literal(cch[0]).and_then(|x| x.as_int())
3249 } else {
3250 return None;
3251 }?;
3252 let then_ok = match lang.kind(ich[1]) {
3253 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3254 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3255 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3256 }
3257 _ => false,
3258 };
3259 if !then_ok {
3260 return None;
3261 }
3262 Some(sent)
3263 }
3264 };
3265
3266 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3268 let mut default_term: Option<Vec<JavaId>> = None;
3269 for &c in &sch[1..] {
3270 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3271 return None;
3272 };
3273 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3274 let ch = lang.children(c).to_vec();
3275 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3276 if !arrow
3278 && stmts
3279 .last()
3280 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3281 {
3282 stmts.pop();
3283 }
3284 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3285 let mut inner = lang.children(stmts[0]).to_vec();
3286 if !arrow
3287 && inner
3288 .last()
3289 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3290 {
3291 inner.pop();
3292 }
3293 stmts = inner;
3294 }
3295 if is_default {
3296 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3297 default_term = Some(stmts);
3298 continue;
3299 }
3300 return None;
3301 }
3302 if labels != 1 {
3304 return None;
3305 }
3306 let state = match lang.literal(ch[0]) {
3307 Some(LitRef::Int(v)) => v,
3308 _ => return None,
3309 };
3310 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3311 for &st in &stmts {
3313 if subtree_has_var(lang, st, &v) {
3314 return None;
3315 }
3316 }
3317 cases.insert(state, CffCase { body: stmts, tail });
3318 }
3319 if cases.is_empty() {
3320 return None;
3321 }
3322
3323 if idx == 0 {
3326 return None;
3327 }
3328 let block_ch = lang.children(parent).to_vec();
3329 let mut init = None;
3330 let mut init_idx = idx;
3331 {
3332 let mut up = idx;
3333 for _ in 0..8 {
3334 if up == 0 {
3335 break;
3336 }
3337 up -= 1;
3338 let st = block_ch[up];
3339 match scan_cff_event(lang, st, &v) {
3340 Some(CffEvent::Read) => return None,
3341 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3342 init = parse_cff_init(lang, &v, st);
3343 init_idx = up;
3344 break;
3345 }
3346 None => continue,
3347 }
3348 }
3349 }
3350 let init = init?;
3351
3352 for &st in &block_ch[idx + 1..] {
3354 if subtree_has_var(lang, st, &v) {
3355 return None;
3356 }
3357 }
3358
3359 let mut to_exit: Vec<i64> = Vec::new();
3361 for (k, ci) in cases.iter() {
3362 if let CffTail::Goto(g) = ci.tail {
3363 if !cases.contains_key(&g) {
3364 if !(sentinel == Some(g) || default_term.is_some()) {
3365 return None;
3366 }
3367 to_exit.push(*k);
3368 }
3369 }
3370 }
3371 for k in to_exit {
3372 if let Some(ci) = cases.get_mut(&k) {
3373 ci.tail = CffTail::Exit;
3374 }
3375 }
3376
3377 let mut c = CffCtx {
3378 lang,
3379 cases,
3380 default_term,
3381 used_default: false,
3382 reached: Default::default(),
3383 emitted: Default::default(),
3384 };
3385 let stmts = cff_run(&mut c, init)?;
3386
3387 if stmts.is_empty() {
3390 return None;
3391 }
3392
3393 Some(Edit::Multi(vec![
3396 Edit::Splice {
3397 node: parent,
3398 index: idx,
3399 remove: 1,
3400 insert: stmts,
3401 },
3402 Edit::Delete {
3403 node: block_ch[init_idx],
3404 },
3405 ]))
3406 }
3407}
3408
3409fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3410 let last = *stmts.last()?;
3411 if lang.kind(last) == NodeKind::Return {
3412 stmts.pop();
3413 return Some(CffTail::Term(last));
3414 }
3415 let assign_of = |n: JavaId| -> Option<JavaId> {
3416 let inner = match lang.kind(n) {
3417 NodeKind::Assign => n,
3418 NodeKind::ExprStmt => {
3419 let ch = lang.children(n);
3420 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3421 ch[0]
3422 } else {
3423 return None;
3424 }
3425 }
3426 _ => return None,
3427 };
3428 if lang.assign_op(inner).is_some() {
3429 return None;
3430 }
3431 let ch = lang.children(inner).to_vec();
3432 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3433 return None;
3434 }
3435 Some(inner)
3436 };
3437 if let Some(assign) = assign_of(last) {
3438 let value = lang.children(assign)[1];
3439 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3440 stmts.pop();
3441 return Some(CffTail::Goto(k));
3442 }
3443 if lang.kind(value) == NodeKind::Ternary {
3444 let tch = lang.children(value).to_vec();
3445 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3446 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3447 stmts.pop();
3448 return Some(CffTail::Cond { cond: tch[0], a, b });
3449 }
3450 return None;
3451 }
3452 if lang.kind(last) == NodeKind::If {
3453 let ich = lang.children(last).to_vec();
3454 if ich.len() != 3 {
3455 return None;
3456 }
3457 let arm = |n: JavaId| -> Option<i64> {
3458 let target = match lang.kind(n) {
3459 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3460 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3461 _ => return None,
3462 };
3463 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3464 };
3465 let a = arm(ich[1])?;
3466 let b = arm(ich[2])?;
3467 stmts.pop();
3468 return Some(CffTail::Cond { cond: ich[0], a, b });
3469 }
3470 None
3471}
3472
3473
3474enum CffEvent {
3476 Read,
3477 Write,
3478 Decl,
3480}
3481
3482fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3483 match lang.kind(node) {
3484 NodeKind::VarRef => {
3485 if lang.var_name(node) == Some(name) {
3486 Some(CffEvent::Read)
3487 } else {
3488 None
3489 }
3490 }
3491 NodeKind::VarDecl => {
3492 if lang.var_name(node) == Some(name) {
3493 Some(CffEvent::Decl)
3494 } else {
3495 for &c in lang.children(node) {
3496 if let Some(e) = scan_cff_event(lang, c, name) {
3497 return Some(e);
3498 }
3499 }
3500 None
3501 }
3502 }
3503 NodeKind::Assign => {
3504 let ch = lang.children(node);
3505 if let Some(&val) = ch.get(1) {
3506 if let Some(e) = scan_cff_event(lang, val, name) {
3507 if matches!(e, CffEvent::Read) {
3508 return Some(e);
3509 }
3510 }
3511 }
3512 if lang.assign_op(node).is_none() {
3513 if let Some(&t) = ch.first() {
3514 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3515 return Some(CffEvent::Write);
3516 }
3517 }
3518 }
3519 None
3520 }
3521 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3522 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3523 NodeKind::Binary => {
3524 let op = lang.bin_op(node)?;
3525 let ch = lang.children(node);
3526 if op.is_short_circuit() {
3527 scan_cff_event(lang, *ch.first()?, name)
3528 } else {
3529 for &c in ch {
3530 if let Some(e) = scan_cff_event(lang, c, name) {
3531 return Some(e);
3532 }
3533 }
3534 None
3535 }
3536 }
3537 _ => {
3538 for &c in lang.children(node) {
3539 if let Some(e) = scan_cff_event(lang, c, name) {
3540 return Some(e);
3541 }
3542 }
3543 None
3544 }
3545 }
3546}
3547
3548fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3549 match lang.kind(stmt) {
3550 NodeKind::VarDecl => {
3551 let ch = lang.children(stmt).to_vec();
3552 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3553 return None;
3554 }
3555 lang.literal(ch[0]).and_then(|x| x.as_int())
3556 }
3557 NodeKind::Assign => {
3558 if lang.assign_op(stmt).is_some() {
3559 return None;
3560 }
3561 let ch = lang.children(stmt).to_vec();
3562 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3563 return None;
3564 }
3565 lang.literal(ch[1]).and_then(|x| x.as_int())
3566 }
3567 NodeKind::ExprStmt => {
3568 let ch = lang.children(stmt).to_vec();
3569 if ch.len() == 1 {
3570 parse_cff_init(lang, v, ch[0])
3571 } else {
3572 None
3573 }
3574 }
3575 _ => None,
3576 }
3577}
3578
3579fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3580 for (s, stmts) in pairs {
3581 c.emitted.insert(*s);
3582 out.extend(stmts.iter().copied());
3583 }
3584}
3585
3586fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3587 for (s, stmts) in pairs {
3588 if *s == m {
3589 break;
3590 }
3591 out.extend(stmts.iter().copied());
3592 }
3593}
3594
3595fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3596 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3597 pairs[pos..].to_vec()
3598}
3599
3600fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3602 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3603 let mut cur = start;
3604 let mut guard = 0usize;
3605 loop {
3606 guard += 1;
3607 if guard > 10_000 {
3608 return None;
3609 }
3610 if Some(cur) == stop {
3611 return Some((path, Flow::Stop));
3612 }
3613 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3614 return Some((path, Flow::Cycle(i)));
3615 }
3616 if c.emitted.contains(&cur) {
3617 return Some((path, Flow::External(cur)));
3618 }
3619 c.reached.insert(cur);
3620 let ci = c.cases.get(&cur)?;
3621 let mut body = ci.body.clone();
3622 match ci.tail {
3623 CffTail::Goto(k) => {
3624 path.push((cur, body));
3625 cur = k;
3626 }
3627 CffTail::Term(r) => {
3628 body.push(r);
3629 path.push((cur, body));
3630 return Some((path, Flow::Done));
3631 }
3632 CffTail::Exit => {
3633 if c.default_term.is_some() {
3634 if let Some(dt) = c.default_term.clone() {
3635 body.extend(dt);
3636 c.used_default = true;
3637 }
3638 }
3639 path.push((cur, body));
3640 return Some((path, Flow::Done));
3641 }
3642 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3643 }
3644 }
3645}
3646
3647fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3649 let (path, flow) = collect(c, init, None)?;
3650 cff_structure(c, path, flow)
3651}
3652
3653fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3655 let (path, flow) = collect(c, start, None)?;
3656 cff_structure(c, path, flow)
3657}
3658
3659fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3663 let mut stmts: Vec<JavaId> = Vec::new();
3664 loop {
3665 match flow {
3666 Flow::Done => {
3667 flatten(&path, &mut stmts, c);
3668 return Some(stmts);
3669 }
3670 Flow::Cycle(i) => {
3671 flatten(&path[..i], &mut stmts, c);
3672 let mut suffix = Vec::new();
3673 flatten(&path[i..], &mut suffix, c);
3674 let inner = c.lang.build_block(suffix);
3675 let cond = c.lang.build_bool(true);
3676 let w = c.lang.while_(cond, inner);
3677 stmts.push(w);
3678 return Some(stmts);
3679 }
3680 Flow::Stop | Flow::External(_) => return None,
3681 Flow::Cond(state) => {
3682 flatten(&path, &mut stmts, c);
3683 path.clear();
3684 let ci = c.cases.get(&state)?;
3685 stmts.extend(ci.body.iter().copied());
3686 c.emitted.insert(state);
3687 let CffTail::Cond { cond, a, b } = ci.tail else {
3688 return None;
3689 };
3690 let (pa, fa) = collect(c, a, Some(state))?;
3692 if fa == Flow::Stop {
3693 let mut lb = Vec::new();
3694 flatten(&pa, &mut lb, c);
3695 let inner = c.lang.build_block(lb);
3696 let w = c.lang.while_(cond, inner);
3697 stmts.push(w);
3698 let (p2, f2) = collect(c, b, None)?;
3699 path = p2;
3700 flow = f2;
3701 continue;
3702 }
3703 let (pb, fb) = collect(c, b, Some(state))?;
3704 if fb == Flow::Stop {
3705 let mut lb = Vec::new();
3706 flatten(&pb, &mut lb, c);
3707 let inner = c.lang.build_block(lb);
3708 let nc = c.lang.build_unary(UnOp::Not, cond);
3709 let w = c.lang.while_(nc, inner);
3710 stmts.push(w);
3711 let (p2, f2) = collect(c, a, None)?;
3712 path = p2;
3713 flow = f2;
3714 continue;
3715 }
3716 let common = pa
3718 .iter()
3719 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3720 .map(|(s, _)| *s);
3721 let same_end = match (fa, fb) {
3722 (Flow::Done, Flow::Done) => true,
3723 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3724 _ => false,
3725 };
3726 match common {
3727 Some(m) if same_end => {
3728 let mut pre_a = Vec::new();
3729 let mut pre_b = Vec::new();
3730 flatten_prefix(&pa, m, &mut pre_a);
3731 flatten_prefix(&pb, m, &mut pre_b);
3732 let ba = c.lang.build_block(pre_a);
3733 let bb = c.lang.build_block(pre_b);
3734 let ifs = c.lang.if_(cond, ba, Some(bb));
3735 stmts.push(ifs);
3736 path = suffix_pairs(&pa, m);
3737 flow = fa;
3738 continue;
3739 }
3740 Some(_) => return None,
3741 None => {
3742 let mut fa_stmts = Vec::new();
3744 let mut fb_stmts = Vec::new();
3745 flatten(&pa, &mut fa_stmts, c);
3746 flatten(&pb, &mut fb_stmts, c);
3747 match (fa, fb) {
3748 (Flow::Done, Flow::Done) => {
3749 let ba = c.lang.build_block(fa_stmts);
3750 let bb = c.lang.build_block(fb_stmts);
3751 let ifs = c.lang.if_(cond, ba, Some(bb));
3752 stmts.push(ifs);
3753 return Some(stmts);
3754 }
3755 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3757 let ba = c.lang.build_block(fa_stmts);
3758 let bb = c.lang.build_block(fb_stmts);
3759 let ifs = c.lang.if_(cond, ba, Some(bb));
3760 stmts.push(ifs);
3761 let (p2, f2) = collect(c, x, None)?;
3762 path = p2;
3763 flow = f2;
3764 continue;
3765 }
3766 (Flow::Done, Flow::Cond(x)) => {
3768 let cont = cff_run_from(c, x)?;
3769 fb_stmts.extend(cont);
3770 let ba = c.lang.build_block(fa_stmts);
3771 let bb = c.lang.build_block(fb_stmts);
3772 let ifs = c.lang.if_(cond, ba, Some(bb));
3773 stmts.push(ifs);
3774 return Some(stmts);
3775 }
3776 (Flow::Cond(x), Flow::Done) => {
3777 let cont = cff_run_from(c, x)?;
3778 fa_stmts.extend(cont);
3779 let ba = c.lang.build_block(fa_stmts);
3780 let bb = c.lang.build_block(fb_stmts);
3781 let ifs = c.lang.if_(cond, ba, Some(bb));
3782 stmts.push(ifs);
3783 return Some(stmts);
3784 }
3785 _ => return None,
3786 }
3787 }
3788 }
3789 }
3790 }
3791 }
3792}
3793
3794
3795
3796pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3806 ast.const_fields.clear();
3807 ast.const_scalars.clear();
3808 ast.inline_methods.clear();
3809 ast.field_types.clear();
3810 ast.final_fields.clear();
3811 ast.raw_generic_names = {
3814 let mut m: std::collections::HashSet<String> = [
3815 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3816 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3817 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3818 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3819 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3820 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3821 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3822 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3823 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3824 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3826 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3827 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3828 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3829 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3830 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3831 ]
3832 .iter()
3833 .map(|x| x.to_string())
3834 .collect();
3835 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3836 if !ty.ty_params.trim().is_empty() {
3837 m.insert(ty.name.clone());
3838 }
3839 for mem in &ty.members {
3840 if let Member::Type(t) = mem {
3841 collect_generics(t, m);
3842 }
3843 }
3844 }
3845 for ty in &unit.types {
3846 collect_generics(ty, &mut m);
3847 }
3848 m
3849 };
3850 ast.unit_shadows_lang = {
3851 fn decl_shadows(ty: &TypeDecl) -> bool {
3852 if ty.name == "String" || ty.name == "StringBuilder" {
3853 return true;
3854 }
3855 ty.members.iter().any(|m| match m {
3856 Member::Type(t) => decl_shadows(t),
3857 _ => false,
3858 })
3859 }
3860 unit.types.iter().any(decl_shadows)
3861 };
3862
3863 let mut shadowed: std::collections::HashSet<String> = Default::default();
3868 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3869 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3870 let mut bodies: Vec<JavaId> = Vec::new();
3871 for ty in &unit.types {
3872 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3873 }
3874 for ty in &unit.types {
3877 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
3878 }
3879 for &root in &bodies {
3883 let mut stack = vec![root];
3884 while let Some(n) = stack.pop() {
3885 if ast.kind(n) == NodeKind::VarDecl {
3886 if let Some(nm) = ast.var_name(n) {
3887 shadowed.insert(nm.to_string());
3888 }
3889 }
3890 match ast.data(n) {
3891 NodeData::Assign { .. } => {
3892 let t = ast.children(n)[0];
3893 if ast.kind(t) == NodeKind::Member {
3894 if let NodeData::Member { name: sym } = ast.data(t) {
3895 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
3896 }
3897 }
3898 let mut ts = vec![t];
3899 while let Some(x) = ts.pop() {
3900 if ast.kind(x) == NodeKind::VarRef {
3901 if let Some(nm) = ast.var_name(x) {
3902 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3903 }
3904 }
3905 for &c in ast.children(x) {
3906 ts.push(c);
3907 }
3908 }
3909 }
3910 NodeData::Unary { op } if op.is_incdec() => {
3911 let t = ast.children(n)[0];
3912 if ast.kind(t) == NodeKind::VarRef {
3913 if let Some(nm) = ast.var_name(t) {
3914 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3915 }
3916 }
3917 }
3918 _ => {}
3919 }
3920 for &c in ast.children(n) {
3921 stack.push(c);
3922 }
3923 }
3924 }
3925
3926 for ty in &unit.types {
3928 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
3930 collect_own_clinit_lits(ast, ty, &mut own_lits);
3931 for m in &ty.members {
3932 if let Member::Field { mods, ty: fty, declarators } = m {
3933 let words: Vec<&str> = mods.split_whitespace().collect();
3934 let is_static = words.contains(&"static");
3935 let is_final = words.contains(&"final");
3936 let is_private = words.contains(&"private");
3937 for d in declarators {
3938 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
3942 continue;
3943 }
3944 let init = match d.init {
3945 Some(init) => {
3946 if !is_final
3948 || !is_const_init(ast, init)
3949 || shadowed.contains(&d.name)
3950 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
3951 {
3952 continue;
3953 }
3954 init
3955 }
3956 None => {
3957 if !is_static
3964 || (!is_final && !is_private)
3965 || shadowed.contains(&d.name)
3966 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
3967 {
3968 continue;
3969 }
3970 match own_lits.get(&d.name) {
3971 Some(&init) if is_const_init(ast, init) => init,
3972 _ => continue,
3973 }
3974 }
3975 };
3976 if ast.const_fields.contains_key(&d.name) {
3978 ast.const_fields.remove(&d.name);
3979 ast.const_scalars.remove(&d.name);
3980 } else {
3981 let scalar_ok = scalar_lit_of(ast, init)
3982 .is_some_and(|l| lit_type_matches_decl(fty, l));
3983 ast.const_fields.insert(d.name.clone(), init);
3984 if scalar_ok {
3985 ast.const_scalars.insert(d.name.clone());
3986 }
3987 }
3988 }
3989 }
3990 }
3991 }
3992
3993 {
4000 let identity_assert = [
4001 "assertSame",
4002 "assertNotSame",
4003 "isSameInstanceAs",
4004 "isNotSameInstanceAs",
4005 "referenceEquals",
4006 "same",
4007 ];
4008 let mut hit = false;
4009 for root in &bodies {
4010 let mut stack = vec![*root];
4011 while let Some(n) = stack.pop() {
4012 if let NodeData::Call = ast.data(n) {
4013 if let Some(&callee) = ast.children(n).first() {
4014 let nm = match ast.data(callee) {
4015 NodeData::Member { name } => ast.sn(*name),
4016 NodeData::VarRef { name } => ast.sn(*name),
4017 _ => "",
4018 };
4019 if identity_assert.contains(&nm) {
4020 hit = true;
4021 break;
4022 }
4023 }
4024 }
4025 for &c in ast.children(n) {
4026 stack.push(c);
4027 }
4028 }
4029 if hit {
4030 break;
4031 }
4032 }
4033 ast.unit_string_identity =
4034 ast.any_string_identity(&bodies) || hit;
4035 }
4036
4037 let mut method_name_count: std::collections::HashMap<String, usize> =
4047 std::collections::HashMap::new();
4048 for_each_type(unit, &mut |ty: &TypeDecl| {
4049 for m in &ty.members {
4050 if let Member::Method { name, .. } = m {
4051 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4052 }
4053 }
4054 });
4055 for ty in &unit.types {
4056 for m in &ty.members {
4057 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4058 let words: Vec<&str> = mods.split_whitespace().collect();
4059 let non_overridable = words.contains(&"private")
4060 || words.contains(&"static")
4061 || words.contains(&"final");
4062 if !non_overridable {
4063 continue;
4064 }
4065 let ch = ast.children(*b).to_vec();
4066 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4067 if let Some(&expr) = ast.children(ch[0]).first() {
4068 if params.len() <= 3
4069 && !subtree_has_anon(ast, expr)
4074 && !params.iter().any(|p| p.varargs)
4078 && !subtree_calls_self(ast, expr, name)
4079 && !subtree_has_type_sensitive(ast, expr)
4085 && method_name_count.get(name).copied() == Some(1)
4086 {
4087 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4088 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4089 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4090 }
4091 }
4092 }
4093 }
4094 }
4095 }
4096}
4097
4098fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4100 match ast.kind(init) {
4101 NodeKind::Literal => true,
4102 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4103 NodeKind::NewArray => {
4104 let ch = ast.children(init).to_vec();
4105 ch.len() == 1
4106 && ast.kind(ch[0]) == NodeKind::ArrayLit
4107 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4108 }
4109 _ => false,
4110 }
4111}
4112
4113fn collect_bodies_recursive(
4117 ty: &TypeDecl,
4118 bodies: &mut Vec<JavaId>,
4119 shadowed: &mut std::collections::HashSet<String>,
4120 field_decls: &mut std::collections::HashMap<String, u32>,
4121) {
4122 if ty.kind == TypeKind::Enum {
4130 for c in &ty.enum_constants {
4131 if c.trim() == "," {
4132 continue;
4133 }
4134 let last = c.split('(').next().unwrap_or(c)
4135 .split_whitespace()
4136 .next_back()
4137 .unwrap_or("");
4138 let name: String = last
4139 .chars()
4140 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4141 .collect();
4142 if !name.is_empty() {
4143 *field_decls.entry(name).or_insert(0) += 1;
4144 }
4145 }
4146 }
4147 for m in &ty.members {
4148 match m {
4149 Member::Method { body: Some(b), params, .. }
4150 | Member::Constructor { body: Some(b), params, .. } => {
4151 bodies.push(*b);
4152 for p in params {
4153 shadowed.insert(p.name.clone());
4154 }
4155 }
4156 Member::Initializer { body, .. } => bodies.push(*body),
4157 Member::Field { declarators, .. } => {
4158 for d in declarators {
4159 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4160 if let Some(init) = d.init {
4161 bodies.push(init);
4162 }
4163 }
4164 }
4165 Member::Type(t) => {
4166 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4167 }
4168 _ => {}
4169 }
4170 }
4171}
4172
4173fn collect_field_types(
4175 ty: &TypeDecl,
4176 out: &mut std::collections::HashMap<String, JType>,
4177 finals: &mut std::collections::HashSet<String>,
4178) {
4179 for m in &ty.members {
4180 match m {
4181 Member::Field { mods, ty: fty, declarators } => {
4182 let is_final = mods.split_whitespace().any(|w| w == "final");
4183 for d in declarators {
4184 if out.contains_key(&d.name) {
4185 out.remove(&d.name);
4186 finals.remove(&d.name);
4187 } else {
4188 out.insert(d.name.clone(), fty.clone());
4189 if is_final {
4190 finals.insert(d.name.clone());
4191 }
4192 }
4193 }
4194 }
4195 Member::Type(t) => collect_field_types(t, out, finals),
4196 _ => {}
4197 }
4198 }
4199}
4200
4201fn collect_own_clinit_lits(
4206 ast: &JavaAst,
4207 ty: &TypeDecl,
4208 out: &mut std::collections::HashMap<String, JavaId>,
4209) {
4210 for m in &ty.members {
4211 if let Member::Initializer { is_static: true, body } = m {
4212 if ast.kind(*body) != NodeKind::Block {
4213 continue;
4214 }
4215 for &st in ast.children(*body) {
4216 let asg = match ast.kind(st) {
4217 NodeKind::ExprStmt => {
4218 let Some(&c) = ast.children(st).first() else {
4222 continue;
4223 };
4224 if ast.kind(c) == NodeKind::Assign {
4225 c
4226 } else {
4227 continue;
4228 }
4229 }
4230 NodeKind::Assign => st,
4231 _ => continue,
4232 };
4233 let ch = ast.children(asg).to_vec();
4234 if ast.kind(ch[0]) != NodeKind::VarRef {
4235 continue;
4236 }
4237 let Some(nm) = ast.var_name(ch[0]) else { continue };
4238 if is_const_init(ast, ch[1]) {
4239 out.insert(nm.to_string(), ch[1]);
4240 }
4241 }
4242 }
4243 }
4244}
4245
4246fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4248 if ast.kind(init) == NodeKind::Literal {
4249 ast.literal(init)
4250 } else {
4251 None
4252 }
4253}
4254
4255fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4260 use cure_engine::kind::LitRef;
4261 match (fty, lit) {
4262 (JType::Ref(r), LitRef::Str(_)) => {
4263 let base = r.split('<').next().unwrap_or(r).trim();
4264 base == "String" || base == "java.lang.String"
4265 }
4266 (JType::Char, LitRef::Char(_)) => true,
4267 (JType::Bool, LitRef::Bool(_)) => true,
4268 (JType::Int, LitRef::Int(_)) => true,
4269 (JType::Long, LitRef::Long(_)) => true,
4270 (JType::Float, LitRef::Float(_)) => true,
4271 (JType::Double, LitRef::Double(_)) => true,
4272 _ => false,
4273 }
4274}
4275
4276fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4280 let mut stack = vec![root];
4281 while let Some(n) = stack.pop() {
4282 match ast.kind(n) {
4283 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4284 _ => {}
4285 }
4286 for &c in ast.children(n) {
4287 stack.push(c);
4288 }
4289 }
4290 false
4291}
4292
4293fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4294 let mut stack = vec![root];
4295 while let Some(n) = stack.pop() {
4296 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4297 return true;
4298 }
4299 for &c in ast.children(n) {
4300 stack.push(c);
4301 }
4302 }
4303 false
4304}
4305
4306fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4307 let mut stack = vec![root];
4308 while let Some(n) = stack.pop() {
4309 if ast.kind(n) == NodeKind::Call {
4310 let callee = ast.children(n)[0];
4311 if ast.var_name(callee) == Some(name) {
4312 return true;
4313 }
4314 if let NodeData::Member { name: m } = ast.data(callee) {
4319 if ast.sn(*m) == name {
4320 return true;
4321 }
4322 }
4323 }
4324 for &c in ast.children(n) {
4325 stack.push(c);
4326 }
4327 }
4328 false
4329}
4330
4331pub struct StaticArrayIndexFold;
4336
4337impl Rule<JavaAst> for StaticArrayIndexFold {
4338 fn name(&self) -> &'static str {
4339 "static_array_index_fold"
4340 }
4341 fn kinds(&self) -> &'static [NodeKind] {
4342 &[NodeKind::Index]
4343 }
4344 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4345 let lang = ctx.lang;
4346 if lang.kind(id) != NodeKind::Index {
4347 return None;
4348 }
4349 let ch = lang.children(id).to_vec();
4350 if lang.kind(ch[0]) != NodeKind::VarRef {
4351 return None;
4352 }
4353 let name = lang.var_name(ch[0])?.to_string();
4354 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4355 let init = *lang.const_fields.get(&name)?;
4356 let arr = match lang.kind(init) {
4357 NodeKind::ArrayLit => init,
4358 NodeKind::NewArray => {
4359 let ic = lang.children(init).to_vec();
4360 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4361 ic[0]
4362 } else {
4363 return None;
4364 }
4365 }
4366 _ => return None,
4367 };
4368 let elems = lang.children(arr).to_vec();
4369 if idx < 0 || idx >= elems.len() as i64 {
4370 return None;
4371 }
4372 let elem = lang.copy_subtree(elems[idx as usize]);
4373 Some(Edit::Replace {
4374 target: id,
4375 with: elem,
4376 })
4377 }
4378}
4379
4380pub struct CtorDelegationHoist;
4449
4450fn hoist_resolve_arg(
4455 lang: &mut JavaAst,
4456 assign: &std::collections::HashMap<String, JavaId>,
4457 a: JavaId,
4458 visiting: &mut std::collections::HashSet<String>,
4459) -> Option<JavaId> {
4460 let name = match lang.data(a) {
4461 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4462 _ => return Some(a), };
4464 let Some(&expr) = assign.get(&name) else {
4466 return Some(a);
4467 };
4468 if !visiting.insert(name.clone()) {
4469 return None; }
4471 let r = hoist_subst(lang, assign, expr, visiting);
4472 visiting.remove(&name);
4473 r
4474}
4475
4476fn hoist_subst(
4478 lang: &mut JavaAst,
4479 assign: &std::collections::HashMap<String, JavaId>,
4480 e: JavaId,
4481 visiting: &mut std::collections::HashSet<String>,
4482) -> Option<JavaId> {
4483 match lang.data(e) {
4484 NodeData::VarRef { name } => {
4485 let n = lang.sn(*name).to_string();
4486 if let Some(&src) = assign.get(&n) {
4487 if !visiting.insert(n.clone()) {
4488 return None;
4489 }
4490 let r = hoist_subst(lang, assign, src, visiting);
4491 visiting.remove(&n);
4492 r
4493 } else {
4494 Some(e)
4495 }
4496 }
4497 _ => {
4498 let ch = lang.children(e).to_vec();
4499 let mut new_ch = Vec::with_capacity(ch.len());
4500 for c in ch {
4501 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4502 }
4503 Some(lang.clone_node(e, new_ch))
4504 }
4505 }
4506}
4507
4508impl Rule<JavaAst> for CtorDelegationHoist {
4509 fn name(&self) -> &'static str {
4510 "ctor_delegation_hoist"
4511 }
4512 fn kinds(&self) -> &'static [NodeKind] {
4513 &[NodeKind::Block]
4514 }
4515 fn structural(&self) -> bool {
4516 true
4517 }
4518 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4519 if ctx.parent(id).is_some() {
4522 return None;
4523 }
4524 let lang = ctx.lang;
4525 if lang.kind(id) != NodeKind::Block {
4526 return None;
4527 }
4528 let stmts = lang.children(id).to_vec();
4529 let mut ctor_idx: Option<usize> = None;
4531 for (i, &st) in stmts.iter().enumerate() {
4532 if *lang.data(st) != NodeData::ExprStmt {
4533 continue;
4534 }
4535 let Some(&inner) = lang.children(st).first() else { continue };
4536 if lang.kind(inner) != NodeKind::Call {
4537 continue;
4538 }
4539 let Some(&callee) = lang.children(inner).first() else { continue };
4540 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4541 ctor_idx = Some(i);
4542 break;
4543 }
4544 }
4545 let ci = ctor_idx?;
4546 if ci == 0 {
4547 return None; }
4549 let mut last_assign: std::collections::HashMap<String, JavaId> =
4552 std::collections::HashMap::new();
4553 for &st in &stmts[..ci] {
4554 let (name, value) = match lang.data(st) {
4555 NodeData::VarDecl { name, .. } => {
4556 let init = lang.children(st).first().copied();
4557 match init {
4558 Some(e) => (name.clone(), e),
4559 None => continue, }
4561 }
4562 NodeData::ExprStmt => {
4563 let Some(&a) = lang.children(st).first() else {
4564 return None;
4565 };
4566 if lang.kind(a) != NodeKind::Assign
4569 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4570 {
4571 return None;
4572 }
4573 let ch = lang.children(a).to_vec();
4574 let (t, v) = (*ch.first()?, *ch.get(1)?);
4575 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4576 return None; }
4578 let vn = lang.var_name(t)?;
4579 (vn.to_string(), v)
4580 }
4581 _ => return None, };
4583 last_assign.insert(name, value);
4588 }
4589 let call_stmt = stmts[ci];
4591 let call = *lang.children(call_stmt).first()?;
4592 let cch = lang.children(call).to_vec();
4593 let callee = *cch.first()?;
4594 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4595 for &a in &cch[1..] {
4596 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4597 }
4598 let callee_copy = lang.copy_subtree(callee);
4600 let new_call = lang.call(callee_copy, new_args);
4601 let new_stmt = lang.expr_stmt(new_call);
4602 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4603 insert.push(new_stmt);
4604 insert.extend(stmts[..ci].iter().copied());
4605 Some(Edit::Splice {
4606 node: id,
4607 index: 0,
4608 remove: ci + 1,
4609 insert,
4610 })
4611 }
4612}
4613
4614pub struct TrailingIfContinue;
4615
4616impl Rule<JavaAst> for TrailingIfContinue {
4617 fn name(&self) -> &'static str {
4618 "trailing_if_continue"
4619 }
4620 fn kinds(&self) -> &'static [NodeKind] {
4621 &[NodeKind::If]
4622 }
4623 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4624 let parent = ctx.parent(id);
4625 let grandparent = parent.and_then(|p| ctx.parent(p));
4626 let lang = ctx.lang;
4627 if lang.kind(id) != NodeKind::If {
4628 return None;
4629 }
4630 let ch = lang.children(id).to_vec();
4631 if ch.len() != 2 {
4633 return None;
4634 }
4635 let then = ch[1];
4636 if lang.kind(then) != NodeKind::Block {
4637 return None;
4638 }
4639 let tbody = lang.children(then).to_vec();
4640 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4641 return None;
4642 }
4643 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4645 return None;
4646 }
4647 let p = parent?;
4650 if lang.kind(p) != NodeKind::Block {
4651 return None;
4652 }
4653 let pch = lang.children(p).to_vec();
4654 if pch.last() != Some(&id) {
4655 return None;
4656 }
4657 let gp = grandparent?;
4659 match lang.kind(gp) {
4660 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4661 let gch = lang.children(gp).to_vec();
4662 if gch.last() == Some(&p) {
4663 Some(Edit::Delete { node: id })
4664 } else {
4665 None
4666 }
4667 }
4668 _ => None,
4669 }
4670 }
4671}
4672
4673pub struct TrailingAssignReturn;
4674
4675impl Rule<JavaAst> for TrailingAssignReturn {
4676 fn name(&self) -> &'static str {
4677 "trailing_assign_return"
4678 }
4679 fn kinds(&self) -> &'static [NodeKind] {
4680 &[NodeKind::Assign]
4681 }
4682 fn structural(&self) -> bool {
4685 true
4686 }
4687 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4688 let parent = ctx.parent(id);
4689 let grandparent = parent.and_then(|p| ctx.parent(p));
4690 let lang = ctx.lang;
4691 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4693 return None;
4694 }
4695 let ch = lang.children(id).to_vec();
4696 if ch.len() != 2 {
4697 return None;
4698 }
4699 let (target, value) = (ch[0], ch[1]);
4700 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4702 return None;
4703 }
4704 let name = lang.var_name(target)?.to_string();
4705 let parent = parent?;
4707 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4708 parent
4709 } else if lang.kind(parent) == NodeKind::Block {
4710 id
4711 } else {
4712 return None;
4713 };
4714 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4715 grandparent?
4716 } else {
4717 parent
4718 };
4719 if lang.kind(block) != NodeKind::Block {
4720 return None;
4721 }
4722 let stmts = lang.children(block).to_vec();
4723 let idx = stmts.iter().position(|&x| x == stmt)?;
4724 let next = *stmts.get(idx + 1)?;
4726 let (flow_val, is_return) = match lang.kind(next) {
4727 NodeKind::Return => (lang.children(next).first().copied(), true),
4728 NodeKind::Throw => (lang.children(next).first().copied(), false),
4729 _ => return None,
4730 };
4731 let flow_val = flow_val?;
4732 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4733 return None;
4734 }
4735 for &s in &stmts[idx + 2..] {
4738 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4739 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4740 }) {
4741 return None;
4742 }
4743 }
4744 let new_flow = if is_return {
4746 lang.ret(Some(value))
4747 } else {
4748 lang.throw(value)
4749 };
4750 Some(Edit::Multi(vec![
4751 Edit::Replace { target: next, with: new_flow },
4752 Edit::Delete { node: stmt },
4753 ]))
4754 }
4755}
4756
4757pub struct ConstFieldPropagate;
4758
4759impl Rule<JavaAst> for ConstFieldPropagate {
4760 fn name(&self) -> &'static str {
4761 "const_field_propagate"
4762 }
4763 fn kinds(&self) -> &'static [NodeKind] {
4764 &[NodeKind::VarRef]
4765 }
4766 fn structural(&self) -> bool {
4769 true
4770 }
4771 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4772 let parent = ctx.parent(id);
4773 let _grandparent = parent.and_then(|p| ctx.parent(p));
4774 let lang = ctx.lang;
4775 if lang.kind(id) != NodeKind::VarRef {
4776 return None;
4777 }
4778 let name = lang.var_name(id)?.to_string();
4779 if !lang.const_scalars.contains(&name) {
4780 return None;
4781 }
4782 if let Some(p) = parent {
4783 let is_first = lang.children(p).first() == Some(&id);
4784 match lang.kind(p) {
4785 NodeKind::Assign if is_first => return None,
4787 NodeKind::Unary if is_first => {
4788 if let NodeData::Unary { op } = lang.data(p) {
4789 if op.is_incdec() {
4790 return None;
4791 }
4792 }
4793 }
4794 NodeKind::Call if is_first => return None,
4796 _ => {}
4797 }
4798 }
4799 let init = *lang.const_fields.get(&name)?;
4800 let lit = lang.copy_subtree(init);
4801 Some(Edit::Replace { target: id, with: lit })
4802 }
4803}
4804
4805pub struct NoopPrivateCall;
4806
4807impl Rule<JavaAst> for NoopPrivateCall {
4808 fn name(&self) -> &'static str {
4809 "noop_private_call"
4810 }
4811 fn kinds(&self) -> &'static [NodeKind] {
4812 &[NodeKind::ExprStmt]
4813 }
4814 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4815 let lang = ctx.lang;
4816 if lang.kind(id) != NodeKind::ExprStmt {
4817 return None;
4818 }
4819 let ch = lang.children(id).to_vec();
4820 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
4821 return None;
4822 }
4823 let call = ch[0];
4824 let cch = lang.children(call).to_vec();
4825 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
4826 return None; }
4828 let name = lang.var_name(cch[0])?.to_string();
4829 let arity = cch.len() - 1;
4830 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
4831 if cands.is_empty() {
4832 return None;
4833 }
4834 let arg_types: Vec<Option<JType>> =
4838 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
4839 let mut applicable_noop = false;
4840 let mut applicable_count = 0usize;
4841 for c in cands {
4842 let ok = c
4843 .params
4844 .iter()
4845 .zip(arg_types.iter())
4846 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
4847 if ok {
4848 applicable_count += 1;
4849 applicable_noop = c.noop;
4850 }
4851 }
4852 if applicable_count != 1 || !applicable_noop {
4853 return None;
4854 }
4855 for &arg in &cch[1..] {
4857 if lang.effect(arg) > Effect::MayRead {
4858 return None;
4859 }
4860 }
4861 Some(Edit::Delete { node: id })
4862 }
4863}
4864
4865fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
4869 match lang.kind(arg) {
4870 NodeKind::Literal => match lang.literal(arg)? {
4871 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
4872 LitRef::Char(_) => Some(JType::Char),
4873 LitRef::Bool(_) => Some(JType::Bool),
4874 LitRef::Int(_) => Some(JType::Int),
4875 LitRef::Long(_) => Some(JType::Long),
4876 LitRef::Float(_) => Some(JType::Float),
4877 LitRef::Double(_) => Some(JType::Double),
4878 LitRef::Null => None,
4879 },
4880 NodeKind::VarRef => {
4881 let nm = lang.var_name(arg)?;
4882 lang.var_type(arg)
4883 .cloned()
4884 .or_else(|| lang.field_types.get(nm).cloned())
4885 }
4886 _ => None,
4887 }
4888}
4889
4890fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
4895 let Some(a) = at else { return true };
4896 match pt {
4897 JType::Ref(p) => {
4898 let pbase = p.split('<').next().unwrap_or(p).trim();
4899 pbase == "Object"
4900 || pbase == "java.lang.Object"
4901 || match a {
4902 JType::Ref(arend) => {
4903 let abase = arend.split('<').next().unwrap_or(arend).trim();
4904 abase == pbase
4905 }
4906 _ => false,
4907 }
4908 }
4909 JType::Array(_) => false, prim => match a {
4911 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
4912 },
4913 }
4914}
4915
4916pub struct ConstMethodInline;
4926
4927impl Rule<JavaAst> for ConstMethodInline {
4928 fn name(&self) -> &'static str {
4929 "const_method_inline"
4930 }
4931 fn kinds(&self) -> &'static [NodeKind] {
4932 &[NodeKind::Call]
4933 }
4934 fn structural(&self) -> bool {
4935 true
4936 }
4937 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4938 let at_stmt_pos = ctx
4940 .parent(id)
4941 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
4942 .unwrap_or(false);
4943 let lang = ctx.lang;
4944 if lang.kind(id) != NodeKind::Call {
4945 return None;
4946 }
4947 let ch = lang.children(id).to_vec();
4948 let (method_name, recv_class) = match lang.data(ch[0]) {
4952 NodeData::VarRef { name } => (*name, None),
4953 NodeData::Member { name } => {
4954 let recv = lang.children(ch[0])[0];
4955 match lang.var_name(recv) {
4956 Some(r) if r == "this" => (*name, None),
4957 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
4958 (*name, Some(r.to_string()))
4959 }
4960 _ => return None,
4961 }
4962 }
4963 _ => return None,
4964 };
4965 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
4966 let (params, body_expr, owner) = entry;
4967 if let Some(recv) = recv_class {
4974 if recv != owner {
4975 return None;
4976 }
4977 }
4978 {
4980 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
4981 for nm in &names {
4982 if subtree_calls_self(lang, body_expr, nm) {
4983 return None;
4984 }
4985 }
4986 }
4987 let args = ch[1..].to_vec();
4988 if args.len() != params.len() || params.is_empty() {
4989 return None;
4990 }
4991 for &a in &args {
4992 lang.literal(a)?;
4993 }
4994 let map: std::collections::HashMap<String, JavaId> = params
4995 .iter()
4996 .zip(args.iter())
4997 .map(|(p, &a)| (p.clone(), a))
4998 .collect();
4999 let with = copy_subst(lang, body_expr, &map);
5000 if at_stmt_pos {
5006 match lang.kind(with) {
5007 NodeKind::Call | NodeKind::New => {}
5008 _ => return None,
5009 }
5010 }
5011 Some(Edit::Replace {
5012 target: id,
5013 with,
5014 })
5015 }
5016}
5017
5018fn copy_subst(
5020 lang: &mut JavaAst,
5021 node: JavaId,
5022 map: &std::collections::HashMap<String, JavaId>,
5023) -> JavaId {
5024 if let NodeData::VarRef { name } = lang.data(node) {
5025 if let Some(&repl) = map.get(lang.sn(*name)) {
5026 return lang.copy_subtree(repl);
5027 }
5028 }
5029 let children: Vec<JavaId> = lang.children(node).to_vec();
5030 let new_children: Vec<JavaId> = children
5031 .iter()
5032 .map(|&c| copy_subst(lang, c, map))
5033 .collect();
5034 lang.clone_node(node, new_children)
5035}
5036
5037
5038pub struct TwrRecover;
5071
5072fn twr_is_throwable(ty_raw: &str) -> bool {
5073 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5074}
5075
5076fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5078 if lang.kind(n) == NodeKind::ExprStmt {
5079 if let Some(&e) = lang.children(n).first() {
5080 return e;
5081 }
5082 }
5083 n
5084}
5085
5086fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5088 if lang.kind(n) == NodeKind::Block {
5089 lang.children(n).to_vec()
5090 } else {
5091 vec![n]
5092 }
5093}
5094
5095fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5097 if lang.kind(e) != NodeKind::Call {
5098 return false;
5099 }
5100 let ch = lang.children(e);
5101 if ch.len() != 1 {
5102 return false;
5103 }
5104 match lang.data(ch[0]) {
5105 NodeData::Member { name } if lang.sn(*name) == "close" => {
5106 let Some(&recv) = lang.children(ch[0]).first() else {
5107 return false;
5108 };
5109 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5110 }
5111 _ => false,
5112 }
5113}
5114
5115fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5116 if lang.kind(n) != NodeKind::ExprStmt {
5117 return false;
5118 }
5119 lang.children(n)
5120 .first()
5121 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5122}
5123
5124fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5126 if lang.kind(n) != NodeKind::ExprStmt {
5127 return false;
5128 }
5129 let Some(&e) = lang.children(n).first() else {
5130 return false;
5131 };
5132 if lang.kind(e) != NodeKind::Call {
5133 return false;
5134 }
5135 let ch = lang.children(e);
5136 if ch.len() != 2 {
5137 return false;
5138 }
5139 match lang.data(ch[0]) {
5140 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5141 let Some(&recv) = lang.children(ch[0]).first() else {
5142 return false;
5143 };
5144 lang.kind(recv) == NodeKind::VarRef
5145 && lang.var_name(recv) == Some(primary)
5146 && lang.kind(ch[1]) == NodeKind::VarRef
5147 && lang.var_name(ch[1]) == Some(sup)
5148 }
5149 _ => false,
5150 }
5151}
5152
5153fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5154 let mut stack = vec![root];
5155 while let Some(n) = stack.pop() {
5156 if n == target {
5157 return true;
5158 }
5159 stack.extend(lang.children(n).iter().copied());
5160 }
5161 false
5162}
5163
5164fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5166 let mut stack = vec![root];
5167 while let Some(n) = stack.pop() {
5168 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5169 let ok = allowed
5170 .iter()
5171 .any(|&a| a == n || node_contains(lang, a, n));
5172 if !ok {
5173 return true;
5174 }
5175 }
5176 if lang.kind(n) == NodeKind::VarDecl {
5177 if let Some(nm) = lang.var_name(n) {
5178 if nm == name && !allowed.contains(&n) {
5179 return true;
5181 }
5182 }
5183 }
5184 stack.extend(lang.children(n).iter().copied());
5185 }
5186 false
5187}
5188
5189fn twr_close_with_suppressed(
5193 lang: &JavaAst,
5194 try_node: JavaId,
5195 primary: &str,
5196) -> Option<(String, String)> {
5197 let ch = lang.children(try_node).to_vec();
5198 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5199 return None;
5200 }
5201 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5202 return None;
5203 };
5204 if !twr_is_throwable(ty_raw) {
5205 return None;
5206 }
5207 let sup = sup.clone();
5208 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5209 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5210 return None;
5211 }
5212 let close_stmts = twr_stmts(lang, ch[0]);
5213 if close_stmts.len() != 1 {
5214 return None;
5215 }
5216 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5217 return None;
5218 }
5219 let e = *lang.children(close_stmts[0]).first()?;
5220 let cc = lang.children(e);
5221 if cc.len() != 1 {
5222 return None;
5223 }
5224 let NodeData::Member { name } = lang.data(cc[0]) else {
5225 return None;
5226 };
5227 if lang.sn(*name) != "close" {
5228 return None;
5229 }
5230 let recv = *lang.children(cc[0]).first()?;
5231 if lang.kind(recv) != NodeKind::VarRef {
5232 return None;
5233 }
5234 Some((lang.var_name(recv)?.to_string(), sup))
5235}
5236
5237impl Rule<JavaAst> for TwrRecover {
5238 fn name(&self) -> &'static str {
5239 "twr_recover"
5240 }
5241 fn kinds(&self) -> &'static [NodeKind] {
5242 &[NodeKind::Try]
5243 }
5244 fn structural(&self) -> bool {
5245 true
5246 }
5247 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5248 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5250 let lang = ctx.lang;
5251 if lang.kind(id) != NodeKind::Try {
5252 return None;
5253 }
5254 let ch = lang.children(id).to_vec();
5255 let try_idx = ch
5257 .iter()
5258 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5259 if try_idx != 0 {
5260 return None;
5261 }
5262 let try_block = ch[0];
5263 if lang.kind(parent) != NodeKind::Block {
5264 return None;
5265 }
5266 let pch = lang.children(parent).to_vec();
5267
5268 if ch.len() == 2 {
5270 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5271 if twr_is_throwable(ty_raw) {
5272 let t = t.clone();
5273 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5274 if catch_stmts.len() == 2 {
5275 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5277 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5278 && lang
5279 .children(throw_stmt)
5280 .first()
5281 .is_some_and(|&x| {
5282 lang.kind(x) == NodeKind::VarRef
5283 && lang.var_name(x) == Some(t.as_str())
5284 });
5285 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5286 if let Some((r, sup)) =
5287 twr_close_with_suppressed(lang, inner_try, t.as_str())
5288 {
5289 if idx >= 1 && idx + 1 < pch.len() {
5290 let decl_r = pch[idx - 1];
5291 let trailing = pch[idx + 1];
5292 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5293 if name == &r {
5294 let (name, ty) = (name.clone(), ty.clone());
5295 let init =
5296 lang.children(decl_r).first().copied();
5297 if let Some(init) = init {
5298 let provably_new = lang.kind(init)
5300 == NodeKind::New;
5301 let close_ok =
5302 twr_is_close_stmt(lang, trailing, &r);
5303 if provably_new && close_ok {
5304 let root = walk_root;
5306 let r_ok = !refs_escape(
5307 lang,
5308 root,
5309 &r,
5310 &[
5311 decl_r,
5312 init,
5313 *lang
5314 .children(inner_try)
5315 .first()
5316 .unwrap_or(&inner_try),
5317 trailing,
5318 try_block,
5319 ],
5320 );
5321 let t_ok = !refs_escape(
5322 lang, root, &t,
5323 &[throw_stmt, ch[1]],
5324 );
5325 let sup_ok = !refs_escape(
5326 lang, root, &sup,
5327 &[ch[1]],
5328 );
5329 if r_ok && t_ok && sup_ok {
5330 let init_copy =
5331 lang.copy_subtree(init);
5332 let res = lang.var_decl(
5333 &name, ty,
5334 Some(init_copy),
5335 );
5336 let new_try = lang.try_(
5337 vec![res],
5338 try_block,
5339 Vec::new(),
5340 None,
5341 );
5342 return Some(Edit::Splice {
5343 node: parent,
5344 index: idx - 1,
5345 remove: 3,
5346 insert: vec![new_try],
5347 });
5348 }
5349 }
5350 }
5351 }
5352 }
5353 }
5354 }
5355 }
5356 }
5357 }
5358 }
5359 }
5360
5361 if ch.len() == 3 {
5363 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5364 return None;
5365 };
5366 if !twr_is_throwable(ty_raw) {
5367 return None;
5368 }
5369 let t = t.clone();
5370 if lang.kind(ch[2]) != NodeKind::Block {
5372 return None;
5373 }
5374 let fin_stmts = twr_stmts(lang, ch[2]);
5375 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5376 return None;
5377 }
5378 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5380 if catch_stmts.len() != 2 {
5381 return None;
5382 }
5383 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5384 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5385 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5386 return None;
5387 };
5388 let as_ch = lang.children(assign_expr);
5389 if as_ch.len() != 2 {
5390 return None;
5391 }
5392 let primary = match lang.data(as_ch[0]) {
5393 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5394 _ => return None,
5395 };
5396 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5397 && lang.var_name(as_ch[1]) == Some(t.as_str())
5398 && lang.kind(as_ch[0]) == NodeKind::VarRef
5399 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5400 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5401 && lang.children(throw_stmt)
5402 .first()
5403 .is_some_and(|&x| {
5404 lang.kind(x) == NodeKind::VarRef
5405 && lang.var_name(x) == Some(t.as_str())
5406 });
5407 if !assign_ok || !throw_ok {
5408 return None;
5409 }
5410 let if1 = fin_stmts[0];
5412 let i1 = lang.children(if1).to_vec();
5413 if i1.len() < 2 {
5414 return None;
5415 }
5416 let cond1 = i1[0];
5417 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5418 return None;
5419 };
5420 let c1 = lang.children(cond1);
5421 let r = match lang.data(c1[0]) {
5422 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5423 lang.sn(*name).to_string()
5424 }
5425 _ => {
5426 return None;
5427 }
5428 };
5429 let then1 = i1[1];
5430 let inner_stmts = twr_stmts(lang, then1);
5431 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5432 return None;
5433 }
5434 let if2 = inner_stmts[0];
5435 let i2 = lang.children(if2).to_vec();
5436 if i2.len() != 3 {
5437 return None;
5438 }
5439 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5440 return None;
5441 };
5442 let c2 = lang.children(i2[0]);
5443 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5444 && lang.var_name(c2[0]) == Some(primary.as_str())
5445 && lang.literal(c2[1]) == Some(LitRef::Null);
5446 if !primary_ok {
5447 return None;
5448 }
5449 let (then2, els2) = (i2[1], i2[2]);
5450 let t2_stmts = twr_stmts(lang, then2);
5452 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5453 return None;
5454 }
5455 let Some((r2, sup)) =
5456 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5457 else {
5458 return None;
5459 };
5460 if r2 != r {
5461 return None;
5462 }
5463 let e2_stmts = twr_stmts(lang, els2);
5465 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5466 return None;
5467 }
5468 if idx < 2 {
5470 return None;
5471 }
5472 let decl_r = pch[idx - 2];
5473 let decl_primary = pch[idx - 1];
5474 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5475 (
5476 NodeData::VarDecl { name: rn, .. },
5477 NodeData::VarDecl { name: pn, .. },
5478 ) => (rn.clone(), pn.clone()),
5479 _ => return None,
5480 };
5481 if r_decl != r || primary_decl != primary {
5482 return None;
5483 }
5484 let pinit_null = lang
5486 .children(decl_primary)
5487 .first()
5488 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5489 if !pinit_null {
5490 return None;
5491 }
5492 let r_init = lang.children(decl_r).first().copied()?;
5493 let ty = match lang.data(decl_r) {
5494 NodeData::VarDecl { ty, .. } => ty.clone(),
5495 _ => return None,
5496 };
5497 let root = walk_root;
5499 let r_allowed = [decl_r, r_init, ch[2], try_block];
5501 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5502 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5503 if refs_escape(lang, root, &r, &r_allowed)
5504 || refs_escape(lang, root, &primary, &p_allowed)
5505 || refs_escape(lang, root, &t, &t_allowed)
5506 || refs_escape(lang, root, &sup, &[ch[2]])
5507 {
5508 return None;
5509 }
5510 let init_copy = lang.copy_subtree(r_init);
5511 let res = lang.var_decl(&r, ty, Some(init_copy));
5512 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5513 return Some(Edit::Splice {
5514 node: parent,
5515 index: idx - 2,
5516 remove: 3,
5517 insert: vec![new_try],
5518 });
5519 }
5520 None
5521 }
5522}
5523
5524pub struct StringSwitchRecover;
5549
5550fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5551 out.push_str(&format!("{:?} ", lang.data(n)));
5552 for &c in lang.children(n) {
5553 subtree_sig(lang, c, out);
5554 }
5555}
5556
5557fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5559 let ch = lang.children(sw).to_vec();
5560 let mut out = Vec::new();
5561 for &c in &ch[1..] {
5562 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5563 return None;
5564 };
5565 let is_default = *is_default;
5566 let cc = lang.children(c);
5567 let label = if is_default {
5568 None
5569 } else {
5570 if *labels != 1 {
5571 return None; }
5573 let lab = cc[0];
5574 let lit = lang.literal(lab)?;
5575 let v = match lit {
5576 LitRef::Int(v) | LitRef::Long(v) => v,
5577 _ => return None, };
5579 Some(v)
5580 };
5581 out.push((label, cc[*labels as usize..].to_vec()));
5582 }
5583 Some(out)
5584}
5585
5586fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5587 let Some(&last) = stmts.last() else {
5588 return false; };
5590 let last = if lang.kind(last) == NodeKind::Block {
5591 match lang.children(last).last() {
5592 Some(&l) => l,
5593 None => return false,
5594 }
5595 } else {
5596 last
5597 };
5598 matches!(
5599 lang.kind(last),
5600 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5601 )
5602}
5603
5604impl Rule<JavaAst> for StringSwitchRecover {
5605 fn name(&self) -> &'static str {
5606 "string_switch_recover"
5607 }
5608 fn kinds(&self) -> &'static [NodeKind] {
5609 &[NodeKind::Switch]
5610 }
5611 fn structural(&self) -> bool {
5612 true
5613 }
5614 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5615 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5616 let lang = ctx.lang;
5617 if lang.kind(id) != NodeKind::Switch {
5618 return None;
5619 }
5620 let ch = lang.children(id).to_vec();
5621 if ch.len() < 2 {
5622 return None;
5623 }
5624 let x = match lang.data(ch[0]) {
5626 NodeData::Call => {
5627 let cc = lang.children(ch[0]);
5628 if cc.len() != 1 {
5629 return None;
5630 }
5631 match lang.data(cc[0]) {
5632 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5633 let recv = *lang.children(cc[0]).first()?;
5634 if lang.kind(recv) != NodeKind::VarRef {
5635 return None;
5636 }
5637 lang.var_name(recv)?.to_string()
5638 }
5639 _ => return None,
5640 }
5641 }
5642 _ => {
5643 return None;
5644 }
5645 };
5646 struct CaseInfo {
5648 lit: String,
5649 k: i64,
5650 inner_switch: JavaId,
5651 }
5652 let mut infos: Vec<CaseInfo> = Vec::new();
5653 let mut v_name: Option<String> = None;
5654 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5656 for &c in &ch[1..] {
5657 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5658 return None;
5659 };
5660 let is_default = *is_default;
5661 if is_default {
5662 return None; }
5664 if *labels != 1 {
5665 return None;
5666 }
5667 let cc = lang.children(c);
5668 let h = match lang.literal(cc[0])? {
5669 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5670 _ => return None,
5671 };
5672 let body = cc[1..].to_vec();
5673 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5675 lang.children(body[0]).to_vec()
5676 } else {
5677 body
5678 };
5679 let mut rest = body;
5680 let mut _e_decl: Option<(String, JavaId)> = None;
5682 if let Some(&first) = rest.first() {
5683 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5684 if let Some(&init) = lang.children(first).first() {
5685 if is_x_equals(lang, init, &x) {
5686 _e_decl = Some((name.clone(), first));
5687 rest = rest[1..].to_vec();
5688 }
5689 }
5690 }
5691 }
5692 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5693 return None;
5694 }
5695 let ifn = rest[0];
5696 let ic = lang.children(ifn).to_vec();
5697 if ic.len() != 3 {
5698 return None;
5699 }
5700 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5702 return None;
5703 };
5704 let guard = *lang.children(ic[0]).first()?;
5705 let lit = match lang.data(guard) {
5706 NodeData::VarRef { name } => {
5707 let Some((en, decl)) = &_e_decl else {
5709 return None;
5710 };
5711 if lang.sn(*name) != en {
5712 return None;
5713 }
5714 let init = *lang.children(*decl).first()?;
5715 equals_lit(lang, init, &x)?
5716 }
5717 NodeData::Call => equals_lit(lang, guard, &x)?,
5718 _ => return None,
5719 };
5720 guard_nodes.push(guard);
5721 if let Some((_, decl)) = &_e_decl {
5722 guard_nodes.push(*decl);
5723 }
5724 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5726 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5727 NodeData::Break { label: None });
5728 if !then_ok {
5729 return None;
5730 }
5731 let els = twr_stmts(lang, ic[2]);
5733 let (k, inner_switch, assign_node) = match els.len() {
5734 2 => {
5735 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5736 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5737 return None;
5738 };
5739 let a_ch = lang.children(assign_expr);
5740 let vn = match lang.data(a_ch[0]) {
5741 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5742 _ => return None,
5743 };
5744 let k = match lang.literal(a_ch[1])? {
5745 LitRef::Int(v) | LitRef::Long(v) => v,
5746 _ => return None,
5747 };
5748 if lang.kind(els[1]) != NodeKind::Switch {
5749 return None;
5750 }
5751 let sel = *lang.children(els[1]).first()?;
5753 if lang.kind(sel) != NodeKind::VarRef
5754 || lang.var_name(sel) != Some(vn.as_str())
5755 {
5756 return None;
5757 }
5758 match &v_name {
5759 Some(v) if *v != vn => return None,
5760 _ => v_name = Some(vn),
5761 }
5762 (k, els[1], Some(els[0]))
5763 }
5764 1 => {
5765 if lang.kind(els[0]) != NodeKind::Switch {
5766 return None;
5767 }
5768 let sel = *lang.children(els[0]).first()?;
5770 let k = match lang.literal(sel)? {
5771 LitRef::Int(v) | LitRef::Long(v) => v,
5772 _ => return None,
5773 };
5774 (k, els[0], None)
5775 }
5776 _ => return None,
5777 };
5778 if let Some(a) = assign_node {
5779 v_nodes.push(a);
5780 }
5781 v_nodes.push(*lang.children(inner_switch).first()?);
5782 let hlit: i32 = lit
5784 .chars()
5785 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
5786 if hlit != h {
5787 return None;
5788 }
5789 let _ = assign_node;
5790 infos.push(CaseInfo {
5791 lit,
5792 k,
5793 inner_switch,
5794 });
5795 }
5796 if infos.is_empty() {
5797 return None;
5798 }
5799 if lang.kind(parent) != NodeKind::Block {
5801 return None;
5802 }
5803 let pch = lang.children(parent).to_vec();
5804 let mut k = idx + 1;
5805 if k < pch.len() {
5807 let texpr = unwrap_expr_stmt(lang, pch[k]);
5808 if let NodeData::Assign { op: None } = lang.data(texpr) {
5809 let a_ch = lang.children(texpr);
5810 if a_ch.len() == 2
5811 && lang.kind(a_ch[0]) == NodeKind::VarRef
5812 && lang.var_name(a_ch[0]) == v_name.as_deref()
5813 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
5814 {
5815 v_nodes.push(pch[k]);
5816 k += 1;
5817 }
5818 }
5819 }
5820 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
5821 return None;
5822 }
5823 let trailing = pch[k];
5824 let tsel = *lang.children(trailing).first()?;
5826 match lang.data(tsel) {
5827 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
5828 v_nodes.push(tsel);
5829 }
5830 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
5831 _ => {
5832 lang.literal(tsel)?;
5833 }
5834 }
5835 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
5838 for &c in &lang.children(sw)[1..] {
5839 subtree_sig(lang, c, out);
5840 }
5841 }
5842 let mut sig0 = String::new();
5843 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
5844 for info in &infos[1..] {
5845 let mut sig = String::new();
5846 switch_cases_sig(lang, info.inner_switch, &mut sig);
5847 if sig != sig0 {
5848 return None;
5849 }
5850 }
5851 {
5852 let mut sig = String::new();
5853 switch_cases_sig(lang, trailing, &mut sig);
5854 if sig != sig0 {
5855 return None;
5856 }
5857 }
5858 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
5860 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
5861 std::collections::HashMap::new();
5862 let mut default_body: Option<Vec<JavaId>> = None;
5863 for (lab, stmts) in &bodies {
5864 match lab {
5865 Some(v) => {
5866 body_map.insert(*v, stmts.clone());
5867 }
5868 None => default_body = Some(stmts.clone()),
5869 }
5870 }
5871 let Some(default_body) = default_body else {
5872 return None;
5873 };
5874 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
5876 if !ssw_body_terminal(lang, stmts) {
5877 return None;
5878 }
5879 }
5880 let root = ctx_root;
5882 if let Some(vn) = &v_name {
5883 let mut allowed: Vec<JavaId> = v_nodes.clone();
5884 allowed.push(trailing);
5885 if idx >= 1 {
5886 let cand = pch[idx - 1];
5887 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
5888 if name == vn {
5889 allowed.push(cand);
5890 if idx != 1 {
5892 allowed.pop();
5893 }
5894 }
5895 }
5896 }
5897 if refs_escape(lang, root, vn, &allowed) {
5898 return None;
5899 }
5900 }
5901 let mut new_cases: Vec<JavaId> = Vec::new();
5903 for info in &infos {
5904 let Some(body) = body_map.get(&info.k) else {
5905 return None;
5906 };
5907 let lab = lang.lit(Lit::Str(info.lit.clone()));
5908 let case = lang.case_(vec![lab], false, false, body.clone());
5909 new_cases.push(case);
5910 }
5911 let def_case = lang.case_(Vec::new(), true, false, default_body);
5912 new_cases.push(def_case);
5913 let sel = lang.var(&x);
5914 let new_switch = lang.switch_(sel, new_cases);
5915 let remove = k - idx + 1;
5917 Some(Edit::Splice {
5918 node: parent,
5919 index: idx,
5920 remove,
5921 insert: vec![new_switch],
5922 })
5923 }
5924}
5925
5926fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
5928 if lang.kind(e) != NodeKind::Call {
5929 return None;
5930 }
5931 let cc = lang.children(e);
5932 if cc.len() != 2 {
5933 return None;
5934 }
5935 match lang.data(cc[0]) {
5936 NodeData::Member { name } if lang.sn(*name) == "equals" => {
5937 let recv = *lang.children(cc[0]).first()?;
5938 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
5939 return None;
5940 }
5941 match lang.literal(cc[1])? {
5942 LitRef::Str(s) => Some(s.to_string()),
5943 _ => None,
5944 }
5945 }
5946 _ => None,
5947 }
5948}
5949
5950fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
5951 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
5952}
5953
5954pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
5961 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
5962 rules.push(Box::new(CastSimplify));
5963 rules.push(Box::new(SelfCompare));
5964 rules.push(Box::new(StringBuilderFold));
5965 rules.push(Box::new(BoxUnboxChain));
5966 rules.push(Box::new(IteratorToForEach));
5967 rules.push(Box::new(NewStringFold));
5968 rules.push(Box::new(NewStringCharArrayFold));
5969 rules.push(Box::new(EmptyFinallyStrip));
5970 rules.push(Box::new(TryUnwrapNoCatch));
5971 rules.push(Box::new(TryUnwrapRethrow));
5972 rules.push(Box::new(StaticExec));
5973 rules.push(Box::new(LoopHeadBreak));
5974 rules.push(Box::new(WhileIteratorToForEach));
5975 rules.push(Box::new(ConcatValueOfDrop));
5976 rules.push(Box::new(StringBuilderStatements));
5977 rules.push(Box::new(TrailingContinueJava));
5978 rules.push(Box::new(XorNoise));
5979 rules.push(Box::new(StrLenFold));
5980 rules.push(Box::new(LiteralEval));
5981 rules.push(Box::new(Base64NewStringFold));
5982 rules.push(Box::new(UrlDecodeFold));
5983 rules.push(Box::new(CffRecover));
5984 rules.push(Box::new(StaticArrayIndexFold));
5985 rules.push(Box::new(TrailingAssignReturn));
5986 rules.push(Box::new(CtorDelegationHoist));
5987 rules.push(Box::new(TrailingIfContinue));
5988 rules.push(Box::new(ConstFieldPropagate));
5989 rules.push(Box::new(NoopPrivateCall));
5990 rules.push(Box::new(ConstMethodInline));
5991 rules.push(Box::new(TwrRecover));
5992 rules.push(Box::new(StringSwitchRecover));
5993 rules
5994}
5995
5996pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
5998 let mut rules = default_java_rules();
5999 let mut engine_all = cure_engine::rules::all_rules();
6000 rules.append(&mut engine_all);
6001 rules
6002}
6003
6004pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6006 let rules = default_java_rules();
6007 cure_engine::simplify(ast, root, &rules, cfg)
6008}
6009
6010pub fn simplify_unit_with_facts(
6015 ast: &mut JavaAst,
6016 unit: &mut CompilationUnit,
6017 cfg: &Config,
6018 facts: &cure_java_ast::JavaFacts,
6019) -> Report {
6020 ast.set_facts(facts.clone());
6021 let r = simplify_unit(ast, unit, cfg);
6022 ast.clear_facts();
6023 r
6024}
6025
6026pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6027 let mut total = Report::default();
6028 for _cycle in 0..6 {
6033 let mut round = Report::default();
6034 collect_unit_consts(ast, unit);
6035 if cfg.remove_dead_methods {
6038 collect_noop_private_methods(ast, unit);
6039 } else {
6040 ast.noop_private_methods.clear();
6041 }
6042 for ty in &mut unit.types {
6043 simplify_type(ast, ty, cfg, &mut round);
6044 }
6045 if cfg.remove_dead_methods {
6046 round.edits += remove_dead_private_fields(ast, unit);
6047 round.edits += remove_dead_private_methods(ast, unit);
6048 round.edits += remove_unused_imports(ast, unit);
6049 }
6050 total.edits += round.edits;
6051 total.iterations += round.iterations;
6052 total.discarded += round.discarded;
6053 for (k, v) in round.by_rule {
6054 *total.by_rule.entry(k).or_insert(0) += v;
6055 }
6056 if round.edits == 0 {
6061 break;
6062 }
6063 }
6064 total
6065}
6066
6067fn collect_anon_and_string_names(
6074 ast: &JavaAst,
6075 unit: &CompilationUnit,
6076) -> std::collections::HashSet<String> {
6077 let mut out: std::collections::HashSet<String> = Default::default();
6078 let mut bodies: Vec<JavaId> = Vec::new();
6079 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6080 for root in bodies {
6081 let mut stack = vec![root];
6082 while let Some(n) = stack.pop() {
6083 match ast.data(n) {
6084 NodeData::New { anon_raw: Some(raw), .. } => {
6085 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6086 if !id.is_empty() {
6087 out.insert(id.to_string());
6088 }
6089 }
6090 }
6091 NodeData::Literal(Lit::Str(text)) => {
6092 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6093 if !id.is_empty() {
6094 out.insert(id.to_string());
6095 }
6096 }
6097 }
6098 _ => {}
6099 }
6100 for &c in ast.children(n) {
6101 stack.push(c);
6102 }
6103 }
6104 }
6105 out
6106}
6107
6108fn collect_all_blind_refs(
6137 ast: &JavaAst,
6138 unit: &CompilationUnit,
6139) -> std::collections::HashSet<String> {
6140 let mut out: std::collections::HashSet<String> = Default::default();
6141 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6142 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6143 if !id.is_empty() {
6144 out.insert(id.to_string());
6145 }
6146 }
6147 };
6148 let mut bodies: Vec<JavaId> = Vec::new();
6150 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6151 for root in bodies {
6152 let mut stack = vec![root];
6153 while let Some(n) = stack.pop() {
6154 match ast.data(n) {
6155 NodeData::Raw { text } => split_into(text, &mut out),
6156 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6157 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6158 NodeData::ForEach { ty, .. } => {
6160 collect_ty_idents(ty, &mut out);
6161 }
6162 NodeData::NewArray { ty, .. } => {
6164 collect_ty_idents(ty, &mut out);
6165 }
6166 NodeData::Lambda { params_raw } => {
6170 split_into(params_raw, &mut out);
6171 }
6172 _ => {}
6173 }
6174 for &c in ast.children(n) {
6175 stack.push(c);
6176 }
6177 }
6178 }
6179 for r in &unit.raws {
6183 split_into(r, &mut out);
6184 }
6185 for_each_type(unit, &mut |ty: &TypeDecl| {
6188 collect_mods_idents_all(&ty.mods, &mut out);
6189 for e in &ty.enum_constants {
6190 split_into(e, &mut out);
6191 }
6192 split_into(&ty.ty_params, &mut out);
6193 split_into(&ty.header, &mut out);
6194 for m in &ty.members {
6195 match m {
6196 Member::Method { mods, name, ty_params, params, .. }
6197 | Member::Constructor { mods, name, ty_params, params, .. } => {
6198 collect_mods_idents_all(mods, &mut out);
6199 split_into(ty_params, &mut out);
6200 for p in params {
6201 collect_mods_idents_all(&p.mods, &mut out);
6202 collect_ty_idents(&p.ty, &mut out);
6203 }
6204 if has_bare_methodsource(mods) {
6209 out.insert(name.clone());
6210 }
6211 }
6212 Member::Field { mods, declarators, .. } => {
6213 collect_mods_idents_all(mods, &mut out);
6214 let _ = declarators;
6216 }
6217 Member::Type(t) => {
6218 let _ = t; }
6220 Member::Raw(text) => {
6227 split_into(text, &mut out);
6228 }
6229 _ => {}
6230 }
6231 }
6232 });
6233 out
6234}
6235
6236fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6240 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6241 if !id.is_empty() {
6242 out.insert(id.to_string());
6243 }
6244 }
6245}
6246
6247fn has_bare_methodsource(mods: &str) -> bool {
6254 let hay = mods.as_bytes();
6255 const NEEDLE: &[u8] = b"MethodSource";
6256 let mut i = 0usize;
6257 while let Some(p) = mods[i..].find("MethodSource") {
6258 let at = i + p;
6259 let end = at + NEEDLE.len();
6260 let prev_ok = at == 0
6263 || !(hay[at - 1].is_ascii_alphanumeric()
6264 || hay[at - 1] == b'_'
6265 || hay[at - 1] == b'$');
6266 let next_ident =
6267 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6268 if prev_ok && !next_ident {
6269 let mut j = end;
6270 while j < hay.len() && hay[j].is_ascii_whitespace() {
6271 j += 1;
6272 }
6273 if j >= hay.len() || hay[j] != b'(' {
6274 return true;
6275 }
6276 }
6277 i = end;
6278 }
6279 false
6280}
6281
6282const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6286 "writeObject",
6287 "readObject",
6288 "readObjectNoData",
6289 "writeReplace",
6290 "readResolve",
6291 "validateObject",
6292];
6293
6294fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6295 use std::collections::HashSet;
6296 let mut removed = 0usize;
6297 let anon_names = collect_anon_and_string_names(ast, unit);
6299 let blind = collect_all_blind_refs(ast, unit);
6304 loop {
6305 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6312 let mut veto: HashSet<String> = HashSet::new();
6313 let mut roots: Vec<JavaId> = Vec::new();
6314 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6315 for body in roots {
6316 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6317 }
6318 for_each_type(unit, &mut |ty| {
6319 for m in &ty.members {
6320 if let Member::Method { name, params, .. } = m {
6321 if params.iter().any(|p| p.varargs) {
6322 veto.insert(name.clone());
6323 }
6324 }
6325 }
6326 });
6327 let mut changed = false;
6329 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6330 let before = ty.members.len();
6331 ty.members.retain(|m| {
6332 if let Member::Method { mods, name, params, .. } = m {
6333 let is_private = mods.split_whitespace().any(|w| w == "private");
6334 let annotated = mods.contains('@');
6335 let keep = !is_private
6336 || annotated
6337 || params.iter().any(|p| p.varargs)
6339 || veto.contains(name)
6340 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6344 || anon_names.contains(name)
6345 || blind.contains(name)
6346 || referenced.contains(&(name.clone(), params.len()));
6347 keep
6348 } else {
6349 true
6350 }
6351 });
6352 if ty.members.len() != before {
6353 removed += before - ty.members.len();
6354 changed = true;
6355 }
6356 });
6357 if !changed {
6358 return removed;
6359 }
6360 }
6361}
6362
6363fn strip_ta_prefix(name: &str) -> &str {
6368 if !name.starts_with('<') {
6369 return name;
6370 }
6371 let b = name.as_bytes();
6372 let mut depth = 0i32;
6373 for (i, &c) in b.iter().enumerate() {
6374 match c {
6375 b'<' => depth += 1,
6376 b'>' => {
6377 depth -= 1;
6378 if depth == 0 {
6379 return &name[i + 1..];
6380 }
6381 }
6382 _ => {}
6383 }
6384 }
6385 name }
6387
6388fn collect_call_arity_refs(
6390 ast: &JavaAst,
6391 root: JavaId,
6392 out: &mut std::collections::HashSet<(String, usize)>,
6393 veto: &mut std::collections::HashSet<String>,
6394) {
6395 let mut stack = vec![root];
6396 while let Some(id) = stack.pop() {
6397 match ast.data(id) {
6398 NodeData::Call => {
6399 let ch = ast.children(id).to_vec();
6400 if let Some(&callee) = ch.first() {
6401 let name = match ast.data(callee) {
6402 NodeData::Member { name } | NodeData::VarRef { name } => {
6403 ast.sn(*name).to_string()
6404 }
6405 _ => String::new(),
6406 };
6407 if !name.is_empty() {
6408 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6409 }
6410 }
6411 }
6412 NodeData::MethodRef { name } => {
6413 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6414 veto.insert(strip_ta_prefix(short).to_string());
6415 }
6416 }
6417 _ => {}
6418 }
6419 for &c in ast.children(id) {
6420 stack.push(c);
6421 }
6422 }
6423}
6424
6425fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6427 for ty in &unit.types {
6428 for_each_type_inner(ty, f);
6429 }
6430}
6431fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6432 f(ty);
6433 for m in &ty.members {
6434 if let Member::Type(t) = m {
6435 for_each_type_inner(t, f);
6436 }
6437 }
6438}
6439
6440fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6442 for ty in &mut unit.types {
6443 for_each_type_mut_inner(ty, f);
6444 }
6445}
6446fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6447 f(ty);
6448 for m in &mut ty.members {
6449 if let Member::Type(t) = m {
6450 for_each_type_mut_inner(t, f);
6451 }
6452 }
6453}
6454
6455fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6457 for m in &ty.members {
6458 match m {
6459 Member::Method { body: Some(b), .. }
6460 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6461 Member::Initializer { body, .. } => out.push(*body),
6462 Member::Field { declarators, .. } => {
6463 for d in declarators {
6464 if let Some(init) = d.init {
6465 out.push(init);
6466 }
6467 }
6468 }
6469 Member::Type(t) => collect_member_roots(t, out),
6470 _ => {}
6471 }
6472 }
6473}
6474
6475fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6490 use std::collections::{HashMap, HashSet};
6491 ast.noop_private_methods.clear();
6492 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6493 let mut veto: HashSet<String> = HashSet::new();
6494 for_each_type(unit, &mut |ty: &TypeDecl| {
6495 for m in &ty.members {
6496 if let Member::Method { mods, name, params, body, throws, .. } = m {
6497 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6498 veto.insert(name.clone());
6499 continue;
6500 }
6501 let is_private = mods.split_whitespace().any(|w| w == "private");
6502 let empty = match body {
6503 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6504 None => false,
6505 };
6506 let no_throws = throws.is_empty();
6510 let cand = NoopCandidate {
6511 params: params.iter().map(|p| p.ty.clone()).collect(),
6512 noop: is_private && empty && no_throws,
6513 };
6514 by_name
6515 .entry(name.clone())
6516 .or_default()
6517 .entry(params.len())
6518 .or_default()
6519 .push(cand);
6520 }
6521 }
6522 });
6523 let blind = collect_all_blind_refs(ast, unit);
6526 let mut roots: Vec<JavaId> = Vec::new();
6529 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6530 for root in roots {
6531 let mut stack = vec![root];
6532 while let Some(n) = stack.pop() {
6533 if let NodeData::MethodRef { name } = ast.data(n) {
6534 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6535 veto.insert(strip_ta_prefix(short).to_string());
6536 }
6537 }
6538 for &c in ast.children(n) {
6539 stack.push(c);
6540 }
6541 }
6542 }
6543 for (name, arities) in by_name {
6544 if veto.contains(&name) || blind.contains(&name) {
6545 continue;
6546 }
6547 ast.noop_private_methods.insert(name, arities);
6548 }
6549}
6550
6551fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6556 use std::collections::HashSet;
6557 let mut idents: HashSet<String> = HashSet::new();
6558 let mut roots: Vec<JavaId> = Vec::new();
6560 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6561 for root in roots {
6562 let mut stack = vec![root];
6563 while let Some(n) = stack.pop() {
6564 match ast.data(n) {
6565 NodeData::VarDecl { name, ty, .. } => {
6566 idents.insert(name.clone());
6567 collect_ty_idents(ty, &mut idents);
6568 }
6569 NodeData::VarRef { name } => {
6570 let n = ast.sn(*name);
6574 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6575 if !id.is_empty() {
6576 idents.insert(id.to_string());
6577 }
6578 }
6579 }
6580 NodeData::Member { name } => {
6581 idents.insert(ast.sn(*name).to_string());
6582 }
6583 NodeData::MethodRef { name } => {
6584 let n = ast.sn(*name);
6585 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6586 if !id.is_empty() {
6587 idents.insert(id.to_string());
6588 }
6589 }
6590 }
6591 NodeData::Label { name } => {
6592 idents.insert(name.clone());
6593 }
6594 NodeData::New { ty, anon_raw, .. } => {
6595 collect_ty_idents(ty, &mut idents);
6596 if let Some(raw) = anon_raw {
6599 for id in
6600 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6601 {
6602 if !id.is_empty() {
6603 idents.insert(id.to_string());
6604 }
6605 }
6606 }
6607 }
6608 NodeData::Cast { ty } => {
6609 collect_ty_idents(ty, &mut idents);
6610 }
6611 NodeData::InstanceOf { ty, .. } => {
6612 collect_ty_idents(ty, &mut idents);
6613 }
6614 NodeData::Catch { ty_raw, .. } => {
6615 for id in
6617 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6618 {
6619 if !id.is_empty() {
6620 idents.insert(id.to_string());
6621 }
6622 }
6623 }
6624 NodeData::Raw { text } => {
6625 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6626 if !id.is_empty() {
6627 idents.insert(id.to_string());
6628 }
6629 }
6630 }
6631 _ => {}
6632 }
6633 for &c in ast.children(n) {
6634 stack.push(c);
6635 }
6636 }
6637 }
6638 for name in collect_all_blind_refs(ast, unit) {
6642 idents.insert(name);
6643 }
6644 for r in &unit.raws {
6647 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6648 if !id.is_empty() {
6649 idents.insert(id.to_string());
6650 }
6651 }
6652 }
6653 for_each_type(unit, &mut |ty| {
6656 collect_mods_idents(&ty.mods, &mut idents);
6657 idents.insert(ty.name.clone());
6658 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6659 if !id.is_empty() {
6660 idents.insert(id.to_string());
6661 }
6662 }
6663 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6667 if !id.is_empty() {
6668 idents.insert(id.to_string());
6669 }
6670 }
6671 for e in ty.extends.iter().chain(ty.implements.iter()) {
6672 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6673 if !id.is_empty() {
6674 idents.insert(id.to_string());
6675 }
6676 }
6677 }
6678 for m in &ty.members {
6679 match m {
6680 Member::Field { mods, ty, declarators } => {
6681 collect_mods_idents(mods, &mut idents);
6682 collect_ty_idents(ty, &mut idents);
6683 for d in declarators {
6684 idents.insert(d.name.clone());
6685 }
6686 }
6687 Member::Method { mods, ret, name, params, throws, .. } => {
6688 collect_mods_idents(mods, &mut idents);
6689 collect_ty_idents(ret, &mut idents);
6690 idents.insert(name.clone());
6691 for p in params {
6692 collect_mods_idents(&p.mods, &mut idents);
6693 collect_ty_idents(&p.ty, &mut idents);
6694 idents.insert(p.name.clone());
6695 }
6696 for t in throws {
6697 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6698 if !id.is_empty() {
6699 idents.insert(id.to_string());
6700 }
6701 }
6702 }
6703 }
6704 Member::Constructor { mods, name, params, throws, .. } => {
6705 collect_mods_idents(mods, &mut idents);
6706 idents.insert(name.clone());
6707 for p in params {
6708 collect_mods_idents(&p.mods, &mut idents);
6709 collect_ty_idents(&p.ty, &mut idents);
6710 idents.insert(p.name.clone());
6711 }
6712 for t in throws {
6713 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6714 if !id.is_empty() {
6715 idents.insert(id.to_string());
6716 }
6717 }
6718 }
6719 }
6720 Member::Type(t) => {
6721 let _ = t;
6723 }
6724 _ => {}
6725 }
6726 }
6727 });
6728 let before = unit.imports.len();
6730 unit.imports.retain(|imp| {
6731 if imp.contains('*') {
6732 return true; }
6734 let path = imp.trim_start_matches("static ").trim();
6736 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6737 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6738 return true; }
6740 idents.contains(simple)
6741 });
6742 before - unit.imports.len()
6743}
6744
6745fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6746 match t {
6747 JType::Ref(r) => {
6748 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6749 if !id.is_empty() {
6750 out.insert(id.to_string());
6751 }
6752 }
6753 }
6754 JType::Array(inner) => collect_ty_idents(inner, out),
6755 _ => {}
6756 }
6757}
6758
6759fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6760 if mods.contains('@') {
6762 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6763 if !id.is_empty() {
6764 out.insert(id.to_string());
6765 }
6766 }
6767 }
6768}
6769
6770fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6776 use std::collections::HashSet;
6777 let mut removed = 0usize;
6778 let anon_names = collect_anon_and_string_names(ast, unit);
6780 let blind = collect_all_blind_refs(ast, unit);
6783 let mut referenced: HashSet<String> = HashSet::new();
6785 let mut bodies: Vec<JavaId> = Vec::new();
6786 for_each_type(unit, &mut |ty: &TypeDecl| {
6787 for m in &ty.members {
6788 match m {
6789 Member::Method { body: Some(b), .. }
6790 | Member::Constructor { body: Some(b), .. }
6791 | Member::Initializer { body: b, .. } => bodies.push(*b),
6792 Member::Field { declarators, .. } => {
6793 for d in declarators {
6794 if let Some(init) = d.init {
6795 bodies.push(init);
6796 }
6797 }
6798 }
6799 _ => {}
6800 }
6801 }
6802 });
6803 for root in bodies {
6804 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
6806 while let Some((n, is_callee)) = stack.pop() {
6807 match ast.kind(n) {
6808 NodeKind::VarRef if !is_callee => {
6809 if let Some(nm) = ast.var_name(n) {
6810 referenced.insert(nm.to_string());
6811 }
6812 }
6813 NodeKind::Member => {
6814 if let NodeData::Member { name: sym } = ast.data(n) {
6815 referenced.insert(ast.sn(*sym).to_string());
6816 }
6817 }
6818 _ => {}
6819 }
6820 if ast.kind(n) == NodeKind::Call {
6821 let ch = ast.children(n).to_vec();
6822 if let Some(&first) = ch.first() {
6823 stack.push((first, true));
6824 }
6825 for &c in &ch[1..] {
6826 stack.push((c, false));
6827 }
6828 continue;
6829 }
6830 for &c in ast.children(n) {
6831 stack.push((c, false));
6832 }
6833 }
6834 }
6835 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6841 for m in ty.members.iter_mut() {
6842 if let Member::Field { mods, declarators, .. } = m {
6843 let is_private = mods.split_whitespace().any(|w| w == "private");
6844 let annotated = mods.contains('@');
6845 if !is_private || annotated {
6846 continue;
6847 }
6848 let dead: Vec<bool> = declarators
6849 .iter()
6850 .map(|d| {
6851 let reflective =
6852 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
6853 let init_ok =
6854 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
6855 !reflective
6856 && init_ok
6857 && !referenced.contains(&d.name)
6858 && !anon_names.contains(&d.name)
6859 && !blind.contains(&d.name)
6860 })
6861 .collect();
6862 let before = declarators.len();
6863 let mut idx = 0usize;
6864 declarators.retain(|_| {
6865 let keep = !dead[idx];
6866 idx += 1;
6867 keep
6868 });
6869 removed += before - declarators.len();
6870 }
6871 }
6872 let before = ty.members.len();
6873 ty.members
6874 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
6875 removed += before - ty.members.len();
6876 });
6877 removed
6878}
6879
6880fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
6881 for m in &mut ty.members {
6882 simplify_member(ast, m, cfg, total);
6883 }
6884}
6885
6886fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
6887 let params: Vec<(String, JType)> = match m {
6889 Member::Method { params, .. } | Member::Constructor { params, .. } => params
6890 .iter()
6891 .map(|p| (p.name.clone(), p.ty.clone()))
6892 .collect(),
6893 _ => Vec::new(),
6894 };
6895 ast.set_param_scope(¶ms);
6896 match m {
6897 Member::Method { body: Some(b), .. }
6898 | Member::Constructor { body: Some(b), .. } => {
6899 let r = simplify(ast, *b, cfg);
6900 merge_report(total, r);
6901 }
6902 Member::Initializer { body, .. } => {
6903 let r = simplify(ast, *body, cfg);
6904 merge_report(total, r);
6905 }
6906 Member::Field { declarators, .. } => {
6907 for d in declarators.iter_mut() {
6908 if let Some(init) = d.init {
6909 let r = simplify(ast, init, cfg);
6910 merge_report(total, r);
6911 let rules = default_java_rules();
6915 let mut cur: JavaId = init;
6916 for _ in 0..8 {
6917 match cure_engine::fold_root(ast, cur, &rules, cfg) {
6918 Some((new_root, name)) => {
6919 d.init = Some(new_root);
6920 cur = new_root;
6921 *total.by_rule.entry(name).or_insert(0) += 1;
6922 total.edits += 1;
6923 let r = simplify(ast, new_root, cfg);
6925 merge_report(total, r);
6926 }
6927 None => break,
6928 }
6929 }
6930 }
6931 }
6932 }
6933 Member::Type(t) => simplify_type(ast, t, cfg, total),
6934 Member::Raw(_) => {}
6935 _ => {}
6936 }
6937}
6938
6939fn merge_report(total: &mut Report, r: Report) {
6940 total.edits += r.edits;
6941 total.iterations += r.iterations;
6942 for (k, v) in r.by_rule {
6943 *total.by_rule.entry(k).or_insert(0) += v;
6944 }
6945}
6946
6947pub fn default_config() -> Config {
6949 Config::default()
6950}