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