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