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 return Some(Edit::Replace {
71 target: inner,
72 with: operand,
73 });
74 }
75 }
76 None
77 }
78}
79
80pub struct SelfCompare;
83
84impl Rule<JavaAst> for SelfCompare {
85 fn name(&self) -> &'static str {
86 "self_compare"
87 }
88 fn kinds(&self) -> &'static [NodeKind] {
89 &[NodeKind::Binary]
90 }
91 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
92 use cure_engine::kind::BinOp::{Eq, Ne};
93 let lang = ctx.lang;
94 let NodeData::Binary { op } = lang.data(id) else {
95 return None;
96 };
97 if !matches!(op, Eq | Ne) {
98 return None;
99 }
100 let ch = lang.children(id);
101 let (NodeData::VarRef { name: n1 }, NodeData::VarRef { name: n2 }) =
102 (lang.data(ch[0]), lang.data(ch[1]))
103 else {
104 return None;
105 };
106 if n1 != n2 || !lang.is_exact_int(ch[0]) {
107 return None;
108 }
109 let val = lang.build_bool(*op == Eq);
110 Some(Edit::Replace {
111 target: id,
112 with: val,
113 })
114 }
115}
116
117
118pub struct StringBuilderFold;
129
130impl Rule<JavaAst> for StringBuilderFold {
131 fn name(&self) -> &'static str {
132 "string_builder_fold"
133 }
134 fn kinds(&self) -> &'static [NodeKind] {
135 &[NodeKind::Call]
136 }
137 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
138 let root = ctx.root();
139 let lang = ctx.lang;
140 if lang.kind(id) != NodeKind::Call {
142 return None;
143 }
144 let callee = lang.children(id)[0];
145 if let NodeData::Member { name } = lang.data(callee) {
146 if lang.sn(*name) != "toString" || !lang.children(id)[1..].is_empty() {
147 return None;
148 }
149 } else {
150 return None;
151 }
152 let mut node = lang.children(callee)[0]; let mut parts: Vec<JavaId> = Vec::new();
154 loop {
156 match lang.data(node) {
157 NodeData::Call => {
158 let c = lang.children(node);
159 if let NodeData::Member { name } = lang.data(c[0]) {
160 if lang.sn(*name) == "append" && c.len() == 2 {
161 parts.push(c[1]);
162 node = lang.children(c[0])[0];
163 continue;
164 }
165 }
166 return None;
167 }
168 NodeData::New { ty, .. } => {
169 let is_sb = !lang.unit_shadows_lang
170 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
171 if !is_sb {
172 return None;
173 }
174 let args = lang.children(node).to_vec();
175 match args.len() {
176 0 => {} 1 => {
178 let a = args[0];
183 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
184 || lang
185 .var_type(a)
186 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
187 if stringy {
188 parts.push(a);
189 } else if !matches!(
190 lang.literal(a),
191 Some(LitRef::Int(_) | LitRef::Long(_))
192 ) {
193 return None;
194 }
195 }
196 _ => return None,
197 }
198 break;
199 }
200 _ => return None,
201 }
202 }
203 if parts.iter().all(|&p| lang.literal(p).is_some())
207 && lang.has_string_identity_compare(root)
208 {
209 return None;
210 }
211 if parts.is_empty() {
212 let empty = lang.build_str("");
214 return Some(Edit::Replace {
215 target: id,
216 with: empty,
217 });
218 }
219 parts.reverse();
221 let first = parts[0];
222 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
223 || lang
224 .var_type(first)
225 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
226 let mut acc = if stringy_first {
227 first
228 } else {
229 let empty = lang.build_str("");
230 lang.build_bin(BinOp::Add, empty, first)
231 };
232 for &p in &parts[1..] {
233 acc = lang.build_bin(BinOp::Add, acc, p);
234 }
235 Some(Edit::Replace {
236 target: id,
237 with: acc,
238 })
239 }
240}
241
242pub struct BoxUnboxChain;
250
251const BOXERS: &[(&str, &str)] = &[
252 ("Integer", "intValue"),
253 ("java.lang.Integer", "intValue"),
254 ("Long", "longValue"),
255 ("java.lang.Long", "longValue"),
256 ("Boolean", "booleanValue"),
257 ("java.lang.Boolean", "booleanValue"),
258 ("Double", "doubleValue"),
259 ("java.lang.Double", "doubleValue"),
260 ("Float", "floatValue"),
261 ("java.lang.Float", "floatValue"),
262 ("Character", "charValue"),
263 ("java.lang.Character", "charValue"),
264 ("Short", "shortValue"),
265 ("java.lang.Short", "shortValue"),
266 ("Byte", "byteValue"),
267 ("java.lang.Byte", "byteValue"),
268];
269
270impl Rule<JavaAst> for BoxUnboxChain {
271 fn name(&self) -> &'static str {
272 "box_unbox_chain"
273 }
274 fn kinds(&self) -> &'static [NodeKind] {
275 &[NodeKind::Call]
276 }
277 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
278 let lang = ctx.lang;
279 if lang.kind(id) != NodeKind::Call {
281 return None;
282 }
283 let callee = lang.children(id)[0];
284 let NodeData::Member { name: unbox } = lang.data(callee) else {
285 return None;
286 };
287 if !lang.children(id)[1..].is_empty() {
288 return None;
289 }
290 let inner = lang.children(callee)[0];
291 if lang.kind(inner) != NodeKind::Call {
292 return None;
293 }
294 let ich = lang.children(inner).to_vec();
296 let (box_name, arg): (String, JavaId) = if let NodeData::New { ty, .. } = lang.data(inner)
297 {
298 let n = match ty {
299 JType::Ref(r) => r.clone(),
300 _ => return None,
301 };
302 if ich.len() != 1 {
303 return None;
304 }
305 (n, ich[0])
306 } else {
307 let icallee = ich.first().copied()?;
309 if let NodeData::Member { name } = lang.data(icallee) {
310 let recv = lang.children(icallee)[0];
312 if lang.sn(*name) != "valueOf" {
313 return None;
314 }
315 {
316 let n = lang.var_name(recv)?;
317 (n.to_string(), *ich.get(1)?)
318 }
319 } else if let NodeData::VarRef { name } = lang.data(icallee) {
320 let _ = name;
323 return None;
324 } else {
325 return None;
326 }
327 };
328 let unbox_name = *unbox;
329 let expected = BOXERS
330 .iter()
331 .find(|(b, _)| *b == box_name.as_str())
332 .map(|(_, u)| *u)?;
333 if expected != lang.sn(unbox_name) {
334 return None;
335 }
336 let var_is = |t: fn(&JType) -> bool| lang.var_type(arg).is_some_and(t);
339 let lit_is_num = lang.literal(arg).is_some_and(|l| {
340 matches!(
341 l,
342 LitRef::Int(_) | LitRef::Long(_) | LitRef::Float(_) | LitRef::Double(_)
343 )
344 });
345 let ok = match box_name.trim_start_matches("java.lang.") {
346 "Integer" | "Long" | "Short" | "Byte" => lang.is_exact_int(arg),
347 "Double" | "Float" => {
348 lit_is_num
349 || var_is(|t| matches!(t, JType::Double | JType::Float | JType::Int | JType::Long))
350 }
351 "Boolean" => lang.is_bool(arg),
352 "Character" => {
353 matches!(lang.literal(arg), Some(LitRef::Char(_))) || var_is(|t| *t == JType::Char)
354 }
355 _ => false,
356 };
357 if !ok {
358 return None;
359 }
360 Some(Edit::Replace {
361 target: id,
362 with: arg,
363 })
364 }
365}
366
367pub struct IteratorToForEach;
379
380impl Rule<JavaAst> for IteratorToForEach {
381 fn name(&self) -> &'static str {
382 "iterator_to_for_each"
383 }
384 fn kinds(&self) -> &'static [NodeKind] {
385 &[NodeKind::For]
386 }
387 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
388 let lang = ctx.lang;
389 let NodeData::For {
390 inits,
391 has_cond,
392 steps,
393 } = lang.data(id)
394 else {
395 return None;
396 };
397 if *inits != 1 || !*has_cond || *steps != 0 {
398 return None;
399 }
400 let ch = lang.children(id).to_vec();
401 let (init, cond, body) = (ch[0], ch[1], ch[2]);
403
404 let NodeData::VarDecl { name: it_name, .. } = lang.data(init) else {
406 return None;
407 };
408 let it_name = it_name.clone();
409 let init_call = lang.children(init).first().copied()?;
410 if lang.kind(init_call) != NodeKind::Call {
411 return None;
412 }
413 let ic = lang.children(init_call).to_vec();
414 let icallee = *ic.first()?;
415 let NodeData::Member { name: m0 } = lang.data(icallee) else {
416 return None;
417 };
418 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
419 return None;
420 }
421 let iterable = lang.children(icallee)[0];
422 if subtree_has_var(&*lang, iterable, &it_name) {
424 return None;
425 }
426
427 if lang.kind(cond) != NodeKind::Call {
429 return None;
430 }
431 let cc = lang.children(cond).to_vec();
432 let ccallee = *cc.first()?;
433 let NodeData::Member { name: m1 } = lang.data(ccallee) else {
434 return None;
435 };
436 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
437 return None;
438 }
439 if lang.var_name(lang.children(ccallee)[0]) != Some(it_name.as_str()) {
440 return None;
441 }
442
443 if lang.kind(body) != NodeKind::Block {
445 return None;
446 }
447 let bch = lang.children(body).to_vec();
448 let first = *bch.first()?;
449 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
450 return None;
451 };
452 let (e_name, ty) = (e_name.clone(), ty.clone());
453 let next_call = lang.children(first).first().copied()?;
454 if lang.kind(next_call) != NodeKind::Call {
455 return None;
456 }
457 let nc = lang.children(next_call).to_vec();
458 let ncallee = *nc.first()?;
459 let NodeData::Member { name: m2 } = lang.data(ncallee) else {
460 return None;
461 };
462 if lang.sn(*m2) != "next" || nc.len() != 1 {
463 return None;
464 }
465 if lang.var_name(lang.children(ncallee)[0]) != Some(it_name.as_str()) {
466 return None;
467 }
468 let rest = bch[1..].to_vec();
470 for &r in &rest {
471 if subtree_has_var(&*lang, r, &it_name) {
472 return None;
473 }
474 }
475 let new_body = lang.build_block(rest);
476 let with = lang.for_each(&e_name, ty, iterable, new_body);
477 Some(Edit::Replace {
478 target: id,
479 with,
480 })
481 }
482}
483
484fn unwrap_paren_cast(lang: &JavaAst, mut n: JavaId) -> JavaId {
486 loop {
487 match lang.kind(n) {
488 NodeKind::Paren | NodeKind::Cast => {
489 let ch = lang.children(n);
490 if ch.is_empty() {
491 return n;
492 }
493 n = ch[0];
494 }
495 _ => return n,
496 }
497 }
498}
499
500fn generic_elem_ty(ty: &JType) -> Option<JType> {
502 let name = match ty {
503 JType::Ref(n) => n.as_str(),
504 _ => return None,
505 };
506 let lt = name.find('<')?;
507 let gt = name.rfind('>')?;
508 if gt <= lt {
509 return None;
510 }
511 let inner = name[lt + 1..gt].trim();
512 if inner.is_empty() || inner.contains('<') {
513 return None; }
515 Some(JType::Ref(inner.to_string()))
516}
517
518fn statically_iterable(ty: &JType) -> bool {
525 match ty {
526 JType::Array(_) => true,
527 JType::Ref(name) => {
528 let base = name.split('<').next().unwrap_or(name).trim();
529 let last = base.rsplit('.').next().unwrap_or(base);
530 matches!(
531 last,
532 "Iterable"
533 | "Collection"
534 | "List"
535 | "ArrayList"
536 | "LinkedList"
537 | "Vector"
538 | "Stack"
539 | "AbstractList"
540 | "AbstractCollection"
541 | "Set"
542 | "HashSet"
543 | "LinkedHashSet"
544 | "TreeSet"
545 | "SortedSet"
546 | "NavigableSet"
547 | "AbstractSet"
548 | "Queue"
549 | "Deque"
550 | "ArrayDeque"
551 | "PriorityQueue"
552 | "BlockingQueue"
553 | "BlockingDeque"
554 | "ConcurrentLinkedQueue"
555 | "ConcurrentLinkedDeque"
556 | "ConcurrentSkipListSet"
557 | "CopyOnWriteArrayList"
558 | "CopyOnWriteArraySet"
559 | "EnumSet"
560 | "LinkedBlockingQueue"
561 | "LinkedBlockingDeque"
562 | "ArrayBlockingQueue"
563 | "PriorityBlockingQueue"
564 | "DelayQueue"
565 | "SynchronousQueue"
566 | "LinkedTransferQueue"
567 | "TransferQueue"
568 )
569 }
570 _ => false,
571 }
572}
573
574fn for_each_elem_ok(ty: &JType) -> bool {
578 match ty {
579 JType::Ref(n) => !n.trim_start().starts_with('?'),
580 _ => true,
581 }
582}
583
584fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
586 let mut stack = vec![root];
587 while let Some(n) = stack.pop() {
588 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
589 out.push(n);
590 }
591 for &c in lang.children(n) {
592 stack.push(c);
593 }
594 }
595}
596
597fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
599 let mut stack = vec![root];
600 while let Some(n) = stack.pop() {
601 for &c in lang.children(n) {
602 if c == target {
603 return n;
604 }
605 stack.push(c);
606 }
607 }
608 root
609}
610
611fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
612 let mut stack = vec![id];
613 while let Some(n) = stack.pop() {
614 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
615 return true;
616 }
617 for &c in lang.children(n) {
618 stack.push(c);
619 }
620 }
621 false
622}
623
624fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
626 let mut stack = vec![id];
627 while let Some(n) = stack.pop() {
628 match lang.data(n) {
629 NodeData::Catch { name: cn, .. } if cn == name => return true,
630 NodeData::ForEach { name: vn, .. } if vn == name => return true,
631 NodeData::Lambda { params_raw } => {
634 for part in
635 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
636 {
637 if part == name {
638 return true;
639 }
640 }
641 }
642 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
644 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
647 _ => {}
648 }
649 for &c in lang.children(n) {
650 stack.push(c);
651 }
652 }
653 false
654}
655
656pub struct NewStringFold;
662
663impl Rule<JavaAst> for NewStringFold {
664 fn name(&self) -> &'static str {
665 "new_string_fold"
666 }
667 fn kinds(&self) -> &'static [NodeKind] {
668 &[NodeKind::New]
669 }
670 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
671 let root = ctx.root();
672 let lang = ctx.lang;
673 let NodeData::New { ty, .. } = lang.data(id) else {
674 return None;
675 };
676 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
677 return None;
678 }
679 if lang.unit_shadows_lang {
683 return None;
684 }
685 let _ = root;
688 if lang.unit_string_identity {
689 return None;
690 }
691 let args = lang.children(id).to_vec();
692 match args.len() {
693 0 => {
694 let empty = lang.build_str("");
695 Some(Edit::Replace {
696 target: id,
697 with: empty,
698 })
699 }
700 1 => {
701 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
702 Some(Edit::Replace {
703 target: id,
704 with: args[0],
705 })
706 } else {
707 None
708 }
709 }
710 _ => None,
711 }
712 }
713}
714
715
716pub struct NewStringCharArrayFold;
722
723impl Rule<JavaAst> for NewStringCharArrayFold {
724 fn name(&self) -> &'static str {
725 "new_string_char_array_fold"
726 }
727 fn kinds(&self) -> &'static [NodeKind] {
728 &[NodeKind::New]
729 }
730 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
731 let root = ctx.root();
732 let lang = ctx.lang;
733 let NodeData::New { ty, .. } = lang.data(id) else {
734 return None;
735 };
736 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
737 return None;
738 }
739 let _ = root;
740 if lang.unit_shadows_lang || lang.unit_string_identity {
741 return None;
742 }
743 let args = lang.children(id).to_vec();
745 if args.len() != 1 {
746 return None;
747 }
748 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
749 return None;
750 };
751 if *sized != 0 {
752 return None;
753 }
754 let arr_children = lang.children(args[0]).to_vec();
755 let lit = *arr_children.first()?;
756 if lang.kind(lit) != NodeKind::ArrayLit {
757 return None;
758 }
759 let mut text = String::new();
760 for &c in lang.children(lit) {
761 match lang.literal(c) {
762 Some(LitRef::Char(ch)) => text.push(ch),
763 _ => return None, }
765 }
766 let with = lang.build_str(&text);
767 Some(Edit::Replace {
768 target: id,
769 with,
770 })
771 }
772}
773
774pub struct EmptyFinallyStrip;
782
783impl Rule<JavaAst> for EmptyFinallyStrip {
784 fn name(&self) -> &'static str {
785 "empty_finally_strip"
786 }
787 fn kinds(&self) -> &'static [NodeKind] {
788 &[NodeKind::Try]
789 }
790 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
791 let lang = ctx.lang;
792 if lang.kind(id) != NodeKind::Try {
793 return None;
794 }
795 let ch = lang.children(id).to_vec();
796 let is_empty_block = |n: JavaId| -> bool {
803 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
804 };
805 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
806 if ch.len() < try_idx + 2 {
807 return None; }
809 let last = *ch.last()?;
811 if lang.kind(last) == NodeKind::Catch {
812 return None;
813 }
814 if !is_empty_block(last) {
815 return None;
816 }
817 Some(Edit::Splice {
820 node: id,
821 index: ch.len() - 1,
822 remove: 1,
823 insert: Vec::new(),
824 })
825 }
826}
827
828fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
874 if lang.kind(a) != lang.kind(b) {
875 let (ia, ib) = (lang.kind(a), lang.kind(b));
877 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
878 if k == NodeKind::ExprStmt {
879 lang.children(x).first().copied()
880 } else {
881 Some(x)
882 }
883 };
884 return match (unwrap(ia, a), unwrap(ib, b)) {
885 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
886 _ => false,
887 };
888 }
889 print_equiv_inner(lang, a, b)
890}
891
892fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
893 if lang.kind(a) != lang.kind(b) {
894 return false;
895 }
896 match (lang.literal(a), lang.literal(b)) {
898 (Some(x), Some(y)) => {
899 if x != y {
900 return false;
901 }
902 }
903 (None, None) => {}
904 _ => return false,
905 }
906 let (ca, cb) = (lang.children(a), lang.children(b));
907 if ca.len() != cb.len() {
908 return false;
909 }
910 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
911}
912
913fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
917 for &root in rest {
918 let mut stack = vec![root];
919 while let Some(n) = stack.pop() {
920 match lang.kind(n) {
921 NodeKind::Assign => {
922 let t = lang.children(n)[0];
923 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
924 return true;
925 }
926 }
927 NodeKind::Unary => {
928 if let NodeData::Unary { op } = lang.data(n) {
929 if op.is_incdec() {
930 let t = lang.children(n)[0];
931 if lang.kind(t) == NodeKind::VarRef
932 && lang.var_name(t) == Some(name)
933 {
934 return true;
935 }
936 }
937 }
938 }
939 _ => {}
940 }
941 for &c in lang.children(n) {
942 stack.push(c);
943 }
944 }
945 }
946 false
947}
948
949pub struct StaticExec;
950
951impl Rule<JavaAst> for StaticExec {
952 fn name(&self) -> &'static str {
953 "static_exec"
954 }
955 fn kinds(&self) -> &'static [NodeKind] {
956 &[NodeKind::Block]
957 }
958 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
959 if ctx.parent(id).is_some() {
962 return None;
963 }
964 let lang = ctx.lang;
965 if lang.kind(id) != NodeKind::Block {
966 return None;
967 }
968 let stmts = lang.children(id).to_vec();
969 if stmts.len() < 3 {
970 return None; }
972 let mut ex = vexec::Exec::new(lang);
978 let run = ex.run_prefix(&stmts).ok()?;
979 if std::env::var("CURE_DBG_P1").is_ok() {
980 eprintln!(
981 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
982 run.cuts.len(),
983 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
984 ex.budget_exhausted(),
985 ex.effect_log().len(),
986 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
987 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
988 ex.effect_log().iter().map(|e| match e {
989 vexec::EffectEvent::FieldWrite(_) => "FW",
990 vexec::EffectEvent::ClassLoad { .. } => "CL",
991 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
992 }).collect::<Vec<_>>()
993 );
994 }
995 if ex.budget_exhausted() {
997 return None;
998 }
999 let root_idx = run.root_cut_index();
1000 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
1002 if total_completed < 3 {
1003 return None;
1004 }
1005 let true_rest = run.true_rest();
1009 if !true_rest.is_empty() {
1010 for ev in ex.effect_log().iter() {
1011 if let vexec::EffectEvent::FieldWrite(k) = ev {
1012 if let Some(name) = lang.name_of_key(*k) {
1013 if lang.final_fields.contains(&name)
1018 && rest_reassigns_field(lang, &true_rest, &name)
1019 {
1020 return None;
1021 }
1022 }
1023 }
1024 }
1025 }
1026 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1028 let field_finals: Vec<(u32, vexec::VVal)> =
1029 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1030 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1031 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1034 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1035 _ => None,
1036 };
1037 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1040 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1046 for (j, cut) in run.cuts.iter().enumerate() {
1047 let range_end = if j == 0 {
1048 decl_order.len()
1049 } else {
1050 run.cuts[j - 1].decl_start
1051 };
1052 let rest_j = run.rest_for_cut(j);
1053 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1054 let mut live_names: std::collections::HashSet<String> =
1055 std::collections::HashSet::new();
1056 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1057 if ex.var_value(k).is_none() {
1058 continue; }
1060 let name = lang.name_of_key(k)?;
1061 live_names.insert(name.clone());
1062 let referenced = rest_j.iter().any(|&st| {
1063 cure_engine::analysis::subtree_contains(lang, st, |n| {
1064 lang.kind(n) == NodeKind::VarRef
1065 && lang.var_name(n) == Some(name.as_str())
1066 })
1067 });
1068 if referenced {
1069 esc.push((
1070 k,
1071 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1072 ex.var_domain(k),
1073 ex.var_decl_ty(k).unwrap_or(JType::Var),
1076 ));
1077 }
1078 }
1079 escaped_by_cut.push(esc);
1080 cut_live_names.push(live_names);
1081 }
1082 drop(ex);
1083 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1085 for (j, cut) in run.cuts.iter().enumerate() {
1086 let block_id = if j == root_idx {
1087 id
1088 } else {
1089 cut.block_hint?
1090 };
1091 let mut insert: Vec<JavaId> = Vec::new();
1092 for pos in cut.log_start..cut.log_end {
1094 match &log[pos] {
1095 vexec::EffectEvent::FieldWrite(k) => {
1096 let is_last = !log[pos + 1..]
1098 .iter()
1099 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1100 if !is_last {
1101 continue;
1102 }
1103 let name = lang.name_of_key(*k)?;
1104 if lang.is_param_name(&name) {
1106 return None;
1107 }
1108 let v = field_finals
1109 .iter()
1110 .find(|(fk, _)| fk == k)
1111 .map(|(_, v)| v.clone())?;
1112 let v = match (&v, lang.field_types.get(&name)) {
1117 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1118 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1119 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1120 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1121 _ => v,
1122 };
1123 let (val, mut extra) = materialize(lang, &v)?;
1124 let tgt = lang.var(&name);
1125 insert.push(lang.assign(tgt, val));
1126 insert.append(&mut extra);
1127 }
1128 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1129 if std::env::var("CURE_DBG_P1").is_ok() {
1130 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1131 }
1132 let name = lang.name_of_key(*k)?;
1137 if lang.is_param_name(&name) {
1139 return None;
1140 }
1141 let cch = lang.children(*call).to_vec();
1142 let mut new_args: Vec<JavaId> = Vec::new();
1143 let mut kept_original: Vec<bool> = Vec::new();
1144 for (i, &arg) in cch.iter().enumerate().skip(1) {
1145 let val = args.get(i - 1).cloned().flatten();
1146 let replaced = match val {
1147 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1148 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1149 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1150 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1151 _ => None,
1152 };
1153 kept_original.push(replaced.is_none());
1154 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1155 }
1156 let names_j = cut_live_names.get(j)?;
1160 for ((i, &arg), &kept) in
1161 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1162 {
1163 let _ = i;
1164 if kept
1165 && !names_j.is_empty()
1166 && cure_engine::analysis::subtree_contains(
1167 lang,
1168 arg,
1169 |n| {
1170 lang.kind(n) == NodeKind::VarRef
1171 && lang
1172 .var_name(n)
1173 .is_some_and(|nm| names_j.contains(nm))
1174 },
1175 )
1176 {
1177 return None;
1178 }
1179 }
1180 let callee = *lang.children(*call).first()?;
1181 let callee_copy = lang.copy_subtree(callee);
1182 let mut ch = vec![callee_copy];
1183 ch.extend(new_args);
1184 let new_call = lang.clone_node(*call, ch);
1185 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1186 lang.clone_node(*stmt, vec![new_call])
1187 } else {
1188 new_call
1189 };
1190 let tgt = lang.var(&name);
1192 let assign = lang.assign(tgt, new_stmt);
1193 insert.push(assign);
1194 }
1195 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1196 let lit = lang.lit(Lit::Str(name.clone()));
1204 let callee = *lang.children(*call).first()?;
1205 let callee_copy = lang.copy_subtree(callee);
1206 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1207 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1208 lang.clone_node(*stmt, vec![new_call])
1209 } else {
1210 new_call
1211 };
1212 let try_block = lang.block(vec![new_stmt]);
1213 let catch_block = lang.block(vec![]);
1214 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1215 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1216 insert.push(wrapped);
1217 }
1218 }
1219 }
1220 let mut alias_first: std::collections::HashMap<usize, String> =
1225 std::collections::HashMap::new();
1226 for (k, v, dom, dty) in &escaped_by_cut[j] {
1227 let name = lang.name_of_key(*k)?;
1228 let (val, mut extra) = match v {
1229 vexec::VVal::CA(a) => {
1230 let addr = std::rc::Rc::as_ptr(a) as usize;
1231 match alias_first.get(&addr) {
1232 Some(first) => (lang.var(first), vec![]),
1233 None => {
1234 alias_first.insert(addr, name.clone());
1235 materialize(lang, v)?
1236 }
1237 }
1238 }
1239 vexec::VVal::SA(a) => {
1240 let addr = std::rc::Rc::as_ptr(a) as usize;
1241 match alias_first.get(&addr) {
1242 Some(first) => (lang.var(first), vec![]),
1243 None => {
1244 alias_first.insert(addr, name.clone());
1245 materialize(lang, v)?
1246 }
1247 }
1248 }
1249 _ => materialize(lang, v)?,
1250 };
1251 let (ty, val) = match (*dom, val) {
1256 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1257 match lang.literal(lit) {
1260 Some(LitRef::Int(v)) => {
1261 match u32::try_from(v).ok().and_then(char::from_u32) {
1262 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1263 None => (JType::Char, lit),
1264 }
1265 }
1266 Some(LitRef::Long(v)) => {
1267 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1268 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1269 None => (JType::Char, lit),
1270 }
1271 }
1272 _ => (JType::Char, lit),
1273 }
1274 }
1275 (3, lit) => (JType::Byte, lit),
1276 (4, lit) => (JType::Short, lit),
1277 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1278 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1279 (JType::Long, lang.build_int(v, true))
1280 } else {
1281 (JType::Long, lit)
1282 }
1283 }
1284 (_, lit) => {
1289 if matches!(dty, JType::Ref(_)) {
1290 (dty.clone(), lit)
1291 } else {
1292 (JType::Var, lit)
1293 }
1294 }
1295 };
1296 let stmt = lang.var_decl(&name, ty, Some(val));
1297 insert.push(stmt);
1298 insert.append(&mut extra);
1299 }
1300 if j == root_idx {
1302 if let Some(v) = ret_payload.clone().flatten() {
1303 let (val, mut extra) = materialize(lang, &v)?;
1304 let stmt = lang.ret(Some(val));
1305 insert.push(stmt);
1306 insert.append(&mut extra);
1307 }
1308 }
1309 if insert.is_empty() && cut.completed == 0 {
1311 continue;
1312 }
1313 {
1316 let removed = lang.children(block_id).to_vec();
1317 if insert.len() == cut.completed
1318 && cut.completed <= removed.len()
1319 && insert
1320 .iter()
1321 .zip(removed[..cut.completed].iter())
1322 .all(|(&n, &o)| print_equiv(lang, n, o))
1323 {
1324 continue;
1325 }
1326 }
1327 edits.push(Edit::Splice {
1328 node: block_id,
1329 index: 0,
1330 remove: cut.completed,
1331 insert,
1332 });
1333 }
1334 if std::env::var("CURE_DBG_P1").is_ok() {
1335 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1336 }
1337 if edits.is_empty() {
1338 return None;
1339 }
1340 if edits.len() == 1 {
1342 Some(edits.pop().unwrap())
1343 } else {
1344 Some(Edit::Multi(edits))
1345 }
1346 }
1347}
1348
1349fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1352 use vexec::VVal;
1353 match v {
1354 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1355 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1356 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1357 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1358 VVal::CA(a) => {
1359 let chars: Vec<char> = a.borrow().clone();
1361 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1362 let lit_arr = lang.array_lit(lits);
1363 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1364 Some((node, vec![]))
1365 }
1366 VVal::SA(elems) => {
1367 let arr = elems.borrow().clone();
1370 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1371 let lits: Vec<JavaId> = arr
1372 .iter()
1373 .map(|e| match e {
1374 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1375 _ => unreachable!(),
1376 })
1377 .collect();
1378 let lit_arr = lang.array_lit(lits);
1379 let node =
1380 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1381 Some((node, vec![]))
1382 } else {
1383 None
1388 }
1389 }
1390 VVal::Undef => None,
1392 }
1393}
1394
1395pub struct TryUnwrapRethrow;
1396
1397impl Rule<JavaAst> for TryUnwrapRethrow {
1398 fn name(&self) -> &'static str {
1399 "try_unwrap_rethrow"
1400 }
1401 fn kinds(&self) -> &'static [NodeKind] {
1402 &[NodeKind::Try]
1403 }
1404 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1405 let lang = ctx.lang;
1406 if lang.kind(id) != NodeKind::Try {
1407 return None;
1408 }
1409 let ch = lang.children(id).to_vec();
1410 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1412 let catches: Vec<JavaId> =
1413 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1414 if catches.is_empty() {
1415 return None;
1416 }
1417 let rethrow_only = |c: JavaId| -> bool {
1418 let block = match lang.children(c).first() {
1420 Some(&b) => b,
1421 None => return false,
1422 };
1423 let stmts = lang.children(block);
1424 if stmts.len() != 1 {
1425 return false;
1426 }
1427 let stmt = stmts[0];
1428 if lang.kind(stmt) != NodeKind::Throw {
1429 return false;
1430 }
1431 let Some(&thrown) = lang.children(stmt).first() else { return false };
1432 if lang.kind(thrown) != NodeKind::VarRef {
1434 return false;
1435 }
1436 match &lang.node(c).data {
1437 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1438 _ => false,
1439 }
1440 };
1441 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1442 if all_rethrow {
1443 let extras: Vec<JavaId> = ch
1445 .iter()
1446 .copied()
1447 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1448 .collect();
1449 if extras.is_empty() {
1450 return Some(Edit::Replace {
1451 target: id,
1452 with: try_block,
1453 });
1454 }
1455 return None;
1456 }
1457 let catch_slots: Vec<usize> = ch
1463 .iter()
1464 .enumerate()
1465 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1466 .map(|(i, _)| i)
1467 .collect();
1468 for &slot in &catch_slots {
1469 let c = ch[slot];
1470 if !rethrow_only(c) {
1471 continue;
1472 }
1473 let later_all_rethrow = catch_slots
1474 .iter()
1475 .filter(|&&s2| s2 > slot)
1476 .all(|&s2| rethrow_only(ch[s2]));
1477 if !later_all_rethrow {
1478 continue;
1479 }
1480 return Some(Edit::Splice {
1481 node: id,
1482 index: slot,
1483 remove: 1,
1484 insert: vec![],
1485 });
1486 }
1487 None
1488 }
1489}
1490
1491pub struct TryUnwrapNoCatch;
1492
1493impl Rule<JavaAst> for TryUnwrapNoCatch {
1494 fn name(&self) -> &'static str {
1495 "try_unwrap_no_catch"
1496 }
1497 fn kinds(&self) -> &'static [NodeKind] {
1498 &[NodeKind::Try]
1499 }
1500 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1501 let lang = ctx.lang;
1502 if lang.kind(id) != NodeKind::Try {
1503 return None;
1504 }
1505 let ch = lang.children(id).to_vec();
1506 if ch.is_empty() {
1507 return None;
1508 }
1509 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1512 if has_catch {
1513 return None;
1514 }
1515 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1519 let rest: Vec<JavaId> = ch
1520 .iter()
1521 .copied()
1522 .filter(|&c| c != try_block)
1523 .collect();
1524 for &r in &rest {
1527 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1528 if !is_empty_block {
1529 return None; }
1531 }
1532 Some(Edit::Replace {
1533 target: id,
1534 with: try_block,
1535 })
1536 }
1537}
1538
1539pub struct LoopHeadBreak;
1547
1548impl Rule<JavaAst> for LoopHeadBreak {
1549 fn name(&self) -> &'static str {
1550 "loop_head_break"
1551 }
1552 fn kinds(&self) -> &'static [NodeKind] {
1553 &[NodeKind::While, NodeKind::DoWhile]
1554 }
1555 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1556 let lang = ctx.lang;
1557 let body = match lang.kind(id) {
1558 NodeKind::While => {
1559 let ch = lang.children(id).to_vec();
1560 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1561 return None;
1562 }
1563 ch[1]
1564 }
1565 NodeKind::DoWhile => {
1566 let ch = lang.children(id).to_vec();
1567 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1568 return None;
1569 }
1570 ch[0]
1571 }
1572 _ => return None,
1573 };
1574 if lang.kind(body) != NodeKind::Block {
1575 return None;
1576 }
1577 let bch = lang.children(body).to_vec();
1578 let is_do = lang.kind(id) == NodeKind::DoWhile;
1579 if is_do && bch.len() != 1 {
1581 return None;
1582 }
1583 let first = *bch.first()?;
1584 if lang.kind(first) != NodeKind::If {
1585 return None;
1586 }
1587 let ich = lang.children(first).to_vec();
1588 let c = ich[0];
1589
1590 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1591 let is_break = |n: JavaId| match lang.kind(n) {
1592 NodeKind::Break => bare_break(n),
1593 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1594 _ => false,
1595 };
1596 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1597 let ends_with_continue = |n: JavaId| -> bool {
1598 match lang.kind(n) {
1599 NodeKind::Continue => bare_continue(n),
1600 NodeKind::Block => lang
1601 .children(n)
1602 .last()
1603 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1604 _ => false,
1605 }
1606 };
1607 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1608 match lang.kind(n) {
1609 NodeKind::Continue => vec![],
1610 NodeKind::Block => {
1611 let ch = lang.children(n).to_vec();
1612 if ch
1613 .last()
1614 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1615 {
1616 ch[..ch.len() - 1].to_vec()
1617 } else {
1618 ch
1619 }
1620 }
1621 _ => vec![n],
1622 }
1623 };
1624
1625 match ich.len() {
1626 2 => {
1627 if is_do || !is_break(ich[1]) {
1629 return None;
1630 }
1631 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1632 if let Some(&last) = rest.last() {
1633 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1634 rest.pop();
1635 }
1636 }
1637 let nc = lang.build_unary(UnOp::Not, c);
1638 let inner = lang.build_block(rest);
1639 let w = lang.while_(nc, inner);
1640 Some(Edit::Replace {
1641 target: id,
1642 with: w,
1643 })
1644 }
1645 3 => {
1646 let (then, els) = (ich[1], ich[2]);
1647 if ends_with_continue(then) && is_break(els) {
1649 let rest = strip_tail_continue(then);
1650 let inner = lang.build_block(rest);
1651 let w = lang.while_(c, inner);
1652 return Some(Edit::Replace {
1653 target: id,
1654 with: w,
1655 });
1656 }
1657 if is_break(then) && ends_with_continue(els) {
1659 let rest = strip_tail_continue(els);
1660 let inner = lang.build_block(rest);
1661 let nc = lang.build_unary(UnOp::Not, c);
1662 let w = lang.while_(nc, inner);
1663 return Some(Edit::Replace {
1664 target: id,
1665 with: w,
1666 });
1667 }
1668 if !is_do && is_break(then) {
1670 let mut all = strip_tail_continue(els);
1671 all.extend(bch[1..].iter().copied());
1672 let inner = lang.build_block(all);
1673 let nc = lang.build_unary(UnOp::Not, c);
1674 let w = lang.while_(nc, inner);
1675 return Some(Edit::Replace {
1676 target: id,
1677 with: w,
1678 });
1679 }
1680 None
1681 }
1682 _ => None,
1683 }
1684 }
1685}
1686
1687pub struct WhileIteratorToForEach;
1688
1689impl Rule<JavaAst> for WhileIteratorToForEach {
1690 fn name(&self) -> &'static str {
1691 "while_iterator_to_for_each"
1692 }
1693 fn kinds(&self) -> &'static [NodeKind] {
1694 &[NodeKind::While]
1695 }
1696 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1697 let parent = ctx.parent(id)?;
1699 let idx = ctx.index(id)?;
1700 let lang = ctx.lang;
1701 if lang.kind(id) != NodeKind::While {
1702 return None;
1703 }
1704 let wch = lang.children(id).to_vec();
1705 let body = wch[1];
1706 if lang.kind(parent) != NodeKind::Block {
1708 return None;
1709 }
1710 if idx == 0 {
1711 return None;
1712 }
1713 let stmts = lang.children(parent).to_vec();
1714 let cond = wch[0];
1717 let it_hint = match lang.kind(cond) {
1718 NodeKind::Call => {
1719 let cc0 = lang.children(cond).to_vec();
1720 match lang.data(*cc0.first()?) {
1721 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1722 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1723 }
1724 _ => None,
1725 }
1726 }
1727 _ => None,
1728 };
1729 let mut decl = None;
1730 if let Some(hint) = it_hint {
1731 let mut up = idx;
1732 for _ in 0..4 {
1733 if up == 0 {
1734 break;
1735 }
1736 up -= 1;
1737 let st = stmts[up];
1738 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1739 decl = Some(st);
1740 break;
1741 }
1742 if subtree_has_var(lang, st, &hint) {
1743 break;
1744 }
1745 }
1746 }
1747 let decl = decl?;
1748 let it_name = lang.var_name(decl)?.to_string();
1749 if stmts[idx + 1..]
1754 .iter()
1755 .any(|&s| subtree_has_var(lang, s, &it_name))
1756 {
1757 return None;
1758 }
1759 let init_call = lang.children(decl).first().copied()?;
1760 if lang.kind(init_call) != NodeKind::Call {
1761 return None;
1762 }
1763 let ic = lang.children(init_call).to_vec();
1764 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1765 return None;
1766 };
1767 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1768 return None;
1769 }
1770 let iterable = lang.children(ic[0])[0];
1771 if subtree_has_var(&*lang, iterable, &it_name) {
1772 return None;
1773 }
1774 {
1784 let ty = lang.var_type(iterable);
1785 let ok = ty.is_some_and(|t| statically_iterable(t));
1786 if !ok {
1787 return None;
1788 }
1789 }
1790 if lang.kind(cond) != NodeKind::Call {
1792 return None;
1793 }
1794 let cc = lang.children(cond).to_vec();
1795 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1796 return None;
1797 };
1798 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1799 return None;
1800 }
1801 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1802 return None;
1803 }
1804 if lang.kind(body) != NodeKind::Block {
1806 return None;
1807 }
1808 let bch = lang.children(body).to_vec();
1809 let first = *bch.first()?;
1810 if lang.kind(first) != NodeKind::VarDecl {
1811 let it_refs_in_body: Vec<JavaId> = {
1814 let mut v = Vec::new();
1815 collect_var_refs(&*lang, body, &it_name, &mut v);
1816 v
1817 };
1818 if it_refs_in_body.len() != 1 {
1819 return None;
1820 }
1821 let next_recv = it_refs_in_body[0];
1822 let member = parent_of_recv(lang, body, next_recv);
1824 if lang.kind(member) != NodeKind::Member {
1825 return None;
1826 }
1827 let NodeData::Member { name: mn } = lang.data(member) else {
1828 return None;
1829 };
1830 if lang.sn(*mn) != "next" {
1831 return None;
1832 }
1833 let next_call = parent_of_recv(lang, body, member);
1834 if lang.kind(next_call) != NodeKind::Call {
1835 return None;
1836 }
1837 if lang.children(next_call).len() != 1 {
1838 return None;
1839 }
1840 let mut wrapped = next_call;
1842 loop {
1843 let par = parent_of_recv(lang, body, wrapped);
1844 match lang.kind(par) {
1845 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1846 _ => break,
1847 }
1848 }
1849 {
1859 let mut cur = wrapped;
1860 while cur != body {
1861 let par = parent_of_recv(lang, body, cur);
1862 if par == body || par == cur {
1863 break; }
1865 let ch = lang.children(par).to_vec();
1866 let idx = ch.iter().position(|&c| c == cur);
1867 match lang.kind(par) {
1868 NodeKind::Block
1869 | NodeKind::ExprStmt
1870 | NodeKind::Label
1871 | NodeKind::Return
1872 | NodeKind::Throw => {}
1873 NodeKind::Ternary => {
1874 if idx.unwrap_or(0) > 0 {
1876 return None;
1877 }
1878 }
1879 NodeKind::Binary => {
1880 if let Some(op) = lang.bin_op(par) {
1881 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1882 return None; }
1884 }
1885 }
1886 NodeKind::If
1887 | NodeKind::While
1888 | NodeKind::DoWhile
1889 | NodeKind::For
1890 | NodeKind::Switch
1891 | NodeKind::Try
1892 | NodeKind::Catch
1893 | NodeKind::Synchronized
1894 | NodeKind::ForEach => return None,
1895 NodeKind::Lambda | NodeKind::Assert => return None,
1900 _ => {} }
1902 cur = par;
1903 }
1904 }
1905 let it_decl_elem = match lang.data(decl) {
1913 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
1914 _ => None,
1915 };
1916 let elem = match it_decl_elem {
1917 Some(t) => t,
1918 None => match lang.var_type(iterable) {
1919 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
1920 None => JType::Ref("Object".into()),
1921 },
1922 };
1923 let elem = if for_each_elem_ok(&elem) {
1924 elem
1925 } else {
1926 JType::Ref("Object".into())
1927 };
1928 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
1929 let (replace_target, e_ty) = match lang.data(wrapped) {
1930 NodeData::Cast { .. } if is_object => (next_call, elem),
1931 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
1932 _ => (wrapped, elem),
1933 };
1934 let taken = |nm: &str| {
1938 subtree_has_var(&*lang, body, nm)
1939 || subtree_has_binding(&*lang, body, nm)
1940 };
1941 let e_name = if taken("e") {
1942 if taken("e2") { "e3" } else { "e2" }
1943 } else {
1944 "e"
1945 };
1946 let e_ref = lang.var(e_name);
1947 let foreach = lang.for_each(e_name, e_ty, iterable, body);
1948 let wrapped_parent = parent_of_recv(lang, body, wrapped);
1952 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
1953 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
1954 {
1955 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
1956 if let Some(si) = lang
1957 .children(stmt_block)
1958 .iter()
1959 .position(|&c| c == wrapped_parent)
1960 {
1961 return Some(Edit::Multi(vec![
1962 Edit::Splice {
1963 node: stmt_block,
1964 index: si,
1965 remove: 1,
1966 insert: vec![],
1967 },
1968 Edit::Splice {
1969 node: parent,
1970 index: idx,
1971 remove: 1,
1972 insert: vec![foreach],
1973 },
1974 Edit::Delete { node: decl },
1975 ]));
1976 }
1977 }
1978 return Some(Edit::Multi(vec![
1979 Edit::Replace {
1980 target: replace_target,
1981 with: e_ref,
1982 },
1983 Edit::Splice {
1984 node: parent,
1985 index: idx,
1986 remove: 1,
1987 insert: vec![foreach],
1988 },
1989 Edit::Delete { node: decl },
1990 ]));
1991 }
1992 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
1993 return None;
1994 };
1995 let (e_name, ty) = (e_name.clone(), ty.clone());
1996 let next_call = lang.children(first).first().copied()?;
1997 let next_call = unwrap_paren_cast(&*lang, next_call);
1998 if lang.kind(next_call) != NodeKind::Call {
1999 return None;
2000 }
2001 let nc = lang.children(next_call).to_vec();
2002 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
2003 return None;
2004 };
2005 if lang.sn(*m2) != "next" || nc.len() != 1 {
2006 return None;
2007 }
2008 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
2009 return None;
2010 }
2011 let elem_is_object = match lang.var_type(iterable) {
2013 Some(it_t) => generic_elem_ty(it_t).is_none(),
2014 None => true,
2015 };
2016 if elem_is_object && ty != JType::Ref("Object".into()) {
2017 return None;
2018 }
2019 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2021 let rest = bch[1..].to_vec();
2022 for &r in &rest {
2023 if subtree_has_var(&*lang, r, &it_name) {
2024 return None;
2025 }
2026 }
2027 let new_body = lang.build_block(rest);
2028 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2029 Some(Edit::Multi(vec![
2032 Edit::Splice {
2033 node: parent,
2034 index: idx,
2035 remove: 1,
2036 insert: vec![foreach],
2037 },
2038 Edit::Delete { node: decl },
2039 ]))
2040 }
2041}
2042
2043pub struct ConcatValueOfDrop;
2052
2053impl Rule<JavaAst> for ConcatValueOfDrop {
2054 fn name(&self) -> &'static str {
2055 "concat_value_of_drop"
2056 }
2057 fn kinds(&self) -> &'static [NodeKind] {
2058 &[NodeKind::Binary]
2059 }
2060 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2061 let root = ctx.root();
2062 let lang = ctx.lang;
2063 if lang.kind(id) != NodeKind::Binary {
2064 return None;
2065 }
2066 if lang.bin_op(id) != Some(BinOp::Add) {
2067 return None;
2068 }
2069 let ch = lang.children(id).to_vec();
2070 let (l, r) = (ch[0], ch[1]);
2071 let becomes_constant = |x: JavaId, other: JavaId| {
2074 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2075 };
2076 let is_value_of = |n: JavaId| -> Option<JavaId> {
2077 if lang.kind(n) != NodeKind::Call {
2078 return None;
2079 }
2080 let c = lang.children(n).to_vec();
2081 if c.len() != 2 {
2082 return None;
2083 }
2084 let callee = c[0];
2085 if let NodeData::Member { name } = lang.data(callee) {
2086 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2087 return Some(c[1]);
2088 }
2089 }
2090 if let NodeData::VarRef { name } = lang.data(callee) {
2091 if lang.sn(*name) == "valueOf" {
2092 return Some(c[1]);
2093 }
2094 }
2095 None
2096 };
2097 let stringy = |n: JavaId| -> bool {
2098 matches!(lang.literal(n), Some(LitRef::Str(_)))
2099 || lang
2100 .var_type(n)
2101 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2102 };
2103 if let Some(x) = is_value_of(l) {
2105 if stringy(r) {
2106 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2107 return None;
2108 }
2109 let with = lang.build_bin(BinOp::Add, x, r);
2110 return Some(Edit::Replace {
2111 target: id,
2112 with,
2113 });
2114 }
2115 }
2116 if let Some(x) = is_value_of(r) {
2118 if stringy(l) {
2119 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2120 return None;
2121 }
2122 let with = lang.build_bin(BinOp::Add, l, x);
2123 return Some(Edit::Replace {
2124 target: id,
2125 with,
2126 });
2127 }
2128 }
2129 None
2130 }
2131}
2132
2133
2134pub struct StringBuilderStatements;
2147
2148fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2149 let mut n = 0;
2152 let mut stack = vec![root];
2153 while let Some(x) = stack.pop() {
2154 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2155 let ch = lang.children(x).to_vec();
2156 if let Some(&t) = ch.first() {
2157 let is_named_target =
2158 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2159 if !is_named_target {
2160 stack.push(t);
2161 }
2162 for &c in &ch[1..] {
2163 stack.push(c);
2164 }
2165 }
2166 continue;
2167 }
2168 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2169 n += 1;
2170 }
2171 for &c in lang.children(x) {
2172 stack.push(c);
2173 }
2174 }
2175 n
2176}
2177
2178impl Rule<JavaAst> for StringBuilderStatements {
2179 fn name(&self) -> &'static str {
2180 "string_builder_statements"
2181 }
2182 fn kinds(&self) -> &'static [NodeKind] {
2183 &[NodeKind::VarDecl]
2184 }
2185 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2186 let parent = ctx.parent(id)?;
2188 let idx = ctx.index(id)?;
2189 let root = ctx.root();
2190 let lang = ctx.lang;
2191
2192 if lang.kind(id) != NodeKind::VarDecl {
2193 return None;
2194 }
2195 if lang.kind(parent) != NodeKind::Block {
2196 return None;
2197 }
2198 let v0 = lang.var_name(id)?.to_string();
2199 let init = *lang.children(id).first()?;
2201 if lang.kind(init) != NodeKind::Call {
2202 return None;
2203 }
2204 let ic = lang.children(init).to_vec();
2205 if ic.len() != 2 {
2206 return None;
2207 }
2208 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2209 return None;
2210 };
2211 if lang.sn(*m0) != "append" {
2212 return None;
2213 }
2214 let sb_new = lang.children(ic[0])[0];
2215 let (ctor_arg, a0) = match lang.data(sb_new) {
2216 NodeData::New { ty, .. } => {
2217 let is_sb = !lang.unit_shadows_lang
2218 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2219 if !is_sb {
2220 return None;
2221 }
2222 let args = lang.children(sb_new).to_vec();
2223 match args.len() {
2224 0 => (None, ic[1]),
2225 1 => {
2226 let a = args[0];
2227 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2228 || lang
2229 .var_type(a)
2230 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2231 if stringy {
2232 (Some(a), ic[1])
2233 } else if matches!(
2234 lang.literal(a),
2235 Some(LitRef::Int(_) | LitRef::Long(_))
2236 ) {
2237 (None, ic[1])
2239 } else {
2240 return None; }
2242 }
2243 _ => return None,
2244 }
2245 }
2246 _ => return None,
2247 };
2248
2249 let stmts = lang.children(parent).to_vec();
2250 let mut parts: Vec<JavaId> = vec![a0];
2252 let mut expected_reads: std::collections::HashMap<String, usize> =
2253 std::collections::HashMap::new();
2254 let mut chain_len = 1usize; let mut cur = v0.clone();
2256 while idx + chain_len < stmts.len() {
2257 let s = stmts[idx + chain_len];
2258 let (recv, arg, new_var, consumed) = match lang.data(s) {
2259 NodeData::Assign { op: None } => {
2261 let ch = lang.children(s).to_vec();
2262 if ch.len() != 2 {
2263 break;
2264 }
2265 let (t, v) = (ch[0], ch[1]);
2266 if lang.kind(t) != NodeKind::VarRef
2267 || lang.var_name(t) != Some(cur.as_str())
2268 || lang.kind(v) != NodeKind::Call
2269 {
2270 break;
2271 }
2272 let vc = lang.children(v).to_vec();
2273 if vc.len() != 2 {
2274 break;
2275 }
2276 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2277 break;
2278 };
2279 if lang.sn(*mn) != "append" {
2280 break;
2281 }
2282 let recv = lang.children(vc[0])[0];
2283 (recv, vc[1], false, s)
2284 }
2285 NodeData::ExprStmt => {
2286 let ch = lang.children(s).to_vec();
2287 if ch.len() != 1 {
2288 break;
2289 }
2290 let inner = ch[0];
2291 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2292 break;
2293 }
2294 let ach = lang.children(inner).to_vec();
2295 if ach.len() != 2 {
2296 break;
2297 }
2298 let (t, v) = (ach[0], ach[1]);
2299 if lang.kind(t) != NodeKind::VarRef
2300 || lang.var_name(t) != Some(cur.as_str())
2301 || lang.kind(v) != NodeKind::Call
2302 {
2303 break;
2304 }
2305 let vc = lang.children(v).to_vec();
2306 if vc.len() != 2 {
2307 break;
2308 }
2309 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2310 break;
2311 };
2312 if lang.sn(*mn) != "append" {
2313 break;
2314 }
2315 let recv = lang.children(vc[0])[0];
2316 (recv, vc[1], false, s)
2317 }
2318 NodeData::VarDecl { .. } => {
2320 let init = match lang.children(s).first() {
2321 Some(&x) => x,
2322 None => break,
2323 };
2324 if lang.kind(init) != NodeKind::Call {
2325 break;
2326 }
2327 let vc = lang.children(init).to_vec();
2328 if vc.len() != 2 {
2329 break;
2330 }
2331 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2332 break;
2333 };
2334 if lang.sn(*mn) != "append" {
2335 break;
2336 }
2337 let recv = lang.children(vc[0])[0];
2338 if lang.var_name(recv) != Some(cur.as_str()) {
2339 break;
2340 }
2341 (recv, vc[1], true, s)
2342 }
2343 _ => break,
2344 };
2345 if lang.var_name(recv) != Some(cur.as_str()) {
2347 break;
2348 }
2349 parts.push(arg);
2350 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2351 if new_var {
2352 cur = lang.var_name(consumed)?.to_string();
2353 }
2354 chain_len += 1;
2355 let _ = consumed;
2356 }
2357
2358 let final_stmt = *stmts.get(idx + chain_len)?;
2360 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2361 return None;
2362 };
2363 let s_init = *lang.children(final_stmt).first()?;
2364 if lang.kind(s_init) != NodeKind::Call {
2365 return None;
2366 }
2367 let sc = lang.children(s_init).to_vec();
2368 if sc.len() != 1 {
2369 return None;
2370 }
2371 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2372 return None;
2373 };
2374 if lang.sn(*mn) != "toString" {
2375 return None;
2376 }
2377 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2378 return None;
2379 }
2380
2381 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2385 if count_var_uses_in(&*lang, parent, v) != *exp {
2386 return None;
2387 }
2388 }
2389
2390 let mut all_parts: Vec<JavaId> = Vec::new();
2392 if let Some(ca) = ctor_arg {
2393 all_parts.push(ca);
2394 }
2395 all_parts.extend(parts);
2396 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2397 && lang.has_string_identity_compare(root)
2398 {
2399 return None;
2400 }
2401 if all_parts.is_empty() {
2402 let empty = lang.build_str("");
2403 return Some(Edit::Multi(vec![
2404 Edit::Replace {
2405 target: s_init,
2406 with: empty,
2407 },
2408 Edit::Splice {
2409 node: parent,
2410 index: idx,
2411 remove: chain_len,
2412 insert: Vec::new(),
2413 },
2414 ]));
2415 }
2416 let first = all_parts[0];
2417 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2418 || lang
2419 .var_type(first)
2420 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2421 let mut acc = if stringy_first {
2422 first
2423 } else {
2424 let empty = lang.build_str("");
2425 lang.build_bin(BinOp::Add, empty, first)
2426 };
2427 for &p in &all_parts[1..] {
2428 acc = lang.build_bin(BinOp::Add, acc, p);
2429 }
2430 Some(Edit::Multi(vec![
2431 Edit::Replace {
2432 target: s_init,
2433 with: acc,
2434 },
2435 Edit::Splice {
2436 node: parent,
2437 index: idx,
2438 remove: chain_len,
2439 insert: Vec::new(),
2440 },
2441 ]))
2442 }
2443}
2444
2445
2446pub struct TrailingContinueJava;
2453
2454impl Rule<JavaAst> for TrailingContinueJava {
2455 fn name(&self) -> &'static str {
2456 "trailing_continue"
2457 }
2458 fn kinds(&self) -> &'static [NodeKind] {
2459 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2460 }
2461 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2462 let parent = ctx.parent(id)?;
2463 let lang = ctx.lang;
2464 let body = match lang.kind(id) {
2465 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2466 *lang.children(id).last()?
2467 }
2468 _ => return None,
2469 };
2470 if lang.kind(body) != NodeKind::Block {
2471 return None;
2472 }
2473 let ch = lang.children(body).to_vec();
2474 let last = *ch.last()?;
2475 if lang.kind(last) != NodeKind::Continue {
2476 return None;
2477 }
2478 let label = match lang.data(last) {
2479 NodeData::Continue { label } => label.clone(),
2480 _ => return None,
2481 };
2482 match label {
2483 None => Some(Edit::Delete { node: last }),
2484 Some(l) => {
2485 if lang.kind(parent) == NodeKind::Label {
2487 if let NodeData::Label { name } = lang.data(parent) {
2488 if *name == l {
2489 return Some(Edit::Delete { node: last });
2490 }
2491 }
2492 }
2493 None
2494 }
2495 }
2496 }
2497}
2498
2499
2500pub struct XorNoise;
2508
2509impl Rule<JavaAst> for XorNoise {
2510 fn name(&self) -> &'static str {
2511 "xor_noise"
2512 }
2513 fn kinds(&self) -> &'static [NodeKind] {
2514 &[NodeKind::Binary]
2515 }
2516 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2517 let lang = ctx.lang;
2518 if lang.kind(id) != NodeKind::Binary {
2519 return None;
2520 }
2521 let op = lang.bin_op(id)?;
2522 if op != BinOp::BitXor {
2523 return None;
2524 }
2525 let ch = lang.children(id).to_vec();
2526 let (l, r) = (ch[0], ch[1]);
2527 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2529 if int_lit(r) == Some(0) {
2530 return Some(Edit::Replace {
2531 target: id,
2532 with: l,
2533 });
2534 }
2535 if int_lit(l) == Some(0) {
2536 return Some(Edit::Replace {
2537 target: id,
2538 with: r,
2539 });
2540 }
2541 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2543 return None;
2544 }
2545 let ich = lang.children(l).to_vec();
2546 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2547 let k2 = int_lit(r)?;
2548 let x = ich[0];
2549 let k = k1 ^ k2;
2550 let with = if k == 0 {
2551 x
2552 } else {
2553 let lit = lang.build_int(k, false);
2554 lang.build_bin(BinOp::BitXor, x, lit)
2555 };
2556 Some(Edit::Replace {
2557 target: id,
2558 with,
2559 })
2560 }
2561}
2562
2563pub struct StrLenFold;
2568
2569impl Rule<JavaAst> for StrLenFold {
2570 fn name(&self) -> &'static str {
2571 "str_len_fold"
2572 }
2573 fn kinds(&self) -> &'static [NodeKind] {
2574 &[NodeKind::Call]
2575 }
2576 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2577 let _at_stmt_pos = ctx
2579 .parent(id)
2580 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2581 .unwrap_or(false);
2582 let lang = ctx.lang;
2583 if lang.kind(id) != NodeKind::Call {
2584 return None;
2585 }
2586 let ch = lang.children(id).to_vec();
2587 if ch.len() != 1 {
2588 return None;
2589 }
2590 let NodeData::Member { name } = lang.data(ch[0]) else {
2591 return None;
2592 };
2593 if lang.sn(*name) != "length" {
2594 return None;
2595 }
2596 let recv = lang.children(ch[0])[0];
2597 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2598 let with = lang.build_int(s.chars().count() as i64, false);
2599 return Some(Edit::Replace {
2600 target: id,
2601 with,
2602 });
2603 }
2604 None
2605 }
2606}
2607
2608
2609pub struct LiteralEval;
2622
2623impl Rule<JavaAst> for LiteralEval {
2624 fn name(&self) -> &'static str {
2625 "literal_eval"
2626 }
2627 fn kinds(&self) -> &'static [NodeKind] {
2628 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2629 }
2630 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2631 let root = ctx.root();
2632 let lang = ctx.lang;
2633 let edit = match lang.kind(id) {
2634 NodeKind::Call => self.eval_call(&mut *lang, id),
2635 NodeKind::Index => self.eval_index(&mut *lang, id),
2636 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2637 _ => None,
2638 };
2639 if let Some(Edit::Replace { with, .. }) = &edit {
2643 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2644 && lang.has_string_identity_compare(root)
2645 {
2646 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2647 eprintln!("[gate] blocked literal_eval at {id:?}");
2648 }
2649 return None;
2650 }
2651 }
2652 edit
2653 }
2654}
2655
2656impl LiteralEval {
2657 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2658 let ch = lang.children(id).to_vec();
2659 let callee = ch[0];
2660 let NodeData::Member { name: method } = lang.data(callee) else {
2661 return None;
2662 };
2663 let method = *method;
2664 let recv = lang.children(callee)[0];
2665 let args: Vec<Lit> = ch[1..]
2667 .iter()
2668 .map(|&a| litref_to_lit(lang.literal(a)))
2669 .collect::<Option<Vec<_>>>()?;
2670
2671 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2672 eval_str_method(s, lang.sn(method), &args)
2673 } else if let Some(cls) = lang.var_name(recv) {
2674 eval_static_method(cls, lang.sn(method), &args)
2675 } else {
2676 None
2677 };
2678 let lit = result?;
2679 let with = lit_to_node(lang, &lit);
2680 Some(Edit::Replace {
2681 target: id,
2682 with,
2683 })
2684 }
2685
2686 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2687 let ch = lang.children(id).to_vec();
2689 let arr = match lang.kind(ch[0]) {
2691 NodeKind::ArrayLit => ch[0],
2692 NodeKind::NewArray => {
2693 let nc = lang.children(ch[0]);
2694 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2695 return None;
2696 };
2697 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2698 return None;
2699 }
2700 nc[0]
2701 }
2702 _ => return None,
2703 };
2704 let idx = lang.literal(ch[1])?.as_int()?;
2705 let elems = lang.children(arr).to_vec();
2706 if idx < 0 || idx >= elems.len() as i64 {
2707 return None;
2708 }
2709 Some(Edit::Replace {
2710 target: id,
2711 with: elems[idx as usize],
2712 })
2713 }
2714}
2715
2716fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2717 Some(match l? {
2718 LitRef::Str(s) => Lit::Str(s.to_string()),
2719 LitRef::Int(v) => Lit::Int(v),
2720 LitRef::Long(v) => Lit::Long(v),
2721 LitRef::Bool(b) => Lit::Bool(b),
2722 LitRef::Char(c) => Lit::Char(c),
2723 LitRef::Float(v) => Lit::Float(v),
2724 LitRef::Double(v) => Lit::Double(v),
2725 LitRef::Null => return None,
2726 })
2727}
2728
2729fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2730 match lit {
2731 Lit::Str(s) => lang.build_str(s),
2732 Lit::Int(v) => lang.build_int(*v, false),
2733 Lit::Long(v) => lang.build_int(*v, true),
2734 Lit::Bool(b) => lang.build_bool(*b),
2735 Lit::Char(c) => lang.build_char(*c),
2736 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2737 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2738 _ => lang.lit(Lit::Null),
2739 }
2740}
2741
2742fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2744 let NodeData::Cast { ty } = lang.data(id) else {
2745 return None;
2746 };
2747 let ty = ty.clone();
2748 let inner = lang.children(id)[0];
2749 let lit = litref_to_lit(lang.literal(inner))?;
2750 let with = match (&ty, &lit) {
2751 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2752 lang.build_char(char::from_u32(*v as u32)?)
2753 }
2754 (JType::Char, Lit::Char(_)) => inner,
2755 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2756 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2757 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2758 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2759 lang.build_int(*v, false)
2760 }
2761 (JType::Int, Lit::Int(_)) => inner,
2762 _ => return None,
2763 };
2764 Some(Edit::Replace {
2765 target: id,
2766 with,
2767 })
2768}
2769
2770fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2771 let chars: Vec<char> = s.chars().collect();
2772 let n = chars.len() as i64;
2773 Some(match (method, args) {
2774 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2775 ("intern", []) => Lit::Str(s.to_string()),
2777 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2778 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2779 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2780 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2781 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2782 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2783 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2784 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2785 }
2786 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2787 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2788 ),
2789 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2790 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2791 ),
2792 ("length", []) => Lit::Int(n),
2793 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2794 Lit::Char(chars[*i as usize])
2795 }
2796 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2797 Lit::Str(s.chars().skip(*b as usize).collect())
2798 }
2799 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2800 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2801 }
2802 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2803 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2804 }
2805 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2806 Lit::Str(s.replace(a.as_str(), b))
2807 }
2808 ("hashCode", []) => Lit::Int(
2810 s.chars()
2811 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2812 as i64,
2813 ),
2814 _ => return None,
2815 })
2816}
2817
2818fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2819 let cls = cls.rsplit('.').next().unwrap_or(cls);
2820 Some(match (cls, method, args) {
2821 ("Integer", "parseInt", [Lit::Str(s)]) => {
2822 Lit::Int(s.trim().parse::<i64>().ok()?)
2823 }
2824 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2825 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2826 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2827 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2828 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2829 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2830 Lit::Bool(s.eq_ignore_ascii_case("true"))
2831 }
2832 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2833 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2834 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2835 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2836 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2837 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2838 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2839 let fmt = match &fmt_args[0] {
2840 Lit::Str(s) => s.clone(),
2841 _ => return None,
2842 };
2843 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2844 Lit::Str(out)
2845 }
2846 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2847 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2848 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2849 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2850 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2851 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2852 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2853 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2854 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2855 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2856 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2857 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2858 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2859 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2860 _ => return None,
2861 })
2862}
2863
2864fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2866 let mut out = String::new();
2867 let mut it = fmt.chars().peekable();
2868 let mut arg_i = 0usize;
2869 while let Some(c) = it.next() {
2870 if c != '%' {
2871 out.push(c);
2872 continue;
2873 }
2874 match it.next()? {
2875 '%' => out.push('%'),
2876 's' => {
2877 let a = args.get(arg_i)?;
2878 arg_i += 1;
2879 match a {
2880 Lit::Str(s) => out.push_str(s),
2881 Lit::Char(ch) => out.push(*ch),
2882 Lit::Bool(b) => out.push_str(&b.to_string()),
2883 _ => out.push_str(&lit_debug_str(a)),
2884 }
2885 }
2886 'd' => {
2887 let a = args.get(arg_i)?;
2888 arg_i += 1;
2889 match a {
2890 Lit::Int(v) => out.push_str(&v.to_string()),
2891 Lit::Long(v) => out.push_str(&v.to_string()),
2892 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
2893 _ => return None,
2894 }
2895 }
2896 _ => return None,
2898 }
2899 }
2900 if arg_i != args.len() {
2901 return None; }
2903 Some(out)
2904}
2905
2906fn lit_debug_str(l: &Lit) -> String {
2907 match l {
2908 Lit::Int(v) => v.to_string(),
2909 Lit::Long(v) => v.to_string(),
2910 _ => "?".into(),
2911 }
2912}
2913
2914fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
2937 match ast.data(m) {
2938 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
2939 _ => None,
2940 }
2941}
2942
2943pub struct UrlDecodeFold;
2944
2945impl Rule<JavaAst> for UrlDecodeFold {
2946 fn name(&self) -> &'static str {
2947 "url_decode_fold"
2948 }
2949 fn kinds(&self) -> &'static [NodeKind] {
2950 &[NodeKind::Call]
2951 }
2952 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2953 let lang = ctx.lang;
2954 let ch = lang.children(id).to_vec();
2957 if ch.len() != 3 {
2958 return None; }
2960 let callee = ch[0];
2961 if lang.kind(callee) != NodeKind::Member {
2962 return None;
2963 }
2964 if member_name(lang, callee).as_deref() != Some("decode") {
2965 return None;
2966 }
2967 let recv = *lang.children(callee).first()?;
2969 let recv_name = match lang.kind(recv) {
2970 NodeKind::Member => member_name(lang, recv)?,
2971 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
2972 _ => return None,
2973 };
2974 if !recv_name.ends_with("URLDecoder") {
2975 return None;
2976 }
2977 let s_lit = match lang.literal(ch[1])? {
2978 LitRef::Str(s) => s.to_string(),
2979 _ => return None,
2980 };
2981 let cs = match lang.literal(ch[2])? {
2983 LitRef::Str(s) => s.to_string(),
2984 _ => return None,
2985 };
2986 if cs != "UTF-8" {
2987 return None;
2988 }
2989 if s_lit.len() > 8192 {
2991 return None;
2992 }
2993 let decoded = url_decode(&s_lit)?;
2994 if decoded.chars().any(|c| {
2996 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
2997 }) {
2998 return None;
2999 }
3000 let lit = lang.lit(Lit::Str(decoded));
3001 Some(Edit::Replace { target: id, with: lit })
3002 }
3003}
3004
3005fn url_decode(s: &str) -> Option<String> {
3008 let b = s.as_bytes();
3009 let mut out: Vec<u8> = Vec::with_capacity(b.len());
3010 let mut i = 0usize;
3011 while i < b.len() {
3012 match b[i] {
3013 b'+' => {
3014 out.push(b' ');
3015 i += 1;
3016 }
3017 b'%' => {
3018 if i + 3 > b.len() {
3019 return None;
3020 }
3021 let hi = hex_val(b[i + 1])?;
3022 let lo = hex_val(b[i + 2])?;
3023 out.push(hi * 16 + lo);
3024 i += 3;
3025 }
3026 c => {
3027 out.push(c);
3028 i += 1;
3029 }
3030 }
3031 }
3032 String::from_utf8(out).ok()
3033}
3034
3035fn hex_val(c: u8) -> Option<u8> {
3036 match c {
3037 b'0'..=b'9' => Some(c - b'0'),
3038 b'a'..=b'f' => Some(c - b'a' + 10),
3039 b'A'..=b'F' => Some(c - b'A' + 10),
3040 _ => None,
3041 }
3042}
3043
3044pub struct Base64NewStringFold;
3045
3046impl Rule<JavaAst> for Base64NewStringFold {
3047 fn name(&self) -> &'static str {
3048 "base64_new_string_fold"
3049 }
3050 fn kinds(&self) -> &'static [NodeKind] {
3051 &[NodeKind::New]
3052 }
3053 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3054 let root = ctx.root();
3055 let lang = ctx.lang;
3056 let NodeData::New { ty, .. } = lang.data(id) else {
3057 return None;
3058 };
3059 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3060 return None;
3061 }
3062 if lang.has_string_identity_compare(root) {
3064 return None;
3065 }
3066 let args = lang.children(id).to_vec();
3067 if args.len() != 1 {
3068 return None;
3069 }
3070 if lang.kind(args[0]) != NodeKind::Call {
3072 return None;
3073 }
3074 let dc = lang.children(args[0]).to_vec();
3075 if dc.len() != 2 {
3076 return None;
3077 }
3078 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3079 return None;
3080 };
3081 if lang.sn(*dn) != "decode" {
3082 return None;
3083 }
3084 let recv = lang.children(dc[0])[0];
3086 if lang.kind(recv) != NodeKind::Call {
3087 return None;
3088 }
3089 let gc = lang.children(recv).to_vec();
3090 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3091 return None;
3092 };
3093 let url_safe = match lang.sn(*gn) {
3094 "getDecoder" => false,
3095 "getUrlDecoder" => true,
3096 _ => return None,
3097 };
3098 let owner = lang.children(gc[0])[0];
3100 let owner_ok = match lang.data(owner) {
3101 NodeData::Member { name } => lang.sn(*name) == "Base64",
3102 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3103 };
3104 if !owner_ok {
3105 return None;
3106 }
3107 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3108 return None;
3109 };
3110 let bytes = base64_decode(b64, url_safe)?;
3111 let text = String::from_utf8(bytes).ok()?;
3112 let with = lang.build_str(&text);
3113 Some(Edit::Replace {
3114 target: id,
3115 with,
3116 })
3117 }
3118}
3119
3120fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3122 let val = |c: char| -> Option<u32> {
3123 match c {
3124 'A'..='Z' => Some(c as u32 - 'A' as u32),
3125 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3126 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3127 '+' if !url_safe => Some(62),
3128 '/' if !url_safe => Some(63),
3129 '-' if url_safe => Some(62),
3130 '_' if url_safe => Some(63),
3131 _ => None,
3132 }
3133 };
3134 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3135 if clean.len() % 4 == 1 {
3136 return None;
3137 }
3138 let mut out = Vec::new();
3139 let mut buf = 0u32;
3140 let mut bits = 0u32;
3141 for c in clean.chars() {
3142 buf = (buf << 6) | val(c)?;
3143 bits += 6;
3144 if bits >= 8 {
3145 bits -= 8;
3146 out.push((buf >> bits) as u8);
3147 buf &= (1 << bits) - 1;
3148 }
3149 }
3150 Some(out)
3151}
3152
3153
3154pub struct CffRecover;
3176
3177struct CffCase {
3178 body: Vec<JavaId>,
3179 tail: CffTail,
3180}
3181
3182enum CffTail {
3183 Goto(i64),
3184 Cond { cond: JavaId, a: i64, b: i64 },
3185 Term(JavaId),
3186 Exit,
3187}
3188
3189#[derive(Clone, Copy, PartialEq, Debug)]
3190enum Flow {
3191 Done,
3192 Stop,
3193 Cycle(usize),
3194 Cond(i64),
3195 External(i64),
3196}
3197
3198struct CffCtx<'a> {
3199 lang: &'a mut JavaAst,
3200 cases: std::collections::HashMap<i64, CffCase>,
3201 default_term: Option<Vec<JavaId>>,
3203 used_default: bool,
3204 reached: std::collections::HashSet<i64>,
3206 emitted: std::collections::HashSet<i64>,
3208}
3209
3210impl Rule<JavaAst> for CffRecover {
3211 fn name(&self) -> &'static str {
3212 "cff_recover"
3213 }
3214 fn kinds(&self) -> &'static [NodeKind] {
3215 &[NodeKind::While]
3216 }
3217 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3218 let parent = ctx.parent(id)?;
3219 let idx = ctx.index(id)?;
3220 let lang = ctx.lang;
3221 if lang.kind(id) != NodeKind::While {
3222 return None;
3223 }
3224 let wch = lang.children(id).to_vec();
3225 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3226 return None;
3227 }
3228 if lang.kind(wch[1]) != NodeKind::Block {
3229 return None;
3230 }
3231 let body_ch = lang.children(wch[1]).to_vec();
3232 let (switch, exit_if) = match body_ch.len() {
3233 1 => (body_ch[0], None),
3234 2 => (body_ch[0], Some(body_ch[1])),
3235 _ => return None,
3236 };
3237 if lang.kind(switch) != NodeKind::Switch {
3238 return None;
3239 }
3240 let sch = lang.children(switch).to_vec();
3241 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3242 return None;
3243 }
3244 let v = lang.var_name(sch[0])?.to_string();
3245
3246 let sentinel = match exit_if {
3248 None => None,
3249 Some(ei) => {
3250 if lang.kind(ei) != NodeKind::If {
3251 return None;
3252 }
3253 let ich = lang.children(ei).to_vec();
3254 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3255 return None;
3256 }
3257 let cch = lang.children(ich[0]).to_vec();
3258 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3259 && lang.var_name(cch[0]) == Some(v.as_str())
3260 {
3261 lang.literal(cch[1]).and_then(|x| x.as_int())
3262 } else if lang.kind(cch[1]) == NodeKind::VarRef
3263 && lang.var_name(cch[1]) == Some(v.as_str())
3264 {
3265 lang.literal(cch[0]).and_then(|x| x.as_int())
3266 } else {
3267 return None;
3268 }?;
3269 let then_ok = match lang.kind(ich[1]) {
3270 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3271 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3272 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3273 }
3274 _ => false,
3275 };
3276 if !then_ok {
3277 return None;
3278 }
3279 Some(sent)
3280 }
3281 };
3282
3283 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3285 let mut default_term: Option<Vec<JavaId>> = None;
3286 for &c in &sch[1..] {
3287 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3288 return None;
3289 };
3290 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3291 let ch = lang.children(c).to_vec();
3292 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3293 if !arrow
3295 && stmts
3296 .last()
3297 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3298 {
3299 stmts.pop();
3300 }
3301 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3302 let mut inner = lang.children(stmts[0]).to_vec();
3303 if !arrow
3304 && inner
3305 .last()
3306 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3307 {
3308 inner.pop();
3309 }
3310 stmts = inner;
3311 }
3312 if is_default {
3313 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3314 default_term = Some(stmts);
3315 continue;
3316 }
3317 return None;
3318 }
3319 if labels != 1 {
3321 return None;
3322 }
3323 let state = match lang.literal(ch[0]) {
3324 Some(LitRef::Int(v)) => v,
3325 _ => return None,
3326 };
3327 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3328 for &st in &stmts {
3330 if subtree_has_var(lang, st, &v) {
3331 return None;
3332 }
3333 }
3334 cases.insert(state, CffCase { body: stmts, tail });
3335 }
3336 if cases.is_empty() {
3337 return None;
3338 }
3339
3340 if idx == 0 {
3343 return None;
3344 }
3345 let block_ch = lang.children(parent).to_vec();
3346 let mut init = None;
3347 let mut init_idx = idx;
3348 {
3349 let mut up = idx;
3350 for _ in 0..8 {
3351 if up == 0 {
3352 break;
3353 }
3354 up -= 1;
3355 let st = block_ch[up];
3356 match scan_cff_event(lang, st, &v) {
3357 Some(CffEvent::Read) => return None,
3358 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3359 init = parse_cff_init(lang, &v, st);
3360 init_idx = up;
3361 break;
3362 }
3363 None => continue,
3364 }
3365 }
3366 }
3367 let init = init?;
3368
3369 for &st in &block_ch[idx + 1..] {
3371 if subtree_has_var(lang, st, &v) {
3372 return None;
3373 }
3374 }
3375
3376 let mut to_exit: Vec<i64> = Vec::new();
3378 for (k, ci) in cases.iter() {
3379 if let CffTail::Goto(g) = ci.tail {
3380 if !cases.contains_key(&g) {
3381 if !(sentinel == Some(g) || default_term.is_some()) {
3382 return None;
3383 }
3384 to_exit.push(*k);
3385 }
3386 }
3387 }
3388 for k in to_exit {
3389 if let Some(ci) = cases.get_mut(&k) {
3390 ci.tail = CffTail::Exit;
3391 }
3392 }
3393
3394 let mut c = CffCtx {
3395 lang,
3396 cases,
3397 default_term,
3398 used_default: false,
3399 reached: Default::default(),
3400 emitted: Default::default(),
3401 };
3402 let stmts = cff_run(&mut c, init)?;
3403
3404 if stmts.is_empty() {
3407 return None;
3408 }
3409
3410 Some(Edit::Multi(vec![
3413 Edit::Splice {
3414 node: parent,
3415 index: idx,
3416 remove: 1,
3417 insert: stmts,
3418 },
3419 Edit::Delete {
3420 node: block_ch[init_idx],
3421 },
3422 ]))
3423 }
3424}
3425
3426fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3427 let last = *stmts.last()?;
3428 if lang.kind(last) == NodeKind::Return {
3429 stmts.pop();
3430 return Some(CffTail::Term(last));
3431 }
3432 let assign_of = |n: JavaId| -> Option<JavaId> {
3433 let inner = match lang.kind(n) {
3434 NodeKind::Assign => n,
3435 NodeKind::ExprStmt => {
3436 let ch = lang.children(n);
3437 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3438 ch[0]
3439 } else {
3440 return None;
3441 }
3442 }
3443 _ => return None,
3444 };
3445 if lang.assign_op(inner).is_some() {
3446 return None;
3447 }
3448 let ch = lang.children(inner).to_vec();
3449 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3450 return None;
3451 }
3452 Some(inner)
3453 };
3454 if let Some(assign) = assign_of(last) {
3455 let value = lang.children(assign)[1];
3456 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3457 stmts.pop();
3458 return Some(CffTail::Goto(k));
3459 }
3460 if lang.kind(value) == NodeKind::Ternary {
3461 let tch = lang.children(value).to_vec();
3462 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3463 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3464 stmts.pop();
3465 return Some(CffTail::Cond { cond: tch[0], a, b });
3466 }
3467 return None;
3468 }
3469 if lang.kind(last) == NodeKind::If {
3470 let ich = lang.children(last).to_vec();
3471 if ich.len() != 3 {
3472 return None;
3473 }
3474 let arm = |n: JavaId| -> Option<i64> {
3475 let target = match lang.kind(n) {
3476 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3477 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3478 _ => return None,
3479 };
3480 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3481 };
3482 let a = arm(ich[1])?;
3483 let b = arm(ich[2])?;
3484 stmts.pop();
3485 return Some(CffTail::Cond { cond: ich[0], a, b });
3486 }
3487 None
3488}
3489
3490
3491enum CffEvent {
3493 Read,
3494 Write,
3495 Decl,
3497}
3498
3499fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3500 match lang.kind(node) {
3501 NodeKind::VarRef => {
3502 if lang.var_name(node) == Some(name) {
3503 Some(CffEvent::Read)
3504 } else {
3505 None
3506 }
3507 }
3508 NodeKind::VarDecl => {
3509 if lang.var_name(node) == Some(name) {
3510 Some(CffEvent::Decl)
3511 } else {
3512 for &c in lang.children(node) {
3513 if let Some(e) = scan_cff_event(lang, c, name) {
3514 return Some(e);
3515 }
3516 }
3517 None
3518 }
3519 }
3520 NodeKind::Assign => {
3521 let ch = lang.children(node);
3522 if let Some(&val) = ch.get(1) {
3523 if let Some(e) = scan_cff_event(lang, val, name) {
3524 if matches!(e, CffEvent::Read) {
3525 return Some(e);
3526 }
3527 }
3528 }
3529 if lang.assign_op(node).is_none() {
3530 if let Some(&t) = ch.first() {
3531 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3532 return Some(CffEvent::Write);
3533 }
3534 }
3535 }
3536 None
3537 }
3538 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3539 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3540 NodeKind::Binary => {
3541 let op = lang.bin_op(node)?;
3542 let ch = lang.children(node);
3543 if op.is_short_circuit() {
3544 scan_cff_event(lang, *ch.first()?, name)
3545 } else {
3546 for &c in ch {
3547 if let Some(e) = scan_cff_event(lang, c, name) {
3548 return Some(e);
3549 }
3550 }
3551 None
3552 }
3553 }
3554 _ => {
3555 for &c in lang.children(node) {
3556 if let Some(e) = scan_cff_event(lang, c, name) {
3557 return Some(e);
3558 }
3559 }
3560 None
3561 }
3562 }
3563}
3564
3565fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3566 match lang.kind(stmt) {
3567 NodeKind::VarDecl => {
3568 let ch = lang.children(stmt).to_vec();
3569 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3570 return None;
3571 }
3572 lang.literal(ch[0]).and_then(|x| x.as_int())
3573 }
3574 NodeKind::Assign => {
3575 if lang.assign_op(stmt).is_some() {
3576 return None;
3577 }
3578 let ch = lang.children(stmt).to_vec();
3579 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3580 return None;
3581 }
3582 lang.literal(ch[1]).and_then(|x| x.as_int())
3583 }
3584 NodeKind::ExprStmt => {
3585 let ch = lang.children(stmt).to_vec();
3586 if ch.len() == 1 {
3587 parse_cff_init(lang, v, ch[0])
3588 } else {
3589 None
3590 }
3591 }
3592 _ => None,
3593 }
3594}
3595
3596fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3597 for (s, stmts) in pairs {
3598 c.emitted.insert(*s);
3599 out.extend(stmts.iter().copied());
3600 }
3601}
3602
3603fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3604 for (s, stmts) in pairs {
3605 if *s == m {
3606 break;
3607 }
3608 out.extend(stmts.iter().copied());
3609 }
3610}
3611
3612fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3613 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3614 pairs[pos..].to_vec()
3615}
3616
3617fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3619 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3620 let mut cur = start;
3621 let mut guard = 0usize;
3622 loop {
3623 guard += 1;
3624 if guard > 10_000 {
3625 return None;
3626 }
3627 if Some(cur) == stop {
3628 return Some((path, Flow::Stop));
3629 }
3630 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3631 return Some((path, Flow::Cycle(i)));
3632 }
3633 if c.emitted.contains(&cur) {
3634 return Some((path, Flow::External(cur)));
3635 }
3636 c.reached.insert(cur);
3637 let ci = c.cases.get(&cur)?;
3638 let mut body = ci.body.clone();
3639 match ci.tail {
3640 CffTail::Goto(k) => {
3641 path.push((cur, body));
3642 cur = k;
3643 }
3644 CffTail::Term(r) => {
3645 body.push(r);
3646 path.push((cur, body));
3647 return Some((path, Flow::Done));
3648 }
3649 CffTail::Exit => {
3650 if c.default_term.is_some() {
3651 if let Some(dt) = c.default_term.clone() {
3652 body.extend(dt);
3653 c.used_default = true;
3654 }
3655 }
3656 path.push((cur, body));
3657 return Some((path, Flow::Done));
3658 }
3659 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3660 }
3661 }
3662}
3663
3664fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3666 let (path, flow) = collect(c, init, None)?;
3667 cff_structure(c, path, flow)
3668}
3669
3670fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3672 let (path, flow) = collect(c, start, None)?;
3673 cff_structure(c, path, flow)
3674}
3675
3676fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3680 let mut stmts: Vec<JavaId> = Vec::new();
3681 loop {
3682 match flow {
3683 Flow::Done => {
3684 flatten(&path, &mut stmts, c);
3685 return Some(stmts);
3686 }
3687 Flow::Cycle(i) => {
3688 flatten(&path[..i], &mut stmts, c);
3689 let mut suffix = Vec::new();
3690 flatten(&path[i..], &mut suffix, c);
3691 let inner = c.lang.build_block(suffix);
3692 let cond = c.lang.build_bool(true);
3693 let w = c.lang.while_(cond, inner);
3694 stmts.push(w);
3695 return Some(stmts);
3696 }
3697 Flow::Stop | Flow::External(_) => return None,
3698 Flow::Cond(state) => {
3699 flatten(&path, &mut stmts, c);
3700 path.clear();
3701 let ci = c.cases.get(&state)?;
3702 stmts.extend(ci.body.iter().copied());
3703 c.emitted.insert(state);
3704 let CffTail::Cond { cond, a, b } = ci.tail else {
3705 return None;
3706 };
3707 let (pa, fa) = collect(c, a, Some(state))?;
3709 if fa == Flow::Stop {
3710 let mut lb = Vec::new();
3711 flatten(&pa, &mut lb, c);
3712 let inner = c.lang.build_block(lb);
3713 let w = c.lang.while_(cond, inner);
3714 stmts.push(w);
3715 let (p2, f2) = collect(c, b, None)?;
3716 path = p2;
3717 flow = f2;
3718 continue;
3719 }
3720 let (pb, fb) = collect(c, b, Some(state))?;
3721 if fb == Flow::Stop {
3722 let mut lb = Vec::new();
3723 flatten(&pb, &mut lb, c);
3724 let inner = c.lang.build_block(lb);
3725 let nc = c.lang.build_unary(UnOp::Not, cond);
3726 let w = c.lang.while_(nc, inner);
3727 stmts.push(w);
3728 let (p2, f2) = collect(c, a, None)?;
3729 path = p2;
3730 flow = f2;
3731 continue;
3732 }
3733 let common = pa
3735 .iter()
3736 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3737 .map(|(s, _)| *s);
3738 let same_end = match (fa, fb) {
3739 (Flow::Done, Flow::Done) => true,
3740 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3741 _ => false,
3742 };
3743 match common {
3744 Some(m) if same_end => {
3745 let mut pre_a = Vec::new();
3746 let mut pre_b = Vec::new();
3747 flatten_prefix(&pa, m, &mut pre_a);
3748 flatten_prefix(&pb, m, &mut pre_b);
3749 let ba = c.lang.build_block(pre_a);
3750 let bb = c.lang.build_block(pre_b);
3751 let ifs = c.lang.if_(cond, ba, Some(bb));
3752 stmts.push(ifs);
3753 path = suffix_pairs(&pa, m);
3754 flow = fa;
3755 continue;
3756 }
3757 Some(_) => return None,
3758 None => {
3759 let mut fa_stmts = Vec::new();
3761 let mut fb_stmts = Vec::new();
3762 flatten(&pa, &mut fa_stmts, c);
3763 flatten(&pb, &mut fb_stmts, c);
3764 match (fa, fb) {
3765 (Flow::Done, Flow::Done) => {
3766 let ba = c.lang.build_block(fa_stmts);
3767 let bb = c.lang.build_block(fb_stmts);
3768 let ifs = c.lang.if_(cond, ba, Some(bb));
3769 stmts.push(ifs);
3770 return Some(stmts);
3771 }
3772 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3774 let ba = c.lang.build_block(fa_stmts);
3775 let bb = c.lang.build_block(fb_stmts);
3776 let ifs = c.lang.if_(cond, ba, Some(bb));
3777 stmts.push(ifs);
3778 let (p2, f2) = collect(c, x, None)?;
3779 path = p2;
3780 flow = f2;
3781 continue;
3782 }
3783 (Flow::Done, Flow::Cond(x)) => {
3785 let cont = cff_run_from(c, x)?;
3786 fb_stmts.extend(cont);
3787 let ba = c.lang.build_block(fa_stmts);
3788 let bb = c.lang.build_block(fb_stmts);
3789 let ifs = c.lang.if_(cond, ba, Some(bb));
3790 stmts.push(ifs);
3791 return Some(stmts);
3792 }
3793 (Flow::Cond(x), Flow::Done) => {
3794 let cont = cff_run_from(c, x)?;
3795 fa_stmts.extend(cont);
3796 let ba = c.lang.build_block(fa_stmts);
3797 let bb = c.lang.build_block(fb_stmts);
3798 let ifs = c.lang.if_(cond, ba, Some(bb));
3799 stmts.push(ifs);
3800 return Some(stmts);
3801 }
3802 _ => return None,
3803 }
3804 }
3805 }
3806 }
3807 }
3808 }
3809}
3810
3811
3812
3813pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3823 ast.const_fields.clear();
3824 ast.const_scalars.clear();
3825 ast.inline_methods.clear();
3826 ast.field_types.clear();
3827 ast.final_fields.clear();
3828 ast.raw_generic_names = {
3831 let mut m: std::collections::HashSet<String> = [
3832 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3833 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3834 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3835 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3836 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3837 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3838 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3839 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3840 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3841 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3843 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3844 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3845 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3846 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3847 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3848 ]
3849 .iter()
3850 .map(|x| x.to_string())
3851 .collect();
3852 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3853 if !ty.ty_params.trim().is_empty() {
3854 m.insert(ty.name.clone());
3855 }
3856 for mem in &ty.members {
3857 if let Member::Type(t) = mem {
3858 collect_generics(t, m);
3859 }
3860 }
3861 }
3862 for ty in &unit.types {
3863 collect_generics(ty, &mut m);
3864 }
3865 m
3866 };
3867 ast.unit_shadows_lang = {
3868 fn decl_shadows(ty: &TypeDecl) -> bool {
3869 if ty.name == "String" || ty.name == "StringBuilder" {
3870 return true;
3871 }
3872 ty.members.iter().any(|m| match m {
3873 Member::Type(t) => decl_shadows(t),
3874 _ => false,
3875 })
3876 }
3877 unit.types.iter().any(decl_shadows)
3878 };
3879
3880 let mut shadowed: std::collections::HashSet<String> = Default::default();
3885 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3886 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3887 let mut bodies: Vec<JavaId> = Vec::new();
3888 for ty in &unit.types {
3889 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3890 }
3891 for ty in &unit.types {
3894 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
3895 }
3896 for &root in &bodies {
3900 let mut stack = vec![root];
3901 while let Some(n) = stack.pop() {
3902 if ast.kind(n) == NodeKind::VarDecl {
3903 if let Some(nm) = ast.var_name(n) {
3904 shadowed.insert(nm.to_string());
3905 }
3906 }
3907 match ast.data(n) {
3908 NodeData::Assign { .. } => {
3909 let t = ast.children(n)[0];
3910 if ast.kind(t) == NodeKind::Member {
3911 if let NodeData::Member { name: sym } = ast.data(t) {
3912 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
3913 }
3914 }
3915 let mut ts = vec![t];
3916 while let Some(x) = ts.pop() {
3917 if ast.kind(x) == NodeKind::VarRef {
3918 if let Some(nm) = ast.var_name(x) {
3919 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3920 }
3921 }
3922 for &c in ast.children(x) {
3923 ts.push(c);
3924 }
3925 }
3926 }
3927 NodeData::Unary { op } if op.is_incdec() => {
3928 let t = ast.children(n)[0];
3929 if ast.kind(t) == NodeKind::VarRef {
3930 if let Some(nm) = ast.var_name(t) {
3931 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3932 }
3933 }
3934 }
3935 _ => {}
3936 }
3937 for &c in ast.children(n) {
3938 stack.push(c);
3939 }
3940 }
3941 }
3942
3943 for ty in &unit.types {
3945 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
3947 collect_own_clinit_lits(ast, ty, &mut own_lits);
3948 for m in &ty.members {
3949 if let Member::Field { mods, ty: fty, declarators } = m {
3950 let words: Vec<&str> = mods.split_whitespace().collect();
3951 let is_static = words.contains(&"static");
3952 let is_final = words.contains(&"final");
3953 let is_private = words.contains(&"private");
3954 for d in declarators {
3955 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
3959 continue;
3960 }
3961 let init = match d.init {
3962 Some(init) => {
3963 if !is_final
3965 || !is_const_init(ast, init)
3966 || shadowed.contains(&d.name)
3967 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
3968 {
3969 continue;
3970 }
3971 init
3972 }
3973 None => {
3974 if !is_static
3981 || (!is_final && !is_private)
3982 || shadowed.contains(&d.name)
3983 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
3984 {
3985 continue;
3986 }
3987 match own_lits.get(&d.name) {
3988 Some(&init) if is_const_init(ast, init) => init,
3989 _ => continue,
3990 }
3991 }
3992 };
3993 if ast.const_fields.contains_key(&d.name) {
3995 ast.const_fields.remove(&d.name);
3996 ast.const_scalars.remove(&d.name);
3997 } else {
3998 let scalar_ok = scalar_lit_of(ast, init)
3999 .is_some_and(|l| lit_type_matches_decl(fty, l));
4000 ast.const_fields.insert(d.name.clone(), init);
4001 if scalar_ok {
4002 ast.const_scalars.insert(d.name.clone());
4003 }
4004 }
4005 }
4006 }
4007 }
4008 }
4009
4010 {
4017 let identity_assert = [
4018 "assertSame",
4019 "assertNotSame",
4020 "isSameInstanceAs",
4021 "isNotSameInstanceAs",
4022 "referenceEquals",
4023 "same",
4024 ];
4025 let mut hit = false;
4026 for root in &bodies {
4027 let mut stack = vec![*root];
4028 while let Some(n) = stack.pop() {
4029 if let NodeData::Call = ast.data(n) {
4030 if let Some(&callee) = ast.children(n).first() {
4031 let nm = match ast.data(callee) {
4032 NodeData::Member { name } => ast.sn(*name),
4033 NodeData::VarRef { name } => ast.sn(*name),
4034 _ => "",
4035 };
4036 if identity_assert.contains(&nm) {
4037 hit = true;
4038 break;
4039 }
4040 }
4041 }
4042 for &c in ast.children(n) {
4043 stack.push(c);
4044 }
4045 }
4046 if hit {
4047 break;
4048 }
4049 }
4050 ast.unit_string_identity =
4051 ast.any_string_identity(&bodies) || hit;
4052 }
4053
4054 let mut method_name_count: std::collections::HashMap<String, usize> =
4064 std::collections::HashMap::new();
4065 for_each_type(unit, &mut |ty: &TypeDecl| {
4066 for m in &ty.members {
4067 if let Member::Method { name, .. } = m {
4068 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4069 }
4070 }
4071 });
4072 for ty in &unit.types {
4073 for m in &ty.members {
4074 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4075 let words: Vec<&str> = mods.split_whitespace().collect();
4076 let non_overridable = words.contains(&"private")
4077 || words.contains(&"static")
4078 || words.contains(&"final");
4079 if !non_overridable {
4080 continue;
4081 }
4082 let ch = ast.children(*b).to_vec();
4083 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4084 if let Some(&expr) = ast.children(ch[0]).first() {
4085 if params.len() <= 3
4086 && !subtree_has_anon(ast, expr)
4091 && !params.iter().any(|p| p.varargs)
4095 && !subtree_calls_self(ast, expr, name)
4096 && !subtree_has_type_sensitive(ast, expr)
4102 && method_name_count.get(name).copied() == Some(1)
4103 {
4104 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4105 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4106 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4107 }
4108 }
4109 }
4110 }
4111 }
4112 }
4113}
4114
4115fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4117 match ast.kind(init) {
4118 NodeKind::Literal => true,
4119 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4120 NodeKind::NewArray => {
4121 let ch = ast.children(init).to_vec();
4122 ch.len() == 1
4123 && ast.kind(ch[0]) == NodeKind::ArrayLit
4124 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4125 }
4126 _ => false,
4127 }
4128}
4129
4130fn collect_bodies_recursive(
4134 ty: &TypeDecl,
4135 bodies: &mut Vec<JavaId>,
4136 shadowed: &mut std::collections::HashSet<String>,
4137 field_decls: &mut std::collections::HashMap<String, u32>,
4138) {
4139 if ty.kind == TypeKind::Enum {
4147 for c in &ty.enum_constants {
4148 if c.trim() == "," {
4149 continue;
4150 }
4151 let last = c.split('(').next().unwrap_or(c)
4152 .split_whitespace()
4153 .next_back()
4154 .unwrap_or("");
4155 let name: String = last
4156 .chars()
4157 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4158 .collect();
4159 if !name.is_empty() {
4160 *field_decls.entry(name).or_insert(0) += 1;
4161 }
4162 }
4163 }
4164 for m in &ty.members {
4165 match m {
4166 Member::Method { body: Some(b), params, .. }
4167 | Member::Constructor { body: Some(b), params, .. } => {
4168 bodies.push(*b);
4169 for p in params {
4170 shadowed.insert(p.name.clone());
4171 }
4172 }
4173 Member::Initializer { body, .. } => bodies.push(*body),
4174 Member::Field { declarators, .. } => {
4175 for d in declarators {
4176 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4177 if let Some(init) = d.init {
4178 bodies.push(init);
4179 }
4180 }
4181 }
4182 Member::Type(t) => {
4183 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4184 }
4185 _ => {}
4186 }
4187 }
4188}
4189
4190fn collect_field_types(
4192 ty: &TypeDecl,
4193 out: &mut std::collections::HashMap<String, JType>,
4194 finals: &mut std::collections::HashSet<String>,
4195) {
4196 for m in &ty.members {
4197 match m {
4198 Member::Field { mods, ty: fty, declarators } => {
4199 let is_final = mods.split_whitespace().any(|w| w == "final");
4200 for d in declarators {
4201 if out.contains_key(&d.name) {
4202 out.remove(&d.name);
4203 finals.remove(&d.name);
4204 } else {
4205 out.insert(d.name.clone(), fty.clone());
4206 if is_final {
4207 finals.insert(d.name.clone());
4208 }
4209 }
4210 }
4211 }
4212 Member::Type(t) => collect_field_types(t, out, finals),
4213 _ => {}
4214 }
4215 }
4216}
4217
4218fn collect_own_clinit_lits(
4223 ast: &JavaAst,
4224 ty: &TypeDecl,
4225 out: &mut std::collections::HashMap<String, JavaId>,
4226) {
4227 for m in &ty.members {
4228 if let Member::Initializer { is_static: true, body } = m {
4229 if ast.kind(*body) != NodeKind::Block {
4230 continue;
4231 }
4232 for &st in ast.children(*body) {
4233 let asg = match ast.kind(st) {
4234 NodeKind::ExprStmt => {
4235 let Some(&c) = ast.children(st).first() else {
4239 continue;
4240 };
4241 if ast.kind(c) == NodeKind::Assign {
4242 c
4243 } else {
4244 continue;
4245 }
4246 }
4247 NodeKind::Assign => st,
4248 _ => continue,
4249 };
4250 let ch = ast.children(asg).to_vec();
4251 if ast.kind(ch[0]) != NodeKind::VarRef {
4252 continue;
4253 }
4254 let Some(nm) = ast.var_name(ch[0]) else { continue };
4255 if is_const_init(ast, ch[1]) {
4256 out.insert(nm.to_string(), ch[1]);
4257 }
4258 }
4259 }
4260 }
4261}
4262
4263fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4265 if ast.kind(init) == NodeKind::Literal {
4266 ast.literal(init)
4267 } else {
4268 None
4269 }
4270}
4271
4272fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4277 use cure_engine::kind::LitRef;
4278 match (fty, lit) {
4279 (JType::Ref(r), LitRef::Str(_)) => {
4280 let base = r.split('<').next().unwrap_or(r).trim();
4281 base == "String" || base == "java.lang.String"
4282 }
4283 (JType::Char, LitRef::Char(_)) => true,
4284 (JType::Bool, LitRef::Bool(_)) => true,
4285 (JType::Int, LitRef::Int(_)) => true,
4286 (JType::Long, LitRef::Long(_)) => true,
4287 (JType::Float, LitRef::Float(_)) => true,
4288 (JType::Double, LitRef::Double(_)) => true,
4289 _ => false,
4290 }
4291}
4292
4293fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4297 let mut stack = vec![root];
4298 while let Some(n) = stack.pop() {
4299 match ast.kind(n) {
4300 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4301 _ => {}
4302 }
4303 for &c in ast.children(n) {
4304 stack.push(c);
4305 }
4306 }
4307 false
4308}
4309
4310fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4311 let mut stack = vec![root];
4312 while let Some(n) = stack.pop() {
4313 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4314 return true;
4315 }
4316 for &c in ast.children(n) {
4317 stack.push(c);
4318 }
4319 }
4320 false
4321}
4322
4323fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4324 let mut stack = vec![root];
4325 while let Some(n) = stack.pop() {
4326 if ast.kind(n) == NodeKind::Call {
4327 let callee = ast.children(n)[0];
4328 if ast.var_name(callee) == Some(name) {
4329 return true;
4330 }
4331 if let NodeData::Member { name: m } = ast.data(callee) {
4336 if ast.sn(*m) == name {
4337 return true;
4338 }
4339 }
4340 }
4341 for &c in ast.children(n) {
4342 stack.push(c);
4343 }
4344 }
4345 false
4346}
4347
4348pub struct StaticArrayIndexFold;
4353
4354impl Rule<JavaAst> for StaticArrayIndexFold {
4355 fn name(&self) -> &'static str {
4356 "static_array_index_fold"
4357 }
4358 fn kinds(&self) -> &'static [NodeKind] {
4359 &[NodeKind::Index]
4360 }
4361 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4362 let lang = ctx.lang;
4363 if lang.kind(id) != NodeKind::Index {
4364 return None;
4365 }
4366 let ch = lang.children(id).to_vec();
4367 if lang.kind(ch[0]) != NodeKind::VarRef {
4368 return None;
4369 }
4370 let name = lang.var_name(ch[0])?.to_string();
4371 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4372 let init = *lang.const_fields.get(&name)?;
4373 let arr = match lang.kind(init) {
4374 NodeKind::ArrayLit => init,
4375 NodeKind::NewArray => {
4376 let ic = lang.children(init).to_vec();
4377 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4378 ic[0]
4379 } else {
4380 return None;
4381 }
4382 }
4383 _ => return None,
4384 };
4385 let elems = lang.children(arr).to_vec();
4386 if idx < 0 || idx >= elems.len() as i64 {
4387 return None;
4388 }
4389 let elem = lang.copy_subtree(elems[idx as usize]);
4390 Some(Edit::Replace {
4391 target: id,
4392 with: elem,
4393 })
4394 }
4395}
4396
4397pub struct DeadPureCall;
4477
4478impl Rule<JavaAst> for DeadPureCall {
4479 fn name(&self) -> &'static str {
4480 "dead_pure_call"
4481 }
4482 fn kinds(&self) -> &'static [NodeKind] {
4483 &[NodeKind::ExprStmt]
4484 }
4485 fn structural(&self) -> bool {
4486 true
4487 }
4488 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4489 let lang = ctx.lang;
4490 if lang.kind(id) != NodeKind::ExprStmt {
4491 return None;
4492 }
4493 let Some(&call) = lang.children(id).first() else { return None };
4494 if lang.kind(call) != NodeKind::Call {
4495 return None;
4496 }
4497 let cch = lang.children(call).to_vec();
4498 let Some(&callee) = cch.first() else { return None };
4499 let (recv, name) = match lang.data(callee) {
4502 NodeData::Member { name } => {
4503 let Some(&recv_expr) = lang.children(callee).first() else { return None };
4504 let recv_name = match lang.data(recv_expr) {
4507 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4508 NodeData::Member { name } => lang.sn(*name).to_string(),
4509 _ => return None,
4510 };
4511 if !recv_name
4512 .chars()
4513 .next()
4514 .is_some_and(|c| c.is_uppercase())
4515 {
4516 return None;
4517 }
4518 (recv_name, lang.sn(*name))
4519 }
4520 _ => return None,
4521 };
4522 let args = &cch[1..];
4523 let pure = match (recv.as_str(), name) {
4524 ("Long" | "Integer" | "Float" | "Double" | "Short" | "Byte" | "Character", "compare") => {
4525 args.iter().all(|&a| match lang.data(a) {
4529 NodeData::Literal(
4530 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_),
4531 ) => true,
4532 NodeData::Literal(Lit::NumRaw { .. }) => true,
4534 NodeData::VarRef { .. } => {
4535 let t = lang.var_type(a);
4536 matches!(
4537 t,
4538 Some(JType::Int | JType::Long | JType::Float | JType::Double | JType::Char
4539 | JType::Short | JType::Byte)
4540 )
4541 }
4542 _ => false,
4543 })
4544 }
4545 ("Objects", "toString") => args.len() == 1,
4546 _ => false,
4547 };
4548 if !pure {
4549 return None;
4550 }
4551 Some(Edit::Delete { node: id })
4552 }
4553}
4554
4555pub struct CtorDelegationHoist;
4556
4557fn hoist_resolve_arg(
4562 lang: &mut JavaAst,
4563 assign: &std::collections::HashMap<String, JavaId>,
4564 a: JavaId,
4565 visiting: &mut std::collections::HashSet<String>,
4566) -> Option<JavaId> {
4567 let name = match lang.data(a) {
4568 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4569 _ => return Some(a), };
4571 let Some(&expr) = assign.get(&name) else {
4573 return Some(a);
4574 };
4575 if !visiting.insert(name.clone()) {
4576 return None; }
4578 let r = hoist_subst(lang, assign, expr, visiting);
4579 visiting.remove(&name);
4580 r
4581}
4582
4583fn hoist_subst(
4585 lang: &mut JavaAst,
4586 assign: &std::collections::HashMap<String, JavaId>,
4587 e: JavaId,
4588 visiting: &mut std::collections::HashSet<String>,
4589) -> Option<JavaId> {
4590 match lang.data(e) {
4591 NodeData::VarRef { name } => {
4592 let n = lang.sn(*name).to_string();
4593 if let Some(&src) = assign.get(&n) {
4594 if !visiting.insert(n.clone()) {
4595 return None;
4596 }
4597 let r = hoist_subst(lang, assign, src, visiting);
4598 visiting.remove(&n);
4599 r
4600 } else {
4601 Some(e)
4602 }
4603 }
4604 _ => {
4605 let ch = lang.children(e).to_vec();
4606 let mut new_ch = Vec::with_capacity(ch.len());
4607 for c in ch {
4608 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4609 }
4610 Some(lang.clone_node(e, new_ch))
4611 }
4612 }
4613}
4614
4615impl Rule<JavaAst> for CtorDelegationHoist {
4616 fn name(&self) -> &'static str {
4617 "ctor_delegation_hoist"
4618 }
4619 fn kinds(&self) -> &'static [NodeKind] {
4620 &[NodeKind::Block]
4621 }
4622 fn structural(&self) -> bool {
4623 true
4624 }
4625 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4626 if ctx.parent(id).is_some() {
4629 return None;
4630 }
4631 let lang = ctx.lang;
4632 if lang.kind(id) != NodeKind::Block {
4633 return None;
4634 }
4635 let stmts = lang.children(id).to_vec();
4636 let mut ctor_idx: Option<usize> = None;
4638 for (i, &st) in stmts.iter().enumerate() {
4639 if *lang.data(st) != NodeData::ExprStmt {
4640 continue;
4641 }
4642 let Some(&inner) = lang.children(st).first() else { continue };
4643 if lang.kind(inner) != NodeKind::Call {
4644 continue;
4645 }
4646 let Some(&callee) = lang.children(inner).first() else { continue };
4647 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4648 ctor_idx = Some(i);
4649 break;
4650 }
4651 }
4652 let ci = ctor_idx?;
4653 if ci == 0 {
4654 return None; }
4656 let mut last_assign: std::collections::HashMap<String, JavaId> =
4659 std::collections::HashMap::new();
4660 for &st in &stmts[..ci] {
4661 let (name, value) = match lang.data(st) {
4662 NodeData::VarDecl { name, .. } => {
4663 let init = lang.children(st).first().copied();
4664 match init {
4665 Some(e) => (name.clone(), e),
4666 None => continue, }
4668 }
4669 NodeData::ExprStmt => {
4670 let Some(&a) = lang.children(st).first() else {
4671 return None;
4672 };
4673 if lang.kind(a) != NodeKind::Assign
4676 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4677 {
4678 return None;
4679 }
4680 let ch = lang.children(a).to_vec();
4681 let (t, v) = (*ch.first()?, *ch.get(1)?);
4682 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4683 return None; }
4685 let vn = lang.var_name(t)?;
4686 (vn.to_string(), v)
4687 }
4688 _ => return None, };
4690 last_assign.insert(name, value);
4695 }
4696 let call_stmt = stmts[ci];
4698 let call = *lang.children(call_stmt).first()?;
4699 let cch = lang.children(call).to_vec();
4700 let callee = *cch.first()?;
4701 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4702 for &a in &cch[1..] {
4703 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4704 }
4705 let callee_copy = lang.copy_subtree(callee);
4707 let new_call = lang.call(callee_copy, new_args);
4708 let new_stmt = lang.expr_stmt(new_call);
4709 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4710 insert.push(new_stmt);
4711 insert.extend(stmts[..ci].iter().copied());
4712 Some(Edit::Splice {
4713 node: id,
4714 index: 0,
4715 remove: ci + 1,
4716 insert,
4717 })
4718 }
4719}
4720
4721pub struct TrailingIfContinue;
4722
4723impl Rule<JavaAst> for TrailingIfContinue {
4724 fn name(&self) -> &'static str {
4725 "trailing_if_continue"
4726 }
4727 fn kinds(&self) -> &'static [NodeKind] {
4728 &[NodeKind::If]
4729 }
4730 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4731 let parent = ctx.parent(id);
4732 let grandparent = parent.and_then(|p| ctx.parent(p));
4733 let lang = ctx.lang;
4734 if lang.kind(id) != NodeKind::If {
4735 return None;
4736 }
4737 let ch = lang.children(id).to_vec();
4738 if ch.len() != 2 {
4740 return None;
4741 }
4742 let then = ch[1];
4743 if lang.kind(then) != NodeKind::Block {
4744 return None;
4745 }
4746 let tbody = lang.children(then).to_vec();
4747 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4748 return None;
4749 }
4750 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4752 return None;
4753 }
4754 let p = parent?;
4757 if lang.kind(p) != NodeKind::Block {
4758 return None;
4759 }
4760 let pch = lang.children(p).to_vec();
4761 if pch.last() != Some(&id) {
4762 return None;
4763 }
4764 let gp = grandparent?;
4766 match lang.kind(gp) {
4767 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4768 let gch = lang.children(gp).to_vec();
4769 if gch.last() == Some(&p) {
4770 Some(Edit::Delete { node: id })
4771 } else {
4772 None
4773 }
4774 }
4775 _ => None,
4776 }
4777 }
4778}
4779
4780pub struct TrailingAssignReturn;
4781
4782impl Rule<JavaAst> for TrailingAssignReturn {
4783 fn name(&self) -> &'static str {
4784 "trailing_assign_return"
4785 }
4786 fn kinds(&self) -> &'static [NodeKind] {
4787 &[NodeKind::Assign]
4788 }
4789 fn structural(&self) -> bool {
4792 true
4793 }
4794 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4795 let parent = ctx.parent(id);
4796 let grandparent = parent.and_then(|p| ctx.parent(p));
4797 let lang = ctx.lang;
4798 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4800 return None;
4801 }
4802 let ch = lang.children(id).to_vec();
4803 if ch.len() != 2 {
4804 return None;
4805 }
4806 let (target, value) = (ch[0], ch[1]);
4807 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4809 return None;
4810 }
4811 let name = lang.var_name(target)?.to_string();
4812 let parent = parent?;
4814 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4815 parent
4816 } else if lang.kind(parent) == NodeKind::Block {
4817 id
4818 } else {
4819 return None;
4820 };
4821 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4822 grandparent?
4823 } else {
4824 parent
4825 };
4826 if lang.kind(block) != NodeKind::Block {
4827 return None;
4828 }
4829 let stmts = lang.children(block).to_vec();
4830 let idx = stmts.iter().position(|&x| x == stmt)?;
4831 let next = *stmts.get(idx + 1)?;
4833 let (flow_val, is_return) = match lang.kind(next) {
4834 NodeKind::Return => (lang.children(next).first().copied(), true),
4835 NodeKind::Throw => (lang.children(next).first().copied(), false),
4836 _ => return None,
4837 };
4838 let flow_val = flow_val?;
4839 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4840 return None;
4841 }
4842 for &s in &stmts[idx + 2..] {
4845 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4846 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4847 }) {
4848 return None;
4849 }
4850 }
4851 let new_flow = if is_return {
4853 lang.ret(Some(value))
4854 } else {
4855 lang.throw(value)
4856 };
4857 Some(Edit::Multi(vec![
4858 Edit::Replace { target: next, with: new_flow },
4859 Edit::Delete { node: stmt },
4860 ]))
4861 }
4862}
4863
4864pub struct ConstFieldPropagate;
4865
4866impl Rule<JavaAst> for ConstFieldPropagate {
4867 fn name(&self) -> &'static str {
4868 "const_field_propagate"
4869 }
4870 fn kinds(&self) -> &'static [NodeKind] {
4871 &[NodeKind::VarRef]
4872 }
4873 fn structural(&self) -> bool {
4876 true
4877 }
4878 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4879 let parent = ctx.parent(id);
4880 let _grandparent = parent.and_then(|p| ctx.parent(p));
4881 let lang = ctx.lang;
4882 if lang.kind(id) != NodeKind::VarRef {
4883 return None;
4884 }
4885 let name = lang.var_name(id)?.to_string();
4886 if !lang.const_scalars.contains(&name) {
4887 return None;
4888 }
4889 if let Some(p) = parent {
4890 let is_first = lang.children(p).first() == Some(&id);
4891 match lang.kind(p) {
4892 NodeKind::Assign if is_first => return None,
4894 NodeKind::Unary if is_first => {
4895 if let NodeData::Unary { op } = lang.data(p) {
4896 if op.is_incdec() {
4897 return None;
4898 }
4899 }
4900 }
4901 NodeKind::Call if is_first => return None,
4903 _ => {}
4904 }
4905 }
4906 let init = *lang.const_fields.get(&name)?;
4907 let lit = lang.copy_subtree(init);
4908 Some(Edit::Replace { target: id, with: lit })
4909 }
4910}
4911
4912pub struct NoopPrivateCall;
4913
4914impl Rule<JavaAst> for NoopPrivateCall {
4915 fn name(&self) -> &'static str {
4916 "noop_private_call"
4917 }
4918 fn kinds(&self) -> &'static [NodeKind] {
4919 &[NodeKind::ExprStmt]
4920 }
4921 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4922 let lang = ctx.lang;
4923 if lang.kind(id) != NodeKind::ExprStmt {
4924 return None;
4925 }
4926 let ch = lang.children(id).to_vec();
4927 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
4928 return None;
4929 }
4930 let call = ch[0];
4931 let cch = lang.children(call).to_vec();
4932 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
4933 return None; }
4935 let name = lang.var_name(cch[0])?.to_string();
4936 let arity = cch.len() - 1;
4937 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
4938 if cands.is_empty() {
4939 return None;
4940 }
4941 let arg_types: Vec<Option<JType>> =
4945 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
4946 let mut applicable_noop = false;
4947 let mut applicable_count = 0usize;
4948 for c in cands {
4949 let ok = c
4950 .params
4951 .iter()
4952 .zip(arg_types.iter())
4953 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
4954 if ok {
4955 applicable_count += 1;
4956 applicable_noop = c.noop;
4957 }
4958 }
4959 if applicable_count != 1 || !applicable_noop {
4960 return None;
4961 }
4962 for &arg in &cch[1..] {
4964 if lang.effect(arg) > Effect::MayRead {
4965 return None;
4966 }
4967 }
4968 Some(Edit::Delete { node: id })
4969 }
4970}
4971
4972fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
4976 match lang.kind(arg) {
4977 NodeKind::Literal => match lang.literal(arg)? {
4978 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
4979 LitRef::Char(_) => Some(JType::Char),
4980 LitRef::Bool(_) => Some(JType::Bool),
4981 LitRef::Int(_) => Some(JType::Int),
4982 LitRef::Long(_) => Some(JType::Long),
4983 LitRef::Float(_) => Some(JType::Float),
4984 LitRef::Double(_) => Some(JType::Double),
4985 LitRef::Null => None,
4986 },
4987 NodeKind::VarRef => {
4988 let nm = lang.var_name(arg)?;
4989 lang.var_type(arg)
4990 .cloned()
4991 .or_else(|| lang.field_types.get(nm).cloned())
4992 }
4993 _ => None,
4994 }
4995}
4996
4997fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5002 let Some(a) = at else { return true };
5003 match pt {
5004 JType::Ref(p) => {
5005 let pbase = p.split('<').next().unwrap_or(p).trim();
5006 pbase == "Object"
5007 || pbase == "java.lang.Object"
5008 || match a {
5009 JType::Ref(arend) => {
5010 let abase = arend.split('<').next().unwrap_or(arend).trim();
5011 abase == pbase
5012 }
5013 _ => false,
5014 }
5015 }
5016 JType::Array(_) => false, prim => match a {
5018 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5019 },
5020 }
5021}
5022
5023pub struct ConstMethodInline;
5033
5034impl Rule<JavaAst> for ConstMethodInline {
5035 fn name(&self) -> &'static str {
5036 "const_method_inline"
5037 }
5038 fn kinds(&self) -> &'static [NodeKind] {
5039 &[NodeKind::Call]
5040 }
5041 fn structural(&self) -> bool {
5042 true
5043 }
5044 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5045 let at_stmt_pos = ctx
5047 .parent(id)
5048 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5049 .unwrap_or(false);
5050 let lang = ctx.lang;
5051 if lang.kind(id) != NodeKind::Call {
5052 return None;
5053 }
5054 let ch = lang.children(id).to_vec();
5055 let (method_name, recv_class) = match lang.data(ch[0]) {
5059 NodeData::VarRef { name } => (*name, None),
5060 NodeData::Member { name } => {
5061 let recv = lang.children(ch[0])[0];
5062 match lang.var_name(recv) {
5063 Some(r) if r == "this" => (*name, None),
5064 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5065 (*name, Some(r.to_string()))
5066 }
5067 _ => return None,
5068 }
5069 }
5070 _ => return None,
5071 };
5072 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5073 let (params, body_expr, owner) = entry;
5074 if let Some(recv) = recv_class {
5081 if recv != owner {
5082 return None;
5083 }
5084 }
5085 {
5087 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5088 for nm in &names {
5089 if subtree_calls_self(lang, body_expr, nm) {
5090 return None;
5091 }
5092 }
5093 }
5094 let args = ch[1..].to_vec();
5095 if args.len() != params.len() || params.is_empty() {
5096 return None;
5097 }
5098 for &a in &args {
5099 lang.literal(a)?;
5100 }
5101 let map: std::collections::HashMap<String, JavaId> = params
5102 .iter()
5103 .zip(args.iter())
5104 .map(|(p, &a)| (p.clone(), a))
5105 .collect();
5106 let with = copy_subst(lang, body_expr, &map);
5107 if at_stmt_pos {
5113 match lang.kind(with) {
5114 NodeKind::Call | NodeKind::New => {}
5115 _ => return None,
5116 }
5117 }
5118 Some(Edit::Replace {
5119 target: id,
5120 with,
5121 })
5122 }
5123}
5124
5125fn copy_subst(
5127 lang: &mut JavaAst,
5128 node: JavaId,
5129 map: &std::collections::HashMap<String, JavaId>,
5130) -> JavaId {
5131 if let NodeData::VarRef { name } = lang.data(node) {
5132 if let Some(&repl) = map.get(lang.sn(*name)) {
5133 return lang.copy_subtree(repl);
5134 }
5135 }
5136 let children: Vec<JavaId> = lang.children(node).to_vec();
5137 let new_children: Vec<JavaId> = children
5138 .iter()
5139 .map(|&c| copy_subst(lang, c, map))
5140 .collect();
5141 lang.clone_node(node, new_children)
5142}
5143
5144
5145pub struct TwrRecover;
5178
5179fn twr_is_throwable(ty_raw: &str) -> bool {
5180 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5181}
5182
5183fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5185 if lang.kind(n) == NodeKind::ExprStmt {
5186 if let Some(&e) = lang.children(n).first() {
5187 return e;
5188 }
5189 }
5190 n
5191}
5192
5193fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5195 if lang.kind(n) == NodeKind::Block {
5196 lang.children(n).to_vec()
5197 } else {
5198 vec![n]
5199 }
5200}
5201
5202fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5204 if lang.kind(e) != NodeKind::Call {
5205 return false;
5206 }
5207 let ch = lang.children(e);
5208 if ch.len() != 1 {
5209 return false;
5210 }
5211 match lang.data(ch[0]) {
5212 NodeData::Member { name } if lang.sn(*name) == "close" => {
5213 let Some(&recv) = lang.children(ch[0]).first() else {
5214 return false;
5215 };
5216 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5217 }
5218 _ => false,
5219 }
5220}
5221
5222fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5223 if lang.kind(n) != NodeKind::ExprStmt {
5224 return false;
5225 }
5226 lang.children(n)
5227 .first()
5228 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5229}
5230
5231fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5233 if lang.kind(n) != NodeKind::ExprStmt {
5234 return false;
5235 }
5236 let Some(&e) = lang.children(n).first() else {
5237 return false;
5238 };
5239 if lang.kind(e) != NodeKind::Call {
5240 return false;
5241 }
5242 let ch = lang.children(e);
5243 if ch.len() != 2 {
5244 return false;
5245 }
5246 match lang.data(ch[0]) {
5247 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5248 let Some(&recv) = lang.children(ch[0]).first() else {
5249 return false;
5250 };
5251 lang.kind(recv) == NodeKind::VarRef
5252 && lang.var_name(recv) == Some(primary)
5253 && lang.kind(ch[1]) == NodeKind::VarRef
5254 && lang.var_name(ch[1]) == Some(sup)
5255 }
5256 _ => false,
5257 }
5258}
5259
5260fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5261 let mut stack = vec![root];
5262 while let Some(n) = stack.pop() {
5263 if n == target {
5264 return true;
5265 }
5266 stack.extend(lang.children(n).iter().copied());
5267 }
5268 false
5269}
5270
5271fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5273 let mut stack = vec![root];
5274 while let Some(n) = stack.pop() {
5275 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5276 let ok = allowed
5277 .iter()
5278 .any(|&a| a == n || node_contains(lang, a, n));
5279 if !ok {
5280 return true;
5281 }
5282 }
5283 if lang.kind(n) == NodeKind::VarDecl {
5284 if let Some(nm) = lang.var_name(n) {
5285 if nm == name && !allowed.contains(&n) {
5286 return true;
5288 }
5289 }
5290 }
5291 stack.extend(lang.children(n).iter().copied());
5292 }
5293 false
5294}
5295
5296fn twr_close_with_suppressed(
5300 lang: &JavaAst,
5301 try_node: JavaId,
5302 primary: &str,
5303) -> Option<(String, String)> {
5304 let ch = lang.children(try_node).to_vec();
5305 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5306 return None;
5307 }
5308 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5309 return None;
5310 };
5311 if !twr_is_throwable(ty_raw) {
5312 return None;
5313 }
5314 let sup = sup.clone();
5315 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5316 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5317 return None;
5318 }
5319 let close_stmts = twr_stmts(lang, ch[0]);
5320 if close_stmts.len() != 1 {
5321 return None;
5322 }
5323 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5324 return None;
5325 }
5326 let e = *lang.children(close_stmts[0]).first()?;
5327 let cc = lang.children(e);
5328 if cc.len() != 1 {
5329 return None;
5330 }
5331 let NodeData::Member { name } = lang.data(cc[0]) else {
5332 return None;
5333 };
5334 if lang.sn(*name) != "close" {
5335 return None;
5336 }
5337 let recv = *lang.children(cc[0]).first()?;
5338 if lang.kind(recv) != NodeKind::VarRef {
5339 return None;
5340 }
5341 Some((lang.var_name(recv)?.to_string(), sup))
5342}
5343
5344impl Rule<JavaAst> for TwrRecover {
5345 fn name(&self) -> &'static str {
5346 "twr_recover"
5347 }
5348 fn kinds(&self) -> &'static [NodeKind] {
5349 &[NodeKind::Try]
5350 }
5351 fn structural(&self) -> bool {
5352 true
5353 }
5354 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5355 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5357 let lang = ctx.lang;
5358 if lang.kind(id) != NodeKind::Try {
5359 return None;
5360 }
5361 let ch = lang.children(id).to_vec();
5362 let try_idx = ch
5364 .iter()
5365 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5366 if try_idx != 0 {
5367 return None;
5368 }
5369 let try_block = ch[0];
5370 if lang.kind(parent) != NodeKind::Block {
5371 return None;
5372 }
5373 let pch = lang.children(parent).to_vec();
5374
5375 if ch.len() == 2 {
5377 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5378 if twr_is_throwable(ty_raw) {
5379 let t = t.clone();
5380 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5381 if catch_stmts.len() == 2 {
5382 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5384 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5385 && lang
5386 .children(throw_stmt)
5387 .first()
5388 .is_some_and(|&x| {
5389 lang.kind(x) == NodeKind::VarRef
5390 && lang.var_name(x) == Some(t.as_str())
5391 });
5392 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5393 if let Some((r, sup)) =
5394 twr_close_with_suppressed(lang, inner_try, t.as_str())
5395 {
5396 if idx >= 1 && idx + 1 < pch.len() {
5397 let decl_r = pch[idx - 1];
5398 let trailing = pch[idx + 1];
5399 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5400 if name == &r {
5401 let (name, ty) = (name.clone(), ty.clone());
5402 let init =
5403 lang.children(decl_r).first().copied();
5404 if let Some(init) = init {
5405 let provably_new = lang.kind(init)
5407 == NodeKind::New;
5408 let close_ok =
5409 twr_is_close_stmt(lang, trailing, &r);
5410 if provably_new && close_ok {
5411 let root = walk_root;
5413 let r_ok = !refs_escape(
5414 lang,
5415 root,
5416 &r,
5417 &[
5418 decl_r,
5419 init,
5420 *lang
5421 .children(inner_try)
5422 .first()
5423 .unwrap_or(&inner_try),
5424 trailing,
5425 try_block,
5426 ],
5427 );
5428 let t_ok = !refs_escape(
5429 lang, root, &t,
5430 &[throw_stmt, ch[1]],
5431 );
5432 let sup_ok = !refs_escape(
5433 lang, root, &sup,
5434 &[ch[1]],
5435 );
5436 if r_ok && t_ok && sup_ok {
5437 let init_copy =
5438 lang.copy_subtree(init);
5439 let res = lang.var_decl(
5440 &name, ty,
5441 Some(init_copy),
5442 );
5443 let new_try = lang.try_(
5444 vec![res],
5445 try_block,
5446 Vec::new(),
5447 None,
5448 );
5449 return Some(Edit::Splice {
5450 node: parent,
5451 index: idx - 1,
5452 remove: 3,
5453 insert: vec![new_try],
5454 });
5455 }
5456 }
5457 }
5458 }
5459 }
5460 }
5461 }
5462 }
5463 }
5464 }
5465 }
5466 }
5467
5468 if ch.len() == 3 {
5470 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5471 return None;
5472 };
5473 if !twr_is_throwable(ty_raw) {
5474 return None;
5475 }
5476 let t = t.clone();
5477 if lang.kind(ch[2]) != NodeKind::Block {
5479 return None;
5480 }
5481 let fin_stmts = twr_stmts(lang, ch[2]);
5482 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5483 return None;
5484 }
5485 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5487 if catch_stmts.len() != 2 {
5488 return None;
5489 }
5490 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5491 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5492 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5493 return None;
5494 };
5495 let as_ch = lang.children(assign_expr);
5496 if as_ch.len() != 2 {
5497 return None;
5498 }
5499 let primary = match lang.data(as_ch[0]) {
5500 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5501 _ => return None,
5502 };
5503 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5504 && lang.var_name(as_ch[1]) == Some(t.as_str())
5505 && lang.kind(as_ch[0]) == NodeKind::VarRef
5506 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5507 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5508 && lang.children(throw_stmt)
5509 .first()
5510 .is_some_and(|&x| {
5511 lang.kind(x) == NodeKind::VarRef
5512 && lang.var_name(x) == Some(t.as_str())
5513 });
5514 if !assign_ok || !throw_ok {
5515 return None;
5516 }
5517 let if1 = fin_stmts[0];
5519 let i1 = lang.children(if1).to_vec();
5520 if i1.len() < 2 {
5521 return None;
5522 }
5523 let cond1 = i1[0];
5524 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5525 return None;
5526 };
5527 let c1 = lang.children(cond1);
5528 let r = match lang.data(c1[0]) {
5529 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5530 lang.sn(*name).to_string()
5531 }
5532 _ => {
5533 return None;
5534 }
5535 };
5536 let then1 = i1[1];
5537 let inner_stmts = twr_stmts(lang, then1);
5538 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5539 return None;
5540 }
5541 let if2 = inner_stmts[0];
5542 let i2 = lang.children(if2).to_vec();
5543 if i2.len() != 3 {
5544 return None;
5545 }
5546 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5547 return None;
5548 };
5549 let c2 = lang.children(i2[0]);
5550 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5551 && lang.var_name(c2[0]) == Some(primary.as_str())
5552 && lang.literal(c2[1]) == Some(LitRef::Null);
5553 if !primary_ok {
5554 return None;
5555 }
5556 let (then2, els2) = (i2[1], i2[2]);
5557 let t2_stmts = twr_stmts(lang, then2);
5559 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5560 return None;
5561 }
5562 let Some((r2, sup)) =
5563 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5564 else {
5565 return None;
5566 };
5567 if r2 != r {
5568 return None;
5569 }
5570 let e2_stmts = twr_stmts(lang, els2);
5572 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5573 return None;
5574 }
5575 if idx < 2 {
5577 return None;
5578 }
5579 let decl_r = pch[idx - 2];
5580 let decl_primary = pch[idx - 1];
5581 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5582 (
5583 NodeData::VarDecl { name: rn, .. },
5584 NodeData::VarDecl { name: pn, .. },
5585 ) => (rn.clone(), pn.clone()),
5586 _ => return None,
5587 };
5588 if r_decl != r || primary_decl != primary {
5589 return None;
5590 }
5591 let pinit_null = lang
5593 .children(decl_primary)
5594 .first()
5595 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5596 if !pinit_null {
5597 return None;
5598 }
5599 let r_init = lang.children(decl_r).first().copied()?;
5600 let ty = match lang.data(decl_r) {
5601 NodeData::VarDecl { ty, .. } => ty.clone(),
5602 _ => return None,
5603 };
5604 let root = walk_root;
5606 let r_allowed = [decl_r, r_init, ch[2], try_block];
5608 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5609 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5610 if refs_escape(lang, root, &r, &r_allowed)
5611 || refs_escape(lang, root, &primary, &p_allowed)
5612 || refs_escape(lang, root, &t, &t_allowed)
5613 || refs_escape(lang, root, &sup, &[ch[2]])
5614 {
5615 return None;
5616 }
5617 let init_copy = lang.copy_subtree(r_init);
5618 let res = lang.var_decl(&r, ty, Some(init_copy));
5619 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5620 return Some(Edit::Splice {
5621 node: parent,
5622 index: idx - 2,
5623 remove: 3,
5624 insert: vec![new_try],
5625 });
5626 }
5627 None
5628 }
5629}
5630
5631pub struct StringSwitchRecover;
5656
5657fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5658 out.push_str(&format!("{:?} ", lang.data(n)));
5659 for &c in lang.children(n) {
5660 subtree_sig(lang, c, out);
5661 }
5662}
5663
5664fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5666 let ch = lang.children(sw).to_vec();
5667 let mut out = Vec::new();
5668 for &c in &ch[1..] {
5669 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5670 return None;
5671 };
5672 let is_default = *is_default;
5673 let cc = lang.children(c);
5674 let label = if is_default {
5675 None
5676 } else {
5677 if *labels != 1 {
5678 return None; }
5680 let lab = cc[0];
5681 let lit = lang.literal(lab)?;
5682 let v = match lit {
5683 LitRef::Int(v) | LitRef::Long(v) => v,
5684 _ => return None, };
5686 Some(v)
5687 };
5688 out.push((label, cc[*labels as usize..].to_vec()));
5689 }
5690 Some(out)
5691}
5692
5693fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5694 let Some(&last) = stmts.last() else {
5695 return false; };
5697 let last = if lang.kind(last) == NodeKind::Block {
5698 match lang.children(last).last() {
5699 Some(&l) => l,
5700 None => return false,
5701 }
5702 } else {
5703 last
5704 };
5705 matches!(
5706 lang.kind(last),
5707 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5708 )
5709}
5710
5711impl Rule<JavaAst> for StringSwitchRecover {
5712 fn name(&self) -> &'static str {
5713 "string_switch_recover"
5714 }
5715 fn kinds(&self) -> &'static [NodeKind] {
5716 &[NodeKind::Switch]
5717 }
5718 fn structural(&self) -> bool {
5719 true
5720 }
5721 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5722 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5723 let lang = ctx.lang;
5724 if lang.kind(id) != NodeKind::Switch {
5725 return None;
5726 }
5727 let ch = lang.children(id).to_vec();
5728 if ch.len() < 2 {
5729 return None;
5730 }
5731 let x = match lang.data(ch[0]) {
5733 NodeData::Call => {
5734 let cc = lang.children(ch[0]);
5735 if cc.len() != 1 {
5736 return None;
5737 }
5738 match lang.data(cc[0]) {
5739 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5740 let recv = *lang.children(cc[0]).first()?;
5741 if lang.kind(recv) != NodeKind::VarRef {
5742 return None;
5743 }
5744 lang.var_name(recv)?.to_string()
5745 }
5746 _ => return None,
5747 }
5748 }
5749 _ => {
5750 return None;
5751 }
5752 };
5753 struct CaseInfo {
5755 lit: String,
5756 k: i64,
5757 inner_switch: JavaId,
5758 }
5759 let mut infos: Vec<CaseInfo> = Vec::new();
5760 let mut v_name: Option<String> = None;
5761 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5763 for &c in &ch[1..] {
5764 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5765 return None;
5766 };
5767 let is_default = *is_default;
5768 if is_default {
5769 return None; }
5771 if *labels != 1 {
5772 return None;
5773 }
5774 let cc = lang.children(c);
5775 let h = match lang.literal(cc[0])? {
5776 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5777 _ => return None,
5778 };
5779 let body = cc[1..].to_vec();
5780 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5782 lang.children(body[0]).to_vec()
5783 } else {
5784 body
5785 };
5786 let mut rest = body;
5787 let mut _e_decl: Option<(String, JavaId)> = None;
5789 if let Some(&first) = rest.first() {
5790 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5791 if let Some(&init) = lang.children(first).first() {
5792 if is_x_equals(lang, init, &x) {
5793 _e_decl = Some((name.clone(), first));
5794 rest = rest[1..].to_vec();
5795 }
5796 }
5797 }
5798 }
5799 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5800 return None;
5801 }
5802 let ifn = rest[0];
5803 let ic = lang.children(ifn).to_vec();
5804 if ic.len() != 3 {
5805 return None;
5806 }
5807 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5809 return None;
5810 };
5811 let guard = *lang.children(ic[0]).first()?;
5812 let lit = match lang.data(guard) {
5813 NodeData::VarRef { name } => {
5814 let Some((en, decl)) = &_e_decl else {
5816 return None;
5817 };
5818 if lang.sn(*name) != en {
5819 return None;
5820 }
5821 let init = *lang.children(*decl).first()?;
5822 equals_lit(lang, init, &x)?
5823 }
5824 NodeData::Call => equals_lit(lang, guard, &x)?,
5825 _ => return None,
5826 };
5827 guard_nodes.push(guard);
5828 if let Some((_, decl)) = &_e_decl {
5829 guard_nodes.push(*decl);
5830 }
5831 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5833 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5834 NodeData::Break { label: None });
5835 if !then_ok {
5836 return None;
5837 }
5838 let els = twr_stmts(lang, ic[2]);
5840 let (k, inner_switch, assign_node) = match els.len() {
5841 2 => {
5842 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5843 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5844 return None;
5845 };
5846 let a_ch = lang.children(assign_expr);
5847 let vn = match lang.data(a_ch[0]) {
5848 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5849 _ => return None,
5850 };
5851 let k = match lang.literal(a_ch[1])? {
5852 LitRef::Int(v) | LitRef::Long(v) => v,
5853 _ => return None,
5854 };
5855 if lang.kind(els[1]) != NodeKind::Switch {
5856 return None;
5857 }
5858 let sel = *lang.children(els[1]).first()?;
5860 if lang.kind(sel) != NodeKind::VarRef
5861 || lang.var_name(sel) != Some(vn.as_str())
5862 {
5863 return None;
5864 }
5865 match &v_name {
5866 Some(v) if *v != vn => return None,
5867 _ => v_name = Some(vn),
5868 }
5869 (k, els[1], Some(els[0]))
5870 }
5871 1 => {
5872 if lang.kind(els[0]) != NodeKind::Switch {
5873 return None;
5874 }
5875 let sel = *lang.children(els[0]).first()?;
5877 let k = match lang.literal(sel)? {
5878 LitRef::Int(v) | LitRef::Long(v) => v,
5879 _ => return None,
5880 };
5881 (k, els[0], None)
5882 }
5883 _ => return None,
5884 };
5885 if let Some(a) = assign_node {
5886 v_nodes.push(a);
5887 }
5888 v_nodes.push(*lang.children(inner_switch).first()?);
5889 let hlit: i32 = lit
5891 .chars()
5892 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
5893 if hlit != h {
5894 return None;
5895 }
5896 let _ = assign_node;
5897 infos.push(CaseInfo {
5898 lit,
5899 k,
5900 inner_switch,
5901 });
5902 }
5903 if infos.is_empty() {
5904 return None;
5905 }
5906 if lang.kind(parent) != NodeKind::Block {
5908 return None;
5909 }
5910 let pch = lang.children(parent).to_vec();
5911 let mut k = idx + 1;
5912 if k < pch.len() {
5914 let texpr = unwrap_expr_stmt(lang, pch[k]);
5915 if let NodeData::Assign { op: None } = lang.data(texpr) {
5916 let a_ch = lang.children(texpr);
5917 if a_ch.len() == 2
5918 && lang.kind(a_ch[0]) == NodeKind::VarRef
5919 && lang.var_name(a_ch[0]) == v_name.as_deref()
5920 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
5921 {
5922 v_nodes.push(pch[k]);
5923 k += 1;
5924 }
5925 }
5926 }
5927 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
5928 return None;
5929 }
5930 let trailing = pch[k];
5931 let tsel = *lang.children(trailing).first()?;
5933 match lang.data(tsel) {
5934 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
5935 v_nodes.push(tsel);
5936 }
5937 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
5938 _ => {
5939 lang.literal(tsel)?;
5940 }
5941 }
5942 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
5945 for &c in &lang.children(sw)[1..] {
5946 subtree_sig(lang, c, out);
5947 }
5948 }
5949 let mut sig0 = String::new();
5950 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
5951 for info in &infos[1..] {
5952 let mut sig = String::new();
5953 switch_cases_sig(lang, info.inner_switch, &mut sig);
5954 if sig != sig0 {
5955 return None;
5956 }
5957 }
5958 {
5959 let mut sig = String::new();
5960 switch_cases_sig(lang, trailing, &mut sig);
5961 if sig != sig0 {
5962 return None;
5963 }
5964 }
5965 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
5967 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
5968 std::collections::HashMap::new();
5969 let mut default_body: Option<Vec<JavaId>> = None;
5970 for (lab, stmts) in &bodies {
5971 match lab {
5972 Some(v) => {
5973 body_map.insert(*v, stmts.clone());
5974 }
5975 None => default_body = Some(stmts.clone()),
5976 }
5977 }
5978 let Some(default_body) = default_body else {
5979 return None;
5980 };
5981 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
5983 if !ssw_body_terminal(lang, stmts) {
5984 return None;
5985 }
5986 }
5987 let root = ctx_root;
5989 if let Some(vn) = &v_name {
5990 let mut allowed: Vec<JavaId> = v_nodes.clone();
5991 allowed.push(trailing);
5992 if idx >= 1 {
5993 let cand = pch[idx - 1];
5994 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
5995 if name == vn {
5996 allowed.push(cand);
5997 if idx != 1 {
5999 allowed.pop();
6000 }
6001 }
6002 }
6003 }
6004 if refs_escape(lang, root, vn, &allowed) {
6005 return None;
6006 }
6007 }
6008 let mut new_cases: Vec<JavaId> = Vec::new();
6010 for info in &infos {
6011 let Some(body) = body_map.get(&info.k) else {
6012 return None;
6013 };
6014 let lab = lang.lit(Lit::Str(info.lit.clone()));
6015 let case = lang.case_(vec![lab], false, false, body.clone());
6016 new_cases.push(case);
6017 }
6018 let def_case = lang.case_(Vec::new(), true, false, default_body);
6019 new_cases.push(def_case);
6020 let sel = lang.var(&x);
6021 let new_switch = lang.switch_(sel, new_cases);
6022 let remove = k - idx + 1;
6024 Some(Edit::Splice {
6025 node: parent,
6026 index: idx,
6027 remove,
6028 insert: vec![new_switch],
6029 })
6030 }
6031}
6032
6033fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6035 if lang.kind(e) != NodeKind::Call {
6036 return None;
6037 }
6038 let cc = lang.children(e);
6039 if cc.len() != 2 {
6040 return None;
6041 }
6042 match lang.data(cc[0]) {
6043 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6044 let recv = *lang.children(cc[0]).first()?;
6045 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6046 return None;
6047 }
6048 match lang.literal(cc[1])? {
6049 LitRef::Str(s) => Some(s.to_string()),
6050 _ => None,
6051 }
6052 }
6053 _ => None,
6054 }
6055}
6056
6057fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6058 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6059}
6060
6061pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6068 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6069 rules.push(Box::new(CastSimplify));
6070 rules.push(Box::new(SelfCompare));
6071 rules.push(Box::new(StringBuilderFold));
6072 rules.push(Box::new(BoxUnboxChain));
6073 rules.push(Box::new(IteratorToForEach));
6074 rules.push(Box::new(NewStringFold));
6075 rules.push(Box::new(NewStringCharArrayFold));
6076 rules.push(Box::new(EmptyFinallyStrip));
6077 rules.push(Box::new(TryUnwrapNoCatch));
6078 rules.push(Box::new(TryUnwrapRethrow));
6079 rules.push(Box::new(StaticExec));
6080 rules.push(Box::new(LoopHeadBreak));
6081 rules.push(Box::new(WhileIteratorToForEach));
6082 rules.push(Box::new(ConcatValueOfDrop));
6083 rules.push(Box::new(StringBuilderStatements));
6084 rules.push(Box::new(TrailingContinueJava));
6085 rules.push(Box::new(XorNoise));
6086 rules.push(Box::new(StrLenFold));
6087 rules.push(Box::new(LiteralEval));
6088 rules.push(Box::new(Base64NewStringFold));
6089 rules.push(Box::new(UrlDecodeFold));
6090 rules.push(Box::new(CffRecover));
6091 rules.push(Box::new(StaticArrayIndexFold));
6092 rules.push(Box::new(TrailingAssignReturn));
6093 rules.push(Box::new(DeadPureCall));
6094 rules.push(Box::new(CtorDelegationHoist));
6095 rules.push(Box::new(TrailingIfContinue));
6096 rules.push(Box::new(ConstFieldPropagate));
6097 rules.push(Box::new(NoopPrivateCall));
6098 rules.push(Box::new(ConstMethodInline));
6099 rules.push(Box::new(TwrRecover));
6100 rules.push(Box::new(StringSwitchRecover));
6101 rules
6102}
6103
6104pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6106 let mut rules = default_java_rules();
6107 let mut engine_all = cure_engine::rules::all_rules();
6108 rules.append(&mut engine_all);
6109 rules
6110}
6111
6112pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6114 let rules = default_java_rules();
6115 cure_engine::simplify(ast, root, &rules, cfg)
6116}
6117
6118pub fn simplify_unit_with_facts(
6123 ast: &mut JavaAst,
6124 unit: &mut CompilationUnit,
6125 cfg: &Config,
6126 facts: &cure_java_ast::JavaFacts,
6127) -> Report {
6128 ast.set_facts(facts.clone());
6129 let r = simplify_unit(ast, unit, cfg);
6130 ast.clear_facts();
6131 r
6132}
6133
6134pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6135 let mut total = Report::default();
6136 for _cycle in 0..6 {
6141 let mut round = Report::default();
6142 collect_unit_consts(ast, unit);
6143 if cfg.remove_dead_methods {
6146 collect_noop_private_methods(ast, unit);
6147 } else {
6148 ast.noop_private_methods.clear();
6149 }
6150 for ty in &mut unit.types {
6151 simplify_type(ast, ty, cfg, &mut round);
6152 }
6153 if cfg.remove_dead_methods {
6154 round.edits += remove_dead_private_fields(ast, unit);
6155 round.edits += remove_dead_private_methods(ast, unit);
6156 round.edits += remove_unused_imports(ast, unit);
6157 }
6158 total.edits += round.edits;
6159 total.iterations += round.iterations;
6160 total.discarded += round.discarded;
6161 for (k, v) in round.by_rule {
6162 *total.by_rule.entry(k).or_insert(0) += v;
6163 }
6164 if round.edits == 0 {
6169 break;
6170 }
6171 }
6172 total
6173}
6174
6175fn collect_anon_and_string_names(
6182 ast: &JavaAst,
6183 unit: &CompilationUnit,
6184) -> std::collections::HashSet<String> {
6185 let mut out: std::collections::HashSet<String> = Default::default();
6186 let mut bodies: Vec<JavaId> = Vec::new();
6187 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6188 for root in bodies {
6189 let mut stack = vec![root];
6190 while let Some(n) = stack.pop() {
6191 match ast.data(n) {
6192 NodeData::New { anon_raw: Some(raw), .. } => {
6193 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6194 if !id.is_empty() {
6195 out.insert(id.to_string());
6196 }
6197 }
6198 }
6199 NodeData::Literal(Lit::Str(text)) => {
6200 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6201 if !id.is_empty() {
6202 out.insert(id.to_string());
6203 }
6204 }
6205 }
6206 _ => {}
6207 }
6208 for &c in ast.children(n) {
6209 stack.push(c);
6210 }
6211 }
6212 }
6213 out
6214}
6215
6216fn collect_all_blind_refs(
6245 ast: &JavaAst,
6246 unit: &CompilationUnit,
6247) -> std::collections::HashSet<String> {
6248 let mut out: std::collections::HashSet<String> = Default::default();
6249 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6250 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6251 if !id.is_empty() {
6252 out.insert(id.to_string());
6253 }
6254 }
6255 };
6256 let mut bodies: Vec<JavaId> = Vec::new();
6258 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6259 for root in bodies {
6260 let mut stack = vec![root];
6261 while let Some(n) = stack.pop() {
6262 match ast.data(n) {
6263 NodeData::Raw { text } => split_into(text, &mut out),
6264 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6265 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6266 NodeData::ForEach { ty, .. } => {
6268 collect_ty_idents(ty, &mut out);
6269 }
6270 NodeData::NewArray { ty, .. } => {
6272 collect_ty_idents(ty, &mut out);
6273 }
6274 NodeData::Lambda { params_raw } => {
6278 split_into(params_raw, &mut out);
6279 }
6280 _ => {}
6281 }
6282 for &c in ast.children(n) {
6283 stack.push(c);
6284 }
6285 }
6286 }
6287 for r in &unit.raws {
6291 split_into(r, &mut out);
6292 }
6293 for_each_type(unit, &mut |ty: &TypeDecl| {
6296 collect_mods_idents_all(&ty.mods, &mut out);
6297 for e in &ty.enum_constants {
6298 split_into(e, &mut out);
6299 }
6300 split_into(&ty.ty_params, &mut out);
6301 split_into(&ty.header, &mut out);
6302 for m in &ty.members {
6303 match m {
6304 Member::Method { mods, name, ty_params, params, .. }
6305 | Member::Constructor { mods, name, ty_params, params, .. } => {
6306 collect_mods_idents_all(mods, &mut out);
6307 split_into(ty_params, &mut out);
6308 for p in params {
6309 collect_mods_idents_all(&p.mods, &mut out);
6310 collect_ty_idents(&p.ty, &mut out);
6311 }
6312 if has_bare_methodsource(mods) {
6317 out.insert(name.clone());
6318 }
6319 }
6320 Member::Field { mods, declarators, .. } => {
6321 collect_mods_idents_all(mods, &mut out);
6322 let _ = declarators;
6324 }
6325 Member::Type(t) => {
6326 let _ = t; }
6328 Member::Raw(text) => {
6335 split_into(text, &mut out);
6336 }
6337 _ => {}
6338 }
6339 }
6340 });
6341 out
6342}
6343
6344fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6348 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6349 if !id.is_empty() {
6350 out.insert(id.to_string());
6351 }
6352 }
6353}
6354
6355fn has_bare_methodsource(mods: &str) -> bool {
6362 let hay = mods.as_bytes();
6363 const NEEDLE: &[u8] = b"MethodSource";
6364 let mut i = 0usize;
6365 while let Some(p) = mods[i..].find("MethodSource") {
6366 let at = i + p;
6367 let end = at + NEEDLE.len();
6368 let prev_ok = at == 0
6371 || !(hay[at - 1].is_ascii_alphanumeric()
6372 || hay[at - 1] == b'_'
6373 || hay[at - 1] == b'$');
6374 let next_ident =
6375 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6376 if prev_ok && !next_ident {
6377 let mut j = end;
6378 while j < hay.len() && hay[j].is_ascii_whitespace() {
6379 j += 1;
6380 }
6381 if j >= hay.len() || hay[j] != b'(' {
6382 return true;
6383 }
6384 }
6385 i = end;
6386 }
6387 false
6388}
6389
6390const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6394 "writeObject",
6395 "readObject",
6396 "readObjectNoData",
6397 "writeReplace",
6398 "readResolve",
6399 "validateObject",
6400];
6401
6402fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6403 use std::collections::HashSet;
6404 let mut removed = 0usize;
6405 let anon_names = collect_anon_and_string_names(ast, unit);
6407 let blind = collect_all_blind_refs(ast, unit);
6412 loop {
6413 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6420 let mut veto: HashSet<String> = HashSet::new();
6421 let mut roots: Vec<JavaId> = Vec::new();
6422 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6423 for body in roots {
6424 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6425 }
6426 for_each_type(unit, &mut |ty| {
6427 for m in &ty.members {
6428 if let Member::Method { name, params, .. } = m {
6429 if params.iter().any(|p| p.varargs) {
6430 veto.insert(name.clone());
6431 }
6432 }
6433 }
6434 });
6435 let mut changed = false;
6437 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6438 let before = ty.members.len();
6439 ty.members.retain(|m| {
6440 if let Member::Method { mods, name, params, .. } = m {
6441 let is_private = mods.split_whitespace().any(|w| w == "private");
6442 let annotated = mods.contains('@');
6443 let keep = !is_private
6444 || annotated
6445 || params.iter().any(|p| p.varargs)
6447 || veto.contains(name)
6448 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6452 || anon_names.contains(name)
6453 || blind.contains(name)
6454 || referenced.contains(&(name.clone(), params.len()));
6455 keep
6456 } else {
6457 true
6458 }
6459 });
6460 if ty.members.len() != before {
6461 removed += before - ty.members.len();
6462 changed = true;
6463 }
6464 });
6465 if !changed {
6466 return removed;
6467 }
6468 }
6469}
6470
6471fn strip_ta_prefix(name: &str) -> &str {
6476 if !name.starts_with('<') {
6477 return name;
6478 }
6479 let b = name.as_bytes();
6480 let mut depth = 0i32;
6481 for (i, &c) in b.iter().enumerate() {
6482 match c {
6483 b'<' => depth += 1,
6484 b'>' => {
6485 depth -= 1;
6486 if depth == 0 {
6487 return &name[i + 1..];
6488 }
6489 }
6490 _ => {}
6491 }
6492 }
6493 name }
6495
6496fn collect_call_arity_refs(
6498 ast: &JavaAst,
6499 root: JavaId,
6500 out: &mut std::collections::HashSet<(String, usize)>,
6501 veto: &mut std::collections::HashSet<String>,
6502) {
6503 let mut stack = vec![root];
6504 while let Some(id) = stack.pop() {
6505 match ast.data(id) {
6506 NodeData::Call => {
6507 let ch = ast.children(id).to_vec();
6508 if let Some(&callee) = ch.first() {
6509 let name = match ast.data(callee) {
6510 NodeData::Member { name } | NodeData::VarRef { name } => {
6511 ast.sn(*name).to_string()
6512 }
6513 _ => String::new(),
6514 };
6515 if !name.is_empty() {
6516 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6517 }
6518 }
6519 }
6520 NodeData::MethodRef { name } => {
6521 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6522 veto.insert(strip_ta_prefix(short).to_string());
6523 }
6524 }
6525 _ => {}
6526 }
6527 for &c in ast.children(id) {
6528 stack.push(c);
6529 }
6530 }
6531}
6532
6533fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6535 for ty in &unit.types {
6536 for_each_type_inner(ty, f);
6537 }
6538}
6539fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6540 f(ty);
6541 for m in &ty.members {
6542 if let Member::Type(t) = m {
6543 for_each_type_inner(t, f);
6544 }
6545 }
6546}
6547
6548fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6550 for ty in &mut unit.types {
6551 for_each_type_mut_inner(ty, f);
6552 }
6553}
6554fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6555 f(ty);
6556 for m in &mut ty.members {
6557 if let Member::Type(t) = m {
6558 for_each_type_mut_inner(t, f);
6559 }
6560 }
6561}
6562
6563fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6565 for m in &ty.members {
6566 match m {
6567 Member::Method { body: Some(b), .. }
6568 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6569 Member::Initializer { body, .. } => out.push(*body),
6570 Member::Field { declarators, .. } => {
6571 for d in declarators {
6572 if let Some(init) = d.init {
6573 out.push(init);
6574 }
6575 }
6576 }
6577 Member::Type(t) => collect_member_roots(t, out),
6578 _ => {}
6579 }
6580 }
6581}
6582
6583fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6598 use std::collections::{HashMap, HashSet};
6599 ast.noop_private_methods.clear();
6600 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6601 let mut veto: HashSet<String> = HashSet::new();
6602 for_each_type(unit, &mut |ty: &TypeDecl| {
6603 for m in &ty.members {
6604 if let Member::Method { mods, name, params, body, throws, .. } = m {
6605 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6606 veto.insert(name.clone());
6607 continue;
6608 }
6609 let is_private = mods.split_whitespace().any(|w| w == "private");
6610 let empty = match body {
6611 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6612 None => false,
6613 };
6614 let no_throws = throws.is_empty();
6618 let cand = NoopCandidate {
6619 params: params.iter().map(|p| p.ty.clone()).collect(),
6620 noop: is_private && empty && no_throws,
6621 };
6622 by_name
6623 .entry(name.clone())
6624 .or_default()
6625 .entry(params.len())
6626 .or_default()
6627 .push(cand);
6628 }
6629 }
6630 });
6631 let blind = collect_all_blind_refs(ast, unit);
6634 let mut roots: Vec<JavaId> = Vec::new();
6637 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6638 for root in roots {
6639 let mut stack = vec![root];
6640 while let Some(n) = stack.pop() {
6641 if let NodeData::MethodRef { name } = ast.data(n) {
6642 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6643 veto.insert(strip_ta_prefix(short).to_string());
6644 }
6645 }
6646 for &c in ast.children(n) {
6647 stack.push(c);
6648 }
6649 }
6650 }
6651 for (name, arities) in by_name {
6652 if veto.contains(&name) || blind.contains(&name) {
6653 continue;
6654 }
6655 ast.noop_private_methods.insert(name, arities);
6656 }
6657}
6658
6659fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6664 use std::collections::HashSet;
6665 let mut idents: HashSet<String> = HashSet::new();
6666 let mut roots: Vec<JavaId> = Vec::new();
6668 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6669 for root in roots {
6670 let mut stack = vec![root];
6671 while let Some(n) = stack.pop() {
6672 match ast.data(n) {
6673 NodeData::VarDecl { name, ty, .. } => {
6674 idents.insert(name.clone());
6675 collect_ty_idents(ty, &mut idents);
6676 }
6677 NodeData::VarRef { name } => {
6678 let n = ast.sn(*name);
6682 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6683 if !id.is_empty() {
6684 idents.insert(id.to_string());
6685 }
6686 }
6687 }
6688 NodeData::Member { name } => {
6689 idents.insert(ast.sn(*name).to_string());
6690 }
6691 NodeData::MethodRef { name } => {
6692 let n = ast.sn(*name);
6693 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6694 if !id.is_empty() {
6695 idents.insert(id.to_string());
6696 }
6697 }
6698 }
6699 NodeData::Label { name } => {
6700 idents.insert(name.clone());
6701 }
6702 NodeData::New { ty, anon_raw, .. } => {
6703 collect_ty_idents(ty, &mut idents);
6704 if let Some(raw) = anon_raw {
6707 for id in
6708 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6709 {
6710 if !id.is_empty() {
6711 idents.insert(id.to_string());
6712 }
6713 }
6714 }
6715 }
6716 NodeData::Cast { ty } => {
6717 collect_ty_idents(ty, &mut idents);
6718 }
6719 NodeData::InstanceOf { ty, .. } => {
6720 collect_ty_idents(ty, &mut idents);
6721 }
6722 NodeData::Catch { ty_raw, .. } => {
6723 for id in
6725 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6726 {
6727 if !id.is_empty() {
6728 idents.insert(id.to_string());
6729 }
6730 }
6731 }
6732 NodeData::Raw { text } => {
6733 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6734 if !id.is_empty() {
6735 idents.insert(id.to_string());
6736 }
6737 }
6738 }
6739 _ => {}
6740 }
6741 for &c in ast.children(n) {
6742 stack.push(c);
6743 }
6744 }
6745 }
6746 for name in collect_all_blind_refs(ast, unit) {
6750 idents.insert(name);
6751 }
6752 for r in &unit.raws {
6755 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6756 if !id.is_empty() {
6757 idents.insert(id.to_string());
6758 }
6759 }
6760 }
6761 for_each_type(unit, &mut |ty| {
6764 collect_mods_idents(&ty.mods, &mut idents);
6765 idents.insert(ty.name.clone());
6766 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6767 if !id.is_empty() {
6768 idents.insert(id.to_string());
6769 }
6770 }
6771 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6775 if !id.is_empty() {
6776 idents.insert(id.to_string());
6777 }
6778 }
6779 for e in ty.extends.iter().chain(ty.implements.iter()) {
6780 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6781 if !id.is_empty() {
6782 idents.insert(id.to_string());
6783 }
6784 }
6785 }
6786 for m in &ty.members {
6787 match m {
6788 Member::Field { mods, ty, declarators } => {
6789 collect_mods_idents(mods, &mut idents);
6790 collect_ty_idents(ty, &mut idents);
6791 for d in declarators {
6792 idents.insert(d.name.clone());
6793 }
6794 }
6795 Member::Method { mods, ret, name, params, throws, .. } => {
6796 collect_mods_idents(mods, &mut idents);
6797 collect_ty_idents(ret, &mut idents);
6798 idents.insert(name.clone());
6799 for p in params {
6800 collect_mods_idents(&p.mods, &mut idents);
6801 collect_ty_idents(&p.ty, &mut idents);
6802 idents.insert(p.name.clone());
6803 }
6804 for t in throws {
6805 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6806 if !id.is_empty() {
6807 idents.insert(id.to_string());
6808 }
6809 }
6810 }
6811 }
6812 Member::Constructor { mods, name, params, throws, .. } => {
6813 collect_mods_idents(mods, &mut idents);
6814 idents.insert(name.clone());
6815 for p in params {
6816 collect_mods_idents(&p.mods, &mut idents);
6817 collect_ty_idents(&p.ty, &mut idents);
6818 idents.insert(p.name.clone());
6819 }
6820 for t in throws {
6821 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6822 if !id.is_empty() {
6823 idents.insert(id.to_string());
6824 }
6825 }
6826 }
6827 }
6828 Member::Type(t) => {
6829 let _ = t;
6831 }
6832 _ => {}
6833 }
6834 }
6835 });
6836 let before = unit.imports.len();
6838 unit.imports.retain(|imp| {
6839 if imp.contains('*') {
6840 return true; }
6842 let path = imp.trim_start_matches("static ").trim();
6844 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6845 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6846 return true; }
6848 idents.contains(simple)
6849 });
6850 before - unit.imports.len()
6851}
6852
6853fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6854 match t {
6855 JType::Ref(r) => {
6856 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6857 if !id.is_empty() {
6858 out.insert(id.to_string());
6859 }
6860 }
6861 }
6862 JType::Array(inner) => collect_ty_idents(inner, out),
6863 _ => {}
6864 }
6865}
6866
6867fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6868 if mods.contains('@') {
6870 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6871 if !id.is_empty() {
6872 out.insert(id.to_string());
6873 }
6874 }
6875 }
6876}
6877
6878fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6884 use std::collections::HashSet;
6885 let mut removed = 0usize;
6886 let anon_names = collect_anon_and_string_names(ast, unit);
6888 let blind = collect_all_blind_refs(ast, unit);
6891 let mut referenced: HashSet<String> = HashSet::new();
6893 let mut bodies: Vec<JavaId> = Vec::new();
6894 for_each_type(unit, &mut |ty: &TypeDecl| {
6895 for m in &ty.members {
6896 match m {
6897 Member::Method { body: Some(b), .. }
6898 | Member::Constructor { body: Some(b), .. }
6899 | Member::Initializer { body: b, .. } => bodies.push(*b),
6900 Member::Field { declarators, .. } => {
6901 for d in declarators {
6902 if let Some(init) = d.init {
6903 bodies.push(init);
6904 }
6905 }
6906 }
6907 _ => {}
6908 }
6909 }
6910 });
6911 for root in bodies {
6912 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
6914 while let Some((n, is_callee)) = stack.pop() {
6915 match ast.kind(n) {
6916 NodeKind::VarRef if !is_callee => {
6917 if let Some(nm) = ast.var_name(n) {
6918 referenced.insert(nm.to_string());
6919 }
6920 }
6921 NodeKind::Member => {
6922 if let NodeData::Member { name: sym } = ast.data(n) {
6923 referenced.insert(ast.sn(*sym).to_string());
6924 }
6925 }
6926 _ => {}
6927 }
6928 if ast.kind(n) == NodeKind::Call {
6929 let ch = ast.children(n).to_vec();
6930 if let Some(&first) = ch.first() {
6931 stack.push((first, true));
6932 }
6933 for &c in &ch[1..] {
6934 stack.push((c, false));
6935 }
6936 continue;
6937 }
6938 for &c in ast.children(n) {
6939 stack.push((c, false));
6940 }
6941 }
6942 }
6943 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6949 for m in ty.members.iter_mut() {
6950 if let Member::Field { mods, declarators, .. } = m {
6951 let is_private = mods.split_whitespace().any(|w| w == "private");
6952 let annotated = mods.contains('@');
6953 if !is_private || annotated {
6954 continue;
6955 }
6956 let dead: Vec<bool> = declarators
6957 .iter()
6958 .map(|d| {
6959 let reflective =
6960 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
6961 let init_ok =
6962 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
6963 !reflective
6964 && init_ok
6965 && !referenced.contains(&d.name)
6966 && !anon_names.contains(&d.name)
6967 && !blind.contains(&d.name)
6968 })
6969 .collect();
6970 let before = declarators.len();
6971 let mut idx = 0usize;
6972 declarators.retain(|_| {
6973 let keep = !dead[idx];
6974 idx += 1;
6975 keep
6976 });
6977 removed += before - declarators.len();
6978 }
6979 }
6980 let before = ty.members.len();
6981 ty.members
6982 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
6983 removed += before - ty.members.len();
6984 });
6985 removed
6986}
6987
6988fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
6989 for m in &mut ty.members {
6990 simplify_member(ast, m, cfg, total);
6991 }
6992}
6993
6994fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
6995 let params: Vec<(String, JType)> = match m {
6997 Member::Method { params, .. } | Member::Constructor { params, .. } => params
6998 .iter()
6999 .map(|p| (p.name.clone(), p.ty.clone()))
7000 .collect(),
7001 _ => Vec::new(),
7002 };
7003 ast.set_param_scope(¶ms);
7004 match m {
7005 Member::Method { body: Some(b), .. }
7006 | Member::Constructor { body: Some(b), .. } => {
7007 let r = simplify(ast, *b, cfg);
7008 merge_report(total, r);
7009 }
7010 Member::Initializer { body, .. } => {
7011 let r = simplify(ast, *body, cfg);
7012 merge_report(total, r);
7013 }
7014 Member::Field { declarators, .. } => {
7015 for d in declarators.iter_mut() {
7016 if let Some(init) = d.init {
7017 let r = simplify(ast, init, cfg);
7018 merge_report(total, r);
7019 let rules = default_java_rules();
7023 let mut cur: JavaId = init;
7024 for _ in 0..8 {
7025 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7026 Some((new_root, name)) => {
7027 d.init = Some(new_root);
7028 cur = new_root;
7029 *total.by_rule.entry(name).or_insert(0) += 1;
7030 total.edits += 1;
7031 let r = simplify(ast, new_root, cfg);
7033 merge_report(total, r);
7034 }
7035 None => break,
7036 }
7037 }
7038 }
7039 }
7040 }
7041 Member::Type(t) => simplify_type(ast, t, cfg, total),
7042 Member::Raw(_) => {}
7043 _ => {}
7044 }
7045}
7046
7047fn merge_report(total: &mut Report, r: Report) {
7048 total.edits += r.edits;
7049 total.iterations += r.iterations;
7050 for (k, v) in r.by_rule {
7051 *total.by_rule.entry(k).or_insert(0) += v;
7052 }
7053}
7054
7055pub fn default_config() -> Config {
7057 Config::default()
7058}