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 if let NodeData::Cast { ty: inner_cast_ty } = lang.data(inner) {
66 let is_obj = matches!(inner_cast_ty,
67 JType::Ref(r) if r == "java.lang.Object" || r == "Object");
68 if is_obj {
69 let operand = lang.children(inner).first().copied()?;
70 let operand_is_object = match lang.data(operand) {
74 NodeData::Literal(_) | NodeData::This => true,
75 NodeData::VarRef { .. } => {
76 matches!(lang.var_type(operand),
77 Some(JType::Ref(r)) if r == "java.lang.Object" || r == "Object")
78 }
79 _ => false,
80 };
81 if operand_is_object {
82 return Some(Edit::Replace {
83 target: inner,
84 with: operand,
85 });
86 }
87 }
88 }
89 None
90 }
91}
92
93pub struct SelfCompare;
96
97impl Rule<JavaAst> for SelfCompare {
98 fn name(&self) -> &'static str {
99 "self_compare"
100 }
101 fn kinds(&self) -> &'static [NodeKind] {
102 &[NodeKind::Binary]
103 }
104 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
105 use cure_engine::kind::BinOp::{Eq, Ne};
106 let lang = ctx.lang;
107 let NodeData::Binary { op } = lang.data(id) else {
108 return None;
109 };
110 if !matches!(op, Eq | Ne) {
111 return None;
112 }
113 let ch = lang.children(id);
114 let (NodeData::VarRef { name: n1 }, NodeData::VarRef { name: n2 }) =
115 (lang.data(ch[0]), lang.data(ch[1]))
116 else {
117 return None;
118 };
119 if n1 != n2 || !lang.is_exact_int(ch[0]) {
120 return None;
121 }
122 let val = lang.build_bool(*op == Eq);
123 Some(Edit::Replace {
124 target: id,
125 with: val,
126 })
127 }
128}
129
130
131pub struct StringBuilderFold;
142
143impl Rule<JavaAst> for StringBuilderFold {
144 fn name(&self) -> &'static str {
145 "string_builder_fold"
146 }
147 fn kinds(&self) -> &'static [NodeKind] {
148 &[NodeKind::Call]
149 }
150 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
151 let root = ctx.root();
152 let lang = ctx.lang;
153 if lang.kind(id) != NodeKind::Call {
155 return None;
156 }
157 let callee = lang.children(id)[0];
158 if let NodeData::Member { name } = lang.data(callee) {
159 if lang.sn(*name) != "toString" || !lang.children(id)[1..].is_empty() {
160 return None;
161 }
162 } else {
163 return None;
164 }
165 let mut node = lang.children(callee)[0]; let mut parts: Vec<JavaId> = Vec::new();
167 loop {
169 match lang.data(node) {
170 NodeData::Call => {
171 let c = lang.children(node);
172 if let NodeData::Member { name } = lang.data(c[0]) {
173 if lang.sn(*name) == "append" && c.len() == 2 {
174 parts.push(c[1]);
175 node = lang.children(c[0])[0];
176 continue;
177 }
178 }
179 return None;
180 }
181 NodeData::New { ty, .. } => {
182 let is_sb = !lang.unit_shadows_lang
183 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
184 if !is_sb {
185 return None;
186 }
187 let args = lang.children(node).to_vec();
188 match args.len() {
189 0 => {} 1 => {
191 let a = args[0];
196 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
197 || lang
198 .var_type(a)
199 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
200 if stringy {
201 parts.push(a);
202 } else if !matches!(
203 lang.literal(a),
204 Some(LitRef::Int(_) | LitRef::Long(_))
205 ) {
206 return None;
207 }
208 }
209 _ => return None,
210 }
211 break;
212 }
213 _ => return None,
214 }
215 }
216 if parts.iter().all(|&p| lang.literal(p).is_some())
220 && lang.has_string_identity_compare(root)
221 {
222 return None;
223 }
224 if parts.is_empty() {
225 let empty = lang.build_str("");
227 return Some(Edit::Replace {
228 target: id,
229 with: empty,
230 });
231 }
232 parts.reverse();
234 let first = parts[0];
235 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
236 || lang
237 .var_type(first)
238 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
239 let mut acc = if stringy_first {
240 first
241 } else {
242 let empty = lang.build_str("");
243 lang.build_bin(BinOp::Add, empty, first)
244 };
245 for &p in &parts[1..] {
246 acc = lang.build_bin(BinOp::Add, acc, p);
247 }
248 Some(Edit::Replace {
249 target: id,
250 with: acc,
251 })
252 }
253}
254
255pub struct BoxUnboxChain;
263
264const BOXERS: &[(&str, &str)] = &[
265 ("Integer", "intValue"),
266 ("java.lang.Integer", "intValue"),
267 ("Long", "longValue"),
268 ("java.lang.Long", "longValue"),
269 ("Boolean", "booleanValue"),
270 ("java.lang.Boolean", "booleanValue"),
271 ("Double", "doubleValue"),
272 ("java.lang.Double", "doubleValue"),
273 ("Float", "floatValue"),
274 ("java.lang.Float", "floatValue"),
275 ("Character", "charValue"),
276 ("java.lang.Character", "charValue"),
277 ("Short", "shortValue"),
278 ("java.lang.Short", "shortValue"),
279 ("Byte", "byteValue"),
280 ("java.lang.Byte", "byteValue"),
281];
282
283impl Rule<JavaAst> for BoxUnboxChain {
284 fn name(&self) -> &'static str {
285 "box_unbox_chain"
286 }
287 fn kinds(&self) -> &'static [NodeKind] {
288 &[NodeKind::Call]
289 }
290 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
291 let at_stmt_pos = ctx
293 .parent(id)
294 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
295 .unwrap_or(false);
296 let lang = ctx.lang;
297 if lang.kind(id) != NodeKind::Call {
299 return None;
300 }
301 let callee = lang.children(id)[0];
302 let NodeData::Member { name: unbox } = lang.data(callee) else {
303 return None;
304 };
305 if !lang.children(id)[1..].is_empty() {
306 return None;
307 }
308 let inner = lang.children(callee)[0];
309 if lang.kind(inner) != NodeKind::Call {
310 return None;
311 }
312 let ich = lang.children(inner).to_vec();
314 let (box_name, arg): (String, JavaId) = if let NodeData::New { ty, .. } = lang.data(inner)
315 {
316 let n = match ty {
317 JType::Ref(r) => r.clone(),
318 _ => return None,
319 };
320 if ich.len() != 1 {
321 return None;
322 }
323 (n, ich[0])
324 } else {
325 let icallee = ich.first().copied()?;
327 if let NodeData::Member { name } = lang.data(icallee) {
328 let recv = lang.children(icallee)[0];
330 if lang.sn(*name) != "valueOf" {
331 return None;
332 }
333 {
334 let n = lang.var_name(recv)?;
335 (n.to_string(), *ich.get(1)?)
336 }
337 } else if let NodeData::VarRef { name } = lang.data(icallee) {
338 let _ = name;
341 return None;
342 } else {
343 return None;
344 }
345 };
346 let unbox_name = *unbox;
347 let expected = BOXERS
348 .iter()
349 .find(|(b, _)| *b == box_name.as_str())
350 .map(|(_, u)| *u)?;
351 if expected != lang.sn(unbox_name) {
352 return None;
353 }
354 let var_is = |t: fn(&JType) -> bool| lang.var_type(arg).is_some_and(t);
357 let lit_is_num = lang.literal(arg).is_some_and(|l| {
358 matches!(
359 l,
360 LitRef::Int(_) | LitRef::Long(_) | LitRef::Float(_) | LitRef::Double(_)
361 )
362 });
363 let ok = match box_name.trim_start_matches("java.lang.") {
364 "Integer" | "Long" | "Short" | "Byte" => lang.is_exact_int(arg),
365 "Double" | "Float" => {
366 lit_is_num
367 || var_is(|t| matches!(t, JType::Double | JType::Float | JType::Int | JType::Long))
368 }
369 "Boolean" => lang.is_bool(arg),
370 "Character" => {
371 matches!(lang.literal(arg), Some(LitRef::Char(_))) || var_is(|t| *t == JType::Char)
372 }
373 _ => false,
374 };
375 if !ok {
376 return None;
377 }
378 if at_stmt_pos {
382 let stmt_expr = match lang.kind(arg) {
383 NodeKind::Call | NodeKind::New | NodeKind::Assign => true,
384 NodeKind::Unary => lang.un_op(arg).is_some_and(|op| op.is_incdec()),
385 _ => false,
386 };
387 if !stmt_expr {
388 return None;
389 }
390 }
391 Some(Edit::Replace {
392 target: id,
393 with: arg,
394 })
395 }
396}
397
398fn iterable_type_ok(ast: &JavaAst, iterable: JavaId) -> bool {
411 let Some(ty) = ast.var_type(iterable) else {
412 return true;
413 };
414 match ty {
415 JType::Array(_) | JType::Var => true,
416 JType::Ref(n) => {
417 let base = n.split('<').next().unwrap_or(n);
418 let base = base.rsplit('.').next().unwrap_or(base);
419 matches!(
420 base,
421 "Iterable"
422 | "Collection"
423 | "List"
424 | "Set"
425 | "Queue"
426 | "Deque"
427 | "SortedSet"
428 | "NavigableSet"
429 | "BlockingQueue"
430 | "BlockingDeque"
431 | "TransferQueue"
432 | "ArrayList"
433 | "LinkedList"
434 | "Vector"
435 | "Stack"
436 | "HashSet"
437 | "LinkedHashSet"
438 | "TreeSet"
439 | "EnumSet"
440 | "ArrayDeque"
441 | "PriorityQueue"
442 | "CopyOnWriteArrayList"
443 | "CopyOnWriteArraySet"
444 | "ConcurrentLinkedQueue"
445 | "ConcurrentLinkedDeque"
446 | "LinkedBlockingQueue"
447 | "LinkedBlockingDeque"
448 | "ArrayBlockingQueue"
449 | "SynchronousQueue"
450 | "PriorityBlockingQueue"
451 | "DelayQueue"
452 | "ConcurrentSkipListSet"
453 ) || base.ends_with("Iterable")
454 }
455 _ => false,
456 }
457}
458
459pub struct IteratorToForEach;
460
461impl Rule<JavaAst> for IteratorToForEach {
462 fn name(&self) -> &'static str {
463 "iterator_to_for_each"
464 }
465 fn kinds(&self) -> &'static [NodeKind] {
466 &[NodeKind::For]
467 }
468 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
469 let lang = ctx.lang;
470 let NodeData::For {
471 inits,
472 has_cond,
473 steps,
474 } = lang.data(id)
475 else {
476 return None;
477 };
478 if *inits != 1 || !*has_cond || *steps != 0 {
479 return None;
480 }
481 let ch = lang.children(id).to_vec();
482 let (init, cond, body) = (ch[0], ch[1], ch[2]);
484
485 let NodeData::VarDecl { name: it_name, .. } = lang.data(init) else {
487 return None;
488 };
489 let it_name = it_name.clone();
490 let init_call = lang.children(init).first().copied()?;
491 if lang.kind(init_call) != NodeKind::Call {
492 return None;
493 }
494 let ic = lang.children(init_call).to_vec();
495 let icallee = *ic.first()?;
496 let NodeData::Member { name: m0 } = lang.data(icallee) else {
497 return None;
498 };
499 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
500 return None;
501 }
502 let iterable = lang.children(icallee)[0];
503 if subtree_has_var(&*lang, iterable, &it_name) {
505 return None;
506 }
507 if !iterable_type_ok(lang, iterable) {
511 return None;
512 }
513
514 if lang.kind(cond) != NodeKind::Call {
516 return None;
517 }
518 let cc = lang.children(cond).to_vec();
519 let ccallee = *cc.first()?;
520 let NodeData::Member { name: m1 } = lang.data(ccallee) else {
521 return None;
522 };
523 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
524 return None;
525 }
526 if lang.var_name(lang.children(ccallee)[0]) != Some(it_name.as_str()) {
527 return None;
528 }
529
530 if lang.kind(body) != NodeKind::Block {
532 return None;
533 }
534 let bch = lang.children(body).to_vec();
535 let first = *bch.first()?;
536 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
537 return None;
538 };
539 let (e_name, ty) = (e_name.clone(), ty.clone());
540 let next_call = lang.children(first).first().copied()?;
541 if lang.kind(next_call) != NodeKind::Call {
542 return None;
543 }
544 let nc = lang.children(next_call).to_vec();
545 let ncallee = *nc.first()?;
546 let NodeData::Member { name: m2 } = lang.data(ncallee) else {
547 return None;
548 };
549 if lang.sn(*m2) != "next" || nc.len() != 1 {
550 return None;
551 }
552 if lang.var_name(lang.children(ncallee)[0]) != Some(it_name.as_str()) {
553 return None;
554 }
555 let rest = bch[1..].to_vec();
557 for &r in &rest {
558 if subtree_has_var(&*lang, r, &it_name) {
559 return None;
560 }
561 }
562 let new_body = lang.build_block(rest);
563 let with = lang.for_each(&e_name, ty, iterable, new_body);
564 Some(Edit::Replace {
565 target: id,
566 with,
567 })
568 }
569}
570
571fn unwrap_paren_cast(lang: &JavaAst, mut n: JavaId) -> JavaId {
573 loop {
574 match lang.kind(n) {
575 NodeKind::Paren | NodeKind::Cast => {
576 let ch = lang.children(n);
577 if ch.is_empty() {
578 return n;
579 }
580 n = ch[0];
581 }
582 _ => return n,
583 }
584 }
585}
586
587fn type_inner_raw(ty: &JType) -> Option<&str> {
591 let JType::Ref(n) = ty else {
592 return None;
593 };
594 let lt = n.find('<')?;
595 let gt = n.rfind('>')?;
596 if gt <= lt {
597 None
598 } else {
599 Some(n[lt + 1..gt].trim())
600 }
601}
602
603fn generic_elem_ty(ty: &JType) -> Option<JType> {
604 let name = match ty {
605 JType::Ref(n) => n.as_str(),
606 _ => return None,
607 };
608 let lt = name.find('<')?;
609 let gt = name.rfind('>')?;
610 if gt <= lt {
611 return None;
612 }
613 let inner = name[lt + 1..gt].trim();
614 if inner.is_empty() || inner.contains('<') {
615 return None; }
617 Some(JType::Ref(inner.to_string()))
618}
619
620fn statically_iterable(ty: &JType) -> bool {
627 match ty {
628 JType::Array(_) => true,
629 JType::Ref(name) => {
630 let base = name.split('<').next().unwrap_or(name).trim();
631 let last = base.rsplit('.').next().unwrap_or(base);
632 matches!(
633 last,
634 "Iterable"
635 | "Collection"
636 | "List"
637 | "ArrayList"
638 | "LinkedList"
639 | "Vector"
640 | "Stack"
641 | "AbstractList"
642 | "AbstractCollection"
643 | "Set"
644 | "HashSet"
645 | "LinkedHashSet"
646 | "TreeSet"
647 | "SortedSet"
648 | "NavigableSet"
649 | "AbstractSet"
650 | "Queue"
651 | "Deque"
652 | "ArrayDeque"
653 | "PriorityQueue"
654 | "BlockingQueue"
655 | "BlockingDeque"
656 | "ConcurrentLinkedQueue"
657 | "ConcurrentLinkedDeque"
658 | "ConcurrentSkipListSet"
659 | "CopyOnWriteArrayList"
660 | "CopyOnWriteArraySet"
661 | "EnumSet"
662 | "LinkedBlockingQueue"
663 | "LinkedBlockingDeque"
664 | "ArrayBlockingQueue"
665 | "PriorityBlockingQueue"
666 | "DelayQueue"
667 | "SynchronousQueue"
668 | "LinkedTransferQueue"
669 | "TransferQueue"
670 )
671 }
672 _ => false,
673 }
674}
675
676fn map_view_synthetic_ty(lang: &JavaAst, call: JavaId) -> Option<JType> {
684 if lang.kind(call) != NodeKind::Call {
685 return None;
686 }
687 let ch = lang.children(call).to_vec();
688 if ch.len() != 1 {
689 return None; }
691 let callee = *ch.first()?;
692 let NodeData::Member { name } = lang.data(callee) else { return None };
693 let view = lang.sn(*name);
694 if !matches!(view, "keySet" | "values") {
695 return None;
696 }
697 let recv = lang.children(callee).first().copied()?;
698 let NodeData::VarRef { name } = lang.data(recv) else { return None };
699 let recv_ty = lang
703 .var_type(recv)
704 .cloned()
705 .or_else(|| lang.field_types.get(lang.sn(*name)).cloned())?;
706 let JType::Ref(n) = recv_ty else { return None };
707 let lt = n.find('<')?;
708 let gt = n.rfind('>')?;
709 if gt <= lt {
710 return None; }
712 let args = n[lt + 1..gt].trim();
714 let depth = args.chars().fold(0i32, |d, c| match c {
715 '<' => d + 1,
716 '>' => d - 1,
717 _ => d,
718 });
719 let top = args.find(',').filter(|_| depth == 0)?;
720 let k = args[..top].trim();
721 let v = args[top + 1..].trim();
722 let base = n[..lt].trim();
723 let last = base.rsplit('.').next().unwrap_or(base);
724 let map_family = matches!(
725 last,
726 "Map"
727 | "SortedMap"
728 | "NavigableMap"
729 | "HashMap"
730 | "LinkedHashMap"
731 | "TreeMap"
732 | "Hashtable"
733 | "Properties"
734 | "ConcurrentMap"
735 | "ConcurrentHashMap"
736 | "ConcurrentSkipListMap"
737 | "ConcurrentNavigableMap"
738 | "EnumMap"
739 | "WeakHashMap"
740 | "IdentityHashMap"
741 | "AbstractMap"
742 );
743 if !map_family || k.is_empty() || v.is_empty() {
744 return None;
745 }
746 match view {
747 "keySet" => Some(JType::Ref(format!("java.util.Set<{k}>"))),
748 _ => Some(JType::Ref(format!("java.util.Collection<{v}>"))),
749 }
750}
751
752fn for_each_elem_ok(ty: &JType) -> bool {
756 match ty {
757 JType::Ref(n) => !n.trim_start().starts_with('?'),
758 _ => true,
759 }
760}
761
762fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
764 let mut stack = vec![root];
765 while let Some(n) = stack.pop() {
766 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
767 out.push(n);
768 }
769 for &c in lang.children(n) {
770 stack.push(c);
771 }
772 }
773}
774
775fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
777 let mut stack = vec![root];
778 while let Some(n) = stack.pop() {
779 for &c in lang.children(n) {
780 if c == target {
781 return n;
782 }
783 stack.push(c);
784 }
785 }
786 root
787}
788
789fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
790 let mut stack = vec![id];
791 while let Some(n) = stack.pop() {
792 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
793 return true;
794 }
795 for &c in lang.children(n) {
796 stack.push(c);
797 }
798 }
799 false
800}
801
802fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
804 let mut stack = vec![id];
805 while let Some(n) = stack.pop() {
806 match lang.data(n) {
807 NodeData::Catch { name: cn, .. } if cn == name => return true,
808 NodeData::ForEach { name: vn, .. } if vn == name => return true,
809 NodeData::Lambda { params_raw } => {
812 for part in
813 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
814 {
815 if part == name {
816 return true;
817 }
818 }
819 }
820 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
822 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
825 _ => {}
826 }
827 for &c in lang.children(n) {
828 stack.push(c);
829 }
830 }
831 false
832}
833
834pub struct NewStringFold;
840
841impl Rule<JavaAst> for NewStringFold {
842 fn name(&self) -> &'static str {
843 "new_string_fold"
844 }
845 fn kinds(&self) -> &'static [NodeKind] {
846 &[NodeKind::New]
847 }
848 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
849 let root = ctx.root();
850 let lang = ctx.lang;
851 let NodeData::New { ty, .. } = lang.data(id) else {
852 return None;
853 };
854 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
855 return None;
856 }
857 if lang.unit_shadows_lang {
861 return None;
862 }
863 let _ = root;
866 if lang.unit_string_identity {
867 return None;
868 }
869 let args = lang.children(id).to_vec();
870 match args.len() {
871 0 => {
872 let empty = lang.build_str("");
873 Some(Edit::Replace {
874 target: id,
875 with: empty,
876 })
877 }
878 1 => {
879 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
880 Some(Edit::Replace {
881 target: id,
882 with: args[0],
883 })
884 } else {
885 None
886 }
887 }
888 _ => None,
889 }
890 }
891}
892
893
894pub struct NewStringCharArrayFold;
900
901impl Rule<JavaAst> for NewStringCharArrayFold {
902 fn name(&self) -> &'static str {
903 "new_string_char_array_fold"
904 }
905 fn kinds(&self) -> &'static [NodeKind] {
906 &[NodeKind::New]
907 }
908 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
909 let root = ctx.root();
910 let lang = ctx.lang;
911 let NodeData::New { ty, .. } = lang.data(id) else {
912 return None;
913 };
914 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
915 return None;
916 }
917 let _ = root;
918 if lang.unit_shadows_lang || lang.unit_string_identity {
919 return None;
920 }
921 let args = lang.children(id).to_vec();
923 if args.len() != 1 {
924 return None;
925 }
926 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
927 return None;
928 };
929 if *sized != 0 {
930 return None;
931 }
932 let arr_children = lang.children(args[0]).to_vec();
933 let lit = *arr_children.first()?;
934 if lang.kind(lit) != NodeKind::ArrayLit {
935 return None;
936 }
937 let mut text = String::new();
938 for &c in lang.children(lit) {
939 match lang.literal(c) {
940 Some(LitRef::Char(ch)) => text.push(ch),
941 _ => return None, }
943 }
944 let with = lang.build_str(&text);
945 Some(Edit::Replace {
946 target: id,
947 with,
948 })
949 }
950}
951
952pub struct EmptyFinallyStrip;
960
961impl Rule<JavaAst> for EmptyFinallyStrip {
962 fn name(&self) -> &'static str {
963 "empty_finally_strip"
964 }
965 fn kinds(&self) -> &'static [NodeKind] {
966 &[NodeKind::Try]
967 }
968 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
969 let lang = ctx.lang;
970 if lang.kind(id) != NodeKind::Try {
971 return None;
972 }
973 let ch = lang.children(id).to_vec();
974 let is_empty_block = |n: JavaId| -> bool {
981 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
982 };
983 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
984 if ch.len() < try_idx + 2 {
985 return None; }
987 let last = *ch.last()?;
989 if lang.kind(last) == NodeKind::Catch {
990 return None;
991 }
992 if !is_empty_block(last) {
993 return None;
994 }
995 Some(Edit::Splice {
998 node: id,
999 index: ch.len() - 1,
1000 remove: 1,
1001 insert: Vec::new(),
1002 })
1003 }
1004}
1005
1006fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
1052 if lang.kind(a) != lang.kind(b) {
1053 let (ia, ib) = (lang.kind(a), lang.kind(b));
1055 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
1056 if k == NodeKind::ExprStmt {
1057 lang.children(x).first().copied()
1058 } else {
1059 Some(x)
1060 }
1061 };
1062 return match (unwrap(ia, a), unwrap(ib, b)) {
1063 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
1064 _ => false,
1065 };
1066 }
1067 print_equiv_inner(lang, a, b)
1068}
1069
1070fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
1071 if lang.kind(a) != lang.kind(b) {
1072 return false;
1073 }
1074 match (lang.literal(a), lang.literal(b)) {
1076 (Some(x), Some(y)) => {
1077 if x != y {
1078 return false;
1079 }
1080 }
1081 (None, None) => {}
1082 _ => return false,
1083 }
1084 let (ca, cb) = (lang.children(a), lang.children(b));
1085 if ca.len() != cb.len() {
1086 return false;
1087 }
1088 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
1089}
1090
1091fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
1095 for &root in rest {
1096 let mut stack = vec![root];
1097 while let Some(n) = stack.pop() {
1098 match lang.kind(n) {
1099 NodeKind::Assign => {
1100 let t = lang.children(n)[0];
1101 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
1102 return true;
1103 }
1104 }
1105 NodeKind::Unary => {
1106 if let NodeData::Unary { op } = lang.data(n) {
1107 if op.is_incdec() {
1108 let t = lang.children(n)[0];
1109 if lang.kind(t) == NodeKind::VarRef
1110 && lang.var_name(t) == Some(name)
1111 {
1112 return true;
1113 }
1114 }
1115 }
1116 }
1117 _ => {}
1118 }
1119 for &c in lang.children(n) {
1120 stack.push(c);
1121 }
1122 }
1123 }
1124 false
1125}
1126
1127pub struct StaticExec;
1128
1129impl Rule<JavaAst> for StaticExec {
1130 fn name(&self) -> &'static str {
1131 "static_exec"
1132 }
1133 fn kinds(&self) -> &'static [NodeKind] {
1134 &[NodeKind::Block]
1135 }
1136 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1137 if ctx.parent(id).is_some() {
1140 return None;
1141 }
1142 let lang = ctx.lang;
1143 if lang.kind(id) != NodeKind::Block {
1144 return None;
1145 }
1146 let stmts = lang.children(id).to_vec();
1147 if stmts.len() < 3 {
1148 return None; }
1150 let mut ex = vexec::Exec::new(lang);
1156 let run = ex.run_prefix(&stmts).ok()?;
1157 if std::env::var("CURE_DBG_P1").is_ok() {
1158 eprintln!(
1159 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
1160 run.cuts.len(),
1161 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
1162 ex.budget_exhausted(),
1163 ex.effect_log().len(),
1164 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
1165 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
1166 ex.effect_log().iter().map(|e| match e {
1167 vexec::EffectEvent::FieldWrite(_) => "FW",
1168 vexec::EffectEvent::ClassLoad { .. } => "CL",
1169 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
1170 }).collect::<Vec<_>>()
1171 );
1172 }
1173 if ex.budget_exhausted() {
1175 return None;
1176 }
1177 let root_idx = run.root_cut_index();
1178 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
1180 if total_completed < 3 {
1181 return None;
1182 }
1183 let true_rest = run.true_rest();
1187 if !true_rest.is_empty() {
1188 for ev in ex.effect_log().iter() {
1189 if let vexec::EffectEvent::FieldWrite(k) = ev {
1190 if let Some(name) = lang.name_of_key(*k) {
1191 if lang.final_fields.contains(&name)
1196 && rest_reassigns_field(lang, &true_rest, &name)
1197 {
1198 return None;
1199 }
1200 }
1201 }
1202 }
1203 }
1204 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1206 let field_finals: Vec<(u32, vexec::VVal)> =
1207 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1208 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1209 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1212 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1213 _ => None,
1214 };
1215 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1218 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1224 for (j, cut) in run.cuts.iter().enumerate() {
1225 let range_end = if j == 0 {
1226 decl_order.len()
1227 } else {
1228 run.cuts[j - 1].decl_start
1229 };
1230 let rest_j = run.rest_for_cut(j);
1231 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1232 let mut live_names: std::collections::HashSet<String> =
1233 std::collections::HashSet::new();
1234 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1235 if ex.var_value(k).is_none() {
1236 continue; }
1238 let name = lang.name_of_key(k)?;
1239 live_names.insert(name.clone());
1240 let referenced = rest_j.iter().any(|&st| {
1241 cure_engine::analysis::subtree_contains(lang, st, |n| {
1242 lang.kind(n) == NodeKind::VarRef
1243 && lang.var_name(n) == Some(name.as_str())
1244 })
1245 });
1246 if referenced {
1247 esc.push((
1248 k,
1249 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1250 ex.var_domain(k),
1251 ex.var_decl_ty(k).unwrap_or(JType::Var),
1254 ));
1255 }
1256 }
1257 escaped_by_cut.push(esc);
1258 cut_live_names.push(live_names);
1259 }
1260 drop(ex);
1261 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1263 for (j, cut) in run.cuts.iter().enumerate() {
1264 let block_id = if j == root_idx {
1265 id
1266 } else {
1267 cut.block_hint?
1268 };
1269 let mut insert: Vec<JavaId> = Vec::new();
1270 for pos in cut.log_start..cut.log_end {
1272 match &log[pos] {
1273 vexec::EffectEvent::FieldWrite(k) => {
1274 let is_last = !log[pos + 1..]
1276 .iter()
1277 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1278 if !is_last {
1279 continue;
1280 }
1281 let name = lang.name_of_key(*k)?;
1282 if lang.is_param_name(&name) {
1284 return None;
1285 }
1286 let v = field_finals
1287 .iter()
1288 .find(|(fk, _)| fk == k)
1289 .map(|(_, v)| v.clone())?;
1290 let v = match (&v, lang.field_types.get(&name)) {
1295 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1296 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1297 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1298 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1299 _ => v,
1300 };
1301 let (val, mut extra) = materialize(lang, &v)?;
1302 let tgt = lang.var(&name);
1303 insert.push(lang.assign(tgt, val));
1304 insert.append(&mut extra);
1305 }
1306 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1307 if std::env::var("CURE_DBG_P1").is_ok() {
1308 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1309 }
1310 let name = lang.name_of_key(*k)?;
1315 if lang.is_param_name(&name) {
1317 return None;
1318 }
1319 let cch = lang.children(*call).to_vec();
1320 let mut new_args: Vec<JavaId> = Vec::new();
1321 let mut kept_original: Vec<bool> = Vec::new();
1322 for (i, &arg) in cch.iter().enumerate().skip(1) {
1323 let val = args.get(i - 1).cloned().flatten();
1324 let replaced = match val {
1325 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1326 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1327 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1328 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1329 _ => None,
1330 };
1331 kept_original.push(replaced.is_none());
1332 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1333 }
1334 let names_j = cut_live_names.get(j)?;
1338 for ((i, &arg), &kept) in
1339 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1340 {
1341 let _ = i;
1342 if kept
1343 && !names_j.is_empty()
1344 && cure_engine::analysis::subtree_contains(
1345 lang,
1346 arg,
1347 |n| {
1348 lang.kind(n) == NodeKind::VarRef
1349 && lang
1350 .var_name(n)
1351 .is_some_and(|nm| names_j.contains(nm))
1352 },
1353 )
1354 {
1355 return None;
1356 }
1357 }
1358 let callee = *lang.children(*call).first()?;
1359 let callee_copy = lang.copy_subtree(callee);
1360 let mut ch = vec![callee_copy];
1361 ch.extend(new_args);
1362 let new_call = lang.clone_node(*call, ch);
1363 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1364 lang.clone_node(*stmt, vec![new_call])
1365 } else {
1366 new_call
1367 };
1368 let tgt = lang.var(&name);
1370 let assign = lang.assign(tgt, new_stmt);
1371 insert.push(assign);
1372 }
1373 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1374 let lit = lang.lit(Lit::Str(name.clone()));
1382 let callee = *lang.children(*call).first()?;
1383 let callee_copy = lang.copy_subtree(callee);
1384 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1385 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1386 lang.clone_node(*stmt, vec![new_call])
1387 } else {
1388 new_call
1389 };
1390 let try_block = lang.block(vec![new_stmt]);
1391 let catch_block = lang.block(vec![]);
1392 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1393 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1394 insert.push(wrapped);
1395 }
1396 }
1397 }
1398 let mut alias_first: std::collections::HashMap<usize, String> =
1403 std::collections::HashMap::new();
1404 for (k, v, dom, dty) in &escaped_by_cut[j] {
1405 let name = lang.name_of_key(*k)?;
1406 let (val, mut extra) = match v {
1407 vexec::VVal::CA(a) => {
1408 let addr = std::rc::Rc::as_ptr(a) as usize;
1409 match alias_first.get(&addr) {
1410 Some(first) => (lang.var(first), vec![]),
1411 None => {
1412 alias_first.insert(addr, name.clone());
1413 materialize(lang, v)?
1414 }
1415 }
1416 }
1417 vexec::VVal::SA(a) => {
1418 let addr = std::rc::Rc::as_ptr(a) as usize;
1419 match alias_first.get(&addr) {
1420 Some(first) => (lang.var(first), vec![]),
1421 None => {
1422 alias_first.insert(addr, name.clone());
1423 materialize(lang, v)?
1424 }
1425 }
1426 }
1427 _ => materialize(lang, v)?,
1428 };
1429 let (ty, val) = match (*dom, val) {
1434 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1435 match lang.literal(lit) {
1438 Some(LitRef::Int(v)) => {
1439 match u32::try_from(v).ok().and_then(char::from_u32) {
1440 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1441 None => (JType::Char, lit),
1442 }
1443 }
1444 Some(LitRef::Long(v)) => {
1445 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1446 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1447 None => (JType::Char, lit),
1448 }
1449 }
1450 _ => (JType::Char, lit),
1451 }
1452 }
1453 (3, lit) => (JType::Byte, lit),
1454 (4, lit) => (JType::Short, lit),
1455 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1456 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1457 (JType::Long, lang.build_int(v, true))
1458 } else {
1459 (JType::Long, lit)
1460 }
1461 }
1462 (_, lit) => {
1467 if matches!(dty, JType::Ref(_)) {
1468 (dty.clone(), lit)
1469 } else {
1470 (JType::Var, lit)
1471 }
1472 }
1473 };
1474 let stmt = lang.var_decl(&name, ty, Some(val));
1475 insert.push(stmt);
1476 insert.append(&mut extra);
1477 }
1478 if j == root_idx {
1480 if let Some(v) = ret_payload.clone().flatten() {
1481 let (val, mut extra) = materialize(lang, &v)?;
1482 let stmt = lang.ret(Some(val));
1483 insert.push(stmt);
1484 insert.append(&mut extra);
1485 }
1486 }
1487 if insert.is_empty() && cut.completed == 0 {
1489 continue;
1490 }
1491 {
1494 let removed = lang.children(block_id).to_vec();
1495 if insert.len() == cut.completed
1496 && cut.completed <= removed.len()
1497 && insert
1498 .iter()
1499 .zip(removed[..cut.completed].iter())
1500 .all(|(&n, &o)| print_equiv(lang, n, o))
1501 {
1502 continue;
1503 }
1504 }
1505 edits.push(Edit::Splice {
1506 node: block_id,
1507 index: 0,
1508 remove: cut.completed,
1509 insert,
1510 });
1511 }
1512 if std::env::var("CURE_DBG_P1").is_ok() {
1513 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1514 }
1515 if edits.is_empty() {
1516 return None;
1517 }
1518 let candidate = if edits.len() == 1 {
1520 edits.pop().unwrap()
1521 } else {
1522 Edit::Multi(edits)
1523 };
1524 if candidate.cost_reduction(lang) <= 0 {
1527 return None;
1528 }
1529 Some(candidate)
1530 }
1531}
1532
1533fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1536 use vexec::VVal;
1537 match v {
1538 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1539 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1540 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1541 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1542 VVal::CA(a) => {
1543 let chars: Vec<char> = a.borrow().clone();
1545 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1546 let lit_arr = lang.array_lit(lits);
1547 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1548 Some((node, vec![]))
1549 }
1550 VVal::SA(elems) => {
1551 let arr = elems.borrow().clone();
1554 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1555 let lits: Vec<JavaId> = arr
1556 .iter()
1557 .map(|e| match e {
1558 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1559 _ => unreachable!(),
1560 })
1561 .collect();
1562 let lit_arr = lang.array_lit(lits);
1563 let node =
1564 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1565 Some((node, vec![]))
1566 } else {
1567 None
1572 }
1573 }
1574 VVal::Undef => None,
1576 }
1577}
1578
1579pub struct TryUnwrapRethrow;
1580
1581impl Rule<JavaAst> for TryUnwrapRethrow {
1582 fn name(&self) -> &'static str {
1583 "try_unwrap_rethrow"
1584 }
1585 fn kinds(&self) -> &'static [NodeKind] {
1586 &[NodeKind::Try]
1587 }
1588 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1589 let lang = ctx.lang;
1590 if lang.kind(id) != NodeKind::Try {
1591 return None;
1592 }
1593 let ch = lang.children(id).to_vec();
1594 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1596 let catches: Vec<JavaId> =
1597 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1598 if catches.is_empty() {
1599 return None;
1600 }
1601 let rethrow_only = |c: JavaId| -> bool {
1602 let block = match lang.children(c).first() {
1604 Some(&b) => b,
1605 None => return false,
1606 };
1607 let stmts = lang.children(block);
1608 if stmts.len() != 1 {
1609 return false;
1610 }
1611 let stmt = stmts[0];
1612 if lang.kind(stmt) != NodeKind::Throw {
1613 return false;
1614 }
1615 let Some(&thrown) = lang.children(stmt).first() else { return false };
1616 if lang.kind(thrown) != NodeKind::VarRef {
1618 return false;
1619 }
1620 match &lang.node(c).data {
1621 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1622 _ => false,
1623 }
1624 };
1625 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1626 if all_rethrow {
1627 let extras: Vec<JavaId> = ch
1629 .iter()
1630 .copied()
1631 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1632 .collect();
1633 if extras.is_empty() {
1634 return Some(Edit::Replace {
1635 target: id,
1636 with: try_block,
1637 });
1638 }
1639 return None;
1640 }
1641 let catch_slots: Vec<usize> = ch
1647 .iter()
1648 .enumerate()
1649 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1650 .map(|(i, _)| i)
1651 .collect();
1652 for &slot in &catch_slots {
1653 let c = ch[slot];
1654 if !rethrow_only(c) {
1655 continue;
1656 }
1657 let later_all_rethrow = catch_slots
1658 .iter()
1659 .filter(|&&s2| s2 > slot)
1660 .all(|&s2| rethrow_only(ch[s2]));
1661 if !later_all_rethrow {
1662 continue;
1663 }
1664 return Some(Edit::Splice {
1665 node: id,
1666 index: slot,
1667 remove: 1,
1668 insert: vec![],
1669 });
1670 }
1671 None
1672 }
1673}
1674
1675pub struct TryUnwrapNoCatch;
1676
1677impl Rule<JavaAst> for TryUnwrapNoCatch {
1678 fn name(&self) -> &'static str {
1679 "try_unwrap_no_catch"
1680 }
1681 fn kinds(&self) -> &'static [NodeKind] {
1682 &[NodeKind::Try]
1683 }
1684 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1685 let lang = ctx.lang;
1686 if lang.kind(id) != NodeKind::Try {
1687 return None;
1688 }
1689 let ch = lang.children(id).to_vec();
1690 if ch.is_empty() {
1691 return None;
1692 }
1693 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1696 if has_catch {
1697 return None;
1698 }
1699 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1703 let rest: Vec<JavaId> = ch
1704 .iter()
1705 .copied()
1706 .filter(|&c| c != try_block)
1707 .collect();
1708 for &r in &rest {
1711 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1712 if !is_empty_block {
1713 return None; }
1715 }
1716 Some(Edit::Replace {
1717 target: id,
1718 with: try_block,
1719 })
1720 }
1721}
1722
1723pub struct LoopHeadBreak;
1731
1732impl Rule<JavaAst> for LoopHeadBreak {
1733 fn name(&self) -> &'static str {
1734 "loop_head_break"
1735 }
1736 fn kinds(&self) -> &'static [NodeKind] {
1737 &[NodeKind::While, NodeKind::DoWhile]
1738 }
1739 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1740 let lang = ctx.lang;
1741 let body = match lang.kind(id) {
1742 NodeKind::While => {
1743 let ch = lang.children(id).to_vec();
1744 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1745 return None;
1746 }
1747 ch[1]
1748 }
1749 NodeKind::DoWhile => {
1750 let ch = lang.children(id).to_vec();
1751 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1752 return None;
1753 }
1754 ch[0]
1755 }
1756 _ => return None,
1757 };
1758 if lang.kind(body) != NodeKind::Block {
1759 return None;
1760 }
1761 let bch = lang.children(body).to_vec();
1762 let is_do = lang.kind(id) == NodeKind::DoWhile;
1763 if is_do && bch.len() != 1 {
1765 return None;
1766 }
1767 let first = *bch.first()?;
1768 if lang.kind(first) != NodeKind::If {
1769 return None;
1770 }
1771 let ich = lang.children(first).to_vec();
1772 let c = ich[0];
1773
1774 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1775 let is_break = |n: JavaId| match lang.kind(n) {
1776 NodeKind::Break => bare_break(n),
1777 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1778 _ => false,
1779 };
1780 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1781 let ends_with_continue = |n: JavaId| -> bool {
1782 match lang.kind(n) {
1783 NodeKind::Continue => bare_continue(n),
1784 NodeKind::Block => lang
1785 .children(n)
1786 .last()
1787 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1788 _ => false,
1789 }
1790 };
1791 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1792 match lang.kind(n) {
1793 NodeKind::Continue => vec![],
1794 NodeKind::Block => {
1795 let ch = lang.children(n).to_vec();
1796 if ch
1797 .last()
1798 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1799 {
1800 ch[..ch.len() - 1].to_vec()
1801 } else {
1802 ch
1803 }
1804 }
1805 _ => vec![n],
1806 }
1807 };
1808
1809 match ich.len() {
1810 2 => {
1811 if is_do || !is_break(ich[1]) {
1813 return None;
1814 }
1815 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1816 if let Some(&last) = rest.last() {
1817 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1818 rest.pop();
1819 }
1820 }
1821 let nc = lang.build_unary(UnOp::Not, c);
1822 let inner = lang.build_block(rest);
1823 let w = lang.while_(nc, inner);
1824 Some(Edit::Replace {
1825 target: id,
1826 with: w,
1827 })
1828 }
1829 3 => {
1830 let (then, els) = (ich[1], ich[2]);
1831 if ends_with_continue(then) && is_break(els) {
1833 let rest = strip_tail_continue(then);
1834 let inner = lang.build_block(rest);
1835 let w = lang.while_(c, inner);
1836 return Some(Edit::Replace {
1837 target: id,
1838 with: w,
1839 });
1840 }
1841 if is_break(then) && ends_with_continue(els) {
1843 let rest = strip_tail_continue(els);
1844 let inner = lang.build_block(rest);
1845 let nc = lang.build_unary(UnOp::Not, c);
1846 let w = lang.while_(nc, inner);
1847 return Some(Edit::Replace {
1848 target: id,
1849 with: w,
1850 });
1851 }
1852 if !is_do && is_break(then) {
1854 let mut all = strip_tail_continue(els);
1855 all.extend(bch[1..].iter().copied());
1856 let inner = lang.build_block(all);
1857 let nc = lang.build_unary(UnOp::Not, c);
1858 let w = lang.while_(nc, inner);
1859 return Some(Edit::Replace {
1860 target: id,
1861 with: w,
1862 });
1863 }
1864 None
1865 }
1866 _ => None,
1867 }
1868 }
1869}
1870
1871pub struct WhileIteratorToForEach;
1872
1873impl Rule<JavaAst> for WhileIteratorToForEach {
1874 fn name(&self) -> &'static str {
1875 "while_iterator_to_for_each"
1876 }
1877 fn kinds(&self) -> &'static [NodeKind] {
1878 &[NodeKind::While]
1879 }
1880 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1881 let parent = ctx.parent(id)?;
1883 let idx = ctx.index(id)?;
1884 let lang = ctx.lang;
1885 if lang.kind(id) != NodeKind::While {
1886 return None;
1887 }
1888 let wch = lang.children(id).to_vec();
1889 let body = wch[1];
1890 if lang.kind(parent) != NodeKind::Block {
1892 return None;
1893 }
1894 if idx == 0 {
1895 return None;
1896 }
1897 let stmts = lang.children(parent).to_vec();
1898 let cond = wch[0];
1901 let it_hint = match lang.kind(cond) {
1902 NodeKind::Call => {
1903 let cc0 = lang.children(cond).to_vec();
1904 match lang.data(*cc0.first()?) {
1905 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1906 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1907 }
1908 _ => None,
1909 }
1910 }
1911 _ => None,
1912 };
1913 let mut decl = None;
1914 if let Some(hint) = it_hint {
1915 let mut up = idx;
1916 for _ in 0..4 {
1917 if up == 0 {
1918 break;
1919 }
1920 up -= 1;
1921 let st = stmts[up];
1922 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1923 decl = Some(st);
1924 break;
1925 }
1926 if subtree_has_var(lang, st, &hint) {
1927 break;
1928 }
1929 }
1930 }
1931 let decl = decl?;
1932 let it_name = lang.var_name(decl)?.to_string();
1933 if stmts[idx + 1..]
1938 .iter()
1939 .any(|&s| subtree_has_var(lang, s, &it_name))
1940 {
1941 return None;
1942 }
1943 let init_call = lang.children(decl).first().copied()?;
1944 if lang.kind(init_call) != NodeKind::Call {
1945 return None;
1946 }
1947 let ic = lang.children(init_call).to_vec();
1948 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1949 return None;
1950 };
1951 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1952 return None;
1953 }
1954 let iterable = lang.children(ic[0])[0];
1955 if subtree_has_var(&*lang, iterable, &it_name) {
1956 return None;
1957 }
1958 {
1968 let ok = match lang.var_type(iterable) {
1973 Some(t) => statically_iterable(t),
1974 None => map_view_synthetic_ty(lang, iterable).is_some(),
1975 };
1976 if !ok {
1977 return None;
1978 }
1979 }
1980 if lang.kind(cond) != NodeKind::Call {
1982 return None;
1983 }
1984 let cc = lang.children(cond).to_vec();
1985 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1986 return None;
1987 };
1988 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1989 return None;
1990 }
1991 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1992 return None;
1993 }
1994 if lang.kind(body) != NodeKind::Block {
1996 return None;
1997 }
1998 let bch = lang.children(body).to_vec();
1999 let first = *bch.first()?;
2000 if lang.kind(first) != NodeKind::VarDecl {
2001 let it_refs_in_body: Vec<JavaId> = {
2004 let mut v = Vec::new();
2005 collect_var_refs(&*lang, body, &it_name, &mut v);
2006 v
2007 };
2008 if it_refs_in_body.len() != 1 {
2009 return None;
2010 }
2011 let next_recv = it_refs_in_body[0];
2012 let member = parent_of_recv(lang, body, next_recv);
2014 if lang.kind(member) != NodeKind::Member {
2015 return None;
2016 }
2017 let NodeData::Member { name: mn } = lang.data(member) else {
2018 return None;
2019 };
2020 if lang.sn(*mn) != "next" {
2021 return None;
2022 }
2023 let next_call = parent_of_recv(lang, body, member);
2024 if lang.kind(next_call) != NodeKind::Call {
2025 return None;
2026 }
2027 if lang.children(next_call).len() != 1 {
2028 return None;
2029 }
2030 let mut wrapped = next_call;
2032 loop {
2033 let par = parent_of_recv(lang, body, wrapped);
2034 match lang.kind(par) {
2035 NodeKind::Cast | NodeKind::Paren => wrapped = par,
2036 _ => break,
2037 }
2038 }
2039 {
2049 let mut cur = wrapped;
2050 while cur != body {
2051 let par = parent_of_recv(lang, body, cur);
2052 if par == body || par == cur {
2053 break; }
2055 let ch = lang.children(par).to_vec();
2056 let idx = ch.iter().position(|&c| c == cur);
2057 match lang.kind(par) {
2058 NodeKind::Block
2059 | NodeKind::ExprStmt
2060 | NodeKind::Label
2061 | NodeKind::Return
2062 | NodeKind::Throw => {}
2063 NodeKind::Ternary => {
2064 if idx.unwrap_or(0) > 0 {
2066 return None;
2067 }
2068 }
2069 NodeKind::Binary => {
2070 if let Some(op) = lang.bin_op(par) {
2071 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
2072 return None; }
2074 }
2075 }
2076 NodeKind::If
2077 | NodeKind::While
2078 | NodeKind::DoWhile
2079 | NodeKind::For
2080 | NodeKind::Switch
2081 | NodeKind::Try
2082 | NodeKind::Catch
2083 | NodeKind::Synchronized
2084 | NodeKind::ForEach => {
2085 return None;
2086 }
2087 NodeKind::Lambda | NodeKind::Assert => {
2092 return None;
2093 }
2094 _ => {} }
2096 cur = par;
2097 }
2098 }
2099 let it_decl_elem = match lang.data(decl) {
2107 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
2108 _ => None,
2109 };
2110 let mut elem = match it_decl_elem {
2111 Some(t) => t,
2112 None => match lang.var_type(iterable) {
2113 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
2114 None => JType::Ref("Object".into()),
2115 },
2116 };
2117 {
2124 let wp = parent_of_recv(lang, body, wrapped);
2125 if matches!(lang.data(wp), NodeData::Assign { op: None }) {
2126 let ach = lang.children(wp).to_vec();
2127 if ach.len() == 2 && ach[1] == wrapped {
2128 if let Some(t) = lang.var_type(ach[0]).cloned() {
2129 let t_name = match &t {
2130 JType::Ref(n) => n.as_str(),
2131 _ => "",
2132 };
2133 let elem_object =
2134 matches!(&elem, JType::Ref(n) if n == "Object");
2135 if elem_object && t_name != "Object" && !t_name.is_empty() {
2136 let decl_ty = match lang.data(decl) {
2137 NodeData::VarDecl { ty, .. } => ty,
2138 _ => return None,
2139 };
2140 if type_inner_raw(decl_ty) == Some(t_name) {
2141 elem = t;
2142 } else {
2143 return None;
2144 }
2145 } else if !elem_object && elem != t {
2146 return None;
2147 }
2148 }
2149 }
2150 }
2151 }
2152 let elem = if for_each_elem_ok(&elem) {
2153 elem
2154 } else {
2155 JType::Ref("Object".into())
2156 };
2157 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
2158 let (replace_target, e_ty) = match lang.data(wrapped) {
2159 NodeData::Cast { .. } if is_object => (next_call, elem),
2160 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
2161 _ => (wrapped, elem),
2162 };
2163 let taken = |nm: &str| {
2167 subtree_has_var(&*lang, body, nm)
2168 || subtree_has_binding(&*lang, body, nm)
2169 };
2170 let e_name = if taken("e") {
2171 if taken("e2") { "e3" } else { "e2" }
2172 } else {
2173 "e"
2174 };
2175 let e_ref = lang.var(e_name);
2176 let foreach = lang.for_each(e_name, e_ty, iterable, body);
2177 let wrapped_parent = parent_of_recv(lang, body, wrapped);
2181 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
2182 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
2183 {
2184 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
2185 if let Some(si) = lang
2186 .children(stmt_block)
2187 .iter()
2188 .position(|&c| c == wrapped_parent)
2189 {
2190 return Some(Edit::Multi(vec![
2191 Edit::Splice {
2192 node: stmt_block,
2193 index: si,
2194 remove: 1,
2195 insert: vec![],
2196 },
2197 Edit::Splice {
2198 node: parent,
2199 index: idx,
2200 remove: 1,
2201 insert: vec![foreach],
2202 },
2203 Edit::Delete { node: decl },
2204 ]));
2205 }
2206 }
2207 return Some(Edit::Multi(vec![
2208 Edit::Replace {
2209 target: replace_target,
2210 with: e_ref,
2211 },
2212 Edit::Splice {
2213 node: parent,
2214 index: idx,
2215 remove: 1,
2216 insert: vec![foreach],
2217 },
2218 Edit::Delete { node: decl },
2219 ]));
2220 }
2221 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
2222 return None;
2223 };
2224 let (e_name, ty) = (e_name.clone(), ty.clone());
2225 let next_call = lang.children(first).first().copied()?;
2226 let next_call = unwrap_paren_cast(&*lang, next_call);
2227 if lang.kind(next_call) != NodeKind::Call {
2228 return None;
2229 }
2230 let nc = lang.children(next_call).to_vec();
2231 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
2232 return None;
2233 };
2234 if lang.sn(*m2) != "next" || nc.len() != 1 {
2235 return None;
2236 }
2237 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
2238 return None;
2239 }
2240 let elem_is_object = match lang.var_type(iterable) {
2242 Some(it_t) => generic_elem_ty(it_t).is_none(),
2243 None => map_view_synthetic_ty(lang, iterable)
2244 .as_ref()
2245 .and_then(generic_elem_ty)
2246 .is_none(),
2247 };
2248 if elem_is_object && ty != JType::Ref("Object".into()) {
2249 return None;
2250 }
2251 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2253 let rest = bch[1..].to_vec();
2254 for &r in &rest {
2255 if subtree_has_var(&*lang, r, &it_name) {
2256 return None;
2257 }
2258 }
2259 let new_body = lang.build_block(rest);
2260 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2261 Some(Edit::Multi(vec![
2264 Edit::Splice {
2265 node: parent,
2266 index: idx,
2267 remove: 1,
2268 insert: vec![foreach],
2269 },
2270 Edit::Delete { node: decl },
2271 ]))
2272 }
2273}
2274
2275pub struct ConcatValueOfDrop;
2284
2285impl Rule<JavaAst> for ConcatValueOfDrop {
2286 fn name(&self) -> &'static str {
2287 "concat_value_of_drop"
2288 }
2289 fn kinds(&self) -> &'static [NodeKind] {
2290 &[NodeKind::Binary]
2291 }
2292 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2293 let root = ctx.root();
2294 let lang = ctx.lang;
2295 if lang.kind(id) != NodeKind::Binary {
2296 return None;
2297 }
2298 if lang.bin_op(id) != Some(BinOp::Add) {
2299 return None;
2300 }
2301 let ch = lang.children(id).to_vec();
2302 let (l, r) = (ch[0], ch[1]);
2303 let becomes_constant = |x: JavaId, other: JavaId| {
2306 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2307 };
2308 let is_value_of = |n: JavaId| -> Option<JavaId> {
2309 if lang.kind(n) != NodeKind::Call {
2310 return None;
2311 }
2312 let c = lang.children(n).to_vec();
2313 if c.len() != 2 {
2314 return None;
2315 }
2316 let callee = c[0];
2317 if let NodeData::Member { name } = lang.data(callee) {
2318 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2319 return Some(c[1]);
2320 }
2321 }
2322 if let NodeData::VarRef { name } = lang.data(callee) {
2323 if lang.sn(*name) == "valueOf" {
2324 return Some(c[1]);
2325 }
2326 }
2327 None
2328 };
2329 let stringy = |n: JavaId| -> bool {
2330 matches!(lang.literal(n), Some(LitRef::Str(_)))
2331 || lang
2332 .var_type(n)
2333 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2334 };
2335 if let Some(x) = is_value_of(l) {
2337 if stringy(r) {
2338 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2339 return None;
2340 }
2341 let with = lang.build_bin(BinOp::Add, x, r);
2342 return Some(Edit::Replace {
2343 target: id,
2344 with,
2345 });
2346 }
2347 }
2348 if let Some(x) = is_value_of(r) {
2350 if stringy(l) {
2351 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2352 return None;
2353 }
2354 let with = lang.build_bin(BinOp::Add, l, x);
2355 return Some(Edit::Replace {
2356 target: id,
2357 with,
2358 });
2359 }
2360 }
2361 None
2362 }
2363}
2364
2365
2366pub struct StringBuilderStatements;
2379
2380fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2381 let mut n = 0;
2384 let mut stack = vec![root];
2385 while let Some(x) = stack.pop() {
2386 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2387 let ch = lang.children(x).to_vec();
2388 if let Some(&t) = ch.first() {
2389 let is_named_target =
2390 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2391 if !is_named_target {
2392 stack.push(t);
2393 }
2394 for &c in &ch[1..] {
2395 stack.push(c);
2396 }
2397 }
2398 continue;
2399 }
2400 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2401 n += 1;
2402 }
2403 for &c in lang.children(x) {
2404 stack.push(c);
2405 }
2406 }
2407 n
2408}
2409
2410impl Rule<JavaAst> for StringBuilderStatements {
2411 fn name(&self) -> &'static str {
2412 "string_builder_statements"
2413 }
2414 fn kinds(&self) -> &'static [NodeKind] {
2415 &[NodeKind::VarDecl]
2416 }
2417 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2418 let parent = ctx.parent(id)?;
2420 let idx = ctx.index(id)?;
2421 let root = ctx.root();
2422 let lang = ctx.lang;
2423
2424 if lang.kind(id) != NodeKind::VarDecl {
2425 return None;
2426 }
2427 if lang.kind(parent) != NodeKind::Block {
2428 return None;
2429 }
2430 let v0 = lang.var_name(id)?.to_string();
2431 let init = *lang.children(id).first()?;
2433 if lang.kind(init) != NodeKind::Call {
2434 return None;
2435 }
2436 let ic = lang.children(init).to_vec();
2437 if ic.len() != 2 {
2438 return None;
2439 }
2440 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2441 return None;
2442 };
2443 if lang.sn(*m0) != "append" {
2444 return None;
2445 }
2446 let sb_new = lang.children(ic[0])[0];
2447 let (ctor_arg, a0) = match lang.data(sb_new) {
2448 NodeData::New { ty, .. } => {
2449 let is_sb = !lang.unit_shadows_lang
2450 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2451 if !is_sb {
2452 return None;
2453 }
2454 let args = lang.children(sb_new).to_vec();
2455 match args.len() {
2456 0 => (None, ic[1]),
2457 1 => {
2458 let a = args[0];
2459 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2460 || lang
2461 .var_type(a)
2462 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2463 if stringy {
2464 (Some(a), ic[1])
2465 } else if matches!(
2466 lang.literal(a),
2467 Some(LitRef::Int(_) | LitRef::Long(_))
2468 ) {
2469 (None, ic[1])
2471 } else {
2472 return None; }
2474 }
2475 _ => return None,
2476 }
2477 }
2478 _ => return None,
2479 };
2480
2481 let stmts = lang.children(parent).to_vec();
2482 let mut parts: Vec<JavaId> = vec![a0];
2484 let mut expected_reads: std::collections::HashMap<String, usize> =
2485 std::collections::HashMap::new();
2486 let mut chain_len = 1usize; let mut cur = v0.clone();
2488 while idx + chain_len < stmts.len() {
2489 let s = stmts[idx + chain_len];
2490 let (recv, arg, new_var, consumed) = match lang.data(s) {
2491 NodeData::Assign { op: None } => {
2493 let ch = lang.children(s).to_vec();
2494 if ch.len() != 2 {
2495 break;
2496 }
2497 let (t, v) = (ch[0], ch[1]);
2498 if lang.kind(t) != NodeKind::VarRef
2499 || lang.var_name(t) != Some(cur.as_str())
2500 || lang.kind(v) != NodeKind::Call
2501 {
2502 break;
2503 }
2504 let vc = lang.children(v).to_vec();
2505 if vc.len() != 2 {
2506 break;
2507 }
2508 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2509 break;
2510 };
2511 if lang.sn(*mn) != "append" {
2512 break;
2513 }
2514 let recv = lang.children(vc[0])[0];
2515 (recv, vc[1], false, s)
2516 }
2517 NodeData::ExprStmt => {
2518 let ch = lang.children(s).to_vec();
2519 if ch.len() != 1 {
2520 break;
2521 }
2522 let inner = ch[0];
2523 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2524 break;
2525 }
2526 let ach = lang.children(inner).to_vec();
2527 if ach.len() != 2 {
2528 break;
2529 }
2530 let (t, v) = (ach[0], ach[1]);
2531 if lang.kind(t) != NodeKind::VarRef
2532 || lang.var_name(t) != Some(cur.as_str())
2533 || lang.kind(v) != NodeKind::Call
2534 {
2535 break;
2536 }
2537 let vc = lang.children(v).to_vec();
2538 if vc.len() != 2 {
2539 break;
2540 }
2541 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2542 break;
2543 };
2544 if lang.sn(*mn) != "append" {
2545 break;
2546 }
2547 let recv = lang.children(vc[0])[0];
2548 (recv, vc[1], false, s)
2549 }
2550 NodeData::VarDecl { .. } => {
2552 let init = match lang.children(s).first() {
2553 Some(&x) => x,
2554 None => break,
2555 };
2556 if lang.kind(init) != NodeKind::Call {
2557 break;
2558 }
2559 let vc = lang.children(init).to_vec();
2560 if vc.len() != 2 {
2561 break;
2562 }
2563 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2564 break;
2565 };
2566 if lang.sn(*mn) != "append" {
2567 break;
2568 }
2569 let recv = lang.children(vc[0])[0];
2570 if lang.var_name(recv) != Some(cur.as_str()) {
2571 break;
2572 }
2573 (recv, vc[1], true, s)
2574 }
2575 _ => break,
2576 };
2577 if lang.var_name(recv) != Some(cur.as_str()) {
2579 break;
2580 }
2581 parts.push(arg);
2582 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2583 if new_var {
2584 cur = lang.var_name(consumed)?.to_string();
2585 }
2586 chain_len += 1;
2587 let _ = consumed;
2588 }
2589
2590 let final_stmt = *stmts.get(idx + chain_len)?;
2592 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2593 return None;
2594 };
2595 let s_init = *lang.children(final_stmt).first()?;
2596 if lang.kind(s_init) != NodeKind::Call {
2597 return None;
2598 }
2599 let sc = lang.children(s_init).to_vec();
2600 if sc.len() != 1 {
2601 return None;
2602 }
2603 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2604 return None;
2605 };
2606 if lang.sn(*mn) != "toString" {
2607 return None;
2608 }
2609 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2610 return None;
2611 }
2612
2613 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2617 if count_var_uses_in(&*lang, parent, v) != *exp {
2618 return None;
2619 }
2620 }
2621
2622 let mut all_parts: Vec<JavaId> = Vec::new();
2624 if let Some(ca) = ctor_arg {
2625 all_parts.push(ca);
2626 }
2627 all_parts.extend(parts);
2628 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2629 && lang.has_string_identity_compare(root)
2630 {
2631 return None;
2632 }
2633 if all_parts.is_empty() {
2634 let empty = lang.build_str("");
2635 return Some(Edit::Multi(vec![
2636 Edit::Replace {
2637 target: s_init,
2638 with: empty,
2639 },
2640 Edit::Splice {
2641 node: parent,
2642 index: idx,
2643 remove: chain_len,
2644 insert: Vec::new(),
2645 },
2646 ]));
2647 }
2648 let first = all_parts[0];
2649 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2650 || lang
2651 .var_type(first)
2652 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2653 let mut acc = if stringy_first {
2654 first
2655 } else {
2656 let empty = lang.build_str("");
2657 lang.build_bin(BinOp::Add, empty, first)
2658 };
2659 for &p in &all_parts[1..] {
2660 acc = lang.build_bin(BinOp::Add, acc, p);
2661 }
2662 Some(Edit::Multi(vec![
2663 Edit::Replace {
2664 target: s_init,
2665 with: acc,
2666 },
2667 Edit::Splice {
2668 node: parent,
2669 index: idx,
2670 remove: chain_len,
2671 insert: Vec::new(),
2672 },
2673 ]))
2674 }
2675}
2676
2677
2678pub struct TrailingContinueJava;
2685
2686impl Rule<JavaAst> for TrailingContinueJava {
2687 fn name(&self) -> &'static str {
2688 "trailing_continue"
2689 }
2690 fn kinds(&self) -> &'static [NodeKind] {
2691 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2692 }
2693 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2694 let parent = ctx.parent(id)?;
2695 let lang = ctx.lang;
2696 let body = match lang.kind(id) {
2697 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2698 *lang.children(id).last()?
2699 }
2700 _ => return None,
2701 };
2702 if lang.kind(body) != NodeKind::Block {
2703 return None;
2704 }
2705 let ch = lang.children(body).to_vec();
2706 let last = *ch.last()?;
2707 if lang.kind(last) != NodeKind::Continue {
2708 return None;
2709 }
2710 let label = match lang.data(last) {
2711 NodeData::Continue { label } => label.clone(),
2712 _ => return None,
2713 };
2714 match label {
2715 None => Some(Edit::Delete { node: last }),
2716 Some(l) => {
2717 if lang.kind(parent) == NodeKind::Label {
2719 if let NodeData::Label { name } = lang.data(parent) {
2720 if *name == l {
2721 return Some(Edit::Delete { node: last });
2722 }
2723 }
2724 }
2725 None
2726 }
2727 }
2728 }
2729}
2730
2731
2732pub struct XorNoise;
2740
2741impl Rule<JavaAst> for XorNoise {
2742 fn name(&self) -> &'static str {
2743 "xor_noise"
2744 }
2745 fn kinds(&self) -> &'static [NodeKind] {
2746 &[NodeKind::Binary]
2747 }
2748 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2749 let lang = ctx.lang;
2750 if lang.kind(id) != NodeKind::Binary {
2751 return None;
2752 }
2753 let op = lang.bin_op(id)?;
2754 if op != BinOp::BitXor {
2755 return None;
2756 }
2757 let ch = lang.children(id).to_vec();
2758 let (l, r) = (ch[0], ch[1]);
2759 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2761 if int_lit(r) == Some(0) {
2762 return Some(Edit::Replace {
2763 target: id,
2764 with: l,
2765 });
2766 }
2767 if int_lit(l) == Some(0) {
2768 return Some(Edit::Replace {
2769 target: id,
2770 with: r,
2771 });
2772 }
2773 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2775 return None;
2776 }
2777 let ich = lang.children(l).to_vec();
2778 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2779 let k2 = int_lit(r)?;
2780 let x = ich[0];
2781 let k = k1 ^ k2;
2782 let with = if k == 0 {
2783 x
2784 } else {
2785 let lit = lang.build_int(k, false);
2786 lang.build_bin(BinOp::BitXor, x, lit)
2787 };
2788 Some(Edit::Replace {
2789 target: id,
2790 with,
2791 })
2792 }
2793}
2794
2795pub struct StrLenFold;
2800
2801impl Rule<JavaAst> for StrLenFold {
2802 fn name(&self) -> &'static str {
2803 "str_len_fold"
2804 }
2805 fn kinds(&self) -> &'static [NodeKind] {
2806 &[NodeKind::Call]
2807 }
2808 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2809 let _at_stmt_pos = ctx
2811 .parent(id)
2812 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2813 .unwrap_or(false);
2814 let lang = ctx.lang;
2815 if lang.kind(id) != NodeKind::Call {
2816 return None;
2817 }
2818 let ch = lang.children(id).to_vec();
2819 if ch.len() != 1 {
2820 return None;
2821 }
2822 let NodeData::Member { name } = lang.data(ch[0]) else {
2823 return None;
2824 };
2825 if lang.sn(*name) != "length" {
2826 return None;
2827 }
2828 let recv = lang.children(ch[0])[0];
2829 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2830 let with = lang.build_int(s.chars().count() as i64, false);
2831 return Some(Edit::Replace {
2832 target: id,
2833 with,
2834 });
2835 }
2836 None
2837 }
2838}
2839
2840
2841pub struct LiteralEval;
2854
2855impl Rule<JavaAst> for LiteralEval {
2856 fn name(&self) -> &'static str {
2857 "literal_eval"
2858 }
2859 fn kinds(&self) -> &'static [NodeKind] {
2860 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2861 }
2862 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2863 let root = ctx.root();
2864 let lang = ctx.lang;
2865 let edit = match lang.kind(id) {
2866 NodeKind::Call => self.eval_call(&mut *lang, id),
2867 NodeKind::Index => self.eval_index(&mut *lang, id),
2868 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2869 _ => None,
2870 };
2871 if let Some(Edit::Replace { with, .. }) = &edit {
2875 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2876 && lang.has_string_identity_compare(root)
2877 {
2878 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2879 eprintln!("[gate] blocked literal_eval at {id:?}");
2880 }
2881 return None;
2882 }
2883 }
2884 edit
2885 }
2886}
2887
2888impl LiteralEval {
2889 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2890 let ch = lang.children(id).to_vec();
2891 let callee = ch[0];
2892 let NodeData::Member { name: method } = lang.data(callee) else {
2893 return None;
2894 };
2895 let method = *method;
2896 let recv = lang.children(callee)[0];
2897 let args: Vec<Lit> = ch[1..]
2899 .iter()
2900 .map(|&a| litref_to_lit(lang.literal(a)))
2901 .collect::<Option<Vec<_>>>()?;
2902
2903 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2904 eval_str_method(s, lang.sn(method), &args)
2905 } else if let Some(cls) = lang.var_name(recv) {
2906 eval_static_method(cls, lang.sn(method), &args)
2907 } else {
2908 None
2909 };
2910 let lit = result?;
2911 let with = lit_to_node(lang, &lit);
2912 Some(Edit::Replace {
2913 target: id,
2914 with,
2915 })
2916 }
2917
2918 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2919 let ch = lang.children(id).to_vec();
2921 let arr = match lang.kind(ch[0]) {
2923 NodeKind::ArrayLit => ch[0],
2924 NodeKind::NewArray => {
2925 let nc = lang.children(ch[0]);
2926 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2927 return None;
2928 };
2929 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2930 return None;
2931 }
2932 nc[0]
2933 }
2934 _ => return None,
2935 };
2936 let idx = lang.literal(ch[1])?.as_int()?;
2937 let elems = lang.children(arr).to_vec();
2938 if idx < 0 || idx >= elems.len() as i64 {
2939 return None;
2940 }
2941 Some(Edit::Replace {
2942 target: id,
2943 with: elems[idx as usize],
2944 })
2945 }
2946}
2947
2948fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2949 Some(match l? {
2950 LitRef::Str(s) => Lit::Str(s.to_string()),
2951 LitRef::Int(v) => Lit::Int(v),
2952 LitRef::Long(v) => Lit::Long(v),
2953 LitRef::Bool(b) => Lit::Bool(b),
2954 LitRef::Char(c) => Lit::Char(c),
2955 LitRef::Float(v) => Lit::Float(v),
2956 LitRef::Double(v) => Lit::Double(v),
2957 LitRef::Null => return None,
2958 })
2959}
2960
2961fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2962 match lit {
2963 Lit::Str(s) => lang.build_str(s),
2964 Lit::Int(v) => lang.build_int(*v, false),
2965 Lit::Long(v) => lang.build_int(*v, true),
2966 Lit::Bool(b) => lang.build_bool(*b),
2967 Lit::Char(c) => lang.build_char(*c),
2968 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2969 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2970 _ => lang.lit(Lit::Null),
2971 }
2972}
2973
2974fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2976 let NodeData::Cast { ty } = lang.data(id) else {
2977 return None;
2978 };
2979 let ty = ty.clone();
2980 let inner = lang.children(id)[0];
2981 let lit = litref_to_lit(lang.literal(inner))?;
2982 let with = match (&ty, &lit) {
2983 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2984 lang.build_char(char::from_u32(*v as u32)?)
2985 }
2986 (JType::Char, Lit::Char(_)) => inner,
2987 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2988 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2989 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2990 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2991 lang.build_int(*v, false)
2992 }
2993 (JType::Int, Lit::Int(_)) => inner,
2994 _ => return None,
2995 };
2996 Some(Edit::Replace {
2997 target: id,
2998 with,
2999 })
3000}
3001
3002fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
3003 let chars: Vec<char> = s.chars().collect();
3004 let n = chars.len() as i64;
3005 Some(match (method, args) {
3006 ("isEmpty", []) => Lit::Bool(s.is_empty()),
3007 ("intern", []) => Lit::Str(s.to_string()),
3009 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
3010 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
3011 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
3012 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
3013 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
3014 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
3015 ("equalsIgnoreCase", [Lit::Str(p)]) => {
3016 Lit::Bool(s.to_lowercase() == p.to_lowercase())
3017 }
3018 ("indexOf", [Lit::Char(c)]) => Lit::Int(
3019 s.find(*c).map(|i| i as i64).unwrap_or(-1),
3020 ),
3021 ("indexOf", [Lit::Str(p)]) => Lit::Int(
3022 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
3023 ),
3024 ("length", []) => Lit::Int(n),
3025 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
3026 Lit::Char(chars[*i as usize])
3027 }
3028 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
3029 Lit::Str(s.chars().skip(*b as usize).collect())
3030 }
3031 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
3032 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
3033 }
3034 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
3035 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
3036 }
3037 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
3038 Lit::Str(s.replace(a.as_str(), b))
3039 }
3040 ("hashCode", []) => Lit::Int(
3042 s.chars()
3043 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
3044 as i64,
3045 ),
3046 _ => return None,
3047 })
3048}
3049
3050fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
3051 let cls = cls.rsplit('.').next().unwrap_or(cls);
3052 Some(match (cls, method, args) {
3053 ("Integer", "parseInt", [Lit::Str(s)]) => {
3054 Lit::Int(s.trim().parse::<i64>().ok()?)
3055 }
3056 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
3057 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
3058 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
3059 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
3060 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
3061 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
3062 Lit::Bool(s.eq_ignore_ascii_case("true"))
3063 }
3064 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
3065 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
3066 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
3067 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
3068 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
3069 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
3070 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
3071 let fmt = match &fmt_args[0] {
3072 Lit::Str(s) => s.clone(),
3073 _ => return None,
3074 };
3075 let out = eval_string_format(&fmt, &fmt_args[1..])?;
3076 Lit::Str(out)
3077 }
3078 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
3079 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
3080 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
3081 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
3082 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
3083 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
3084 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
3085 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
3086 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
3087 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
3088 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
3089 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
3090 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
3091 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
3092 _ => return None,
3093 })
3094}
3095
3096fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
3098 let mut out = String::new();
3099 let mut it = fmt.chars().peekable();
3100 let mut arg_i = 0usize;
3101 while let Some(c) = it.next() {
3102 if c != '%' {
3103 out.push(c);
3104 continue;
3105 }
3106 match it.next()? {
3107 '%' => out.push('%'),
3108 's' => {
3109 let a = args.get(arg_i)?;
3110 arg_i += 1;
3111 match a {
3112 Lit::Str(s) => out.push_str(s),
3113 Lit::Char(ch) => out.push(*ch),
3114 Lit::Bool(b) => out.push_str(&b.to_string()),
3115 _ => out.push_str(&lit_debug_str(a)),
3116 }
3117 }
3118 'd' => {
3119 let a = args.get(arg_i)?;
3120 arg_i += 1;
3121 match a {
3122 Lit::Int(v) => out.push_str(&v.to_string()),
3123 Lit::Long(v) => out.push_str(&v.to_string()),
3124 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
3125 _ => return None,
3126 }
3127 }
3128 _ => return None,
3130 }
3131 }
3132 if arg_i != args.len() {
3133 return None; }
3135 Some(out)
3136}
3137
3138fn lit_debug_str(l: &Lit) -> String {
3139 match l {
3140 Lit::Int(v) => v.to_string(),
3141 Lit::Long(v) => v.to_string(),
3142 _ => "?".into(),
3143 }
3144}
3145
3146fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
3169 match ast.data(m) {
3170 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
3171 _ => None,
3172 }
3173}
3174
3175pub struct UrlDecodeFold;
3176
3177impl Rule<JavaAst> for UrlDecodeFold {
3178 fn name(&self) -> &'static str {
3179 "url_decode_fold"
3180 }
3181 fn kinds(&self) -> &'static [NodeKind] {
3182 &[NodeKind::Call]
3183 }
3184 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3185 let lang = ctx.lang;
3186 let ch = lang.children(id).to_vec();
3189 if ch.len() != 3 {
3190 return None; }
3192 let callee = ch[0];
3193 if lang.kind(callee) != NodeKind::Member {
3194 return None;
3195 }
3196 if member_name(lang, callee).as_deref() != Some("decode") {
3197 return None;
3198 }
3199 let recv = *lang.children(callee).first()?;
3201 let recv_name = match lang.kind(recv) {
3202 NodeKind::Member => member_name(lang, recv)?,
3203 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
3204 _ => return None,
3205 };
3206 if !recv_name.ends_with("URLDecoder") {
3207 return None;
3208 }
3209 let s_lit = match lang.literal(ch[1])? {
3210 LitRef::Str(s) => s.to_string(),
3211 _ => return None,
3212 };
3213 let cs = match lang.literal(ch[2])? {
3215 LitRef::Str(s) => s.to_string(),
3216 _ => return None,
3217 };
3218 if cs != "UTF-8" {
3219 return None;
3220 }
3221 if s_lit.len() > 8192 {
3223 return None;
3224 }
3225 let decoded = url_decode(&s_lit)?;
3226 if decoded.chars().any(|c| {
3228 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
3229 }) {
3230 return None;
3231 }
3232 let lit = lang.lit(Lit::Str(decoded));
3233 Some(Edit::Replace { target: id, with: lit })
3234 }
3235}
3236
3237fn url_decode(s: &str) -> Option<String> {
3240 let b = s.as_bytes();
3241 let mut out: Vec<u8> = Vec::with_capacity(b.len());
3242 let mut i = 0usize;
3243 while i < b.len() {
3244 match b[i] {
3245 b'+' => {
3246 out.push(b' ');
3247 i += 1;
3248 }
3249 b'%' => {
3250 if i + 3 > b.len() {
3251 return None;
3252 }
3253 let hi = hex_val(b[i + 1])?;
3254 let lo = hex_val(b[i + 2])?;
3255 out.push(hi * 16 + lo);
3256 i += 3;
3257 }
3258 c => {
3259 out.push(c);
3260 i += 1;
3261 }
3262 }
3263 }
3264 String::from_utf8(out).ok()
3265}
3266
3267fn hex_val(c: u8) -> Option<u8> {
3268 match c {
3269 b'0'..=b'9' => Some(c - b'0'),
3270 b'a'..=b'f' => Some(c - b'a' + 10),
3271 b'A'..=b'F' => Some(c - b'A' + 10),
3272 _ => None,
3273 }
3274}
3275
3276pub struct Base64NewStringFold;
3277
3278impl Rule<JavaAst> for Base64NewStringFold {
3279 fn name(&self) -> &'static str {
3280 "base64_new_string_fold"
3281 }
3282 fn kinds(&self) -> &'static [NodeKind] {
3283 &[NodeKind::New]
3284 }
3285 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3286 let root = ctx.root();
3287 let lang = ctx.lang;
3288 let NodeData::New { ty, .. } = lang.data(id) else {
3289 return None;
3290 };
3291 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3292 return None;
3293 }
3294 if lang.has_string_identity_compare(root) {
3296 return None;
3297 }
3298 let args = lang.children(id).to_vec();
3299 if args.len() != 1 {
3300 return None;
3301 }
3302 if lang.kind(args[0]) != NodeKind::Call {
3304 return None;
3305 }
3306 let dc = lang.children(args[0]).to_vec();
3307 if dc.len() != 2 {
3308 return None;
3309 }
3310 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3311 return None;
3312 };
3313 if lang.sn(*dn) != "decode" {
3314 return None;
3315 }
3316 let recv = lang.children(dc[0])[0];
3318 if lang.kind(recv) != NodeKind::Call {
3319 return None;
3320 }
3321 let gc = lang.children(recv).to_vec();
3322 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3323 return None;
3324 };
3325 let url_safe = match lang.sn(*gn) {
3326 "getDecoder" => false,
3327 "getUrlDecoder" => true,
3328 _ => return None,
3329 };
3330 let owner = lang.children(gc[0])[0];
3332 let owner_ok = match lang.data(owner) {
3333 NodeData::Member { name } => lang.sn(*name) == "Base64",
3334 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3335 };
3336 if !owner_ok {
3337 return None;
3338 }
3339 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3340 return None;
3341 };
3342 let bytes = base64_decode(b64, url_safe)?;
3343 let text = String::from_utf8(bytes).ok()?;
3344 let with = lang.build_str(&text);
3345 Some(Edit::Replace {
3346 target: id,
3347 with,
3348 })
3349 }
3350}
3351
3352fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3354 let val = |c: char| -> Option<u32> {
3355 match c {
3356 'A'..='Z' => Some(c as u32 - 'A' as u32),
3357 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3358 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3359 '+' if !url_safe => Some(62),
3360 '/' if !url_safe => Some(63),
3361 '-' if url_safe => Some(62),
3362 '_' if url_safe => Some(63),
3363 _ => None,
3364 }
3365 };
3366 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3367 if clean.len() % 4 == 1 {
3368 return None;
3369 }
3370 let mut out = Vec::new();
3371 let mut buf = 0u32;
3372 let mut bits = 0u32;
3373 for c in clean.chars() {
3374 buf = (buf << 6) | val(c)?;
3375 bits += 6;
3376 if bits >= 8 {
3377 bits -= 8;
3378 out.push((buf >> bits) as u8);
3379 buf &= (1 << bits) - 1;
3380 }
3381 }
3382 Some(out)
3383}
3384
3385
3386pub struct CffRecover;
3408
3409struct CffCase {
3410 body: Vec<JavaId>,
3411 tail: CffTail,
3412}
3413
3414enum CffTail {
3415 Goto(i64),
3416 Cond { cond: JavaId, a: i64, b: i64 },
3417 Term(JavaId),
3418 Exit,
3419}
3420
3421#[derive(Clone, Copy, PartialEq, Debug)]
3422enum Flow {
3423 Done,
3424 Stop,
3425 Cycle(usize),
3426 Cond(i64),
3427 External(i64),
3428}
3429
3430struct CffCtx<'a> {
3431 lang: &'a mut JavaAst,
3432 cases: std::collections::HashMap<i64, CffCase>,
3433 default_term: Option<Vec<JavaId>>,
3435 used_default: bool,
3436 reached: std::collections::HashSet<i64>,
3438 emitted: std::collections::HashSet<i64>,
3440}
3441
3442impl Rule<JavaAst> for CffRecover {
3443 fn name(&self) -> &'static str {
3444 "cff_recover"
3445 }
3446 fn kinds(&self) -> &'static [NodeKind] {
3447 &[NodeKind::While]
3448 }
3449 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3450 let parent = ctx.parent(id)?;
3451 let idx = ctx.index(id)?;
3452 let lang = ctx.lang;
3453 if lang.kind(id) != NodeKind::While {
3454 return None;
3455 }
3456 let wch = lang.children(id).to_vec();
3457 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3458 return None;
3459 }
3460 if lang.kind(wch[1]) != NodeKind::Block {
3461 return None;
3462 }
3463 let body_ch = lang.children(wch[1]).to_vec();
3464 let (switch, exit_if) = match body_ch.len() {
3465 1 => (body_ch[0], None),
3466 2 => (body_ch[0], Some(body_ch[1])),
3467 _ => return None,
3468 };
3469 if lang.kind(switch) != NodeKind::Switch {
3470 return None;
3471 }
3472 let sch = lang.children(switch).to_vec();
3473 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3474 return None;
3475 }
3476 let v = lang.var_name(sch[0])?.to_string();
3477
3478 let sentinel = match exit_if {
3480 None => None,
3481 Some(ei) => {
3482 if lang.kind(ei) != NodeKind::If {
3483 return None;
3484 }
3485 let ich = lang.children(ei).to_vec();
3486 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3487 return None;
3488 }
3489 let cch = lang.children(ich[0]).to_vec();
3490 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3491 && lang.var_name(cch[0]) == Some(v.as_str())
3492 {
3493 lang.literal(cch[1]).and_then(|x| x.as_int())
3494 } else if lang.kind(cch[1]) == NodeKind::VarRef
3495 && lang.var_name(cch[1]) == Some(v.as_str())
3496 {
3497 lang.literal(cch[0]).and_then(|x| x.as_int())
3498 } else {
3499 return None;
3500 }?;
3501 let then_ok = match lang.kind(ich[1]) {
3502 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3503 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3504 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3505 }
3506 _ => false,
3507 };
3508 if !then_ok {
3509 return None;
3510 }
3511 Some(sent)
3512 }
3513 };
3514
3515 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3517 let mut default_term: Option<Vec<JavaId>> = None;
3518 for &c in &sch[1..] {
3519 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3520 return None;
3521 };
3522 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3523 let ch = lang.children(c).to_vec();
3524 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3525 if !arrow
3527 && stmts
3528 .last()
3529 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3530 {
3531 stmts.pop();
3532 }
3533 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3534 let mut inner = lang.children(stmts[0]).to_vec();
3535 if !arrow
3536 && inner
3537 .last()
3538 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3539 {
3540 inner.pop();
3541 }
3542 stmts = inner;
3543 }
3544 if is_default {
3545 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3546 default_term = Some(stmts);
3547 continue;
3548 }
3549 return None;
3550 }
3551 if labels != 1 {
3553 return None;
3554 }
3555 let state = match lang.literal(ch[0]) {
3556 Some(LitRef::Int(v)) => v,
3557 _ => return None,
3558 };
3559 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3560 for &st in &stmts {
3562 if subtree_has_var(lang, st, &v) {
3563 return None;
3564 }
3565 }
3566 cases.insert(state, CffCase { body: stmts, tail });
3567 }
3568 if cases.is_empty() {
3569 return None;
3570 }
3571
3572 if idx == 0 {
3575 return None;
3576 }
3577 let block_ch = lang.children(parent).to_vec();
3578 let mut init = None;
3579 let mut init_idx = idx;
3580 {
3581 let mut up = idx;
3582 for _ in 0..8 {
3583 if up == 0 {
3584 break;
3585 }
3586 up -= 1;
3587 let st = block_ch[up];
3588 match scan_cff_event(lang, st, &v) {
3589 Some(CffEvent::Read) => return None,
3590 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3591 init = parse_cff_init(lang, &v, st);
3592 init_idx = up;
3593 break;
3594 }
3595 None => continue,
3596 }
3597 }
3598 }
3599 let init = init?;
3600
3601 for &st in &block_ch[idx + 1..] {
3603 if subtree_has_var(lang, st, &v) {
3604 return None;
3605 }
3606 }
3607
3608 let mut to_exit: Vec<i64> = Vec::new();
3610 for (k, ci) in cases.iter() {
3611 if let CffTail::Goto(g) = ci.tail {
3612 if !cases.contains_key(&g) {
3613 if !(sentinel == Some(g) || default_term.is_some()) {
3614 return None;
3615 }
3616 to_exit.push(*k);
3617 }
3618 }
3619 }
3620 for k in to_exit {
3621 if let Some(ci) = cases.get_mut(&k) {
3622 ci.tail = CffTail::Exit;
3623 }
3624 }
3625
3626 let mut c = CffCtx {
3627 lang,
3628 cases,
3629 default_term,
3630 used_default: false,
3631 reached: Default::default(),
3632 emitted: Default::default(),
3633 };
3634 let stmts = cff_run(&mut c, init)?;
3635
3636 if stmts.is_empty() {
3639 return None;
3640 }
3641
3642 Some(Edit::Multi(vec![
3645 Edit::Splice {
3646 node: parent,
3647 index: idx,
3648 remove: 1,
3649 insert: stmts,
3650 },
3651 Edit::Delete {
3652 node: block_ch[init_idx],
3653 },
3654 ]))
3655 }
3656}
3657
3658fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3659 let last = *stmts.last()?;
3660 if lang.kind(last) == NodeKind::Return {
3661 stmts.pop();
3662 return Some(CffTail::Term(last));
3663 }
3664 let assign_of = |n: JavaId| -> Option<JavaId> {
3665 let inner = match lang.kind(n) {
3666 NodeKind::Assign => n,
3667 NodeKind::ExprStmt => {
3668 let ch = lang.children(n);
3669 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3670 ch[0]
3671 } else {
3672 return None;
3673 }
3674 }
3675 _ => return None,
3676 };
3677 if lang.assign_op(inner).is_some() {
3678 return None;
3679 }
3680 let ch = lang.children(inner).to_vec();
3681 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3682 return None;
3683 }
3684 Some(inner)
3685 };
3686 if let Some(assign) = assign_of(last) {
3687 let value = lang.children(assign)[1];
3688 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3689 stmts.pop();
3690 return Some(CffTail::Goto(k));
3691 }
3692 if lang.kind(value) == NodeKind::Ternary {
3693 let tch = lang.children(value).to_vec();
3694 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3695 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3696 stmts.pop();
3697 return Some(CffTail::Cond { cond: tch[0], a, b });
3698 }
3699 return None;
3700 }
3701 if lang.kind(last) == NodeKind::If {
3702 let ich = lang.children(last).to_vec();
3703 if ich.len() != 3 {
3704 return None;
3705 }
3706 let arm = |n: JavaId| -> Option<i64> {
3707 let target = match lang.kind(n) {
3708 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3709 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3710 _ => return None,
3711 };
3712 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3713 };
3714 let a = arm(ich[1])?;
3715 let b = arm(ich[2])?;
3716 stmts.pop();
3717 return Some(CffTail::Cond { cond: ich[0], a, b });
3718 }
3719 None
3720}
3721
3722
3723enum CffEvent {
3725 Read,
3726 Write,
3727 Decl,
3729}
3730
3731fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3732 match lang.kind(node) {
3733 NodeKind::VarRef => {
3734 if lang.var_name(node) == Some(name) {
3735 Some(CffEvent::Read)
3736 } else {
3737 None
3738 }
3739 }
3740 NodeKind::VarDecl => {
3741 if lang.var_name(node) == Some(name) {
3742 Some(CffEvent::Decl)
3743 } else {
3744 for &c in lang.children(node) {
3745 if let Some(e) = scan_cff_event(lang, c, name) {
3746 return Some(e);
3747 }
3748 }
3749 None
3750 }
3751 }
3752 NodeKind::Assign => {
3753 let ch = lang.children(node);
3754 if let Some(&val) = ch.get(1) {
3755 if let Some(e) = scan_cff_event(lang, val, name) {
3756 if matches!(e, CffEvent::Read) {
3757 return Some(e);
3758 }
3759 }
3760 }
3761 if lang.assign_op(node).is_none() {
3762 if let Some(&t) = ch.first() {
3763 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3764 return Some(CffEvent::Write);
3765 }
3766 }
3767 }
3768 None
3769 }
3770 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3771 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3772 NodeKind::Binary => {
3773 let op = lang.bin_op(node)?;
3774 let ch = lang.children(node);
3775 if op.is_short_circuit() {
3776 scan_cff_event(lang, *ch.first()?, name)
3777 } else {
3778 for &c in ch {
3779 if let Some(e) = scan_cff_event(lang, c, name) {
3780 return Some(e);
3781 }
3782 }
3783 None
3784 }
3785 }
3786 _ => {
3787 for &c in lang.children(node) {
3788 if let Some(e) = scan_cff_event(lang, c, name) {
3789 return Some(e);
3790 }
3791 }
3792 None
3793 }
3794 }
3795}
3796
3797fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3798 match lang.kind(stmt) {
3799 NodeKind::VarDecl => {
3800 let ch = lang.children(stmt).to_vec();
3801 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3802 return None;
3803 }
3804 lang.literal(ch[0]).and_then(|x| x.as_int())
3805 }
3806 NodeKind::Assign => {
3807 if lang.assign_op(stmt).is_some() {
3808 return None;
3809 }
3810 let ch = lang.children(stmt).to_vec();
3811 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3812 return None;
3813 }
3814 lang.literal(ch[1]).and_then(|x| x.as_int())
3815 }
3816 NodeKind::ExprStmt => {
3817 let ch = lang.children(stmt).to_vec();
3818 if ch.len() == 1 {
3819 parse_cff_init(lang, v, ch[0])
3820 } else {
3821 None
3822 }
3823 }
3824 _ => None,
3825 }
3826}
3827
3828fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3829 for (s, stmts) in pairs {
3830 c.emitted.insert(*s);
3831 out.extend(stmts.iter().copied());
3832 }
3833}
3834
3835fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3836 for (s, stmts) in pairs {
3837 if *s == m {
3838 break;
3839 }
3840 out.extend(stmts.iter().copied());
3841 }
3842}
3843
3844fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3845 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3846 pairs[pos..].to_vec()
3847}
3848
3849fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3851 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3852 let mut cur = start;
3853 let mut guard = 0usize;
3854 loop {
3855 guard += 1;
3856 if guard > 10_000 {
3857 return None;
3858 }
3859 if Some(cur) == stop {
3860 return Some((path, Flow::Stop));
3861 }
3862 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3863 return Some((path, Flow::Cycle(i)));
3864 }
3865 if c.emitted.contains(&cur) {
3866 return Some((path, Flow::External(cur)));
3867 }
3868 c.reached.insert(cur);
3869 let ci = c.cases.get(&cur)?;
3870 let mut body = ci.body.clone();
3871 match ci.tail {
3872 CffTail::Goto(k) => {
3873 path.push((cur, body));
3874 cur = k;
3875 }
3876 CffTail::Term(r) => {
3877 body.push(r);
3878 path.push((cur, body));
3879 return Some((path, Flow::Done));
3880 }
3881 CffTail::Exit => {
3882 if c.default_term.is_some() {
3883 if let Some(dt) = c.default_term.clone() {
3884 body.extend(dt);
3885 c.used_default = true;
3886 }
3887 }
3888 path.push((cur, body));
3889 return Some((path, Flow::Done));
3890 }
3891 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3892 }
3893 }
3894}
3895
3896fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3898 let (path, flow) = collect(c, init, None)?;
3899 cff_structure(c, path, flow)
3900}
3901
3902fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3904 let (path, flow) = collect(c, start, None)?;
3905 cff_structure(c, path, flow)
3906}
3907
3908fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3912 let mut stmts: Vec<JavaId> = Vec::new();
3913 loop {
3914 match flow {
3915 Flow::Done => {
3916 flatten(&path, &mut stmts, c);
3917 return Some(stmts);
3918 }
3919 Flow::Cycle(i) => {
3920 flatten(&path[..i], &mut stmts, c);
3921 let mut suffix = Vec::new();
3922 flatten(&path[i..], &mut suffix, c);
3923 let inner = c.lang.build_block(suffix);
3924 let cond = c.lang.build_bool(true);
3925 let w = c.lang.while_(cond, inner);
3926 stmts.push(w);
3927 return Some(stmts);
3928 }
3929 Flow::Stop | Flow::External(_) => return None,
3930 Flow::Cond(state) => {
3931 flatten(&path, &mut stmts, c);
3932 path.clear();
3933 let ci = c.cases.get(&state)?;
3934 stmts.extend(ci.body.iter().copied());
3935 c.emitted.insert(state);
3936 let CffTail::Cond { cond, a, b } = ci.tail else {
3937 return None;
3938 };
3939 let (pa, fa) = collect(c, a, Some(state))?;
3941 if fa == Flow::Stop {
3942 let mut lb = Vec::new();
3943 flatten(&pa, &mut lb, c);
3944 let inner = c.lang.build_block(lb);
3945 let w = c.lang.while_(cond, inner);
3946 stmts.push(w);
3947 let (p2, f2) = collect(c, b, None)?;
3948 path = p2;
3949 flow = f2;
3950 continue;
3951 }
3952 let (pb, fb) = collect(c, b, Some(state))?;
3953 if fb == Flow::Stop {
3954 let mut lb = Vec::new();
3955 flatten(&pb, &mut lb, c);
3956 let inner = c.lang.build_block(lb);
3957 let nc = c.lang.build_unary(UnOp::Not, cond);
3958 let w = c.lang.while_(nc, inner);
3959 stmts.push(w);
3960 let (p2, f2) = collect(c, a, None)?;
3961 path = p2;
3962 flow = f2;
3963 continue;
3964 }
3965 let common = pa
3967 .iter()
3968 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3969 .map(|(s, _)| *s);
3970 let same_end = match (fa, fb) {
3971 (Flow::Done, Flow::Done) => true,
3972 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3973 _ => false,
3974 };
3975 match common {
3976 Some(m) if same_end => {
3977 let mut pre_a = Vec::new();
3978 let mut pre_b = Vec::new();
3979 flatten_prefix(&pa, m, &mut pre_a);
3980 flatten_prefix(&pb, m, &mut pre_b);
3981 let ba = c.lang.build_block(pre_a);
3982 let bb = c.lang.build_block(pre_b);
3983 let ifs = c.lang.if_(cond, ba, Some(bb));
3984 stmts.push(ifs);
3985 path = suffix_pairs(&pa, m);
3986 flow = fa;
3987 continue;
3988 }
3989 Some(_) => return None,
3990 None => {
3991 let mut fa_stmts = Vec::new();
3993 let mut fb_stmts = Vec::new();
3994 flatten(&pa, &mut fa_stmts, c);
3995 flatten(&pb, &mut fb_stmts, c);
3996 match (fa, fb) {
3997 (Flow::Done, Flow::Done) => {
3998 let ba = c.lang.build_block(fa_stmts);
3999 let bb = c.lang.build_block(fb_stmts);
4000 let ifs = c.lang.if_(cond, ba, Some(bb));
4001 stmts.push(ifs);
4002 return Some(stmts);
4003 }
4004 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
4006 let ba = c.lang.build_block(fa_stmts);
4007 let bb = c.lang.build_block(fb_stmts);
4008 let ifs = c.lang.if_(cond, ba, Some(bb));
4009 stmts.push(ifs);
4010 let (p2, f2) = collect(c, x, None)?;
4011 path = p2;
4012 flow = f2;
4013 continue;
4014 }
4015 (Flow::Done, Flow::Cond(x)) => {
4017 let cont = cff_run_from(c, x)?;
4018 fb_stmts.extend(cont);
4019 let ba = c.lang.build_block(fa_stmts);
4020 let bb = c.lang.build_block(fb_stmts);
4021 let ifs = c.lang.if_(cond, ba, Some(bb));
4022 stmts.push(ifs);
4023 return Some(stmts);
4024 }
4025 (Flow::Cond(x), Flow::Done) => {
4026 let cont = cff_run_from(c, x)?;
4027 fa_stmts.extend(cont);
4028 let ba = c.lang.build_block(fa_stmts);
4029 let bb = c.lang.build_block(fb_stmts);
4030 let ifs = c.lang.if_(cond, ba, Some(bb));
4031 stmts.push(ifs);
4032 return Some(stmts);
4033 }
4034 _ => return None,
4035 }
4036 }
4037 }
4038 }
4039 }
4040 }
4041}
4042
4043
4044
4045pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
4055 ast.const_fields.clear();
4056 ast.const_scalars.clear();
4057 ast.inline_methods.clear();
4058 ast.field_types.clear();
4059 ast.final_fields.clear();
4060 ast.raw_generic_names = {
4063 let mut m: std::collections::HashSet<String> = [
4064 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
4065 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
4066 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
4067 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
4068 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
4069 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
4070 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
4071 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
4072 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
4073 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
4075 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
4076 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
4077 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
4078 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
4079 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
4080 ]
4081 .iter()
4082 .map(|x| x.to_string())
4083 .collect();
4084 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
4085 if !ty.ty_params.trim().is_empty() {
4086 m.insert(ty.name.clone());
4087 }
4088 for mem in &ty.members {
4089 if let Member::Type(t) = mem {
4090 collect_generics(t, m);
4091 }
4092 }
4093 }
4094 for ty in &unit.types {
4095 collect_generics(ty, &mut m);
4096 }
4097 m
4098 };
4099 ast.unit_shadows_lang = {
4100 fn decl_shadows(ty: &TypeDecl) -> bool {
4101 if ty.name == "String" || ty.name == "StringBuilder" {
4102 return true;
4103 }
4104 ty.members.iter().any(|m| match m {
4105 Member::Type(t) => decl_shadows(t),
4106 _ => false,
4107 })
4108 }
4109 unit.types.iter().any(decl_shadows)
4110 };
4111
4112 let mut shadowed: std::collections::HashSet<String> = Default::default();
4117 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
4118 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
4119 let mut bodies: Vec<JavaId> = Vec::new();
4120 for ty in &unit.types {
4121 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
4122 }
4123 for ty in &unit.types {
4126 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
4127 }
4128 for &root in &bodies {
4132 let mut stack = vec![root];
4133 while let Some(n) = stack.pop() {
4134 if ast.kind(n) == NodeKind::VarDecl {
4135 if let Some(nm) = ast.var_name(n) {
4136 shadowed.insert(nm.to_string());
4137 }
4138 }
4139 match ast.data(n) {
4140 NodeData::Lambda { params_raw } => {
4146 for nm in identifier_words(params_raw) {
4147 shadowed.insert(nm);
4148 }
4149 }
4150 NodeData::ForEach { name, .. } | NodeData::Catch { name, .. } => {
4151 shadowed.insert(name.clone());
4152 }
4153 NodeData::InstanceOf { bind: Some(b), .. } => {
4154 shadowed.insert(b.clone());
4155 }
4156 NodeData::Assign { .. } => {
4157 let t = ast.children(n)[0];
4158 if ast.kind(t) == NodeKind::Member {
4159 if let NodeData::Member { name: sym } = ast.data(t) {
4160 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
4161 }
4162 }
4163 let mut ts = vec![t];
4164 while let Some(x) = ts.pop() {
4165 if ast.kind(x) == NodeKind::VarRef {
4166 if let Some(nm) = ast.var_name(x) {
4167 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
4168 }
4169 }
4170 for &c in ast.children(x) {
4171 ts.push(c);
4172 }
4173 }
4174 }
4175 NodeData::Unary { op } if op.is_incdec() => {
4176 let t = ast.children(n)[0];
4177 if ast.kind(t) == NodeKind::VarRef {
4178 if let Some(nm) = ast.var_name(t) {
4179 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
4180 }
4181 }
4182 }
4183 _ => {}
4184 }
4185 for &c in ast.children(n) {
4186 stack.push(c);
4187 }
4188 }
4189 }
4190
4191 for ty in &unit.types {
4193 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
4195 collect_own_clinit_lits(ast, ty, &mut own_lits);
4196 for m in &ty.members {
4197 if let Member::Field { mods, ty: fty, declarators } = m {
4198 let words: Vec<&str> = mods.split_whitespace().collect();
4199 let is_static = words.contains(&"static");
4200 let is_final = words.contains(&"final");
4201 let is_private = words.contains(&"private");
4202 for d in declarators {
4203 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
4207 continue;
4208 }
4209 let init = match d.init {
4210 Some(init) => {
4211 if !is_final
4213 || !is_const_init(ast, init)
4214 || shadowed.contains(&d.name)
4215 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
4216 {
4217 continue;
4218 }
4219 init
4220 }
4221 None => {
4222 if !is_static
4229 || (!is_final && !is_private)
4230 || shadowed.contains(&d.name)
4231 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
4232 {
4233 continue;
4234 }
4235 match own_lits.get(&d.name) {
4236 Some(&init) if is_const_init(ast, init) => init,
4237 _ => continue,
4238 }
4239 }
4240 };
4241 if ast.const_fields.contains_key(&d.name) {
4243 ast.const_fields.remove(&d.name);
4244 ast.const_scalars.remove(&d.name);
4245 } else {
4246 let scalar_ok = scalar_lit_of(ast, init)
4247 .is_some_and(|l| lit_type_matches_decl(fty, l));
4248 ast.const_fields.insert(d.name.clone(), init);
4249 if scalar_ok {
4250 ast.const_scalars.insert(d.name.clone());
4251 }
4252 }
4253 }
4254 }
4255 }
4256 }
4257
4258 {
4265 let identity_assert = [
4266 "assertSame",
4267 "assertNotSame",
4268 "isSameInstanceAs",
4269 "isNotSameInstanceAs",
4270 "referenceEquals",
4271 "same",
4272 ];
4273 let mut hit = false;
4274 for root in &bodies {
4275 let mut stack = vec![*root];
4276 while let Some(n) = stack.pop() {
4277 if let NodeData::Call = ast.data(n) {
4278 if let Some(&callee) = ast.children(n).first() {
4279 let nm = match ast.data(callee) {
4280 NodeData::Member { name } => ast.sn(*name),
4281 NodeData::VarRef { name } => ast.sn(*name),
4282 _ => "",
4283 };
4284 if identity_assert.contains(&nm) {
4285 hit = true;
4286 break;
4287 }
4288 }
4289 }
4290 for &c in ast.children(n) {
4291 stack.push(c);
4292 }
4293 }
4294 if hit {
4295 break;
4296 }
4297 }
4298 ast.unit_string_identity =
4299 ast.any_string_identity(&bodies) || hit;
4300 }
4301
4302 let mut method_name_count: std::collections::HashMap<String, usize> =
4312 std::collections::HashMap::new();
4313 for_each_type(unit, &mut |ty: &TypeDecl| {
4314 for m in &ty.members {
4315 if let Member::Method { name, .. } = m {
4316 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4317 }
4318 }
4319 });
4320 for ty in &unit.types {
4321 for m in &ty.members {
4322 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4323 let words: Vec<&str> = mods.split_whitespace().collect();
4324 let non_overridable = words.contains(&"private")
4325 || words.contains(&"static")
4326 || words.contains(&"final");
4327 if !non_overridable {
4328 continue;
4329 }
4330 let ch = ast.children(*b).to_vec();
4331 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4332 if let Some(&expr) = ast.children(ch[0]).first() {
4333 if params.len() <= 3
4334 && !subtree_has_anon(ast, expr)
4339 && !params.iter().any(|p| p.varargs)
4343 && !subtree_calls_self(ast, expr, name)
4344 && !subtree_has_type_sensitive(ast, expr)
4350 && method_name_count.get(name).copied() == Some(1)
4351 {
4352 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4353 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4354 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4355 }
4356 }
4357 }
4358 }
4359 }
4360 }
4361}
4362
4363fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4365 match ast.kind(init) {
4366 NodeKind::Literal => true,
4367 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4368 NodeKind::NewArray => {
4369 let ch = ast.children(init).to_vec();
4370 ch.len() == 1
4371 && ast.kind(ch[0]) == NodeKind::ArrayLit
4372 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4373 }
4374 _ => false,
4375 }
4376}
4377
4378fn identifier_words(s: &str) -> Vec<String> {
4380 let mut out = Vec::new();
4381 let mut cur = String::new();
4382 for ch in s.chars() {
4383 if ch.is_alphanumeric() || ch == '_' || ch == '$' {
4384 cur.push(ch);
4385 } else if !cur.is_empty() {
4386 out.push(std::mem::take(&mut cur));
4387 }
4388 }
4389 if !cur.is_empty() {
4390 out.push(cur);
4391 }
4392 out
4393}
4394
4395fn collect_bodies_recursive(
4399 ty: &TypeDecl,
4400 bodies: &mut Vec<JavaId>,
4401 shadowed: &mut std::collections::HashSet<String>,
4402 field_decls: &mut std::collections::HashMap<String, u32>,
4403) {
4404 if ty.kind == TypeKind::Enum {
4412 for c in &ty.enum_constants {
4413 if c.trim() == "," {
4414 continue;
4415 }
4416 let last = c.split('(').next().unwrap_or(c)
4417 .split_whitespace()
4418 .next_back()
4419 .unwrap_or("");
4420 let name: String = last
4421 .chars()
4422 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4423 .collect();
4424 if !name.is_empty() {
4425 *field_decls.entry(name).or_insert(0) += 1;
4426 }
4427 }
4428 }
4429 for m in &ty.members {
4430 match m {
4431 Member::Method { body: Some(b), params, .. }
4432 | Member::Constructor { body: Some(b), params, .. } => {
4433 bodies.push(*b);
4434 for p in params {
4435 shadowed.insert(p.name.clone());
4436 }
4437 }
4438 Member::Initializer { body, .. } => bodies.push(*body),
4439 Member::Field { declarators, .. } => {
4440 for d in declarators {
4441 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4442 if let Some(init) = d.init {
4443 bodies.push(init);
4444 }
4445 }
4446 }
4447 Member::Type(t) => {
4448 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4449 }
4450 _ => {}
4451 }
4452 }
4453}
4454
4455fn collect_field_types(
4457 ty: &TypeDecl,
4458 out: &mut std::collections::HashMap<String, JType>,
4459 finals: &mut std::collections::HashSet<String>,
4460) {
4461 for m in &ty.members {
4462 match m {
4463 Member::Field { mods, ty: fty, declarators } => {
4464 let is_final = mods.split_whitespace().any(|w| w == "final");
4465 for d in declarators {
4466 if out.contains_key(&d.name) {
4467 out.remove(&d.name);
4468 finals.remove(&d.name);
4469 } else {
4470 out.insert(d.name.clone(), fty.clone());
4471 if is_final {
4472 finals.insert(d.name.clone());
4473 }
4474 }
4475 }
4476 }
4477 Member::Type(t) => collect_field_types(t, out, finals),
4478 _ => {}
4479 }
4480 }
4481}
4482
4483fn collect_own_clinit_lits(
4488 ast: &JavaAst,
4489 ty: &TypeDecl,
4490 out: &mut std::collections::HashMap<String, JavaId>,
4491) {
4492 for m in &ty.members {
4493 if let Member::Initializer { is_static: true, body } = m {
4494 if ast.kind(*body) != NodeKind::Block {
4495 continue;
4496 }
4497 for &st in ast.children(*body) {
4498 let asg = match ast.kind(st) {
4499 NodeKind::ExprStmt => {
4500 let Some(&c) = ast.children(st).first() else {
4504 continue;
4505 };
4506 if ast.kind(c) == NodeKind::Assign {
4507 c
4508 } else {
4509 continue;
4510 }
4511 }
4512 NodeKind::Assign => st,
4513 _ => continue,
4514 };
4515 let ch = ast.children(asg).to_vec();
4516 if ast.kind(ch[0]) != NodeKind::VarRef {
4517 continue;
4518 }
4519 let Some(nm) = ast.var_name(ch[0]) else { continue };
4520 if is_const_init(ast, ch[1]) {
4521 out.insert(nm.to_string(), ch[1]);
4522 }
4523 }
4524 }
4525 }
4526}
4527
4528fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4530 if ast.kind(init) == NodeKind::Literal {
4531 ast.literal(init)
4532 } else {
4533 None
4534 }
4535}
4536
4537fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4542 use cure_engine::kind::LitRef;
4543 match (fty, lit) {
4544 (JType::Ref(r), LitRef::Str(_)) => {
4545 let base = r.split('<').next().unwrap_or(r).trim();
4546 base == "String" || base == "java.lang.String"
4547 }
4548 (JType::Char, LitRef::Char(_)) => true,
4549 (JType::Bool, LitRef::Bool(_)) => true,
4550 (JType::Int, LitRef::Int(_)) => true,
4551 (JType::Long, LitRef::Long(_)) => true,
4552 (JType::Float, LitRef::Float(_)) => true,
4553 (JType::Double, LitRef::Double(_)) => true,
4554 _ => false,
4555 }
4556}
4557
4558fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4562 let mut stack = vec![root];
4563 while let Some(n) = stack.pop() {
4564 match ast.kind(n) {
4565 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4566 _ => {}
4567 }
4568 for &c in ast.children(n) {
4569 stack.push(c);
4570 }
4571 }
4572 false
4573}
4574
4575fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4576 let mut stack = vec![root];
4577 while let Some(n) = stack.pop() {
4578 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4579 return true;
4580 }
4581 for &c in ast.children(n) {
4582 stack.push(c);
4583 }
4584 }
4585 false
4586}
4587
4588fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4589 let mut stack = vec![root];
4590 while let Some(n) = stack.pop() {
4591 if ast.kind(n) == NodeKind::Call {
4592 let callee = ast.children(n)[0];
4593 if ast.var_name(callee) == Some(name) {
4594 return true;
4595 }
4596 if let NodeData::Member { name: m } = ast.data(callee) {
4601 if ast.sn(*m) == name {
4602 return true;
4603 }
4604 }
4605 }
4606 for &c in ast.children(n) {
4607 stack.push(c);
4608 }
4609 }
4610 false
4611}
4612
4613pub struct StaticArrayIndexFold;
4618
4619impl Rule<JavaAst> for StaticArrayIndexFold {
4620 fn name(&self) -> &'static str {
4621 "static_array_index_fold"
4622 }
4623 fn kinds(&self) -> &'static [NodeKind] {
4624 &[NodeKind::Index]
4625 }
4626 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4627 let lang = ctx.lang;
4628 if lang.kind(id) != NodeKind::Index {
4629 return None;
4630 }
4631 let ch = lang.children(id).to_vec();
4632 if lang.kind(ch[0]) != NodeKind::VarRef {
4633 return None;
4634 }
4635 let name = lang.var_name(ch[0])?.to_string();
4636 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4637 let init = *lang.const_fields.get(&name)?;
4638 let arr = match lang.kind(init) {
4639 NodeKind::ArrayLit => init,
4640 NodeKind::NewArray => {
4641 let ic = lang.children(init).to_vec();
4642 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4643 ic[0]
4644 } else {
4645 return None;
4646 }
4647 }
4648 _ => return None,
4649 };
4650 let elems = lang.children(arr).to_vec();
4651 if idx < 0 || idx >= elems.len() as i64 {
4652 return None;
4653 }
4654 let elem = lang.copy_subtree(elems[idx as usize]);
4655 Some(Edit::Replace {
4656 target: id,
4657 with: elem,
4658 })
4659 }
4660}
4661
4662pub struct DeadPureCall;
4742
4743impl Rule<JavaAst> for DeadPureCall {
4744 fn name(&self) -> &'static str {
4745 "dead_pure_call"
4746 }
4747 fn kinds(&self) -> &'static [NodeKind] {
4748 &[NodeKind::ExprStmt]
4749 }
4750 fn structural(&self) -> bool {
4751 true
4752 }
4753 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4754 let lang = ctx.lang;
4755 if lang.kind(id) != NodeKind::ExprStmt {
4756 return None;
4757 }
4758 let Some(&call) = lang.children(id).first() else { return None };
4759 if lang.kind(call) != NodeKind::Call {
4760 return None;
4761 }
4762 let cch = lang.children(call).to_vec();
4763 let Some(&callee) = cch.first() else { return None };
4764 let (recv, name) = match lang.data(callee) {
4767 NodeData::Member { name } => {
4768 let Some(&recv_expr) = lang.children(callee).first() else { return None };
4769 let recv_name = match lang.data(recv_expr) {
4772 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4773 NodeData::Member { name } => lang.sn(*name).to_string(),
4774 _ => return None,
4775 };
4776 if !recv_name
4777 .chars()
4778 .next()
4779 .is_some_and(|c| c.is_uppercase())
4780 {
4781 return None;
4782 }
4783 (recv_name, lang.sn(*name))
4784 }
4785 _ => return None,
4786 };
4787 let args = &cch[1..];
4788 let pure = match (recv.as_str(), name) {
4789 ("Long" | "Integer" | "Float" | "Double" | "Short" | "Byte" | "Character", "compare") => {
4790 args.iter().all(|&a| match lang.data(a) {
4794 NodeData::Literal(
4795 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_),
4796 ) => true,
4797 NodeData::Literal(Lit::NumRaw { .. }) => true,
4799 NodeData::VarRef { .. } => {
4800 let t = lang.var_type(a);
4801 matches!(
4802 t,
4803 Some(JType::Int | JType::Long | JType::Float | JType::Double | JType::Char
4804 | JType::Short | JType::Byte)
4805 )
4806 }
4807 _ => false,
4808 })
4809 }
4810 ("Objects", "toString") => args.len() == 1,
4811 _ => false,
4812 };
4813 if !pure {
4814 return None;
4815 }
4816 Some(Edit::Delete { node: id })
4817 }
4818}
4819
4820pub struct CtorDelegationHoist;
4821
4822fn hoist_resolve_arg(
4827 lang: &mut JavaAst,
4828 assign: &std::collections::HashMap<String, JavaId>,
4829 a: JavaId,
4830 visiting: &mut std::collections::HashSet<String>,
4831) -> Option<JavaId> {
4832 let name = match lang.data(a) {
4833 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4834 _ => return Some(a), };
4836 let Some(&expr) = assign.get(&name) else {
4838 return Some(a);
4839 };
4840 if !visiting.insert(name.clone()) {
4841 return None; }
4843 let r = hoist_subst(lang, assign, expr, visiting);
4844 visiting.remove(&name);
4845 r
4846}
4847
4848fn hoist_subst(
4850 lang: &mut JavaAst,
4851 assign: &std::collections::HashMap<String, JavaId>,
4852 e: JavaId,
4853 visiting: &mut std::collections::HashSet<String>,
4854) -> Option<JavaId> {
4855 match lang.data(e) {
4856 NodeData::VarRef { name } => {
4857 let n = lang.sn(*name).to_string();
4858 if let Some(&src) = assign.get(&n) {
4859 if !visiting.insert(n.clone()) {
4860 return None;
4861 }
4862 let r = hoist_subst(lang, assign, src, visiting);
4863 visiting.remove(&n);
4864 r
4865 } else {
4866 Some(e)
4867 }
4868 }
4869 _ => {
4870 let ch = lang.children(e).to_vec();
4871 let mut new_ch = Vec::with_capacity(ch.len());
4872 for c in ch {
4873 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4874 }
4875 Some(lang.clone_node(e, new_ch))
4876 }
4877 }
4878}
4879
4880impl Rule<JavaAst> for CtorDelegationHoist {
4881 fn name(&self) -> &'static str {
4882 "ctor_delegation_hoist"
4883 }
4884 fn kinds(&self) -> &'static [NodeKind] {
4885 &[NodeKind::Block]
4886 }
4887 fn structural(&self) -> bool {
4888 true
4889 }
4890 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4891 if ctx.parent(id).is_some() {
4894 return None;
4895 }
4896 let lang = ctx.lang;
4897 if lang.kind(id) != NodeKind::Block {
4898 return None;
4899 }
4900 let stmts = lang.children(id).to_vec();
4901 let mut ctor_idx: Option<usize> = None;
4903 for (i, &st) in stmts.iter().enumerate() {
4904 if *lang.data(st) != NodeData::ExprStmt {
4905 continue;
4906 }
4907 let Some(&inner) = lang.children(st).first() else { continue };
4908 if lang.kind(inner) != NodeKind::Call {
4909 continue;
4910 }
4911 let Some(&callee) = lang.children(inner).first() else { continue };
4912 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4913 ctor_idx = Some(i);
4914 break;
4915 }
4916 }
4917 let ci = ctor_idx?;
4918 if ci == 0 {
4919 return None; }
4921 let mut last_assign: std::collections::HashMap<String, JavaId> =
4924 std::collections::HashMap::new();
4925 for &st in &stmts[..ci] {
4926 let (name, value) = match lang.data(st) {
4927 NodeData::VarDecl { name, .. } => {
4928 let init = lang.children(st).first().copied();
4929 match init {
4930 Some(e) => (name.clone(), e),
4931 None => continue, }
4933 }
4934 NodeData::ExprStmt => {
4935 let Some(&a) = lang.children(st).first() else {
4936 return None;
4937 };
4938 if lang.kind(a) != NodeKind::Assign
4941 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4942 {
4943 return None;
4944 }
4945 let ch = lang.children(a).to_vec();
4946 let (t, v) = (*ch.first()?, *ch.get(1)?);
4947 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4948 return None; }
4950 let vn = lang.var_name(t)?;
4951 (vn.to_string(), v)
4952 }
4953 _ => return None, };
4955 last_assign.insert(name, value);
4960 }
4961 let call_stmt = stmts[ci];
4963 let call = *lang.children(call_stmt).first()?;
4964 let cch = lang.children(call).to_vec();
4965 let callee = *cch.first()?;
4966 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4967 for &a in &cch[1..] {
4968 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4969 }
4970 let callee_copy = lang.copy_subtree(callee);
4972 let new_call = lang.call(callee_copy, new_args);
4973 let new_stmt = lang.expr_stmt(new_call);
4974 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4975 insert.push(new_stmt);
4976 insert.extend(stmts[..ci].iter().copied());
4977 Some(Edit::Splice {
4978 node: id,
4979 index: 0,
4980 remove: ci + 1,
4981 insert,
4982 })
4983 }
4984}
4985
4986pub struct TrailingElseFlatten;
5004
5005impl Rule<JavaAst> for TrailingElseFlatten {
5006 fn name(&self) -> &'static str {
5007 "trailing_else_flatten"
5008 }
5009 fn kinds(&self) -> &'static [NodeKind] {
5010 &[NodeKind::If]
5011 }
5012 fn structural(&self) -> bool {
5013 true
5014 }
5015 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5016 let (parent, idx) = (ctx.parent(id), ctx.index(id));
5017 let lang = ctx.lang;
5018 if lang.kind(id) != NodeKind::If {
5019 return None;
5020 }
5021 let ch = lang.children(id).to_vec();
5022 if ch.len() != 3 {
5023 return None;
5024 }
5025 let (_cond, then, els) = (ch[0], ch[1], ch[2]);
5026 if lang.kind(then) != NodeKind::Block {
5028 return None;
5029 }
5030 let then_stmts = lang.children(then).to_vec();
5031 let then_terminal = then_stmts
5032 .last()
5033 .is_some_and(|&s| matches!(lang.kind(s), NodeKind::Return | NodeKind::Throw));
5034 if !then_terminal {
5035 return None;
5036 }
5037 if lang.kind(els) == NodeKind::Block {
5042 let else_stmts = lang.children(els).to_vec();
5043 if else_stmts.len() == 1 && lang.kind(else_stmts[0]) == NodeKind::Return {
5044 if lang.children(else_stmts[0]).is_empty() {
5046 return Some(Edit::Splice {
5048 node: id,
5049 index: 0,
5050 remove: 3,
5051 insert: vec![ch[0], then],
5052 });
5053 }
5054 }
5055 }
5056 let parent = parent?;
5058 if lang.kind(parent) != NodeKind::Block {
5059 return None;
5060 }
5061 let idx = idx?;
5062 let new_if = lang.clone_node(id, vec![ch[0], then]);
5064 let mut insert: Vec<JavaId> = vec![new_if];
5065 if lang.kind(els) == NodeKind::Block {
5071 let ech = lang.children(els).to_vec();
5072 let has_decl = ech.iter().any(|&s| {
5073 matches!(lang.kind(s), NodeKind::VarDecl | NodeKind::Raw)
5074 });
5075 if has_decl {
5076 insert.push(els);
5077 } else {
5078 insert.extend(ech);
5079 }
5080 } else {
5081 insert.push(els);
5082 }
5083 Some(Edit::Splice {
5084 node: parent,
5085 index: idx,
5086 remove: 1,
5087 insert,
5088 })
5089 }
5090}
5091
5092pub struct TrailingIfContinue;
5093
5094impl Rule<JavaAst> for TrailingIfContinue {
5095 fn name(&self) -> &'static str {
5096 "trailing_if_continue"
5097 }
5098 fn kinds(&self) -> &'static [NodeKind] {
5099 &[NodeKind::If]
5100 }
5101 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5102 let parent = ctx.parent(id);
5103 let grandparent = parent.and_then(|p| ctx.parent(p));
5104 let lang = ctx.lang;
5105 if lang.kind(id) != NodeKind::If {
5106 return None;
5107 }
5108 let ch = lang.children(id).to_vec();
5109 if ch.len() != 2 {
5111 return None;
5112 }
5113 let then = ch[1];
5114 if lang.kind(then) != NodeKind::Block {
5115 return None;
5116 }
5117 let tbody = lang.children(then).to_vec();
5118 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
5119 return None;
5120 }
5121 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
5123 return None;
5124 }
5125 let p = parent?;
5128 if lang.kind(p) != NodeKind::Block {
5129 return None;
5130 }
5131 let pch = lang.children(p).to_vec();
5132 if pch.last() != Some(&id) {
5133 return None;
5134 }
5135 let gp = grandparent?;
5137 match lang.kind(gp) {
5138 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
5139 let gch = lang.children(gp).to_vec();
5140 if gch.last() == Some(&p) {
5141 Some(Edit::Delete { node: id })
5142 } else {
5143 None
5144 }
5145 }
5146 _ => None,
5147 }
5148 }
5149}
5150
5151pub struct TrailingAssignReturn;
5152
5153impl Rule<JavaAst> for TrailingAssignReturn {
5154 fn name(&self) -> &'static str {
5155 "trailing_assign_return"
5156 }
5157 fn kinds(&self) -> &'static [NodeKind] {
5158 &[NodeKind::Assign]
5159 }
5160 fn structural(&self) -> bool {
5163 true
5164 }
5165 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5166 let parent = ctx.parent(id);
5167 let grandparent = parent.and_then(|p| ctx.parent(p));
5168 let lang = ctx.lang;
5169 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
5171 return None;
5172 }
5173 let ch = lang.children(id).to_vec();
5174 if ch.len() != 2 {
5175 return None;
5176 }
5177 let (target, value) = (ch[0], ch[1]);
5178 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
5180 return None;
5181 }
5182 let name = lang.var_name(target)?.to_string();
5183 let parent = parent?;
5185 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
5186 parent
5187 } else if lang.kind(parent) == NodeKind::Block {
5188 id
5189 } else {
5190 return None;
5191 };
5192 let block = if lang.kind(parent) == NodeKind::ExprStmt {
5193 grandparent?
5194 } else {
5195 parent
5196 };
5197 if lang.kind(block) != NodeKind::Block {
5198 return None;
5199 }
5200 let stmts = lang.children(block).to_vec();
5201 let idx = stmts.iter().position(|&x| x == stmt)?;
5202 let next = *stmts.get(idx + 1)?;
5204 let (flow_val, is_return) = match lang.kind(next) {
5205 NodeKind::Return => (lang.children(next).first().copied(), true),
5206 NodeKind::Throw => (lang.children(next).first().copied(), false),
5207 _ => return None,
5208 };
5209 let flow_val = flow_val?;
5210 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
5211 return None;
5212 }
5213 for &s in &stmts[idx + 2..] {
5216 if cure_engine::analysis::subtree_contains(lang, s, |n| {
5217 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
5218 }) {
5219 return None;
5220 }
5221 }
5222 let new_flow = if is_return {
5224 lang.ret(Some(value))
5225 } else {
5226 lang.throw(value)
5227 };
5228 Some(Edit::Multi(vec![
5229 Edit::Replace { target: next, with: new_flow },
5230 Edit::Delete { node: stmt },
5231 ]))
5232 }
5233}
5234
5235pub struct ConstFieldPropagate;
5236
5237impl Rule<JavaAst> for ConstFieldPropagate {
5238 fn name(&self) -> &'static str {
5239 "const_field_propagate"
5240 }
5241 fn kinds(&self) -> &'static [NodeKind] {
5242 &[NodeKind::VarRef]
5243 }
5244 fn structural(&self) -> bool {
5247 true
5248 }
5249 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5250 {
5255 let mut cur = id;
5256 while let Some(p) = ctx.parent(cur) {
5257 if ctx.lang.kind(p) == NodeKind::Case {
5258 return None;
5259 }
5260 cur = p;
5261 }
5262 }
5263 let parent = ctx.parent(id);
5264 let _grandparent = parent.and_then(|p| ctx.parent(p));
5265 let lang = ctx.lang;
5266 if lang.kind(id) != NodeKind::VarRef {
5267 return None;
5268 }
5269 let name = lang.var_name(id)?.to_string();
5270 if !lang.const_scalars.contains(&name) {
5271 return None;
5272 }
5273 if let Some(p) = parent {
5274 let is_first = lang.children(p).first() == Some(&id);
5275 match lang.kind(p) {
5276 NodeKind::Assign if is_first => return None,
5278 NodeKind::Unary if is_first => {
5279 if let NodeData::Unary { op } = lang.data(p) {
5280 if op.is_incdec() {
5281 return None;
5282 }
5283 }
5284 }
5285 NodeKind::Call if is_first => return None,
5287 _ => {}
5288 }
5289 }
5290 let init = *lang.const_fields.get(&name)?;
5291 let lit = lang.copy_subtree(init);
5292 Some(Edit::Replace { target: id, with: lit })
5293 }
5294}
5295
5296pub struct NoopPrivateCall;
5297
5298impl Rule<JavaAst> for NoopPrivateCall {
5299 fn name(&self) -> &'static str {
5300 "noop_private_call"
5301 }
5302 fn kinds(&self) -> &'static [NodeKind] {
5303 &[NodeKind::ExprStmt]
5304 }
5305 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5306 let lang = ctx.lang;
5307 if lang.kind(id) != NodeKind::ExprStmt {
5308 return None;
5309 }
5310 let ch = lang.children(id).to_vec();
5311 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
5312 return None;
5313 }
5314 let call = ch[0];
5315 let cch = lang.children(call).to_vec();
5316 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
5317 return None; }
5319 let name = lang.var_name(cch[0])?.to_string();
5320 let arity = cch.len() - 1;
5321 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
5322 if cands.is_empty() {
5323 return None;
5324 }
5325 let arg_types: Vec<Option<JType>> =
5329 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
5330 let mut applicable_noop = false;
5331 let mut applicable_count = 0usize;
5332 for c in cands {
5333 let ok = c
5334 .params
5335 .iter()
5336 .zip(arg_types.iter())
5337 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
5338 if ok {
5339 applicable_count += 1;
5340 applicable_noop = c.noop;
5341 }
5342 }
5343 if applicable_count != 1 || !applicable_noop {
5344 return None;
5345 }
5346 for &arg in &cch[1..] {
5348 if lang.effect(arg) > Effect::MayRead {
5349 return None;
5350 }
5351 }
5352 Some(Edit::Delete { node: id })
5353 }
5354}
5355
5356fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
5360 match lang.kind(arg) {
5361 NodeKind::Literal => match lang.literal(arg)? {
5362 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
5363 LitRef::Char(_) => Some(JType::Char),
5364 LitRef::Bool(_) => Some(JType::Bool),
5365 LitRef::Int(_) => Some(JType::Int),
5366 LitRef::Long(_) => Some(JType::Long),
5367 LitRef::Float(_) => Some(JType::Float),
5368 LitRef::Double(_) => Some(JType::Double),
5369 LitRef::Null => None,
5370 },
5371 NodeKind::VarRef => {
5372 let nm = lang.var_name(arg)?;
5373 lang.var_type(arg)
5374 .cloned()
5375 .or_else(|| lang.field_types.get(nm).cloned())
5376 }
5377 _ => None,
5378 }
5379}
5380
5381fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5386 let Some(a) = at else { return true };
5387 match pt {
5388 JType::Ref(p) => {
5389 let pbase = p.split('<').next().unwrap_or(p).trim();
5390 pbase == "Object"
5391 || pbase == "java.lang.Object"
5392 || match a {
5393 JType::Ref(arend) => {
5394 let abase = arend.split('<').next().unwrap_or(arend).trim();
5395 abase == pbase
5396 }
5397 _ => false,
5398 }
5399 }
5400 JType::Array(_) => false, prim => match a {
5402 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5403 },
5404 }
5405}
5406
5407pub struct ConstMethodInline;
5417
5418impl Rule<JavaAst> for ConstMethodInline {
5419 fn name(&self) -> &'static str {
5420 "const_method_inline"
5421 }
5422 fn kinds(&self) -> &'static [NodeKind] {
5423 &[NodeKind::Call]
5424 }
5425 fn structural(&self) -> bool {
5426 true
5427 }
5428 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5429 let at_stmt_pos = ctx
5431 .parent(id)
5432 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5433 .unwrap_or(false);
5434 let lang = ctx.lang;
5435 if lang.kind(id) != NodeKind::Call {
5436 return None;
5437 }
5438 let ch = lang.children(id).to_vec();
5439 let (method_name, recv_class) = match lang.data(ch[0]) {
5443 NodeData::VarRef { name } => (*name, None),
5444 NodeData::Member { name } => {
5445 let recv = lang.children(ch[0])[0];
5446 match lang.var_name(recv) {
5447 Some(r) if r == "this" => (*name, None),
5448 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5449 (*name, Some(r.to_string()))
5450 }
5451 _ => return None,
5452 }
5453 }
5454 _ => return None,
5455 };
5456 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5457 let (params, body_expr, owner) = entry;
5458 if let Some(recv) = recv_class {
5465 if recv != owner {
5466 return None;
5467 }
5468 }
5469 {
5471 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5472 for nm in &names {
5473 if subtree_calls_self(lang, body_expr, nm) {
5474 return None;
5475 }
5476 }
5477 }
5478 let args = ch[1..].to_vec();
5479 if args.len() != params.len() || params.is_empty() {
5480 return None;
5481 }
5482 for p in ¶ms {
5486 if param_written_in_body(lang, body_expr, p) {
5487 return None;
5488 }
5489 }
5490 for &a in &args {
5491 lang.literal(a)?;
5492 }
5493 if cure_engine::analysis::subtree_contains(lang, body_expr, |n| {
5498 matches!(lang.data(n), NodeData::This | NodeData::Super)
5499 }) {
5500 return None;
5501 }
5502 for (p, &a) in params.iter().zip(args.iter()) {
5507 if matches!(lang.literal(a), Some(LitRef::Null))
5508 && param_in_reference_head(lang, body_expr, p)
5509 {
5510 return None;
5511 }
5512 }
5513 let map: std::collections::HashMap<String, JavaId> = params
5514 .iter()
5515 .zip(args.iter())
5516 .map(|(p, &a)| (p.clone(), a))
5517 .collect();
5518 let with = copy_subst(lang, body_expr, &map);
5519 if at_stmt_pos {
5525 match lang.kind(with) {
5526 NodeKind::Call | NodeKind::New => {}
5527 _ => return None,
5528 }
5529 }
5530 Some(Edit::Replace {
5531 target: id,
5532 with,
5533 })
5534 }
5535}
5536
5537fn param_written_in_body(ast: &JavaAst, node: JavaId, name: &str) -> bool {
5539 match ast.data(node) {
5540 NodeData::Assign { .. } => {
5541 if let Some(&t) = ast.children(node).first() {
5542 if subtree_has_var(ast, t, name) {
5543 return true;
5544 }
5545 }
5546 }
5547 NodeData::Unary { op } if op.is_incdec() => {
5548 if let Some(&t) = ast.children(node).first() {
5549 if subtree_has_var(ast, t, name) {
5550 return true;
5551 }
5552 }
5553 }
5554 _ => {}
5555 }
5556 ast.children(node)
5557 .iter()
5558 .any(|&c| param_written_in_body(ast, c, name))
5559}
5560
5561fn param_in_reference_head(ast: &JavaAst, node: JavaId, name: &str) -> bool {
5564 match ast.data(node) {
5565 NodeData::Member { .. }
5566 | NodeData::Index
5567 | NodeData::Synchronized
5568 | NodeData::ForEach { .. }
5569 | NodeData::InstanceOf { .. } => {
5570 if let Some(&first) = ast.children(node).first() {
5571 if subtree_has_var(ast, first, name) {
5572 return true;
5573 }
5574 }
5575 }
5576 _ => {}
5577 }
5578 ast.children(node)
5579 .iter()
5580 .any(|&c| param_in_reference_head(ast, c, name))
5581}
5582
5583fn copy_subst(
5585 lang: &mut JavaAst,
5586 node: JavaId,
5587 map: &std::collections::HashMap<String, JavaId>,
5588) -> JavaId {
5589 if let NodeData::VarRef { name } = lang.data(node) {
5590 if let Some(&repl) = map.get(lang.sn(*name)) {
5591 return lang.copy_subtree(repl);
5592 }
5593 }
5594 let children: Vec<JavaId> = lang.children(node).to_vec();
5595 let new_children: Vec<JavaId> = children
5596 .iter()
5597 .map(|&c| copy_subst(lang, c, map))
5598 .collect();
5599 lang.clone_node(node, new_children)
5600}
5601
5602
5603pub struct TwrRecover;
5636
5637fn twr_is_throwable(ty_raw: &str) -> bool {
5638 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5639}
5640
5641fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5643 if lang.kind(n) == NodeKind::ExprStmt {
5644 if let Some(&e) = lang.children(n).first() {
5645 return e;
5646 }
5647 }
5648 n
5649}
5650
5651fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5653 if lang.kind(n) == NodeKind::Block {
5654 lang.children(n).to_vec()
5655 } else {
5656 vec![n]
5657 }
5658}
5659
5660fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5662 if lang.kind(e) != NodeKind::Call {
5663 return false;
5664 }
5665 let ch = lang.children(e);
5666 if ch.len() != 1 {
5667 return false;
5668 }
5669 match lang.data(ch[0]) {
5670 NodeData::Member { name } if lang.sn(*name) == "close" => {
5671 let Some(&recv) = lang.children(ch[0]).first() else {
5672 return false;
5673 };
5674 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5675 }
5676 _ => false,
5677 }
5678}
5679
5680fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5681 if lang.kind(n) != NodeKind::ExprStmt {
5682 return false;
5683 }
5684 lang.children(n)
5685 .first()
5686 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5687}
5688
5689fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5691 if lang.kind(n) != NodeKind::ExprStmt {
5692 return false;
5693 }
5694 let Some(&e) = lang.children(n).first() else {
5695 return false;
5696 };
5697 if lang.kind(e) != NodeKind::Call {
5698 return false;
5699 }
5700 let ch = lang.children(e);
5701 if ch.len() != 2 {
5702 return false;
5703 }
5704 match lang.data(ch[0]) {
5705 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5706 let Some(&recv) = lang.children(ch[0]).first() else {
5707 return false;
5708 };
5709 lang.kind(recv) == NodeKind::VarRef
5710 && lang.var_name(recv) == Some(primary)
5711 && lang.kind(ch[1]) == NodeKind::VarRef
5712 && lang.var_name(ch[1]) == Some(sup)
5713 }
5714 _ => false,
5715 }
5716}
5717
5718fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5719 let mut stack = vec![root];
5720 while let Some(n) = stack.pop() {
5721 if n == target {
5722 return true;
5723 }
5724 stack.extend(lang.children(n).iter().copied());
5725 }
5726 false
5727}
5728
5729fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5731 let mut stack = vec![root];
5732 while let Some(n) = stack.pop() {
5733 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5734 let ok = allowed
5735 .iter()
5736 .any(|&a| a == n || node_contains(lang, a, n));
5737 if !ok {
5738 return true;
5739 }
5740 }
5741 if lang.kind(n) == NodeKind::VarDecl {
5742 if let Some(nm) = lang.var_name(n) {
5743 if nm == name && !allowed.contains(&n) {
5744 return true;
5746 }
5747 }
5748 }
5749 stack.extend(lang.children(n).iter().copied());
5750 }
5751 false
5752}
5753
5754fn twr_close_with_suppressed(
5758 lang: &JavaAst,
5759 try_node: JavaId,
5760 primary: &str,
5761) -> Option<(String, String)> {
5762 let ch = lang.children(try_node).to_vec();
5763 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5764 return None;
5765 }
5766 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5767 return None;
5768 };
5769 if !twr_is_throwable(ty_raw) {
5770 return None;
5771 }
5772 let sup = sup.clone();
5773 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5774 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5775 return None;
5776 }
5777 let close_stmts = twr_stmts(lang, ch[0]);
5778 if close_stmts.len() != 1 {
5779 return None;
5780 }
5781 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5782 return None;
5783 }
5784 let e = *lang.children(close_stmts[0]).first()?;
5785 let cc = lang.children(e);
5786 if cc.len() != 1 {
5787 return None;
5788 }
5789 let NodeData::Member { name } = lang.data(cc[0]) else {
5790 return None;
5791 };
5792 if lang.sn(*name) != "close" {
5793 return None;
5794 }
5795 let recv = *lang.children(cc[0]).first()?;
5796 if lang.kind(recv) != NodeKind::VarRef {
5797 return None;
5798 }
5799 Some((lang.var_name(recv)?.to_string(), sup))
5800}
5801
5802impl Rule<JavaAst> for TwrRecover {
5803 fn name(&self) -> &'static str {
5804 "twr_recover"
5805 }
5806 fn kinds(&self) -> &'static [NodeKind] {
5807 &[NodeKind::Try]
5808 }
5809 fn structural(&self) -> bool {
5810 true
5811 }
5812 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5813 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5815 let lang = ctx.lang;
5816 if lang.kind(id) != NodeKind::Try {
5817 return None;
5818 }
5819 let ch = lang.children(id).to_vec();
5820 let try_idx = ch
5822 .iter()
5823 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5824 if try_idx != 0 {
5825 return None;
5826 }
5827 let try_block = ch[0];
5828 if lang.kind(parent) != NodeKind::Block {
5829 return None;
5830 }
5831 let pch = lang.children(parent).to_vec();
5832
5833 if ch.len() == 2 {
5835 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5836 if twr_is_throwable(ty_raw) {
5837 let t = t.clone();
5838 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5839 if catch_stmts.len() == 2 {
5840 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5842 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5843 && lang
5844 .children(throw_stmt)
5845 .first()
5846 .is_some_and(|&x| {
5847 lang.kind(x) == NodeKind::VarRef
5848 && lang.var_name(x) == Some(t.as_str())
5849 });
5850 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5851 if let Some((r, sup)) =
5852 twr_close_with_suppressed(lang, inner_try, t.as_str())
5853 {
5854 if idx >= 1 && idx + 1 < pch.len() {
5855 let decl_r = pch[idx - 1];
5856 let trailing = pch[idx + 1];
5857 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5858 if name == &r {
5859 let (name, ty) = (name.clone(), ty.clone());
5860 let init =
5861 lang.children(decl_r).first().copied();
5862 if let Some(init) = init {
5863 let provably_new = lang.kind(init)
5865 == NodeKind::New;
5866 let close_ok =
5867 twr_is_close_stmt(lang, trailing, &r);
5868 if provably_new && close_ok {
5869 let root = walk_root;
5871 let r_ok = !refs_escape(
5872 lang,
5873 root,
5874 &r,
5875 &[
5876 decl_r,
5877 init,
5878 *lang
5879 .children(inner_try)
5880 .first()
5881 .unwrap_or(&inner_try),
5882 trailing,
5883 try_block,
5884 ],
5885 );
5886 let t_ok = !refs_escape(
5887 lang, root, &t,
5888 &[throw_stmt, ch[1]],
5889 );
5890 let sup_ok = !refs_escape(
5891 lang, root, &sup,
5892 &[ch[1]],
5893 );
5894 if r_ok && t_ok && sup_ok {
5895 let init_copy =
5896 lang.copy_subtree(init);
5897 let res = lang.var_decl(
5898 &name, ty,
5899 Some(init_copy),
5900 );
5901 let new_try = lang.try_(
5902 vec![res],
5903 try_block,
5904 Vec::new(),
5905 None,
5906 );
5907 return Some(Edit::Splice {
5908 node: parent,
5909 index: idx - 1,
5910 remove: 3,
5911 insert: vec![new_try],
5912 });
5913 }
5914 }
5915 }
5916 }
5917 }
5918 }
5919 }
5920 }
5921 }
5922 }
5923 }
5924 }
5925
5926 if ch.len() == 3 {
5928 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5929 return None;
5930 };
5931 if !twr_is_throwable(ty_raw) {
5932 return None;
5933 }
5934 let t = t.clone();
5935 if lang.kind(ch[2]) != NodeKind::Block {
5937 return None;
5938 }
5939 let fin_stmts = twr_stmts(lang, ch[2]);
5940 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5941 return None;
5942 }
5943 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5945 if catch_stmts.len() != 2 {
5946 return None;
5947 }
5948 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5949 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5950 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5951 return None;
5952 };
5953 let as_ch = lang.children(assign_expr);
5954 if as_ch.len() != 2 {
5955 return None;
5956 }
5957 let primary = match lang.data(as_ch[0]) {
5958 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5959 _ => return None,
5960 };
5961 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5962 && lang.var_name(as_ch[1]) == Some(t.as_str())
5963 && lang.kind(as_ch[0]) == NodeKind::VarRef
5964 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5965 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5966 && lang.children(throw_stmt)
5967 .first()
5968 .is_some_and(|&x| {
5969 lang.kind(x) == NodeKind::VarRef
5970 && lang.var_name(x) == Some(t.as_str())
5971 });
5972 if !assign_ok || !throw_ok {
5973 return None;
5974 }
5975 let if1 = fin_stmts[0];
5977 let i1 = lang.children(if1).to_vec();
5978 if i1.len() < 2 {
5979 return None;
5980 }
5981 let cond1 = i1[0];
5982 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5983 return None;
5984 };
5985 let c1 = lang.children(cond1);
5986 let r = match lang.data(c1[0]) {
5987 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5988 lang.sn(*name).to_string()
5989 }
5990 _ => {
5991 return None;
5992 }
5993 };
5994 let then1 = i1[1];
5995 let inner_stmts = twr_stmts(lang, then1);
5996 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5997 return None;
5998 }
5999 let if2 = inner_stmts[0];
6000 let i2 = lang.children(if2).to_vec();
6001 if i2.len() != 3 {
6002 return None;
6003 }
6004 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
6005 return None;
6006 };
6007 let c2 = lang.children(i2[0]);
6008 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
6009 && lang.var_name(c2[0]) == Some(primary.as_str())
6010 && lang.literal(c2[1]) == Some(LitRef::Null);
6011 if !primary_ok {
6012 return None;
6013 }
6014 let (then2, els2) = (i2[1], i2[2]);
6015 let t2_stmts = twr_stmts(lang, then2);
6017 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
6018 return None;
6019 }
6020 let Some((r2, sup)) =
6021 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
6022 else {
6023 return None;
6024 };
6025 if r2 != r {
6026 return None;
6027 }
6028 let e2_stmts = twr_stmts(lang, els2);
6030 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
6031 return None;
6032 }
6033 if idx < 2 {
6035 return None;
6036 }
6037 let decl_r = pch[idx - 2];
6038 let decl_primary = pch[idx - 1];
6039 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
6040 (
6041 NodeData::VarDecl { name: rn, .. },
6042 NodeData::VarDecl { name: pn, .. },
6043 ) => (rn.clone(), pn.clone()),
6044 _ => return None,
6045 };
6046 if r_decl != r || primary_decl != primary {
6047 return None;
6048 }
6049 let pinit_null = lang
6051 .children(decl_primary)
6052 .first()
6053 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
6054 if !pinit_null {
6055 return None;
6056 }
6057 let r_init = lang.children(decl_r).first().copied()?;
6058 let ty = match lang.data(decl_r) {
6059 NodeData::VarDecl { ty, .. } => ty.clone(),
6060 _ => return None,
6061 };
6062 let root = walk_root;
6064 let r_allowed = [decl_r, r_init, ch[2], try_block];
6066 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
6067 let p_allowed = [decl_primary, assign_stmt, ch[2]];
6068 if refs_escape(lang, root, &r, &r_allowed)
6069 || refs_escape(lang, root, &primary, &p_allowed)
6070 || refs_escape(lang, root, &t, &t_allowed)
6071 || refs_escape(lang, root, &sup, &[ch[2]])
6072 {
6073 return None;
6074 }
6075 let init_copy = lang.copy_subtree(r_init);
6076 let res = lang.var_decl(&r, ty, Some(init_copy));
6077 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
6078 return Some(Edit::Splice {
6079 node: parent,
6080 index: idx - 2,
6081 remove: 3,
6082 insert: vec![new_try],
6083 });
6084 }
6085 None
6086 }
6087}
6088
6089pub struct StringSwitchRecover;
6114
6115fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
6116 out.push_str(&format!("{:?} ", lang.data(n)));
6117 for &c in lang.children(n) {
6118 subtree_sig(lang, c, out);
6119 }
6120}
6121
6122fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
6124 let ch = lang.children(sw).to_vec();
6125 let mut out = Vec::new();
6126 for &c in &ch[1..] {
6127 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
6128 return None;
6129 };
6130 let is_default = *is_default;
6131 let cc = lang.children(c);
6132 let label = if is_default {
6133 None
6134 } else {
6135 if *labels != 1 {
6136 return None; }
6138 let lab = cc[0];
6139 let lit = lang.literal(lab)?;
6140 let v = match lit {
6141 LitRef::Int(v) | LitRef::Long(v) => v,
6142 _ => return None, };
6144 Some(v)
6145 };
6146 out.push((label, cc[*labels as usize..].to_vec()));
6147 }
6148 Some(out)
6149}
6150
6151fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
6152 let Some(&last) = stmts.last() else {
6153 return false; };
6155 let last = if lang.kind(last) == NodeKind::Block {
6156 match lang.children(last).last() {
6157 Some(&l) => l,
6158 None => return false,
6159 }
6160 } else {
6161 last
6162 };
6163 matches!(
6164 lang.kind(last),
6165 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
6166 )
6167}
6168
6169impl Rule<JavaAst> for StringSwitchRecover {
6170 fn name(&self) -> &'static str {
6171 "string_switch_recover"
6172 }
6173 fn kinds(&self) -> &'static [NodeKind] {
6174 &[NodeKind::Switch]
6175 }
6176 fn structural(&self) -> bool {
6177 true
6178 }
6179 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
6180 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
6181 let lang = ctx.lang;
6182 if lang.kind(id) != NodeKind::Switch {
6183 return None;
6184 }
6185 let ch = lang.children(id).to_vec();
6186 if ch.len() < 2 {
6187 return None;
6188 }
6189 let x = match lang.data(ch[0]) {
6191 NodeData::Call => {
6192 let cc = lang.children(ch[0]);
6193 if cc.len() != 1 {
6194 return None;
6195 }
6196 match lang.data(cc[0]) {
6197 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
6198 let recv = *lang.children(cc[0]).first()?;
6199 if lang.kind(recv) != NodeKind::VarRef {
6200 return None;
6201 }
6202 lang.var_name(recv)?.to_string()
6203 }
6204 _ => return None,
6205 }
6206 }
6207 _ => {
6208 return None;
6209 }
6210 };
6211 struct CaseInfo {
6213 lit: String,
6214 k: i64,
6215 inner_switch: JavaId,
6216 }
6217 let mut infos: Vec<CaseInfo> = Vec::new();
6218 let mut v_name: Option<String> = None;
6219 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
6221 for &c in &ch[1..] {
6222 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
6223 return None;
6224 };
6225 let is_default = *is_default;
6226 if is_default {
6227 return None; }
6229 if *labels != 1 {
6230 return None;
6231 }
6232 let cc = lang.children(c);
6233 let h = match lang.literal(cc[0])? {
6234 LitRef::Int(v) | LitRef::Long(v) => v as i32,
6235 _ => return None,
6236 };
6237 let body = cc[1..].to_vec();
6238 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
6240 lang.children(body[0]).to_vec()
6241 } else {
6242 body
6243 };
6244 let mut rest = body;
6245 let mut _e_decl: Option<(String, JavaId)> = None;
6247 if let Some(&first) = rest.first() {
6248 if let NodeData::VarDecl { name, .. } = lang.data(first) {
6249 if let Some(&init) = lang.children(first).first() {
6250 if is_x_equals(lang, init, &x) {
6251 _e_decl = Some((name.clone(), first));
6252 rest = rest[1..].to_vec();
6253 }
6254 }
6255 }
6256 }
6257 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
6258 return None;
6259 }
6260 let ifn = rest[0];
6261 let ic = lang.children(ifn).to_vec();
6262 if ic.len() != 3 {
6263 return None;
6264 }
6265 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
6267 return None;
6268 };
6269 let guard = *lang.children(ic[0]).first()?;
6270 let lit = match lang.data(guard) {
6271 NodeData::VarRef { name } => {
6272 let Some((en, decl)) = &_e_decl else {
6274 return None;
6275 };
6276 if lang.sn(*name) != en {
6277 return None;
6278 }
6279 let init = *lang.children(*decl).first()?;
6280 equals_lit(lang, init, &x)?
6281 }
6282 NodeData::Call => equals_lit(lang, guard, &x)?,
6283 _ => return None,
6284 };
6285 guard_nodes.push(guard);
6286 if let Some((_, decl)) = &_e_decl {
6287 guard_nodes.push(*decl);
6288 }
6289 let then_ok = twr_stmts(lang, ic[1]).len() == 1
6291 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
6292 NodeData::Break { label: None });
6293 if !then_ok {
6294 return None;
6295 }
6296 let els = twr_stmts(lang, ic[2]);
6298 let (k, inner_switch, assign_node) = match els.len() {
6299 2 => {
6300 let assign_expr = unwrap_expr_stmt(lang, els[0]);
6301 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
6302 return None;
6303 };
6304 let a_ch = lang.children(assign_expr);
6305 let vn = match lang.data(a_ch[0]) {
6306 NodeData::VarRef { name } => lang.sn(*name).to_string(),
6307 _ => return None,
6308 };
6309 let k = match lang.literal(a_ch[1])? {
6310 LitRef::Int(v) | LitRef::Long(v) => v,
6311 _ => return None,
6312 };
6313 if lang.kind(els[1]) != NodeKind::Switch {
6314 return None;
6315 }
6316 let sel = *lang.children(els[1]).first()?;
6318 if lang.kind(sel) != NodeKind::VarRef
6319 || lang.var_name(sel) != Some(vn.as_str())
6320 {
6321 return None;
6322 }
6323 match &v_name {
6324 Some(v) if *v != vn => return None,
6325 _ => v_name = Some(vn),
6326 }
6327 (k, els[1], Some(els[0]))
6328 }
6329 1 => {
6330 if lang.kind(els[0]) != NodeKind::Switch {
6331 return None;
6332 }
6333 let sel = *lang.children(els[0]).first()?;
6335 let k = match lang.literal(sel)? {
6336 LitRef::Int(v) | LitRef::Long(v) => v,
6337 _ => return None,
6338 };
6339 (k, els[0], None)
6340 }
6341 _ => return None,
6342 };
6343 if let Some(a) = assign_node {
6344 v_nodes.push(a);
6345 }
6346 v_nodes.push(*lang.children(inner_switch).first()?);
6347 let hlit: i32 = lit
6349 .chars()
6350 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
6351 if hlit != h {
6352 return None;
6353 }
6354 let _ = assign_node;
6355 infos.push(CaseInfo {
6356 lit,
6357 k,
6358 inner_switch,
6359 });
6360 }
6361 if infos.is_empty() {
6362 return None;
6363 }
6364 if lang.kind(parent) != NodeKind::Block {
6366 return None;
6367 }
6368 let pch = lang.children(parent).to_vec();
6369 let mut k = idx + 1;
6370 if k < pch.len() {
6372 let texpr = unwrap_expr_stmt(lang, pch[k]);
6373 if let NodeData::Assign { op: None } = lang.data(texpr) {
6374 let a_ch = lang.children(texpr);
6375 if a_ch.len() == 2
6376 && lang.kind(a_ch[0]) == NodeKind::VarRef
6377 && lang.var_name(a_ch[0]) == v_name.as_deref()
6378 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
6379 {
6380 v_nodes.push(pch[k]);
6381 k += 1;
6382 }
6383 }
6384 }
6385 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
6386 return None;
6387 }
6388 let trailing = pch[k];
6389 let tsel = *lang.children(trailing).first()?;
6391 match lang.data(tsel) {
6392 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
6393 v_nodes.push(tsel);
6394 }
6395 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
6396 _ => {
6397 lang.literal(tsel)?;
6398 }
6399 }
6400 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
6403 for &c in &lang.children(sw)[1..] {
6404 subtree_sig(lang, c, out);
6405 }
6406 }
6407 let mut sig0 = String::new();
6408 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
6409 for info in &infos[1..] {
6410 let mut sig = String::new();
6411 switch_cases_sig(lang, info.inner_switch, &mut sig);
6412 if sig != sig0 {
6413 return None;
6414 }
6415 }
6416 {
6417 let mut sig = String::new();
6418 switch_cases_sig(lang, trailing, &mut sig);
6419 if sig != sig0 {
6420 return None;
6421 }
6422 }
6423 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
6425 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
6426 std::collections::HashMap::new();
6427 let mut default_body: Option<Vec<JavaId>> = None;
6428 for (lab, stmts) in &bodies {
6429 match lab {
6430 Some(v) => {
6431 body_map.insert(*v, stmts.clone());
6432 }
6433 None => default_body = Some(stmts.clone()),
6434 }
6435 }
6436 let Some(default_body) = default_body else {
6437 return None;
6438 };
6439 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
6441 if !ssw_body_terminal(lang, stmts) {
6442 return None;
6443 }
6444 }
6445 let root = ctx_root;
6447 if let Some(vn) = &v_name {
6448 let mut allowed: Vec<JavaId> = v_nodes.clone();
6449 allowed.push(trailing);
6450 if idx >= 1 {
6451 let cand = pch[idx - 1];
6452 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
6453 if name == vn {
6454 allowed.push(cand);
6455 if idx != 1 {
6457 allowed.pop();
6458 }
6459 }
6460 }
6461 }
6462 if refs_escape(lang, root, vn, &allowed) {
6463 return None;
6464 }
6465 }
6466 let mut new_cases: Vec<JavaId> = Vec::new();
6468 for info in &infos {
6469 let Some(body) = body_map.get(&info.k) else {
6470 return None;
6471 };
6472 let lab = lang.lit(Lit::Str(info.lit.clone()));
6473 let case = lang.case_(vec![lab], false, false, body.clone());
6474 new_cases.push(case);
6475 }
6476 let def_case = lang.case_(Vec::new(), true, false, default_body);
6477 new_cases.push(def_case);
6478 let sel = lang.var(&x);
6479 let new_switch = lang.switch_(sel, new_cases);
6480 let remove = k - idx + 1;
6482 Some(Edit::Splice {
6483 node: parent,
6484 index: idx,
6485 remove,
6486 insert: vec![new_switch],
6487 })
6488 }
6489}
6490
6491fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6493 if lang.kind(e) != NodeKind::Call {
6494 return None;
6495 }
6496 let cc = lang.children(e);
6497 if cc.len() != 2 {
6498 return None;
6499 }
6500 match lang.data(cc[0]) {
6501 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6502 let recv = *lang.children(cc[0]).first()?;
6503 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6504 return None;
6505 }
6506 match lang.literal(cc[1])? {
6507 LitRef::Str(s) => Some(s.to_string()),
6508 _ => None,
6509 }
6510 }
6511 _ => None,
6512 }
6513}
6514
6515fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6516 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6517}
6518
6519pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6526 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6527 rules.push(Box::new(CastSimplify));
6528 rules.push(Box::new(SelfCompare));
6529 rules.push(Box::new(StringBuilderFold));
6530 rules.push(Box::new(BoxUnboxChain));
6531 rules.push(Box::new(IteratorToForEach));
6532 rules.push(Box::new(NewStringFold));
6533 rules.push(Box::new(NewStringCharArrayFold));
6534 rules.push(Box::new(EmptyFinallyStrip));
6535 rules.push(Box::new(TryUnwrapNoCatch));
6536 rules.push(Box::new(TryUnwrapRethrow));
6537 rules.push(Box::new(StaticExec));
6538 rules.push(Box::new(LoopHeadBreak));
6539 rules.push(Box::new(WhileIteratorToForEach));
6540 rules.push(Box::new(ConcatValueOfDrop));
6541 rules.push(Box::new(StringBuilderStatements));
6542 rules.push(Box::new(TrailingContinueJava));
6543 rules.push(Box::new(XorNoise));
6544 rules.push(Box::new(StrLenFold));
6545 rules.push(Box::new(LiteralEval));
6546 rules.push(Box::new(Base64NewStringFold));
6547 rules.push(Box::new(UrlDecodeFold));
6548 rules.push(Box::new(CffRecover));
6549 rules.push(Box::new(StaticArrayIndexFold));
6550 rules.push(Box::new(TrailingAssignReturn));
6551 rules.push(Box::new(DeadPureCall));
6552 rules.push(Box::new(CtorDelegationHoist));
6553 rules.push(Box::new(TrailingElseFlatten));
6554 rules.push(Box::new(TrailingIfContinue));
6555 rules.push(Box::new(ConstFieldPropagate));
6556 rules.push(Box::new(NoopPrivateCall));
6557 rules.push(Box::new(ConstMethodInline));
6558 rules.push(Box::new(TwrRecover));
6559 rules.push(Box::new(StringSwitchRecover));
6560 rules
6561}
6562
6563pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6565 let mut rules = default_java_rules();
6566 rules.push(Box::new(cure_engine::rules::UnreachableAfterTerminal));
6569 rules
6570}
6571
6572pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6574 let rules = default_java_rules();
6575 cure_engine::simplify(ast, root, &rules, cfg)
6576}
6577
6578pub fn simplify_unit_with_facts(
6583 ast: &mut JavaAst,
6584 unit: &mut CompilationUnit,
6585 cfg: &Config,
6586 facts: &cure_java_ast::JavaFacts,
6587) -> Report {
6588 ast.set_facts(facts.clone());
6589 let r = simplify_unit(ast, unit, cfg);
6590 ast.clear_facts();
6591 r
6592}
6593
6594pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6595 let mut total = Report::default();
6596 for _cycle in 0..6 {
6601 let mut round = Report::default();
6602 collect_unit_consts(ast, unit);
6603 if cfg.remove_dead_methods {
6606 collect_noop_private_methods(ast, unit);
6607 } else {
6608 ast.noop_private_methods.clear();
6609 }
6610 for ty in &mut unit.types {
6611 simplify_type(ast, ty, cfg, &mut round);
6612 }
6613 if cfg.remove_dead_methods {
6614 round.edits += remove_dead_private_fields(ast, unit);
6615 round.edits += remove_dead_private_methods(ast, unit);
6616 round.edits += remove_unused_imports(ast, unit);
6617 }
6618 total.edits += round.edits;
6619 total.iterations += round.iterations;
6620 total.discarded += round.discarded;
6621 for (k, v) in round.by_rule {
6622 *total.by_rule.entry(k).or_insert(0) += v;
6623 }
6624 if round.edits == 0 {
6629 break;
6630 }
6631 }
6632 total
6633}
6634
6635fn collect_anon_and_string_names(
6642 ast: &JavaAst,
6643 unit: &CompilationUnit,
6644) -> std::collections::HashSet<String> {
6645 let mut out: std::collections::HashSet<String> = Default::default();
6646 let mut bodies: Vec<JavaId> = Vec::new();
6647 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6648 for root in bodies {
6649 let mut stack = vec![root];
6650 while let Some(n) = stack.pop() {
6651 match ast.data(n) {
6652 NodeData::New { anon_raw: Some(raw), .. } => {
6653 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6654 if !id.is_empty() {
6655 out.insert(id.to_string());
6656 }
6657 }
6658 }
6659 NodeData::Literal(Lit::Str(text)) => {
6660 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6661 if !id.is_empty() {
6662 out.insert(id.to_string());
6663 }
6664 }
6665 }
6666 _ => {}
6667 }
6668 for &c in ast.children(n) {
6669 stack.push(c);
6670 }
6671 }
6672 }
6673 out
6674}
6675
6676fn collect_all_blind_refs(
6705 ast: &JavaAst,
6706 unit: &CompilationUnit,
6707) -> std::collections::HashSet<String> {
6708 let mut out: std::collections::HashSet<String> = Default::default();
6709 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6710 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6711 if !id.is_empty() {
6712 out.insert(id.to_string());
6713 }
6714 }
6715 };
6716 let mut bodies: Vec<JavaId> = Vec::new();
6718 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6719 for root in bodies {
6720 let mut stack = vec![root];
6721 while let Some(n) = stack.pop() {
6722 match ast.data(n) {
6723 NodeData::Raw { text } => split_into(text, &mut out),
6724 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6725 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6726 NodeData::ForEach { ty, .. } => {
6728 collect_ty_idents(ty, &mut out);
6729 }
6730 NodeData::NewArray { ty, .. } => {
6732 collect_ty_idents(ty, &mut out);
6733 }
6734 NodeData::Lambda { params_raw } => {
6738 split_into(params_raw, &mut out);
6739 }
6740 _ => {}
6741 }
6742 for &c in ast.children(n) {
6743 stack.push(c);
6744 }
6745 }
6746 }
6747 for r in &unit.raws {
6751 split_into(r, &mut out);
6752 }
6753 for_each_type(unit, &mut |ty: &TypeDecl| {
6756 collect_mods_idents_all(&ty.mods, &mut out);
6757 for e in &ty.enum_constants {
6758 split_into(e, &mut out);
6759 }
6760 split_into(&ty.ty_params, &mut out);
6761 split_into(&ty.header, &mut out);
6762 for m in &ty.members {
6763 match m {
6764 Member::Method { mods, name, ty_params, params, .. }
6765 | Member::Constructor { mods, name, ty_params, params, .. } => {
6766 collect_mods_idents_all(mods, &mut out);
6767 split_into(ty_params, &mut out);
6768 for p in params {
6769 collect_mods_idents_all(&p.mods, &mut out);
6770 collect_ty_idents(&p.ty, &mut out);
6771 }
6772 if has_bare_methodsource(mods) {
6777 out.insert(name.clone());
6778 }
6779 }
6780 Member::Field { mods, declarators, .. } => {
6781 collect_mods_idents_all(mods, &mut out);
6782 let _ = declarators;
6784 }
6785 Member::Type(t) => {
6786 let _ = t; }
6788 Member::Raw(text) => {
6795 split_into(text, &mut out);
6796 }
6797 _ => {}
6798 }
6799 }
6800 });
6801 out
6802}
6803
6804fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6808 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6809 if !id.is_empty() {
6810 out.insert(id.to_string());
6811 }
6812 }
6813}
6814
6815fn has_bare_methodsource(mods: &str) -> bool {
6822 let hay = mods.as_bytes();
6823 const NEEDLE: &[u8] = b"MethodSource";
6824 let mut i = 0usize;
6825 while let Some(p) = mods[i..].find("MethodSource") {
6826 let at = i + p;
6827 let end = at + NEEDLE.len();
6828 let prev_ok = at == 0
6831 || !(hay[at - 1].is_ascii_alphanumeric()
6832 || hay[at - 1] == b'_'
6833 || hay[at - 1] == b'$');
6834 let next_ident =
6835 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6836 if prev_ok && !next_ident {
6837 let mut j = end;
6838 while j < hay.len() && hay[j].is_ascii_whitespace() {
6839 j += 1;
6840 }
6841 if j >= hay.len() || hay[j] != b'(' {
6842 return true;
6843 }
6844 }
6845 i = end;
6846 }
6847 false
6848}
6849
6850const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6854 "writeObject",
6855 "readObject",
6856 "readObjectNoData",
6857 "writeReplace",
6858 "readResolve",
6859 "validateObject",
6860];
6861
6862fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6863 use std::collections::HashSet;
6864 let mut removed = 0usize;
6865 let anon_names = collect_anon_and_string_names(ast, unit);
6867 let blind = collect_all_blind_refs(ast, unit);
6872 loop {
6873 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6880 let mut veto: HashSet<String> = HashSet::new();
6881 let mut roots: Vec<JavaId> = Vec::new();
6882 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6883 for body in roots {
6884 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6885 }
6886 for_each_type(unit, &mut |ty| {
6887 for m in &ty.members {
6888 if let Member::Method { name, params, .. } = m {
6889 if params.iter().any(|p| p.varargs) {
6890 veto.insert(name.clone());
6891 }
6892 }
6893 }
6894 });
6895 let mut changed = false;
6897 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6898 let before = ty.members.len();
6899 ty.members.retain(|m| {
6900 if let Member::Method { mods, name, params, .. } = m {
6901 let is_private = mods.split_whitespace().any(|w| w == "private");
6902 let annotated = mods.contains('@');
6903 let keep = !is_private
6904 || annotated
6905 || params.iter().any(|p| p.varargs)
6907 || veto.contains(name)
6908 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6912 || anon_names.contains(name)
6913 || blind.contains(name)
6914 || referenced.contains(&(name.clone(), params.len()));
6915 keep
6916 } else {
6917 true
6918 }
6919 });
6920 if ty.members.len() != before {
6921 removed += before - ty.members.len();
6922 changed = true;
6923 }
6924 });
6925 if !changed {
6926 return removed;
6927 }
6928 }
6929}
6930
6931fn strip_ta_prefix(name: &str) -> &str {
6936 if !name.starts_with('<') {
6937 return name;
6938 }
6939 let b = name.as_bytes();
6940 let mut depth = 0i32;
6941 for (i, &c) in b.iter().enumerate() {
6942 match c {
6943 b'<' => depth += 1,
6944 b'>' => {
6945 depth -= 1;
6946 if depth == 0 {
6947 return &name[i + 1..];
6948 }
6949 }
6950 _ => {}
6951 }
6952 }
6953 name }
6955
6956fn collect_call_arity_refs(
6958 ast: &JavaAst,
6959 root: JavaId,
6960 out: &mut std::collections::HashSet<(String, usize)>,
6961 veto: &mut std::collections::HashSet<String>,
6962) {
6963 let mut stack = vec![root];
6964 while let Some(id) = stack.pop() {
6965 match ast.data(id) {
6966 NodeData::Call => {
6967 let ch = ast.children(id).to_vec();
6968 if let Some(&callee) = ch.first() {
6969 let name = match ast.data(callee) {
6970 NodeData::Member { name } | NodeData::VarRef { name } => {
6971 ast.sn(*name).to_string()
6972 }
6973 _ => String::new(),
6974 };
6975 if !name.is_empty() {
6976 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6977 }
6978 }
6979 }
6980 NodeData::MethodRef { name } => {
6981 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6982 veto.insert(strip_ta_prefix(short).to_string());
6983 }
6984 }
6985 _ => {}
6986 }
6987 for &c in ast.children(id) {
6988 stack.push(c);
6989 }
6990 }
6991}
6992
6993fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6995 for ty in &unit.types {
6996 for_each_type_inner(ty, f);
6997 }
6998}
6999fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
7000 f(ty);
7001 for m in &ty.members {
7002 if let Member::Type(t) = m {
7003 for_each_type_inner(t, f);
7004 }
7005 }
7006}
7007
7008fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
7010 for ty in &mut unit.types {
7011 for_each_type_mut_inner(ty, f);
7012 }
7013}
7014fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
7015 f(ty);
7016 for m in &mut ty.members {
7017 if let Member::Type(t) = m {
7018 for_each_type_mut_inner(t, f);
7019 }
7020 }
7021}
7022
7023fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
7025 for m in &ty.members {
7026 match m {
7027 Member::Method { body: Some(b), .. }
7028 | Member::Constructor { body: Some(b), .. } => out.push(*b),
7029 Member::Initializer { body, .. } => out.push(*body),
7030 Member::Field { declarators, .. } => {
7031 for d in declarators {
7032 if let Some(init) = d.init {
7033 out.push(init);
7034 }
7035 }
7036 }
7037 Member::Type(t) => collect_member_roots(t, out),
7038 _ => {}
7039 }
7040 }
7041}
7042
7043fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
7058 use std::collections::{HashMap, HashSet};
7059 ast.noop_private_methods.clear();
7060 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
7061 let mut veto: HashSet<String> = HashSet::new();
7062 for_each_type(unit, &mut |ty: &TypeDecl| {
7063 for m in &ty.members {
7064 if let Member::Method { mods, name, params, body, throws, .. } = m {
7065 if mods.contains('@') || params.iter().any(|p| p.varargs) {
7066 veto.insert(name.clone());
7067 continue;
7068 }
7069 let is_private = mods.split_whitespace().any(|w| w == "private");
7070 let empty = match body {
7071 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
7072 None => false,
7073 };
7074 let no_throws = throws.is_empty();
7078 let cand = NoopCandidate {
7079 params: params.iter().map(|p| p.ty.clone()).collect(),
7080 noop: is_private && empty && no_throws,
7081 };
7082 by_name
7083 .entry(name.clone())
7084 .or_default()
7085 .entry(params.len())
7086 .or_default()
7087 .push(cand);
7088 }
7089 }
7090 });
7091 let blind = collect_all_blind_refs(ast, unit);
7094 let mut roots: Vec<JavaId> = Vec::new();
7097 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
7098 for root in roots {
7099 let mut stack = vec![root];
7100 while let Some(n) = stack.pop() {
7101 if let NodeData::MethodRef { name } = ast.data(n) {
7102 if let Some(short) = ast.sn(*name).rsplit("::").next() {
7103 veto.insert(strip_ta_prefix(short).to_string());
7104 }
7105 }
7106 for &c in ast.children(n) {
7107 stack.push(c);
7108 }
7109 }
7110 }
7111 for (name, arities) in by_name {
7112 if veto.contains(&name) || blind.contains(&name) {
7113 continue;
7114 }
7115 ast.noop_private_methods.insert(name, arities);
7116 }
7117}
7118
7119fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
7124 use std::collections::HashSet;
7125 let mut idents: HashSet<String> = HashSet::new();
7126 let mut roots: Vec<JavaId> = Vec::new();
7128 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
7129 for root in roots {
7130 let mut stack = vec![root];
7131 while let Some(n) = stack.pop() {
7132 match ast.data(n) {
7133 NodeData::VarDecl { name, ty, .. } => {
7134 idents.insert(name.clone());
7135 collect_ty_idents(ty, &mut idents);
7136 }
7137 NodeData::VarRef { name } => {
7138 let n = ast.sn(*name);
7142 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7143 if !id.is_empty() {
7144 idents.insert(id.to_string());
7145 }
7146 }
7147 }
7148 NodeData::Member { name } => {
7149 idents.insert(ast.sn(*name).to_string());
7150 }
7151 NodeData::MethodRef { name } => {
7152 let n = ast.sn(*name);
7153 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7154 if !id.is_empty() {
7155 idents.insert(id.to_string());
7156 }
7157 }
7158 }
7159 NodeData::Label { name } => {
7160 idents.insert(name.clone());
7161 }
7162 NodeData::New { ty, anon_raw, .. } => {
7163 collect_ty_idents(ty, &mut idents);
7164 if let Some(raw) = anon_raw {
7167 for id in
7168 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
7169 {
7170 if !id.is_empty() {
7171 idents.insert(id.to_string());
7172 }
7173 }
7174 }
7175 }
7176 NodeData::Cast { ty } => {
7177 collect_ty_idents(ty, &mut idents);
7178 }
7179 NodeData::InstanceOf { ty, .. } => {
7180 collect_ty_idents(ty, &mut idents);
7181 }
7182 NodeData::Catch { ty_raw, .. } => {
7183 for id in
7185 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
7186 {
7187 if !id.is_empty() {
7188 idents.insert(id.to_string());
7189 }
7190 }
7191 }
7192 NodeData::Raw { text } => {
7193 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7194 if !id.is_empty() {
7195 idents.insert(id.to_string());
7196 }
7197 }
7198 }
7199 _ => {}
7200 }
7201 for &c in ast.children(n) {
7202 stack.push(c);
7203 }
7204 }
7205 }
7206 for name in collect_all_blind_refs(ast, unit) {
7210 idents.insert(name);
7211 }
7212 for r in &unit.raws {
7215 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7216 if !id.is_empty() {
7217 idents.insert(id.to_string());
7218 }
7219 }
7220 }
7221 for_each_type(unit, &mut |ty| {
7224 collect_mods_idents(&ty.mods, &mut idents);
7225 idents.insert(ty.name.clone());
7226 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7227 if !id.is_empty() {
7228 idents.insert(id.to_string());
7229 }
7230 }
7231 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7235 if !id.is_empty() {
7236 idents.insert(id.to_string());
7237 }
7238 }
7239 for e in ty.extends.iter().chain(ty.implements.iter()) {
7240 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7241 if !id.is_empty() {
7242 idents.insert(id.to_string());
7243 }
7244 }
7245 }
7246 for m in &ty.members {
7247 match m {
7248 Member::Field { mods, ty, declarators } => {
7249 collect_mods_idents(mods, &mut idents);
7250 collect_ty_idents(ty, &mut idents);
7251 for d in declarators {
7252 idents.insert(d.name.clone());
7253 }
7254 }
7255 Member::Method { mods, ret, name, params, throws, .. } => {
7256 collect_mods_idents(mods, &mut idents);
7257 collect_ty_idents(ret, &mut idents);
7258 idents.insert(name.clone());
7259 for p in params {
7260 collect_mods_idents(&p.mods, &mut idents);
7261 collect_ty_idents(&p.ty, &mut idents);
7262 idents.insert(p.name.clone());
7263 }
7264 for t in throws {
7265 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7266 if !id.is_empty() {
7267 idents.insert(id.to_string());
7268 }
7269 }
7270 }
7271 }
7272 Member::Constructor { mods, name, params, throws, .. } => {
7273 collect_mods_idents(mods, &mut idents);
7274 idents.insert(name.clone());
7275 for p in params {
7276 collect_mods_idents(&p.mods, &mut idents);
7277 collect_ty_idents(&p.ty, &mut idents);
7278 idents.insert(p.name.clone());
7279 }
7280 for t in throws {
7281 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7282 if !id.is_empty() {
7283 idents.insert(id.to_string());
7284 }
7285 }
7286 }
7287 }
7288 Member::Type(t) => {
7289 let _ = t;
7291 }
7292 _ => {}
7293 }
7294 }
7295 });
7296 let before = unit.imports.len();
7298 unit.imports.retain(|imp| {
7299 if imp.contains('*') {
7300 return true; }
7302 let path = imp.trim_start_matches("static ").trim();
7304 let simple = path.rsplit('.').next().unwrap_or(path).trim();
7305 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
7306 return true; }
7308 idents.contains(simple)
7309 });
7310 before - unit.imports.len()
7311}
7312
7313fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
7314 match t {
7315 JType::Ref(r) => {
7316 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7317 if !id.is_empty() {
7318 out.insert(id.to_string());
7319 }
7320 }
7321 }
7322 JType::Array(inner) => collect_ty_idents(inner, out),
7323 _ => {}
7324 }
7325}
7326
7327fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
7328 if mods.contains('@') {
7330 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7331 if !id.is_empty() {
7332 out.insert(id.to_string());
7333 }
7334 }
7335 }
7336}
7337
7338fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
7344 use std::collections::HashSet;
7345 let mut removed = 0usize;
7346 let anon_names = collect_anon_and_string_names(ast, unit);
7348 let blind = collect_all_blind_refs(ast, unit);
7351 let mut referenced: HashSet<String> = HashSet::new();
7353 let mut bodies: Vec<JavaId> = Vec::new();
7354 for_each_type(unit, &mut |ty: &TypeDecl| {
7355 for m in &ty.members {
7356 match m {
7357 Member::Method { body: Some(b), .. }
7358 | Member::Constructor { body: Some(b), .. }
7359 | Member::Initializer { body: b, .. } => bodies.push(*b),
7360 Member::Field { declarators, .. } => {
7361 for d in declarators {
7362 if let Some(init) = d.init {
7363 bodies.push(init);
7364 }
7365 }
7366 }
7367 _ => {}
7368 }
7369 }
7370 });
7371 for root in bodies {
7372 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
7374 while let Some((n, is_callee)) = stack.pop() {
7375 match ast.kind(n) {
7376 NodeKind::VarRef if !is_callee => {
7377 if let Some(nm) = ast.var_name(n) {
7378 referenced.insert(nm.to_string());
7379 }
7380 }
7381 NodeKind::Member => {
7382 if let NodeData::Member { name: sym } = ast.data(n) {
7383 referenced.insert(ast.sn(*sym).to_string());
7384 }
7385 }
7386 _ => {}
7387 }
7388 if ast.kind(n) == NodeKind::Call {
7389 let ch = ast.children(n).to_vec();
7390 if let Some(&first) = ch.first() {
7391 stack.push((first, true));
7392 }
7393 for &c in &ch[1..] {
7394 stack.push((c, false));
7395 }
7396 continue;
7397 }
7398 for &c in ast.children(n) {
7399 stack.push((c, false));
7400 }
7401 }
7402 }
7403 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
7409 for m in ty.members.iter_mut() {
7410 if let Member::Field { mods, declarators, .. } = m {
7411 let is_private = mods.split_whitespace().any(|w| w == "private");
7412 let annotated = mods.contains('@');
7413 if !is_private || annotated {
7414 continue;
7415 }
7416 let dead: Vec<bool> = declarators
7417 .iter()
7418 .map(|d| {
7419 let reflective =
7420 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
7421 let init_ok =
7422 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
7423 !reflective
7424 && init_ok
7425 && !referenced.contains(&d.name)
7426 && !anon_names.contains(&d.name)
7427 && !blind.contains(&d.name)
7428 })
7429 .collect();
7430 let before = declarators.len();
7431 let mut idx = 0usize;
7432 declarators.retain(|_| {
7433 let keep = !dead[idx];
7434 idx += 1;
7435 keep
7436 });
7437 removed += before - declarators.len();
7438 }
7439 }
7440 let before = ty.members.len();
7441 ty.members
7442 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
7443 removed += before - ty.members.len();
7444 });
7445 removed
7446}
7447
7448fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
7449 for m in &mut ty.members {
7450 simplify_member(ast, m, cfg, total);
7451 }
7452}
7453
7454fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
7455 let params: Vec<(String, JType)> = match m {
7457 Member::Method { params, .. } | Member::Constructor { params, .. } => params
7458 .iter()
7459 .map(|p| (p.name.clone(), p.ty.clone()))
7460 .collect(),
7461 _ => Vec::new(),
7462 };
7463 ast.set_param_scope(¶ms);
7464 let prev_ret = ast.current_return_ty.take();
7465 if let Member::Method { ret, .. } = m {
7466 ast.current_return_ty = Some(ret.clone());
7467 }
7468 match m {
7469 Member::Method { body: Some(b), .. }
7470 | Member::Constructor { body: Some(b), .. } => {
7471 let r = simplify(ast, *b, cfg);
7472 merge_report(total, r);
7473 }
7474 Member::Initializer { body, .. } => {
7475 let r = simplify(ast, *body, cfg);
7476 merge_report(total, r);
7477 }
7478 Member::Field { declarators, .. } => {
7479 for d in declarators.iter_mut() {
7480 if let Some(init) = d.init {
7481 let r = simplify(ast, init, cfg);
7482 merge_report(total, r);
7483 let rules = default_java_rules();
7487 let mut cur: JavaId = init;
7488 for _ in 0..8 {
7489 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7490 Some((new_root, name)) => {
7491 d.init = Some(new_root);
7492 cur = new_root;
7493 *total.by_rule.entry(name).or_insert(0) += 1;
7494 total.edits += 1;
7495 let r = simplify(ast, new_root, cfg);
7497 merge_report(total, r);
7498 }
7499 None => break,
7500 }
7501 }
7502 }
7503 }
7504 }
7505 Member::Type(t) => simplify_type(ast, t, cfg, total),
7506 Member::Raw(_) => {}
7507 _ => {}
7508 }
7509 ast.current_return_ty = prev_ret;
7510}
7511
7512fn merge_report(total: &mut Report, r: Report) {
7513 total.edits += r.edits;
7514 total.iterations += r.iterations;
7515 for (k, v) in r.by_rule {
7516 *total.by_rule.entry(k).or_insert(0) += v;
7517 }
7518}
7519
7520pub fn default_config() -> Config {
7522 Config::default()
7523}