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 TrailingElseFlatten;
4739
4740impl Rule<JavaAst> for TrailingElseFlatten {
4741 fn name(&self) -> &'static str {
4742 "trailing_else_flatten"
4743 }
4744 fn kinds(&self) -> &'static [NodeKind] {
4745 &[NodeKind::If]
4746 }
4747 fn structural(&self) -> bool {
4748 true
4749 }
4750 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4751 let (parent, idx) = (ctx.parent(id), ctx.index(id));
4752 let lang = ctx.lang;
4753 if lang.kind(id) != NodeKind::If {
4754 return None;
4755 }
4756 let ch = lang.children(id).to_vec();
4757 if ch.len() != 3 {
4758 return None;
4759 }
4760 let (_cond, then, els) = (ch[0], ch[1], ch[2]);
4761 if lang.kind(then) != NodeKind::Block {
4763 return None;
4764 }
4765 let then_stmts = lang.children(then).to_vec();
4766 let then_terminal = then_stmts
4767 .last()
4768 .is_some_and(|&s| matches!(lang.kind(s), NodeKind::Return | NodeKind::Throw));
4769 if !then_terminal {
4770 return None;
4771 }
4772 if lang.kind(els) == NodeKind::Block {
4777 let else_stmts = lang.children(els).to_vec();
4778 if else_stmts.len() == 1 && lang.kind(else_stmts[0]) == NodeKind::Return {
4779 if lang.children(else_stmts[0]).is_empty() {
4781 return Some(Edit::Splice {
4783 node: id,
4784 index: 0,
4785 remove: 3,
4786 insert: vec![ch[0], then],
4787 });
4788 }
4789 }
4790 }
4791 let parent = parent?;
4793 if lang.kind(parent) != NodeKind::Block {
4794 return None;
4795 }
4796 let idx = idx?;
4797 let new_if = lang.clone_node(id, vec![ch[0], then]);
4799 let mut insert: Vec<JavaId> = vec![new_if];
4800 if lang.kind(els) == NodeKind::Block {
4802 insert.extend(lang.children(els).to_vec());
4803 } else {
4804 insert.push(els);
4805 }
4806 Some(Edit::Splice {
4807 node: parent,
4808 index: idx,
4809 remove: 1,
4810 insert,
4811 })
4812 }
4813}
4814
4815pub struct TrailingIfContinue;
4816
4817impl Rule<JavaAst> for TrailingIfContinue {
4818 fn name(&self) -> &'static str {
4819 "trailing_if_continue"
4820 }
4821 fn kinds(&self) -> &'static [NodeKind] {
4822 &[NodeKind::If]
4823 }
4824 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4825 let parent = ctx.parent(id);
4826 let grandparent = parent.and_then(|p| ctx.parent(p));
4827 let lang = ctx.lang;
4828 if lang.kind(id) != NodeKind::If {
4829 return None;
4830 }
4831 let ch = lang.children(id).to_vec();
4832 if ch.len() != 2 {
4834 return None;
4835 }
4836 let then = ch[1];
4837 if lang.kind(then) != NodeKind::Block {
4838 return None;
4839 }
4840 let tbody = lang.children(then).to_vec();
4841 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4842 return None;
4843 }
4844 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4846 return None;
4847 }
4848 let p = parent?;
4851 if lang.kind(p) != NodeKind::Block {
4852 return None;
4853 }
4854 let pch = lang.children(p).to_vec();
4855 if pch.last() != Some(&id) {
4856 return None;
4857 }
4858 let gp = grandparent?;
4860 match lang.kind(gp) {
4861 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4862 let gch = lang.children(gp).to_vec();
4863 if gch.last() == Some(&p) {
4864 Some(Edit::Delete { node: id })
4865 } else {
4866 None
4867 }
4868 }
4869 _ => None,
4870 }
4871 }
4872}
4873
4874pub struct TrailingAssignReturn;
4875
4876impl Rule<JavaAst> for TrailingAssignReturn {
4877 fn name(&self) -> &'static str {
4878 "trailing_assign_return"
4879 }
4880 fn kinds(&self) -> &'static [NodeKind] {
4881 &[NodeKind::Assign]
4882 }
4883 fn structural(&self) -> bool {
4886 true
4887 }
4888 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4889 let parent = ctx.parent(id);
4890 let grandparent = parent.and_then(|p| ctx.parent(p));
4891 let lang = ctx.lang;
4892 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4894 return None;
4895 }
4896 let ch = lang.children(id).to_vec();
4897 if ch.len() != 2 {
4898 return None;
4899 }
4900 let (target, value) = (ch[0], ch[1]);
4901 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4903 return None;
4904 }
4905 let name = lang.var_name(target)?.to_string();
4906 let parent = parent?;
4908 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4909 parent
4910 } else if lang.kind(parent) == NodeKind::Block {
4911 id
4912 } else {
4913 return None;
4914 };
4915 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4916 grandparent?
4917 } else {
4918 parent
4919 };
4920 if lang.kind(block) != NodeKind::Block {
4921 return None;
4922 }
4923 let stmts = lang.children(block).to_vec();
4924 let idx = stmts.iter().position(|&x| x == stmt)?;
4925 let next = *stmts.get(idx + 1)?;
4927 let (flow_val, is_return) = match lang.kind(next) {
4928 NodeKind::Return => (lang.children(next).first().copied(), true),
4929 NodeKind::Throw => (lang.children(next).first().copied(), false),
4930 _ => return None,
4931 };
4932 let flow_val = flow_val?;
4933 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4934 return None;
4935 }
4936 for &s in &stmts[idx + 2..] {
4939 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4940 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4941 }) {
4942 return None;
4943 }
4944 }
4945 let new_flow = if is_return {
4947 lang.ret(Some(value))
4948 } else {
4949 lang.throw(value)
4950 };
4951 Some(Edit::Multi(vec![
4952 Edit::Replace { target: next, with: new_flow },
4953 Edit::Delete { node: stmt },
4954 ]))
4955 }
4956}
4957
4958pub struct ConstFieldPropagate;
4959
4960impl Rule<JavaAst> for ConstFieldPropagate {
4961 fn name(&self) -> &'static str {
4962 "const_field_propagate"
4963 }
4964 fn kinds(&self) -> &'static [NodeKind] {
4965 &[NodeKind::VarRef]
4966 }
4967 fn structural(&self) -> bool {
4970 true
4971 }
4972 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4973 let parent = ctx.parent(id);
4974 let _grandparent = parent.and_then(|p| ctx.parent(p));
4975 let lang = ctx.lang;
4976 if lang.kind(id) != NodeKind::VarRef {
4977 return None;
4978 }
4979 let name = lang.var_name(id)?.to_string();
4980 if !lang.const_scalars.contains(&name) {
4981 return None;
4982 }
4983 if let Some(p) = parent {
4984 let is_first = lang.children(p).first() == Some(&id);
4985 match lang.kind(p) {
4986 NodeKind::Assign if is_first => return None,
4988 NodeKind::Unary if is_first => {
4989 if let NodeData::Unary { op } = lang.data(p) {
4990 if op.is_incdec() {
4991 return None;
4992 }
4993 }
4994 }
4995 NodeKind::Call if is_first => return None,
4997 _ => {}
4998 }
4999 }
5000 let init = *lang.const_fields.get(&name)?;
5001 let lit = lang.copy_subtree(init);
5002 Some(Edit::Replace { target: id, with: lit })
5003 }
5004}
5005
5006pub struct NoopPrivateCall;
5007
5008impl Rule<JavaAst> for NoopPrivateCall {
5009 fn name(&self) -> &'static str {
5010 "noop_private_call"
5011 }
5012 fn kinds(&self) -> &'static [NodeKind] {
5013 &[NodeKind::ExprStmt]
5014 }
5015 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5016 let lang = ctx.lang;
5017 if lang.kind(id) != NodeKind::ExprStmt {
5018 return None;
5019 }
5020 let ch = lang.children(id).to_vec();
5021 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
5022 return None;
5023 }
5024 let call = ch[0];
5025 let cch = lang.children(call).to_vec();
5026 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
5027 return None; }
5029 let name = lang.var_name(cch[0])?.to_string();
5030 let arity = cch.len() - 1;
5031 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
5032 if cands.is_empty() {
5033 return None;
5034 }
5035 let arg_types: Vec<Option<JType>> =
5039 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
5040 let mut applicable_noop = false;
5041 let mut applicable_count = 0usize;
5042 for c in cands {
5043 let ok = c
5044 .params
5045 .iter()
5046 .zip(arg_types.iter())
5047 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
5048 if ok {
5049 applicable_count += 1;
5050 applicable_noop = c.noop;
5051 }
5052 }
5053 if applicable_count != 1 || !applicable_noop {
5054 return None;
5055 }
5056 for &arg in &cch[1..] {
5058 if lang.effect(arg) > Effect::MayRead {
5059 return None;
5060 }
5061 }
5062 Some(Edit::Delete { node: id })
5063 }
5064}
5065
5066fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
5070 match lang.kind(arg) {
5071 NodeKind::Literal => match lang.literal(arg)? {
5072 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
5073 LitRef::Char(_) => Some(JType::Char),
5074 LitRef::Bool(_) => Some(JType::Bool),
5075 LitRef::Int(_) => Some(JType::Int),
5076 LitRef::Long(_) => Some(JType::Long),
5077 LitRef::Float(_) => Some(JType::Float),
5078 LitRef::Double(_) => Some(JType::Double),
5079 LitRef::Null => None,
5080 },
5081 NodeKind::VarRef => {
5082 let nm = lang.var_name(arg)?;
5083 lang.var_type(arg)
5084 .cloned()
5085 .or_else(|| lang.field_types.get(nm).cloned())
5086 }
5087 _ => None,
5088 }
5089}
5090
5091fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5096 let Some(a) = at else { return true };
5097 match pt {
5098 JType::Ref(p) => {
5099 let pbase = p.split('<').next().unwrap_or(p).trim();
5100 pbase == "Object"
5101 || pbase == "java.lang.Object"
5102 || match a {
5103 JType::Ref(arend) => {
5104 let abase = arend.split('<').next().unwrap_or(arend).trim();
5105 abase == pbase
5106 }
5107 _ => false,
5108 }
5109 }
5110 JType::Array(_) => false, prim => match a {
5112 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5113 },
5114 }
5115}
5116
5117pub struct ConstMethodInline;
5127
5128impl Rule<JavaAst> for ConstMethodInline {
5129 fn name(&self) -> &'static str {
5130 "const_method_inline"
5131 }
5132 fn kinds(&self) -> &'static [NodeKind] {
5133 &[NodeKind::Call]
5134 }
5135 fn structural(&self) -> bool {
5136 true
5137 }
5138 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5139 let at_stmt_pos = ctx
5141 .parent(id)
5142 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5143 .unwrap_or(false);
5144 let lang = ctx.lang;
5145 if lang.kind(id) != NodeKind::Call {
5146 return None;
5147 }
5148 let ch = lang.children(id).to_vec();
5149 let (method_name, recv_class) = match lang.data(ch[0]) {
5153 NodeData::VarRef { name } => (*name, None),
5154 NodeData::Member { name } => {
5155 let recv = lang.children(ch[0])[0];
5156 match lang.var_name(recv) {
5157 Some(r) if r == "this" => (*name, None),
5158 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5159 (*name, Some(r.to_string()))
5160 }
5161 _ => return None,
5162 }
5163 }
5164 _ => return None,
5165 };
5166 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5167 let (params, body_expr, owner) = entry;
5168 if let Some(recv) = recv_class {
5175 if recv != owner {
5176 return None;
5177 }
5178 }
5179 {
5181 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5182 for nm in &names {
5183 if subtree_calls_self(lang, body_expr, nm) {
5184 return None;
5185 }
5186 }
5187 }
5188 let args = ch[1..].to_vec();
5189 if args.len() != params.len() || params.is_empty() {
5190 return None;
5191 }
5192 for &a in &args {
5193 lang.literal(a)?;
5194 }
5195 let map: std::collections::HashMap<String, JavaId> = params
5196 .iter()
5197 .zip(args.iter())
5198 .map(|(p, &a)| (p.clone(), a))
5199 .collect();
5200 let with = copy_subst(lang, body_expr, &map);
5201 if at_stmt_pos {
5207 match lang.kind(with) {
5208 NodeKind::Call | NodeKind::New => {}
5209 _ => return None,
5210 }
5211 }
5212 Some(Edit::Replace {
5213 target: id,
5214 with,
5215 })
5216 }
5217}
5218
5219fn copy_subst(
5221 lang: &mut JavaAst,
5222 node: JavaId,
5223 map: &std::collections::HashMap<String, JavaId>,
5224) -> JavaId {
5225 if let NodeData::VarRef { name } = lang.data(node) {
5226 if let Some(&repl) = map.get(lang.sn(*name)) {
5227 return lang.copy_subtree(repl);
5228 }
5229 }
5230 let children: Vec<JavaId> = lang.children(node).to_vec();
5231 let new_children: Vec<JavaId> = children
5232 .iter()
5233 .map(|&c| copy_subst(lang, c, map))
5234 .collect();
5235 lang.clone_node(node, new_children)
5236}
5237
5238
5239pub struct TwrRecover;
5272
5273fn twr_is_throwable(ty_raw: &str) -> bool {
5274 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5275}
5276
5277fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5279 if lang.kind(n) == NodeKind::ExprStmt {
5280 if let Some(&e) = lang.children(n).first() {
5281 return e;
5282 }
5283 }
5284 n
5285}
5286
5287fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5289 if lang.kind(n) == NodeKind::Block {
5290 lang.children(n).to_vec()
5291 } else {
5292 vec![n]
5293 }
5294}
5295
5296fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5298 if lang.kind(e) != NodeKind::Call {
5299 return false;
5300 }
5301 let ch = lang.children(e);
5302 if ch.len() != 1 {
5303 return false;
5304 }
5305 match lang.data(ch[0]) {
5306 NodeData::Member { name } if lang.sn(*name) == "close" => {
5307 let Some(&recv) = lang.children(ch[0]).first() else {
5308 return false;
5309 };
5310 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5311 }
5312 _ => false,
5313 }
5314}
5315
5316fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5317 if lang.kind(n) != NodeKind::ExprStmt {
5318 return false;
5319 }
5320 lang.children(n)
5321 .first()
5322 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5323}
5324
5325fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5327 if lang.kind(n) != NodeKind::ExprStmt {
5328 return false;
5329 }
5330 let Some(&e) = lang.children(n).first() else {
5331 return false;
5332 };
5333 if lang.kind(e) != NodeKind::Call {
5334 return false;
5335 }
5336 let ch = lang.children(e);
5337 if ch.len() != 2 {
5338 return false;
5339 }
5340 match lang.data(ch[0]) {
5341 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5342 let Some(&recv) = lang.children(ch[0]).first() else {
5343 return false;
5344 };
5345 lang.kind(recv) == NodeKind::VarRef
5346 && lang.var_name(recv) == Some(primary)
5347 && lang.kind(ch[1]) == NodeKind::VarRef
5348 && lang.var_name(ch[1]) == Some(sup)
5349 }
5350 _ => false,
5351 }
5352}
5353
5354fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5355 let mut stack = vec![root];
5356 while let Some(n) = stack.pop() {
5357 if n == target {
5358 return true;
5359 }
5360 stack.extend(lang.children(n).iter().copied());
5361 }
5362 false
5363}
5364
5365fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5367 let mut stack = vec![root];
5368 while let Some(n) = stack.pop() {
5369 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5370 let ok = allowed
5371 .iter()
5372 .any(|&a| a == n || node_contains(lang, a, n));
5373 if !ok {
5374 return true;
5375 }
5376 }
5377 if lang.kind(n) == NodeKind::VarDecl {
5378 if let Some(nm) = lang.var_name(n) {
5379 if nm == name && !allowed.contains(&n) {
5380 return true;
5382 }
5383 }
5384 }
5385 stack.extend(lang.children(n).iter().copied());
5386 }
5387 false
5388}
5389
5390fn twr_close_with_suppressed(
5394 lang: &JavaAst,
5395 try_node: JavaId,
5396 primary: &str,
5397) -> Option<(String, String)> {
5398 let ch = lang.children(try_node).to_vec();
5399 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5400 return None;
5401 }
5402 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5403 return None;
5404 };
5405 if !twr_is_throwable(ty_raw) {
5406 return None;
5407 }
5408 let sup = sup.clone();
5409 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5410 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5411 return None;
5412 }
5413 let close_stmts = twr_stmts(lang, ch[0]);
5414 if close_stmts.len() != 1 {
5415 return None;
5416 }
5417 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5418 return None;
5419 }
5420 let e = *lang.children(close_stmts[0]).first()?;
5421 let cc = lang.children(e);
5422 if cc.len() != 1 {
5423 return None;
5424 }
5425 let NodeData::Member { name } = lang.data(cc[0]) else {
5426 return None;
5427 };
5428 if lang.sn(*name) != "close" {
5429 return None;
5430 }
5431 let recv = *lang.children(cc[0]).first()?;
5432 if lang.kind(recv) != NodeKind::VarRef {
5433 return None;
5434 }
5435 Some((lang.var_name(recv)?.to_string(), sup))
5436}
5437
5438impl Rule<JavaAst> for TwrRecover {
5439 fn name(&self) -> &'static str {
5440 "twr_recover"
5441 }
5442 fn kinds(&self) -> &'static [NodeKind] {
5443 &[NodeKind::Try]
5444 }
5445 fn structural(&self) -> bool {
5446 true
5447 }
5448 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5449 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5451 let lang = ctx.lang;
5452 if lang.kind(id) != NodeKind::Try {
5453 return None;
5454 }
5455 let ch = lang.children(id).to_vec();
5456 let try_idx = ch
5458 .iter()
5459 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5460 if try_idx != 0 {
5461 return None;
5462 }
5463 let try_block = ch[0];
5464 if lang.kind(parent) != NodeKind::Block {
5465 return None;
5466 }
5467 let pch = lang.children(parent).to_vec();
5468
5469 if ch.len() == 2 {
5471 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5472 if twr_is_throwable(ty_raw) {
5473 let t = t.clone();
5474 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5475 if catch_stmts.len() == 2 {
5476 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5478 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5479 && lang
5480 .children(throw_stmt)
5481 .first()
5482 .is_some_and(|&x| {
5483 lang.kind(x) == NodeKind::VarRef
5484 && lang.var_name(x) == Some(t.as_str())
5485 });
5486 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5487 if let Some((r, sup)) =
5488 twr_close_with_suppressed(lang, inner_try, t.as_str())
5489 {
5490 if idx >= 1 && idx + 1 < pch.len() {
5491 let decl_r = pch[idx - 1];
5492 let trailing = pch[idx + 1];
5493 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5494 if name == &r {
5495 let (name, ty) = (name.clone(), ty.clone());
5496 let init =
5497 lang.children(decl_r).first().copied();
5498 if let Some(init) = init {
5499 let provably_new = lang.kind(init)
5501 == NodeKind::New;
5502 let close_ok =
5503 twr_is_close_stmt(lang, trailing, &r);
5504 if provably_new && close_ok {
5505 let root = walk_root;
5507 let r_ok = !refs_escape(
5508 lang,
5509 root,
5510 &r,
5511 &[
5512 decl_r,
5513 init,
5514 *lang
5515 .children(inner_try)
5516 .first()
5517 .unwrap_or(&inner_try),
5518 trailing,
5519 try_block,
5520 ],
5521 );
5522 let t_ok = !refs_escape(
5523 lang, root, &t,
5524 &[throw_stmt, ch[1]],
5525 );
5526 let sup_ok = !refs_escape(
5527 lang, root, &sup,
5528 &[ch[1]],
5529 );
5530 if r_ok && t_ok && sup_ok {
5531 let init_copy =
5532 lang.copy_subtree(init);
5533 let res = lang.var_decl(
5534 &name, ty,
5535 Some(init_copy),
5536 );
5537 let new_try = lang.try_(
5538 vec![res],
5539 try_block,
5540 Vec::new(),
5541 None,
5542 );
5543 return Some(Edit::Splice {
5544 node: parent,
5545 index: idx - 1,
5546 remove: 3,
5547 insert: vec![new_try],
5548 });
5549 }
5550 }
5551 }
5552 }
5553 }
5554 }
5555 }
5556 }
5557 }
5558 }
5559 }
5560 }
5561
5562 if ch.len() == 3 {
5564 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5565 return None;
5566 };
5567 if !twr_is_throwable(ty_raw) {
5568 return None;
5569 }
5570 let t = t.clone();
5571 if lang.kind(ch[2]) != NodeKind::Block {
5573 return None;
5574 }
5575 let fin_stmts = twr_stmts(lang, ch[2]);
5576 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5577 return None;
5578 }
5579 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5581 if catch_stmts.len() != 2 {
5582 return None;
5583 }
5584 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5585 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5586 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5587 return None;
5588 };
5589 let as_ch = lang.children(assign_expr);
5590 if as_ch.len() != 2 {
5591 return None;
5592 }
5593 let primary = match lang.data(as_ch[0]) {
5594 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5595 _ => return None,
5596 };
5597 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5598 && lang.var_name(as_ch[1]) == Some(t.as_str())
5599 && lang.kind(as_ch[0]) == NodeKind::VarRef
5600 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5601 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5602 && lang.children(throw_stmt)
5603 .first()
5604 .is_some_and(|&x| {
5605 lang.kind(x) == NodeKind::VarRef
5606 && lang.var_name(x) == Some(t.as_str())
5607 });
5608 if !assign_ok || !throw_ok {
5609 return None;
5610 }
5611 let if1 = fin_stmts[0];
5613 let i1 = lang.children(if1).to_vec();
5614 if i1.len() < 2 {
5615 return None;
5616 }
5617 let cond1 = i1[0];
5618 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5619 return None;
5620 };
5621 let c1 = lang.children(cond1);
5622 let r = match lang.data(c1[0]) {
5623 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5624 lang.sn(*name).to_string()
5625 }
5626 _ => {
5627 return None;
5628 }
5629 };
5630 let then1 = i1[1];
5631 let inner_stmts = twr_stmts(lang, then1);
5632 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5633 return None;
5634 }
5635 let if2 = inner_stmts[0];
5636 let i2 = lang.children(if2).to_vec();
5637 if i2.len() != 3 {
5638 return None;
5639 }
5640 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5641 return None;
5642 };
5643 let c2 = lang.children(i2[0]);
5644 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5645 && lang.var_name(c2[0]) == Some(primary.as_str())
5646 && lang.literal(c2[1]) == Some(LitRef::Null);
5647 if !primary_ok {
5648 return None;
5649 }
5650 let (then2, els2) = (i2[1], i2[2]);
5651 let t2_stmts = twr_stmts(lang, then2);
5653 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5654 return None;
5655 }
5656 let Some((r2, sup)) =
5657 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5658 else {
5659 return None;
5660 };
5661 if r2 != r {
5662 return None;
5663 }
5664 let e2_stmts = twr_stmts(lang, els2);
5666 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5667 return None;
5668 }
5669 if idx < 2 {
5671 return None;
5672 }
5673 let decl_r = pch[idx - 2];
5674 let decl_primary = pch[idx - 1];
5675 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5676 (
5677 NodeData::VarDecl { name: rn, .. },
5678 NodeData::VarDecl { name: pn, .. },
5679 ) => (rn.clone(), pn.clone()),
5680 _ => return None,
5681 };
5682 if r_decl != r || primary_decl != primary {
5683 return None;
5684 }
5685 let pinit_null = lang
5687 .children(decl_primary)
5688 .first()
5689 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5690 if !pinit_null {
5691 return None;
5692 }
5693 let r_init = lang.children(decl_r).first().copied()?;
5694 let ty = match lang.data(decl_r) {
5695 NodeData::VarDecl { ty, .. } => ty.clone(),
5696 _ => return None,
5697 };
5698 let root = walk_root;
5700 let r_allowed = [decl_r, r_init, ch[2], try_block];
5702 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5703 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5704 if refs_escape(lang, root, &r, &r_allowed)
5705 || refs_escape(lang, root, &primary, &p_allowed)
5706 || refs_escape(lang, root, &t, &t_allowed)
5707 || refs_escape(lang, root, &sup, &[ch[2]])
5708 {
5709 return None;
5710 }
5711 let init_copy = lang.copy_subtree(r_init);
5712 let res = lang.var_decl(&r, ty, Some(init_copy));
5713 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5714 return Some(Edit::Splice {
5715 node: parent,
5716 index: idx - 2,
5717 remove: 3,
5718 insert: vec![new_try],
5719 });
5720 }
5721 None
5722 }
5723}
5724
5725pub struct StringSwitchRecover;
5750
5751fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5752 out.push_str(&format!("{:?} ", lang.data(n)));
5753 for &c in lang.children(n) {
5754 subtree_sig(lang, c, out);
5755 }
5756}
5757
5758fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5760 let ch = lang.children(sw).to_vec();
5761 let mut out = Vec::new();
5762 for &c in &ch[1..] {
5763 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5764 return None;
5765 };
5766 let is_default = *is_default;
5767 let cc = lang.children(c);
5768 let label = if is_default {
5769 None
5770 } else {
5771 if *labels != 1 {
5772 return None; }
5774 let lab = cc[0];
5775 let lit = lang.literal(lab)?;
5776 let v = match lit {
5777 LitRef::Int(v) | LitRef::Long(v) => v,
5778 _ => return None, };
5780 Some(v)
5781 };
5782 out.push((label, cc[*labels as usize..].to_vec()));
5783 }
5784 Some(out)
5785}
5786
5787fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5788 let Some(&last) = stmts.last() else {
5789 return false; };
5791 let last = if lang.kind(last) == NodeKind::Block {
5792 match lang.children(last).last() {
5793 Some(&l) => l,
5794 None => return false,
5795 }
5796 } else {
5797 last
5798 };
5799 matches!(
5800 lang.kind(last),
5801 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5802 )
5803}
5804
5805impl Rule<JavaAst> for StringSwitchRecover {
5806 fn name(&self) -> &'static str {
5807 "string_switch_recover"
5808 }
5809 fn kinds(&self) -> &'static [NodeKind] {
5810 &[NodeKind::Switch]
5811 }
5812 fn structural(&self) -> bool {
5813 true
5814 }
5815 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5816 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5817 let lang = ctx.lang;
5818 if lang.kind(id) != NodeKind::Switch {
5819 return None;
5820 }
5821 let ch = lang.children(id).to_vec();
5822 if ch.len() < 2 {
5823 return None;
5824 }
5825 let x = match lang.data(ch[0]) {
5827 NodeData::Call => {
5828 let cc = lang.children(ch[0]);
5829 if cc.len() != 1 {
5830 return None;
5831 }
5832 match lang.data(cc[0]) {
5833 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5834 let recv = *lang.children(cc[0]).first()?;
5835 if lang.kind(recv) != NodeKind::VarRef {
5836 return None;
5837 }
5838 lang.var_name(recv)?.to_string()
5839 }
5840 _ => return None,
5841 }
5842 }
5843 _ => {
5844 return None;
5845 }
5846 };
5847 struct CaseInfo {
5849 lit: String,
5850 k: i64,
5851 inner_switch: JavaId,
5852 }
5853 let mut infos: Vec<CaseInfo> = Vec::new();
5854 let mut v_name: Option<String> = None;
5855 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5857 for &c in &ch[1..] {
5858 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5859 return None;
5860 };
5861 let is_default = *is_default;
5862 if is_default {
5863 return None; }
5865 if *labels != 1 {
5866 return None;
5867 }
5868 let cc = lang.children(c);
5869 let h = match lang.literal(cc[0])? {
5870 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5871 _ => return None,
5872 };
5873 let body = cc[1..].to_vec();
5874 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5876 lang.children(body[0]).to_vec()
5877 } else {
5878 body
5879 };
5880 let mut rest = body;
5881 let mut _e_decl: Option<(String, JavaId)> = None;
5883 if let Some(&first) = rest.first() {
5884 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5885 if let Some(&init) = lang.children(first).first() {
5886 if is_x_equals(lang, init, &x) {
5887 _e_decl = Some((name.clone(), first));
5888 rest = rest[1..].to_vec();
5889 }
5890 }
5891 }
5892 }
5893 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5894 return None;
5895 }
5896 let ifn = rest[0];
5897 let ic = lang.children(ifn).to_vec();
5898 if ic.len() != 3 {
5899 return None;
5900 }
5901 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5903 return None;
5904 };
5905 let guard = *lang.children(ic[0]).first()?;
5906 let lit = match lang.data(guard) {
5907 NodeData::VarRef { name } => {
5908 let Some((en, decl)) = &_e_decl else {
5910 return None;
5911 };
5912 if lang.sn(*name) != en {
5913 return None;
5914 }
5915 let init = *lang.children(*decl).first()?;
5916 equals_lit(lang, init, &x)?
5917 }
5918 NodeData::Call => equals_lit(lang, guard, &x)?,
5919 _ => return None,
5920 };
5921 guard_nodes.push(guard);
5922 if let Some((_, decl)) = &_e_decl {
5923 guard_nodes.push(*decl);
5924 }
5925 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5927 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5928 NodeData::Break { label: None });
5929 if !then_ok {
5930 return None;
5931 }
5932 let els = twr_stmts(lang, ic[2]);
5934 let (k, inner_switch, assign_node) = match els.len() {
5935 2 => {
5936 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5937 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5938 return None;
5939 };
5940 let a_ch = lang.children(assign_expr);
5941 let vn = match lang.data(a_ch[0]) {
5942 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5943 _ => return None,
5944 };
5945 let k = match lang.literal(a_ch[1])? {
5946 LitRef::Int(v) | LitRef::Long(v) => v,
5947 _ => return None,
5948 };
5949 if lang.kind(els[1]) != NodeKind::Switch {
5950 return None;
5951 }
5952 let sel = *lang.children(els[1]).first()?;
5954 if lang.kind(sel) != NodeKind::VarRef
5955 || lang.var_name(sel) != Some(vn.as_str())
5956 {
5957 return None;
5958 }
5959 match &v_name {
5960 Some(v) if *v != vn => return None,
5961 _ => v_name = Some(vn),
5962 }
5963 (k, els[1], Some(els[0]))
5964 }
5965 1 => {
5966 if lang.kind(els[0]) != NodeKind::Switch {
5967 return None;
5968 }
5969 let sel = *lang.children(els[0]).first()?;
5971 let k = match lang.literal(sel)? {
5972 LitRef::Int(v) | LitRef::Long(v) => v,
5973 _ => return None,
5974 };
5975 (k, els[0], None)
5976 }
5977 _ => return None,
5978 };
5979 if let Some(a) = assign_node {
5980 v_nodes.push(a);
5981 }
5982 v_nodes.push(*lang.children(inner_switch).first()?);
5983 let hlit: i32 = lit
5985 .chars()
5986 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
5987 if hlit != h {
5988 return None;
5989 }
5990 let _ = assign_node;
5991 infos.push(CaseInfo {
5992 lit,
5993 k,
5994 inner_switch,
5995 });
5996 }
5997 if infos.is_empty() {
5998 return None;
5999 }
6000 if lang.kind(parent) != NodeKind::Block {
6002 return None;
6003 }
6004 let pch = lang.children(parent).to_vec();
6005 let mut k = idx + 1;
6006 if k < pch.len() {
6008 let texpr = unwrap_expr_stmt(lang, pch[k]);
6009 if let NodeData::Assign { op: None } = lang.data(texpr) {
6010 let a_ch = lang.children(texpr);
6011 if a_ch.len() == 2
6012 && lang.kind(a_ch[0]) == NodeKind::VarRef
6013 && lang.var_name(a_ch[0]) == v_name.as_deref()
6014 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
6015 {
6016 v_nodes.push(pch[k]);
6017 k += 1;
6018 }
6019 }
6020 }
6021 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
6022 return None;
6023 }
6024 let trailing = pch[k];
6025 let tsel = *lang.children(trailing).first()?;
6027 match lang.data(tsel) {
6028 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
6029 v_nodes.push(tsel);
6030 }
6031 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
6032 _ => {
6033 lang.literal(tsel)?;
6034 }
6035 }
6036 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
6039 for &c in &lang.children(sw)[1..] {
6040 subtree_sig(lang, c, out);
6041 }
6042 }
6043 let mut sig0 = String::new();
6044 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
6045 for info in &infos[1..] {
6046 let mut sig = String::new();
6047 switch_cases_sig(lang, info.inner_switch, &mut sig);
6048 if sig != sig0 {
6049 return None;
6050 }
6051 }
6052 {
6053 let mut sig = String::new();
6054 switch_cases_sig(lang, trailing, &mut sig);
6055 if sig != sig0 {
6056 return None;
6057 }
6058 }
6059 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
6061 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
6062 std::collections::HashMap::new();
6063 let mut default_body: Option<Vec<JavaId>> = None;
6064 for (lab, stmts) in &bodies {
6065 match lab {
6066 Some(v) => {
6067 body_map.insert(*v, stmts.clone());
6068 }
6069 None => default_body = Some(stmts.clone()),
6070 }
6071 }
6072 let Some(default_body) = default_body else {
6073 return None;
6074 };
6075 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
6077 if !ssw_body_terminal(lang, stmts) {
6078 return None;
6079 }
6080 }
6081 let root = ctx_root;
6083 if let Some(vn) = &v_name {
6084 let mut allowed: Vec<JavaId> = v_nodes.clone();
6085 allowed.push(trailing);
6086 if idx >= 1 {
6087 let cand = pch[idx - 1];
6088 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
6089 if name == vn {
6090 allowed.push(cand);
6091 if idx != 1 {
6093 allowed.pop();
6094 }
6095 }
6096 }
6097 }
6098 if refs_escape(lang, root, vn, &allowed) {
6099 return None;
6100 }
6101 }
6102 let mut new_cases: Vec<JavaId> = Vec::new();
6104 for info in &infos {
6105 let Some(body) = body_map.get(&info.k) else {
6106 return None;
6107 };
6108 let lab = lang.lit(Lit::Str(info.lit.clone()));
6109 let case = lang.case_(vec![lab], false, false, body.clone());
6110 new_cases.push(case);
6111 }
6112 let def_case = lang.case_(Vec::new(), true, false, default_body);
6113 new_cases.push(def_case);
6114 let sel = lang.var(&x);
6115 let new_switch = lang.switch_(sel, new_cases);
6116 let remove = k - idx + 1;
6118 Some(Edit::Splice {
6119 node: parent,
6120 index: idx,
6121 remove,
6122 insert: vec![new_switch],
6123 })
6124 }
6125}
6126
6127fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6129 if lang.kind(e) != NodeKind::Call {
6130 return None;
6131 }
6132 let cc = lang.children(e);
6133 if cc.len() != 2 {
6134 return None;
6135 }
6136 match lang.data(cc[0]) {
6137 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6138 let recv = *lang.children(cc[0]).first()?;
6139 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6140 return None;
6141 }
6142 match lang.literal(cc[1])? {
6143 LitRef::Str(s) => Some(s.to_string()),
6144 _ => None,
6145 }
6146 }
6147 _ => None,
6148 }
6149}
6150
6151fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6152 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6153}
6154
6155pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6162 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6163 rules.push(Box::new(CastSimplify));
6164 rules.push(Box::new(SelfCompare));
6165 rules.push(Box::new(StringBuilderFold));
6166 rules.push(Box::new(BoxUnboxChain));
6167 rules.push(Box::new(IteratorToForEach));
6168 rules.push(Box::new(NewStringFold));
6169 rules.push(Box::new(NewStringCharArrayFold));
6170 rules.push(Box::new(EmptyFinallyStrip));
6171 rules.push(Box::new(TryUnwrapNoCatch));
6172 rules.push(Box::new(TryUnwrapRethrow));
6173 rules.push(Box::new(StaticExec));
6174 rules.push(Box::new(LoopHeadBreak));
6175 rules.push(Box::new(WhileIteratorToForEach));
6176 rules.push(Box::new(ConcatValueOfDrop));
6177 rules.push(Box::new(StringBuilderStatements));
6178 rules.push(Box::new(TrailingContinueJava));
6179 rules.push(Box::new(XorNoise));
6180 rules.push(Box::new(StrLenFold));
6181 rules.push(Box::new(LiteralEval));
6182 rules.push(Box::new(Base64NewStringFold));
6183 rules.push(Box::new(UrlDecodeFold));
6184 rules.push(Box::new(CffRecover));
6185 rules.push(Box::new(StaticArrayIndexFold));
6186 rules.push(Box::new(TrailingAssignReturn));
6187 rules.push(Box::new(DeadPureCall));
6188 rules.push(Box::new(CtorDelegationHoist));
6189 rules.push(Box::new(TrailingElseFlatten));
6190 rules.push(Box::new(TrailingIfContinue));
6191 rules.push(Box::new(ConstFieldPropagate));
6192 rules.push(Box::new(NoopPrivateCall));
6193 rules.push(Box::new(ConstMethodInline));
6194 rules.push(Box::new(TwrRecover));
6195 rules.push(Box::new(StringSwitchRecover));
6196 rules
6197}
6198
6199pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6201 let mut rules = default_java_rules();
6202 let mut engine_all = cure_engine::rules::all_rules();
6203 rules.append(&mut engine_all);
6204 rules
6205}
6206
6207pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6209 let rules = default_java_rules();
6210 cure_engine::simplify(ast, root, &rules, cfg)
6211}
6212
6213pub fn simplify_unit_with_facts(
6218 ast: &mut JavaAst,
6219 unit: &mut CompilationUnit,
6220 cfg: &Config,
6221 facts: &cure_java_ast::JavaFacts,
6222) -> Report {
6223 ast.set_facts(facts.clone());
6224 let r = simplify_unit(ast, unit, cfg);
6225 ast.clear_facts();
6226 r
6227}
6228
6229pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6230 let mut total = Report::default();
6231 for _cycle in 0..6 {
6236 let mut round = Report::default();
6237 collect_unit_consts(ast, unit);
6238 if cfg.remove_dead_methods {
6241 collect_noop_private_methods(ast, unit);
6242 } else {
6243 ast.noop_private_methods.clear();
6244 }
6245 for ty in &mut unit.types {
6246 simplify_type(ast, ty, cfg, &mut round);
6247 }
6248 if cfg.remove_dead_methods {
6249 round.edits += remove_dead_private_fields(ast, unit);
6250 round.edits += remove_dead_private_methods(ast, unit);
6251 round.edits += remove_unused_imports(ast, unit);
6252 }
6253 total.edits += round.edits;
6254 total.iterations += round.iterations;
6255 total.discarded += round.discarded;
6256 for (k, v) in round.by_rule {
6257 *total.by_rule.entry(k).or_insert(0) += v;
6258 }
6259 if round.edits == 0 {
6264 break;
6265 }
6266 }
6267 total
6268}
6269
6270fn collect_anon_and_string_names(
6277 ast: &JavaAst,
6278 unit: &CompilationUnit,
6279) -> std::collections::HashSet<String> {
6280 let mut out: std::collections::HashSet<String> = Default::default();
6281 let mut bodies: Vec<JavaId> = Vec::new();
6282 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6283 for root in bodies {
6284 let mut stack = vec![root];
6285 while let Some(n) = stack.pop() {
6286 match ast.data(n) {
6287 NodeData::New { anon_raw: Some(raw), .. } => {
6288 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6289 if !id.is_empty() {
6290 out.insert(id.to_string());
6291 }
6292 }
6293 }
6294 NodeData::Literal(Lit::Str(text)) => {
6295 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6296 if !id.is_empty() {
6297 out.insert(id.to_string());
6298 }
6299 }
6300 }
6301 _ => {}
6302 }
6303 for &c in ast.children(n) {
6304 stack.push(c);
6305 }
6306 }
6307 }
6308 out
6309}
6310
6311fn collect_all_blind_refs(
6340 ast: &JavaAst,
6341 unit: &CompilationUnit,
6342) -> std::collections::HashSet<String> {
6343 let mut out: std::collections::HashSet<String> = Default::default();
6344 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6345 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6346 if !id.is_empty() {
6347 out.insert(id.to_string());
6348 }
6349 }
6350 };
6351 let mut bodies: Vec<JavaId> = Vec::new();
6353 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6354 for root in bodies {
6355 let mut stack = vec![root];
6356 while let Some(n) = stack.pop() {
6357 match ast.data(n) {
6358 NodeData::Raw { text } => split_into(text, &mut out),
6359 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6360 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6361 NodeData::ForEach { ty, .. } => {
6363 collect_ty_idents(ty, &mut out);
6364 }
6365 NodeData::NewArray { ty, .. } => {
6367 collect_ty_idents(ty, &mut out);
6368 }
6369 NodeData::Lambda { params_raw } => {
6373 split_into(params_raw, &mut out);
6374 }
6375 _ => {}
6376 }
6377 for &c in ast.children(n) {
6378 stack.push(c);
6379 }
6380 }
6381 }
6382 for r in &unit.raws {
6386 split_into(r, &mut out);
6387 }
6388 for_each_type(unit, &mut |ty: &TypeDecl| {
6391 collect_mods_idents_all(&ty.mods, &mut out);
6392 for e in &ty.enum_constants {
6393 split_into(e, &mut out);
6394 }
6395 split_into(&ty.ty_params, &mut out);
6396 split_into(&ty.header, &mut out);
6397 for m in &ty.members {
6398 match m {
6399 Member::Method { mods, name, ty_params, params, .. }
6400 | Member::Constructor { mods, name, ty_params, params, .. } => {
6401 collect_mods_idents_all(mods, &mut out);
6402 split_into(ty_params, &mut out);
6403 for p in params {
6404 collect_mods_idents_all(&p.mods, &mut out);
6405 collect_ty_idents(&p.ty, &mut out);
6406 }
6407 if has_bare_methodsource(mods) {
6412 out.insert(name.clone());
6413 }
6414 }
6415 Member::Field { mods, declarators, .. } => {
6416 collect_mods_idents_all(mods, &mut out);
6417 let _ = declarators;
6419 }
6420 Member::Type(t) => {
6421 let _ = t; }
6423 Member::Raw(text) => {
6430 split_into(text, &mut out);
6431 }
6432 _ => {}
6433 }
6434 }
6435 });
6436 out
6437}
6438
6439fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6443 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6444 if !id.is_empty() {
6445 out.insert(id.to_string());
6446 }
6447 }
6448}
6449
6450fn has_bare_methodsource(mods: &str) -> bool {
6457 let hay = mods.as_bytes();
6458 const NEEDLE: &[u8] = b"MethodSource";
6459 let mut i = 0usize;
6460 while let Some(p) = mods[i..].find("MethodSource") {
6461 let at = i + p;
6462 let end = at + NEEDLE.len();
6463 let prev_ok = at == 0
6466 || !(hay[at - 1].is_ascii_alphanumeric()
6467 || hay[at - 1] == b'_'
6468 || hay[at - 1] == b'$');
6469 let next_ident =
6470 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6471 if prev_ok && !next_ident {
6472 let mut j = end;
6473 while j < hay.len() && hay[j].is_ascii_whitespace() {
6474 j += 1;
6475 }
6476 if j >= hay.len() || hay[j] != b'(' {
6477 return true;
6478 }
6479 }
6480 i = end;
6481 }
6482 false
6483}
6484
6485const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6489 "writeObject",
6490 "readObject",
6491 "readObjectNoData",
6492 "writeReplace",
6493 "readResolve",
6494 "validateObject",
6495];
6496
6497fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6498 use std::collections::HashSet;
6499 let mut removed = 0usize;
6500 let anon_names = collect_anon_and_string_names(ast, unit);
6502 let blind = collect_all_blind_refs(ast, unit);
6507 loop {
6508 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6515 let mut veto: HashSet<String> = HashSet::new();
6516 let mut roots: Vec<JavaId> = Vec::new();
6517 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6518 for body in roots {
6519 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6520 }
6521 for_each_type(unit, &mut |ty| {
6522 for m in &ty.members {
6523 if let Member::Method { name, params, .. } = m {
6524 if params.iter().any(|p| p.varargs) {
6525 veto.insert(name.clone());
6526 }
6527 }
6528 }
6529 });
6530 let mut changed = false;
6532 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6533 let before = ty.members.len();
6534 ty.members.retain(|m| {
6535 if let Member::Method { mods, name, params, .. } = m {
6536 let is_private = mods.split_whitespace().any(|w| w == "private");
6537 let annotated = mods.contains('@');
6538 let keep = !is_private
6539 || annotated
6540 || params.iter().any(|p| p.varargs)
6542 || veto.contains(name)
6543 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6547 || anon_names.contains(name)
6548 || blind.contains(name)
6549 || referenced.contains(&(name.clone(), params.len()));
6550 keep
6551 } else {
6552 true
6553 }
6554 });
6555 if ty.members.len() != before {
6556 removed += before - ty.members.len();
6557 changed = true;
6558 }
6559 });
6560 if !changed {
6561 return removed;
6562 }
6563 }
6564}
6565
6566fn strip_ta_prefix(name: &str) -> &str {
6571 if !name.starts_with('<') {
6572 return name;
6573 }
6574 let b = name.as_bytes();
6575 let mut depth = 0i32;
6576 for (i, &c) in b.iter().enumerate() {
6577 match c {
6578 b'<' => depth += 1,
6579 b'>' => {
6580 depth -= 1;
6581 if depth == 0 {
6582 return &name[i + 1..];
6583 }
6584 }
6585 _ => {}
6586 }
6587 }
6588 name }
6590
6591fn collect_call_arity_refs(
6593 ast: &JavaAst,
6594 root: JavaId,
6595 out: &mut std::collections::HashSet<(String, usize)>,
6596 veto: &mut std::collections::HashSet<String>,
6597) {
6598 let mut stack = vec![root];
6599 while let Some(id) = stack.pop() {
6600 match ast.data(id) {
6601 NodeData::Call => {
6602 let ch = ast.children(id).to_vec();
6603 if let Some(&callee) = ch.first() {
6604 let name = match ast.data(callee) {
6605 NodeData::Member { name } | NodeData::VarRef { name } => {
6606 ast.sn(*name).to_string()
6607 }
6608 _ => String::new(),
6609 };
6610 if !name.is_empty() {
6611 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6612 }
6613 }
6614 }
6615 NodeData::MethodRef { name } => {
6616 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6617 veto.insert(strip_ta_prefix(short).to_string());
6618 }
6619 }
6620 _ => {}
6621 }
6622 for &c in ast.children(id) {
6623 stack.push(c);
6624 }
6625 }
6626}
6627
6628fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6630 for ty in &unit.types {
6631 for_each_type_inner(ty, f);
6632 }
6633}
6634fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6635 f(ty);
6636 for m in &ty.members {
6637 if let Member::Type(t) = m {
6638 for_each_type_inner(t, f);
6639 }
6640 }
6641}
6642
6643fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6645 for ty in &mut unit.types {
6646 for_each_type_mut_inner(ty, f);
6647 }
6648}
6649fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6650 f(ty);
6651 for m in &mut ty.members {
6652 if let Member::Type(t) = m {
6653 for_each_type_mut_inner(t, f);
6654 }
6655 }
6656}
6657
6658fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6660 for m in &ty.members {
6661 match m {
6662 Member::Method { body: Some(b), .. }
6663 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6664 Member::Initializer { body, .. } => out.push(*body),
6665 Member::Field { declarators, .. } => {
6666 for d in declarators {
6667 if let Some(init) = d.init {
6668 out.push(init);
6669 }
6670 }
6671 }
6672 Member::Type(t) => collect_member_roots(t, out),
6673 _ => {}
6674 }
6675 }
6676}
6677
6678fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6693 use std::collections::{HashMap, HashSet};
6694 ast.noop_private_methods.clear();
6695 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6696 let mut veto: HashSet<String> = HashSet::new();
6697 for_each_type(unit, &mut |ty: &TypeDecl| {
6698 for m in &ty.members {
6699 if let Member::Method { mods, name, params, body, throws, .. } = m {
6700 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6701 veto.insert(name.clone());
6702 continue;
6703 }
6704 let is_private = mods.split_whitespace().any(|w| w == "private");
6705 let empty = match body {
6706 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6707 None => false,
6708 };
6709 let no_throws = throws.is_empty();
6713 let cand = NoopCandidate {
6714 params: params.iter().map(|p| p.ty.clone()).collect(),
6715 noop: is_private && empty && no_throws,
6716 };
6717 by_name
6718 .entry(name.clone())
6719 .or_default()
6720 .entry(params.len())
6721 .or_default()
6722 .push(cand);
6723 }
6724 }
6725 });
6726 let blind = collect_all_blind_refs(ast, unit);
6729 let mut roots: Vec<JavaId> = Vec::new();
6732 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6733 for root in roots {
6734 let mut stack = vec![root];
6735 while let Some(n) = stack.pop() {
6736 if let NodeData::MethodRef { name } = ast.data(n) {
6737 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6738 veto.insert(strip_ta_prefix(short).to_string());
6739 }
6740 }
6741 for &c in ast.children(n) {
6742 stack.push(c);
6743 }
6744 }
6745 }
6746 for (name, arities) in by_name {
6747 if veto.contains(&name) || blind.contains(&name) {
6748 continue;
6749 }
6750 ast.noop_private_methods.insert(name, arities);
6751 }
6752}
6753
6754fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6759 use std::collections::HashSet;
6760 let mut idents: HashSet<String> = HashSet::new();
6761 let mut roots: Vec<JavaId> = Vec::new();
6763 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6764 for root in roots {
6765 let mut stack = vec![root];
6766 while let Some(n) = stack.pop() {
6767 match ast.data(n) {
6768 NodeData::VarDecl { name, ty, .. } => {
6769 idents.insert(name.clone());
6770 collect_ty_idents(ty, &mut idents);
6771 }
6772 NodeData::VarRef { name } => {
6773 let n = ast.sn(*name);
6777 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6778 if !id.is_empty() {
6779 idents.insert(id.to_string());
6780 }
6781 }
6782 }
6783 NodeData::Member { name } => {
6784 idents.insert(ast.sn(*name).to_string());
6785 }
6786 NodeData::MethodRef { name } => {
6787 let n = ast.sn(*name);
6788 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6789 if !id.is_empty() {
6790 idents.insert(id.to_string());
6791 }
6792 }
6793 }
6794 NodeData::Label { name } => {
6795 idents.insert(name.clone());
6796 }
6797 NodeData::New { ty, anon_raw, .. } => {
6798 collect_ty_idents(ty, &mut idents);
6799 if let Some(raw) = anon_raw {
6802 for id in
6803 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6804 {
6805 if !id.is_empty() {
6806 idents.insert(id.to_string());
6807 }
6808 }
6809 }
6810 }
6811 NodeData::Cast { ty } => {
6812 collect_ty_idents(ty, &mut idents);
6813 }
6814 NodeData::InstanceOf { ty, .. } => {
6815 collect_ty_idents(ty, &mut idents);
6816 }
6817 NodeData::Catch { ty_raw, .. } => {
6818 for id in
6820 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6821 {
6822 if !id.is_empty() {
6823 idents.insert(id.to_string());
6824 }
6825 }
6826 }
6827 NodeData::Raw { text } => {
6828 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6829 if !id.is_empty() {
6830 idents.insert(id.to_string());
6831 }
6832 }
6833 }
6834 _ => {}
6835 }
6836 for &c in ast.children(n) {
6837 stack.push(c);
6838 }
6839 }
6840 }
6841 for name in collect_all_blind_refs(ast, unit) {
6845 idents.insert(name);
6846 }
6847 for r in &unit.raws {
6850 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6851 if !id.is_empty() {
6852 idents.insert(id.to_string());
6853 }
6854 }
6855 }
6856 for_each_type(unit, &mut |ty| {
6859 collect_mods_idents(&ty.mods, &mut idents);
6860 idents.insert(ty.name.clone());
6861 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6862 if !id.is_empty() {
6863 idents.insert(id.to_string());
6864 }
6865 }
6866 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6870 if !id.is_empty() {
6871 idents.insert(id.to_string());
6872 }
6873 }
6874 for e in ty.extends.iter().chain(ty.implements.iter()) {
6875 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6876 if !id.is_empty() {
6877 idents.insert(id.to_string());
6878 }
6879 }
6880 }
6881 for m in &ty.members {
6882 match m {
6883 Member::Field { mods, ty, declarators } => {
6884 collect_mods_idents(mods, &mut idents);
6885 collect_ty_idents(ty, &mut idents);
6886 for d in declarators {
6887 idents.insert(d.name.clone());
6888 }
6889 }
6890 Member::Method { mods, ret, name, params, throws, .. } => {
6891 collect_mods_idents(mods, &mut idents);
6892 collect_ty_idents(ret, &mut idents);
6893 idents.insert(name.clone());
6894 for p in params {
6895 collect_mods_idents(&p.mods, &mut idents);
6896 collect_ty_idents(&p.ty, &mut idents);
6897 idents.insert(p.name.clone());
6898 }
6899 for t in throws {
6900 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6901 if !id.is_empty() {
6902 idents.insert(id.to_string());
6903 }
6904 }
6905 }
6906 }
6907 Member::Constructor { mods, name, params, throws, .. } => {
6908 collect_mods_idents(mods, &mut idents);
6909 idents.insert(name.clone());
6910 for p in params {
6911 collect_mods_idents(&p.mods, &mut idents);
6912 collect_ty_idents(&p.ty, &mut idents);
6913 idents.insert(p.name.clone());
6914 }
6915 for t in throws {
6916 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6917 if !id.is_empty() {
6918 idents.insert(id.to_string());
6919 }
6920 }
6921 }
6922 }
6923 Member::Type(t) => {
6924 let _ = t;
6926 }
6927 _ => {}
6928 }
6929 }
6930 });
6931 let before = unit.imports.len();
6933 unit.imports.retain(|imp| {
6934 if imp.contains('*') {
6935 return true; }
6937 let path = imp.trim_start_matches("static ").trim();
6939 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6940 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6941 return true; }
6943 idents.contains(simple)
6944 });
6945 before - unit.imports.len()
6946}
6947
6948fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6949 match t {
6950 JType::Ref(r) => {
6951 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6952 if !id.is_empty() {
6953 out.insert(id.to_string());
6954 }
6955 }
6956 }
6957 JType::Array(inner) => collect_ty_idents(inner, out),
6958 _ => {}
6959 }
6960}
6961
6962fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6963 if mods.contains('@') {
6965 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6966 if !id.is_empty() {
6967 out.insert(id.to_string());
6968 }
6969 }
6970 }
6971}
6972
6973fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6979 use std::collections::HashSet;
6980 let mut removed = 0usize;
6981 let anon_names = collect_anon_and_string_names(ast, unit);
6983 let blind = collect_all_blind_refs(ast, unit);
6986 let mut referenced: HashSet<String> = HashSet::new();
6988 let mut bodies: Vec<JavaId> = Vec::new();
6989 for_each_type(unit, &mut |ty: &TypeDecl| {
6990 for m in &ty.members {
6991 match m {
6992 Member::Method { body: Some(b), .. }
6993 | Member::Constructor { body: Some(b), .. }
6994 | Member::Initializer { body: b, .. } => bodies.push(*b),
6995 Member::Field { declarators, .. } => {
6996 for d in declarators {
6997 if let Some(init) = d.init {
6998 bodies.push(init);
6999 }
7000 }
7001 }
7002 _ => {}
7003 }
7004 }
7005 });
7006 for root in bodies {
7007 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
7009 while let Some((n, is_callee)) = stack.pop() {
7010 match ast.kind(n) {
7011 NodeKind::VarRef if !is_callee => {
7012 if let Some(nm) = ast.var_name(n) {
7013 referenced.insert(nm.to_string());
7014 }
7015 }
7016 NodeKind::Member => {
7017 if let NodeData::Member { name: sym } = ast.data(n) {
7018 referenced.insert(ast.sn(*sym).to_string());
7019 }
7020 }
7021 _ => {}
7022 }
7023 if ast.kind(n) == NodeKind::Call {
7024 let ch = ast.children(n).to_vec();
7025 if let Some(&first) = ch.first() {
7026 stack.push((first, true));
7027 }
7028 for &c in &ch[1..] {
7029 stack.push((c, false));
7030 }
7031 continue;
7032 }
7033 for &c in ast.children(n) {
7034 stack.push((c, false));
7035 }
7036 }
7037 }
7038 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
7044 for m in ty.members.iter_mut() {
7045 if let Member::Field { mods, declarators, .. } = m {
7046 let is_private = mods.split_whitespace().any(|w| w == "private");
7047 let annotated = mods.contains('@');
7048 if !is_private || annotated {
7049 continue;
7050 }
7051 let dead: Vec<bool> = declarators
7052 .iter()
7053 .map(|d| {
7054 let reflective =
7055 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
7056 let init_ok =
7057 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
7058 !reflective
7059 && init_ok
7060 && !referenced.contains(&d.name)
7061 && !anon_names.contains(&d.name)
7062 && !blind.contains(&d.name)
7063 })
7064 .collect();
7065 let before = declarators.len();
7066 let mut idx = 0usize;
7067 declarators.retain(|_| {
7068 let keep = !dead[idx];
7069 idx += 1;
7070 keep
7071 });
7072 removed += before - declarators.len();
7073 }
7074 }
7075 let before = ty.members.len();
7076 ty.members
7077 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
7078 removed += before - ty.members.len();
7079 });
7080 removed
7081}
7082
7083fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
7084 for m in &mut ty.members {
7085 simplify_member(ast, m, cfg, total);
7086 }
7087}
7088
7089fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
7090 let params: Vec<(String, JType)> = match m {
7092 Member::Method { params, .. } | Member::Constructor { params, .. } => params
7093 .iter()
7094 .map(|p| (p.name.clone(), p.ty.clone()))
7095 .collect(),
7096 _ => Vec::new(),
7097 };
7098 ast.set_param_scope(¶ms);
7099 match m {
7100 Member::Method { body: Some(b), .. }
7101 | Member::Constructor { body: Some(b), .. } => {
7102 let r = simplify(ast, *b, cfg);
7103 merge_report(total, r);
7104 }
7105 Member::Initializer { body, .. } => {
7106 let r = simplify(ast, *body, cfg);
7107 merge_report(total, r);
7108 }
7109 Member::Field { declarators, .. } => {
7110 for d in declarators.iter_mut() {
7111 if let Some(init) = d.init {
7112 let r = simplify(ast, init, cfg);
7113 merge_report(total, r);
7114 let rules = default_java_rules();
7118 let mut cur: JavaId = init;
7119 for _ in 0..8 {
7120 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7121 Some((new_root, name)) => {
7122 d.init = Some(new_root);
7123 cur = new_root;
7124 *total.by_rule.entry(name).or_insert(0) += 1;
7125 total.edits += 1;
7126 let r = simplify(ast, new_root, cfg);
7128 merge_report(total, r);
7129 }
7130 None => break,
7131 }
7132 }
7133 }
7134 }
7135 }
7136 Member::Type(t) => simplify_type(ast, t, cfg, total),
7137 Member::Raw(_) => {}
7138 _ => {}
7139 }
7140}
7141
7142fn merge_report(total: &mut Report, r: Report) {
7143 total.edits += r.edits;
7144 total.iterations += r.iterations;
7145 for (k, v) in r.by_rule {
7146 *total.by_rule.entry(k).or_insert(0) += v;
7147 }
7148}
7149
7150pub fn default_config() -> Config {
7152 Config::default()
7153}