1mod jdk;
9pub use jdk::set_jdk_home;
10mod vexec;
11
12use cure_engine::kind::{BinOp, UnOp};
13use cure_engine::{Config, Edit, Effect, Lang, LitRef, NodeKind, Report, RewriteCtx, Rule};
14use cure_java_ast::{CompilationUnit, JavaAst, JavaId, JType, Lit, Member, NoopCandidate, NodeData, TypeDecl, TypeKind};
15
16pub struct CastSimplify;
24
25impl Rule<JavaAst> for CastSimplify {
26 fn name(&self) -> &'static str {
27 "cast_simplify"
28 }
29 fn kinds(&self) -> &'static [NodeKind] {
30 &[NodeKind::Cast]
31 }
32 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
33 let lang = ctx.lang;
34 let NodeData::Cast { ty } = lang.data(id) else {
35 return None;
36 };
37 let ty = ty.clone();
38 let inner = lang.children(id)[0];
39
40 if let NodeData::Cast { ty: inner_ty } = lang.data(inner) {
42 if *inner_ty == ty {
43 return Some(Edit::Replace {
44 target: id,
45 with: inner,
46 });
47 }
48 }
49
50 if let NodeData::VarRef { .. } = lang.data(inner) {
52 if lang.var_type(inner) == Some(&ty) {
53 return Some(Edit::Replace {
54 target: id,
55 with: inner,
56 });
57 }
58 }
59 if let NodeData::Cast { ty: inner_cast_ty } = lang.data(inner) {
66 let is_obj = matches!(inner_cast_ty,
67 JType::Ref(r) if r == "java.lang.Object" || r == "Object");
68 if is_obj {
69 let operand = lang.children(inner).first().copied()?;
70 let operand_is_object = match lang.data(operand) {
74 NodeData::Literal(_) | NodeData::This => true,
75 NodeData::VarRef { .. } => {
76 matches!(lang.var_type(operand),
77 Some(JType::Ref(r)) if r == "java.lang.Object" || r == "Object")
78 }
79 _ => false,
80 };
81 if operand_is_object {
82 return Some(Edit::Replace {
83 target: inner,
84 with: operand,
85 });
86 }
87 }
88 }
89 None
90 }
91}
92
93pub struct SelfCompare;
96
97impl Rule<JavaAst> for SelfCompare {
98 fn name(&self) -> &'static str {
99 "self_compare"
100 }
101 fn kinds(&self) -> &'static [NodeKind] {
102 &[NodeKind::Binary]
103 }
104 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
105 use cure_engine::kind::BinOp::{Eq, Ne};
106 let lang = ctx.lang;
107 let NodeData::Binary { op } = lang.data(id) else {
108 return None;
109 };
110 if !matches!(op, Eq | Ne) {
111 return None;
112 }
113 let ch = lang.children(id);
114 let (NodeData::VarRef { name: n1 }, NodeData::VarRef { name: n2 }) =
115 (lang.data(ch[0]), lang.data(ch[1]))
116 else {
117 return None;
118 };
119 if n1 != n2 || !lang.is_exact_int(ch[0]) {
120 return None;
121 }
122 let val = lang.build_bool(*op == Eq);
123 Some(Edit::Replace {
124 target: id,
125 with: val,
126 })
127 }
128}
129
130
131pub struct StringBuilderFold;
142
143impl Rule<JavaAst> for StringBuilderFold {
144 fn name(&self) -> &'static str {
145 "string_builder_fold"
146 }
147 fn kinds(&self) -> &'static [NodeKind] {
148 &[NodeKind::Call]
149 }
150 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
151 let root = ctx.root();
152 let lang = ctx.lang;
153 if lang.kind(id) != NodeKind::Call {
155 return None;
156 }
157 let callee = lang.children(id)[0];
158 if let NodeData::Member { name } = lang.data(callee) {
159 if lang.sn(*name) != "toString" || !lang.children(id)[1..].is_empty() {
160 return None;
161 }
162 } else {
163 return None;
164 }
165 let mut node = lang.children(callee)[0]; let mut parts: Vec<JavaId> = Vec::new();
167 loop {
169 match lang.data(node) {
170 NodeData::Call => {
171 let c = lang.children(node);
172 if let NodeData::Member { name } = lang.data(c[0]) {
173 if lang.sn(*name) == "append" && c.len() == 2 {
174 parts.push(c[1]);
175 node = lang.children(c[0])[0];
176 continue;
177 }
178 }
179 return None;
180 }
181 NodeData::New { ty, .. } => {
182 let is_sb = !lang.unit_shadows_lang
183 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
184 if !is_sb {
185 return None;
186 }
187 let args = lang.children(node).to_vec();
188 match args.len() {
189 0 => {} 1 => {
191 let a = args[0];
196 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
197 || lang
198 .var_type(a)
199 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
200 if stringy {
201 parts.push(a);
202 } else if !matches!(
203 lang.literal(a),
204 Some(LitRef::Int(_) | LitRef::Long(_))
205 ) {
206 return None;
207 }
208 }
209 _ => return None,
210 }
211 break;
212 }
213 _ => return None,
214 }
215 }
216 if parts.iter().all(|&p| lang.literal(p).is_some())
220 && lang.has_string_identity_compare(root)
221 {
222 return None;
223 }
224 if parts.is_empty() {
225 let empty = lang.build_str("");
227 return Some(Edit::Replace {
228 target: id,
229 with: empty,
230 });
231 }
232 parts.reverse();
234 let first = parts[0];
235 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
236 || lang
237 .var_type(first)
238 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
239 let mut acc = if stringy_first {
240 first
241 } else {
242 let empty = lang.build_str("");
243 lang.build_bin(BinOp::Add, empty, first)
244 };
245 for &p in &parts[1..] {
246 acc = lang.build_bin(BinOp::Add, acc, p);
247 }
248 Some(Edit::Replace {
249 target: id,
250 with: acc,
251 })
252 }
253}
254
255pub struct BoxUnboxChain;
263
264const BOXERS: &[(&str, &str)] = &[
265 ("Integer", "intValue"),
266 ("java.lang.Integer", "intValue"),
267 ("Long", "longValue"),
268 ("java.lang.Long", "longValue"),
269 ("Boolean", "booleanValue"),
270 ("java.lang.Boolean", "booleanValue"),
271 ("Double", "doubleValue"),
272 ("java.lang.Double", "doubleValue"),
273 ("Float", "floatValue"),
274 ("java.lang.Float", "floatValue"),
275 ("Character", "charValue"),
276 ("java.lang.Character", "charValue"),
277 ("Short", "shortValue"),
278 ("java.lang.Short", "shortValue"),
279 ("Byte", "byteValue"),
280 ("java.lang.Byte", "byteValue"),
281];
282
283impl Rule<JavaAst> for BoxUnboxChain {
284 fn name(&self) -> &'static str {
285 "box_unbox_chain"
286 }
287 fn kinds(&self) -> &'static [NodeKind] {
288 &[NodeKind::Call]
289 }
290 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
291 let lang = ctx.lang;
292 if lang.kind(id) != NodeKind::Call {
294 return None;
295 }
296 let callee = lang.children(id)[0];
297 let NodeData::Member { name: unbox } = lang.data(callee) else {
298 return None;
299 };
300 if !lang.children(id)[1..].is_empty() {
301 return None;
302 }
303 let inner = lang.children(callee)[0];
304 if lang.kind(inner) != NodeKind::Call {
305 return None;
306 }
307 let ich = lang.children(inner).to_vec();
309 let (box_name, arg): (String, JavaId) = if let NodeData::New { ty, .. } = lang.data(inner)
310 {
311 let n = match ty {
312 JType::Ref(r) => r.clone(),
313 _ => return None,
314 };
315 if ich.len() != 1 {
316 return None;
317 }
318 (n, ich[0])
319 } else {
320 let icallee = ich.first().copied()?;
322 if let NodeData::Member { name } = lang.data(icallee) {
323 let recv = lang.children(icallee)[0];
325 if lang.sn(*name) != "valueOf" {
326 return None;
327 }
328 {
329 let n = lang.var_name(recv)?;
330 (n.to_string(), *ich.get(1)?)
331 }
332 } else if let NodeData::VarRef { name } = lang.data(icallee) {
333 let _ = name;
336 return None;
337 } else {
338 return None;
339 }
340 };
341 let unbox_name = *unbox;
342 let expected = BOXERS
343 .iter()
344 .find(|(b, _)| *b == box_name.as_str())
345 .map(|(_, u)| *u)?;
346 if expected != lang.sn(unbox_name) {
347 return None;
348 }
349 let var_is = |t: fn(&JType) -> bool| lang.var_type(arg).is_some_and(t);
352 let lit_is_num = lang.literal(arg).is_some_and(|l| {
353 matches!(
354 l,
355 LitRef::Int(_) | LitRef::Long(_) | LitRef::Float(_) | LitRef::Double(_)
356 )
357 });
358 let ok = match box_name.trim_start_matches("java.lang.") {
359 "Integer" | "Long" | "Short" | "Byte" => lang.is_exact_int(arg),
360 "Double" | "Float" => {
361 lit_is_num
362 || var_is(|t| matches!(t, JType::Double | JType::Float | JType::Int | JType::Long))
363 }
364 "Boolean" => lang.is_bool(arg),
365 "Character" => {
366 matches!(lang.literal(arg), Some(LitRef::Char(_))) || var_is(|t| *t == JType::Char)
367 }
368 _ => false,
369 };
370 if !ok {
371 return None;
372 }
373 Some(Edit::Replace {
374 target: id,
375 with: arg,
376 })
377 }
378}
379
380pub struct IteratorToForEach;
392
393impl Rule<JavaAst> for IteratorToForEach {
394 fn name(&self) -> &'static str {
395 "iterator_to_for_each"
396 }
397 fn kinds(&self) -> &'static [NodeKind] {
398 &[NodeKind::For]
399 }
400 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
401 let lang = ctx.lang;
402 let NodeData::For {
403 inits,
404 has_cond,
405 steps,
406 } = lang.data(id)
407 else {
408 return None;
409 };
410 if *inits != 1 || !*has_cond || *steps != 0 {
411 return None;
412 }
413 let ch = lang.children(id).to_vec();
414 let (init, cond, body) = (ch[0], ch[1], ch[2]);
416
417 let NodeData::VarDecl { name: it_name, .. } = lang.data(init) else {
419 return None;
420 };
421 let it_name = it_name.clone();
422 let init_call = lang.children(init).first().copied()?;
423 if lang.kind(init_call) != NodeKind::Call {
424 return None;
425 }
426 let ic = lang.children(init_call).to_vec();
427 let icallee = *ic.first()?;
428 let NodeData::Member { name: m0 } = lang.data(icallee) else {
429 return None;
430 };
431 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
432 return None;
433 }
434 let iterable = lang.children(icallee)[0];
435 if subtree_has_var(&*lang, iterable, &it_name) {
437 return None;
438 }
439
440 if lang.kind(cond) != NodeKind::Call {
442 return None;
443 }
444 let cc = lang.children(cond).to_vec();
445 let ccallee = *cc.first()?;
446 let NodeData::Member { name: m1 } = lang.data(ccallee) else {
447 return None;
448 };
449 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
450 return None;
451 }
452 if lang.var_name(lang.children(ccallee)[0]) != Some(it_name.as_str()) {
453 return None;
454 }
455
456 if lang.kind(body) != NodeKind::Block {
458 return None;
459 }
460 let bch = lang.children(body).to_vec();
461 let first = *bch.first()?;
462 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
463 return None;
464 };
465 let (e_name, ty) = (e_name.clone(), ty.clone());
466 let next_call = lang.children(first).first().copied()?;
467 if lang.kind(next_call) != NodeKind::Call {
468 return None;
469 }
470 let nc = lang.children(next_call).to_vec();
471 let ncallee = *nc.first()?;
472 let NodeData::Member { name: m2 } = lang.data(ncallee) else {
473 return None;
474 };
475 if lang.sn(*m2) != "next" || nc.len() != 1 {
476 return None;
477 }
478 if lang.var_name(lang.children(ncallee)[0]) != Some(it_name.as_str()) {
479 return None;
480 }
481 let rest = bch[1..].to_vec();
483 for &r in &rest {
484 if subtree_has_var(&*lang, r, &it_name) {
485 return None;
486 }
487 }
488 let new_body = lang.build_block(rest);
489 let with = lang.for_each(&e_name, ty, iterable, new_body);
490 Some(Edit::Replace {
491 target: id,
492 with,
493 })
494 }
495}
496
497fn unwrap_paren_cast(lang: &JavaAst, mut n: JavaId) -> JavaId {
499 loop {
500 match lang.kind(n) {
501 NodeKind::Paren | NodeKind::Cast => {
502 let ch = lang.children(n);
503 if ch.is_empty() {
504 return n;
505 }
506 n = ch[0];
507 }
508 _ => return n,
509 }
510 }
511}
512
513fn generic_elem_ty(ty: &JType) -> Option<JType> {
515 let name = match ty {
516 JType::Ref(n) => n.as_str(),
517 _ => return None,
518 };
519 let lt = name.find('<')?;
520 let gt = name.rfind('>')?;
521 if gt <= lt {
522 return None;
523 }
524 let inner = name[lt + 1..gt].trim();
525 if inner.is_empty() || inner.contains('<') {
526 return None; }
528 Some(JType::Ref(inner.to_string()))
529}
530
531fn statically_iterable(ty: &JType) -> bool {
538 match ty {
539 JType::Array(_) => true,
540 JType::Ref(name) => {
541 let base = name.split('<').next().unwrap_or(name).trim();
542 let last = base.rsplit('.').next().unwrap_or(base);
543 matches!(
544 last,
545 "Iterable"
546 | "Collection"
547 | "List"
548 | "ArrayList"
549 | "LinkedList"
550 | "Vector"
551 | "Stack"
552 | "AbstractList"
553 | "AbstractCollection"
554 | "Set"
555 | "HashSet"
556 | "LinkedHashSet"
557 | "TreeSet"
558 | "SortedSet"
559 | "NavigableSet"
560 | "AbstractSet"
561 | "Queue"
562 | "Deque"
563 | "ArrayDeque"
564 | "PriorityQueue"
565 | "BlockingQueue"
566 | "BlockingDeque"
567 | "ConcurrentLinkedQueue"
568 | "ConcurrentLinkedDeque"
569 | "ConcurrentSkipListSet"
570 | "CopyOnWriteArrayList"
571 | "CopyOnWriteArraySet"
572 | "EnumSet"
573 | "LinkedBlockingQueue"
574 | "LinkedBlockingDeque"
575 | "ArrayBlockingQueue"
576 | "PriorityBlockingQueue"
577 | "DelayQueue"
578 | "SynchronousQueue"
579 | "LinkedTransferQueue"
580 | "TransferQueue"
581 )
582 }
583 _ => false,
584 }
585}
586
587fn for_each_elem_ok(ty: &JType) -> bool {
591 match ty {
592 JType::Ref(n) => !n.trim_start().starts_with('?'),
593 _ => true,
594 }
595}
596
597fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
599 let mut stack = vec![root];
600 while let Some(n) = stack.pop() {
601 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
602 out.push(n);
603 }
604 for &c in lang.children(n) {
605 stack.push(c);
606 }
607 }
608}
609
610fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
612 let mut stack = vec![root];
613 while let Some(n) = stack.pop() {
614 for &c in lang.children(n) {
615 if c == target {
616 return n;
617 }
618 stack.push(c);
619 }
620 }
621 root
622}
623
624fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
625 let mut stack = vec![id];
626 while let Some(n) = stack.pop() {
627 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
628 return true;
629 }
630 for &c in lang.children(n) {
631 stack.push(c);
632 }
633 }
634 false
635}
636
637fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
639 let mut stack = vec![id];
640 while let Some(n) = stack.pop() {
641 match lang.data(n) {
642 NodeData::Catch { name: cn, .. } if cn == name => return true,
643 NodeData::ForEach { name: vn, .. } if vn == name => return true,
644 NodeData::Lambda { params_raw } => {
647 for part in
648 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
649 {
650 if part == name {
651 return true;
652 }
653 }
654 }
655 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
657 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
660 _ => {}
661 }
662 for &c in lang.children(n) {
663 stack.push(c);
664 }
665 }
666 false
667}
668
669pub struct NewStringFold;
675
676impl Rule<JavaAst> for NewStringFold {
677 fn name(&self) -> &'static str {
678 "new_string_fold"
679 }
680 fn kinds(&self) -> &'static [NodeKind] {
681 &[NodeKind::New]
682 }
683 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
684 let root = ctx.root();
685 let lang = ctx.lang;
686 let NodeData::New { ty, .. } = lang.data(id) else {
687 return None;
688 };
689 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
690 return None;
691 }
692 if lang.unit_shadows_lang {
696 return None;
697 }
698 let _ = root;
701 if lang.unit_string_identity {
702 return None;
703 }
704 let args = lang.children(id).to_vec();
705 match args.len() {
706 0 => {
707 let empty = lang.build_str("");
708 Some(Edit::Replace {
709 target: id,
710 with: empty,
711 })
712 }
713 1 => {
714 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
715 Some(Edit::Replace {
716 target: id,
717 with: args[0],
718 })
719 } else {
720 None
721 }
722 }
723 _ => None,
724 }
725 }
726}
727
728
729pub struct NewStringCharArrayFold;
735
736impl Rule<JavaAst> for NewStringCharArrayFold {
737 fn name(&self) -> &'static str {
738 "new_string_char_array_fold"
739 }
740 fn kinds(&self) -> &'static [NodeKind] {
741 &[NodeKind::New]
742 }
743 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
744 let root = ctx.root();
745 let lang = ctx.lang;
746 let NodeData::New { ty, .. } = lang.data(id) else {
747 return None;
748 };
749 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
750 return None;
751 }
752 let _ = root;
753 if lang.unit_shadows_lang || lang.unit_string_identity {
754 return None;
755 }
756 let args = lang.children(id).to_vec();
758 if args.len() != 1 {
759 return None;
760 }
761 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
762 return None;
763 };
764 if *sized != 0 {
765 return None;
766 }
767 let arr_children = lang.children(args[0]).to_vec();
768 let lit = *arr_children.first()?;
769 if lang.kind(lit) != NodeKind::ArrayLit {
770 return None;
771 }
772 let mut text = String::new();
773 for &c in lang.children(lit) {
774 match lang.literal(c) {
775 Some(LitRef::Char(ch)) => text.push(ch),
776 _ => return None, }
778 }
779 let with = lang.build_str(&text);
780 Some(Edit::Replace {
781 target: id,
782 with,
783 })
784 }
785}
786
787pub struct EmptyFinallyStrip;
795
796impl Rule<JavaAst> for EmptyFinallyStrip {
797 fn name(&self) -> &'static str {
798 "empty_finally_strip"
799 }
800 fn kinds(&self) -> &'static [NodeKind] {
801 &[NodeKind::Try]
802 }
803 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
804 let lang = ctx.lang;
805 if lang.kind(id) != NodeKind::Try {
806 return None;
807 }
808 let ch = lang.children(id).to_vec();
809 let is_empty_block = |n: JavaId| -> bool {
816 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
817 };
818 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
819 if ch.len() < try_idx + 2 {
820 return None; }
822 let last = *ch.last()?;
824 if lang.kind(last) == NodeKind::Catch {
825 return None;
826 }
827 if !is_empty_block(last) {
828 return None;
829 }
830 Some(Edit::Splice {
833 node: id,
834 index: ch.len() - 1,
835 remove: 1,
836 insert: Vec::new(),
837 })
838 }
839}
840
841fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
887 if lang.kind(a) != lang.kind(b) {
888 let (ia, ib) = (lang.kind(a), lang.kind(b));
890 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
891 if k == NodeKind::ExprStmt {
892 lang.children(x).first().copied()
893 } else {
894 Some(x)
895 }
896 };
897 return match (unwrap(ia, a), unwrap(ib, b)) {
898 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
899 _ => false,
900 };
901 }
902 print_equiv_inner(lang, a, b)
903}
904
905fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
906 if lang.kind(a) != lang.kind(b) {
907 return false;
908 }
909 match (lang.literal(a), lang.literal(b)) {
911 (Some(x), Some(y)) => {
912 if x != y {
913 return false;
914 }
915 }
916 (None, None) => {}
917 _ => return false,
918 }
919 let (ca, cb) = (lang.children(a), lang.children(b));
920 if ca.len() != cb.len() {
921 return false;
922 }
923 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
924}
925
926fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
930 for &root in rest {
931 let mut stack = vec![root];
932 while let Some(n) = stack.pop() {
933 match lang.kind(n) {
934 NodeKind::Assign => {
935 let t = lang.children(n)[0];
936 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
937 return true;
938 }
939 }
940 NodeKind::Unary => {
941 if let NodeData::Unary { op } = lang.data(n) {
942 if op.is_incdec() {
943 let t = lang.children(n)[0];
944 if lang.kind(t) == NodeKind::VarRef
945 && lang.var_name(t) == Some(name)
946 {
947 return true;
948 }
949 }
950 }
951 }
952 _ => {}
953 }
954 for &c in lang.children(n) {
955 stack.push(c);
956 }
957 }
958 }
959 false
960}
961
962pub struct StaticExec;
963
964impl Rule<JavaAst> for StaticExec {
965 fn name(&self) -> &'static str {
966 "static_exec"
967 }
968 fn kinds(&self) -> &'static [NodeKind] {
969 &[NodeKind::Block]
970 }
971 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
972 if ctx.parent(id).is_some() {
975 return None;
976 }
977 let lang = ctx.lang;
978 if lang.kind(id) != NodeKind::Block {
979 return None;
980 }
981 let stmts = lang.children(id).to_vec();
982 if stmts.len() < 3 {
983 return None; }
985 let mut ex = vexec::Exec::new(lang);
991 let run = ex.run_prefix(&stmts).ok()?;
992 if std::env::var("CURE_DBG_P1").is_ok() {
993 eprintln!(
994 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
995 run.cuts.len(),
996 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
997 ex.budget_exhausted(),
998 ex.effect_log().len(),
999 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
1000 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
1001 ex.effect_log().iter().map(|e| match e {
1002 vexec::EffectEvent::FieldWrite(_) => "FW",
1003 vexec::EffectEvent::ClassLoad { .. } => "CL",
1004 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
1005 }).collect::<Vec<_>>()
1006 );
1007 }
1008 if ex.budget_exhausted() {
1010 return None;
1011 }
1012 let root_idx = run.root_cut_index();
1013 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
1015 if total_completed < 3 {
1016 return None;
1017 }
1018 let true_rest = run.true_rest();
1022 if !true_rest.is_empty() {
1023 for ev in ex.effect_log().iter() {
1024 if let vexec::EffectEvent::FieldWrite(k) = ev {
1025 if let Some(name) = lang.name_of_key(*k) {
1026 if lang.final_fields.contains(&name)
1031 && rest_reassigns_field(lang, &true_rest, &name)
1032 {
1033 return None;
1034 }
1035 }
1036 }
1037 }
1038 }
1039 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1041 let field_finals: Vec<(u32, vexec::VVal)> =
1042 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1043 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1044 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1047 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1048 _ => None,
1049 };
1050 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1053 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1059 for (j, cut) in run.cuts.iter().enumerate() {
1060 let range_end = if j == 0 {
1061 decl_order.len()
1062 } else {
1063 run.cuts[j - 1].decl_start
1064 };
1065 let rest_j = run.rest_for_cut(j);
1066 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1067 let mut live_names: std::collections::HashSet<String> =
1068 std::collections::HashSet::new();
1069 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1070 if ex.var_value(k).is_none() {
1071 continue; }
1073 let name = lang.name_of_key(k)?;
1074 live_names.insert(name.clone());
1075 let referenced = rest_j.iter().any(|&st| {
1076 cure_engine::analysis::subtree_contains(lang, st, |n| {
1077 lang.kind(n) == NodeKind::VarRef
1078 && lang.var_name(n) == Some(name.as_str())
1079 })
1080 });
1081 if referenced {
1082 esc.push((
1083 k,
1084 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1085 ex.var_domain(k),
1086 ex.var_decl_ty(k).unwrap_or(JType::Var),
1089 ));
1090 }
1091 }
1092 escaped_by_cut.push(esc);
1093 cut_live_names.push(live_names);
1094 }
1095 drop(ex);
1096 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1098 for (j, cut) in run.cuts.iter().enumerate() {
1099 let block_id = if j == root_idx {
1100 id
1101 } else {
1102 cut.block_hint?
1103 };
1104 let mut insert: Vec<JavaId> = Vec::new();
1105 for pos in cut.log_start..cut.log_end {
1107 match &log[pos] {
1108 vexec::EffectEvent::FieldWrite(k) => {
1109 let is_last = !log[pos + 1..]
1111 .iter()
1112 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1113 if !is_last {
1114 continue;
1115 }
1116 let name = lang.name_of_key(*k)?;
1117 if lang.is_param_name(&name) {
1119 return None;
1120 }
1121 let v = field_finals
1122 .iter()
1123 .find(|(fk, _)| fk == k)
1124 .map(|(_, v)| v.clone())?;
1125 let v = match (&v, lang.field_types.get(&name)) {
1130 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1131 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1132 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1133 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1134 _ => v,
1135 };
1136 let (val, mut extra) = materialize(lang, &v)?;
1137 let tgt = lang.var(&name);
1138 insert.push(lang.assign(tgt, val));
1139 insert.append(&mut extra);
1140 }
1141 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1142 if std::env::var("CURE_DBG_P1").is_ok() {
1143 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1144 }
1145 let name = lang.name_of_key(*k)?;
1150 if lang.is_param_name(&name) {
1152 return None;
1153 }
1154 let cch = lang.children(*call).to_vec();
1155 let mut new_args: Vec<JavaId> = Vec::new();
1156 let mut kept_original: Vec<bool> = Vec::new();
1157 for (i, &arg) in cch.iter().enumerate().skip(1) {
1158 let val = args.get(i - 1).cloned().flatten();
1159 let replaced = match val {
1160 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1161 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1162 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1163 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1164 _ => None,
1165 };
1166 kept_original.push(replaced.is_none());
1167 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1168 }
1169 let names_j = cut_live_names.get(j)?;
1173 for ((i, &arg), &kept) in
1174 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1175 {
1176 let _ = i;
1177 if kept
1178 && !names_j.is_empty()
1179 && cure_engine::analysis::subtree_contains(
1180 lang,
1181 arg,
1182 |n| {
1183 lang.kind(n) == NodeKind::VarRef
1184 && lang
1185 .var_name(n)
1186 .is_some_and(|nm| names_j.contains(nm))
1187 },
1188 )
1189 {
1190 return None;
1191 }
1192 }
1193 let callee = *lang.children(*call).first()?;
1194 let callee_copy = lang.copy_subtree(callee);
1195 let mut ch = vec![callee_copy];
1196 ch.extend(new_args);
1197 let new_call = lang.clone_node(*call, ch);
1198 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1199 lang.clone_node(*stmt, vec![new_call])
1200 } else {
1201 new_call
1202 };
1203 let tgt = lang.var(&name);
1205 let assign = lang.assign(tgt, new_stmt);
1206 insert.push(assign);
1207 }
1208 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1209 let lit = lang.lit(Lit::Str(name.clone()));
1217 let callee = *lang.children(*call).first()?;
1218 let callee_copy = lang.copy_subtree(callee);
1219 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1220 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1221 lang.clone_node(*stmt, vec![new_call])
1222 } else {
1223 new_call
1224 };
1225 let try_block = lang.block(vec![new_stmt]);
1226 let catch_block = lang.block(vec![]);
1227 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1228 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1229 insert.push(wrapped);
1230 }
1231 }
1232 }
1233 let mut alias_first: std::collections::HashMap<usize, String> =
1238 std::collections::HashMap::new();
1239 for (k, v, dom, dty) in &escaped_by_cut[j] {
1240 let name = lang.name_of_key(*k)?;
1241 let (val, mut extra) = match v {
1242 vexec::VVal::CA(a) => {
1243 let addr = std::rc::Rc::as_ptr(a) as usize;
1244 match alias_first.get(&addr) {
1245 Some(first) => (lang.var(first), vec![]),
1246 None => {
1247 alias_first.insert(addr, name.clone());
1248 materialize(lang, v)?
1249 }
1250 }
1251 }
1252 vexec::VVal::SA(a) => {
1253 let addr = std::rc::Rc::as_ptr(a) as usize;
1254 match alias_first.get(&addr) {
1255 Some(first) => (lang.var(first), vec![]),
1256 None => {
1257 alias_first.insert(addr, name.clone());
1258 materialize(lang, v)?
1259 }
1260 }
1261 }
1262 _ => materialize(lang, v)?,
1263 };
1264 let (ty, val) = match (*dom, val) {
1269 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1270 match lang.literal(lit) {
1273 Some(LitRef::Int(v)) => {
1274 match u32::try_from(v).ok().and_then(char::from_u32) {
1275 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1276 None => (JType::Char, lit),
1277 }
1278 }
1279 Some(LitRef::Long(v)) => {
1280 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1281 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1282 None => (JType::Char, lit),
1283 }
1284 }
1285 _ => (JType::Char, lit),
1286 }
1287 }
1288 (3, lit) => (JType::Byte, lit),
1289 (4, lit) => (JType::Short, lit),
1290 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1291 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1292 (JType::Long, lang.build_int(v, true))
1293 } else {
1294 (JType::Long, lit)
1295 }
1296 }
1297 (_, lit) => {
1302 if matches!(dty, JType::Ref(_)) {
1303 (dty.clone(), lit)
1304 } else {
1305 (JType::Var, lit)
1306 }
1307 }
1308 };
1309 let stmt = lang.var_decl(&name, ty, Some(val));
1310 insert.push(stmt);
1311 insert.append(&mut extra);
1312 }
1313 if j == root_idx {
1315 if let Some(v) = ret_payload.clone().flatten() {
1316 let (val, mut extra) = materialize(lang, &v)?;
1317 let stmt = lang.ret(Some(val));
1318 insert.push(stmt);
1319 insert.append(&mut extra);
1320 }
1321 }
1322 if insert.is_empty() && cut.completed == 0 {
1324 continue;
1325 }
1326 {
1329 let removed = lang.children(block_id).to_vec();
1330 if insert.len() == cut.completed
1331 && cut.completed <= removed.len()
1332 && insert
1333 .iter()
1334 .zip(removed[..cut.completed].iter())
1335 .all(|(&n, &o)| print_equiv(lang, n, o))
1336 {
1337 continue;
1338 }
1339 }
1340 edits.push(Edit::Splice {
1341 node: block_id,
1342 index: 0,
1343 remove: cut.completed,
1344 insert,
1345 });
1346 }
1347 if std::env::var("CURE_DBG_P1").is_ok() {
1348 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1349 }
1350 if edits.is_empty() {
1351 return None;
1352 }
1353 if edits.len() == 1 {
1355 Some(edits.pop().unwrap())
1356 } else {
1357 Some(Edit::Multi(edits))
1358 }
1359 }
1360}
1361
1362fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1365 use vexec::VVal;
1366 match v {
1367 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1368 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1369 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1370 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1371 VVal::CA(a) => {
1372 let chars: Vec<char> = a.borrow().clone();
1374 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1375 let lit_arr = lang.array_lit(lits);
1376 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1377 Some((node, vec![]))
1378 }
1379 VVal::SA(elems) => {
1380 let arr = elems.borrow().clone();
1383 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1384 let lits: Vec<JavaId> = arr
1385 .iter()
1386 .map(|e| match e {
1387 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1388 _ => unreachable!(),
1389 })
1390 .collect();
1391 let lit_arr = lang.array_lit(lits);
1392 let node =
1393 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1394 Some((node, vec![]))
1395 } else {
1396 None
1401 }
1402 }
1403 VVal::Undef => None,
1405 }
1406}
1407
1408pub struct TryUnwrapRethrow;
1409
1410impl Rule<JavaAst> for TryUnwrapRethrow {
1411 fn name(&self) -> &'static str {
1412 "try_unwrap_rethrow"
1413 }
1414 fn kinds(&self) -> &'static [NodeKind] {
1415 &[NodeKind::Try]
1416 }
1417 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1418 let lang = ctx.lang;
1419 if lang.kind(id) != NodeKind::Try {
1420 return None;
1421 }
1422 let ch = lang.children(id).to_vec();
1423 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1425 let catches: Vec<JavaId> =
1426 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1427 if catches.is_empty() {
1428 return None;
1429 }
1430 let rethrow_only = |c: JavaId| -> bool {
1431 let block = match lang.children(c).first() {
1433 Some(&b) => b,
1434 None => return false,
1435 };
1436 let stmts = lang.children(block);
1437 if stmts.len() != 1 {
1438 return false;
1439 }
1440 let stmt = stmts[0];
1441 if lang.kind(stmt) != NodeKind::Throw {
1442 return false;
1443 }
1444 let Some(&thrown) = lang.children(stmt).first() else { return false };
1445 if lang.kind(thrown) != NodeKind::VarRef {
1447 return false;
1448 }
1449 match &lang.node(c).data {
1450 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1451 _ => false,
1452 }
1453 };
1454 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1455 if all_rethrow {
1456 let extras: Vec<JavaId> = ch
1458 .iter()
1459 .copied()
1460 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1461 .collect();
1462 if extras.is_empty() {
1463 return Some(Edit::Replace {
1464 target: id,
1465 with: try_block,
1466 });
1467 }
1468 return None;
1469 }
1470 let catch_slots: Vec<usize> = ch
1476 .iter()
1477 .enumerate()
1478 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1479 .map(|(i, _)| i)
1480 .collect();
1481 for &slot in &catch_slots {
1482 let c = ch[slot];
1483 if !rethrow_only(c) {
1484 continue;
1485 }
1486 let later_all_rethrow = catch_slots
1487 .iter()
1488 .filter(|&&s2| s2 > slot)
1489 .all(|&s2| rethrow_only(ch[s2]));
1490 if !later_all_rethrow {
1491 continue;
1492 }
1493 return Some(Edit::Splice {
1494 node: id,
1495 index: slot,
1496 remove: 1,
1497 insert: vec![],
1498 });
1499 }
1500 None
1501 }
1502}
1503
1504pub struct TryUnwrapNoCatch;
1505
1506impl Rule<JavaAst> for TryUnwrapNoCatch {
1507 fn name(&self) -> &'static str {
1508 "try_unwrap_no_catch"
1509 }
1510 fn kinds(&self) -> &'static [NodeKind] {
1511 &[NodeKind::Try]
1512 }
1513 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1514 let lang = ctx.lang;
1515 if lang.kind(id) != NodeKind::Try {
1516 return None;
1517 }
1518 let ch = lang.children(id).to_vec();
1519 if ch.is_empty() {
1520 return None;
1521 }
1522 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1525 if has_catch {
1526 return None;
1527 }
1528 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1532 let rest: Vec<JavaId> = ch
1533 .iter()
1534 .copied()
1535 .filter(|&c| c != try_block)
1536 .collect();
1537 for &r in &rest {
1540 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1541 if !is_empty_block {
1542 return None; }
1544 }
1545 Some(Edit::Replace {
1546 target: id,
1547 with: try_block,
1548 })
1549 }
1550}
1551
1552pub struct LoopHeadBreak;
1560
1561impl Rule<JavaAst> for LoopHeadBreak {
1562 fn name(&self) -> &'static str {
1563 "loop_head_break"
1564 }
1565 fn kinds(&self) -> &'static [NodeKind] {
1566 &[NodeKind::While, NodeKind::DoWhile]
1567 }
1568 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1569 let lang = ctx.lang;
1570 let body = match lang.kind(id) {
1571 NodeKind::While => {
1572 let ch = lang.children(id).to_vec();
1573 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1574 return None;
1575 }
1576 ch[1]
1577 }
1578 NodeKind::DoWhile => {
1579 let ch = lang.children(id).to_vec();
1580 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1581 return None;
1582 }
1583 ch[0]
1584 }
1585 _ => return None,
1586 };
1587 if lang.kind(body) != NodeKind::Block {
1588 return None;
1589 }
1590 let bch = lang.children(body).to_vec();
1591 let is_do = lang.kind(id) == NodeKind::DoWhile;
1592 if is_do && bch.len() != 1 {
1594 return None;
1595 }
1596 let first = *bch.first()?;
1597 if lang.kind(first) != NodeKind::If {
1598 return None;
1599 }
1600 let ich = lang.children(first).to_vec();
1601 let c = ich[0];
1602
1603 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1604 let is_break = |n: JavaId| match lang.kind(n) {
1605 NodeKind::Break => bare_break(n),
1606 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1607 _ => false,
1608 };
1609 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1610 let ends_with_continue = |n: JavaId| -> bool {
1611 match lang.kind(n) {
1612 NodeKind::Continue => bare_continue(n),
1613 NodeKind::Block => lang
1614 .children(n)
1615 .last()
1616 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1617 _ => false,
1618 }
1619 };
1620 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1621 match lang.kind(n) {
1622 NodeKind::Continue => vec![],
1623 NodeKind::Block => {
1624 let ch = lang.children(n).to_vec();
1625 if ch
1626 .last()
1627 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1628 {
1629 ch[..ch.len() - 1].to_vec()
1630 } else {
1631 ch
1632 }
1633 }
1634 _ => vec![n],
1635 }
1636 };
1637
1638 match ich.len() {
1639 2 => {
1640 if is_do || !is_break(ich[1]) {
1642 return None;
1643 }
1644 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1645 if let Some(&last) = rest.last() {
1646 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1647 rest.pop();
1648 }
1649 }
1650 let nc = lang.build_unary(UnOp::Not, c);
1651 let inner = lang.build_block(rest);
1652 let w = lang.while_(nc, inner);
1653 Some(Edit::Replace {
1654 target: id,
1655 with: w,
1656 })
1657 }
1658 3 => {
1659 let (then, els) = (ich[1], ich[2]);
1660 if ends_with_continue(then) && is_break(els) {
1662 let rest = strip_tail_continue(then);
1663 let inner = lang.build_block(rest);
1664 let w = lang.while_(c, inner);
1665 return Some(Edit::Replace {
1666 target: id,
1667 with: w,
1668 });
1669 }
1670 if is_break(then) && ends_with_continue(els) {
1672 let rest = strip_tail_continue(els);
1673 let inner = lang.build_block(rest);
1674 let nc = lang.build_unary(UnOp::Not, c);
1675 let w = lang.while_(nc, inner);
1676 return Some(Edit::Replace {
1677 target: id,
1678 with: w,
1679 });
1680 }
1681 if !is_do && is_break(then) {
1683 let mut all = strip_tail_continue(els);
1684 all.extend(bch[1..].iter().copied());
1685 let inner = lang.build_block(all);
1686 let nc = lang.build_unary(UnOp::Not, c);
1687 let w = lang.while_(nc, inner);
1688 return Some(Edit::Replace {
1689 target: id,
1690 with: w,
1691 });
1692 }
1693 None
1694 }
1695 _ => None,
1696 }
1697 }
1698}
1699
1700pub struct WhileIteratorToForEach;
1701
1702impl Rule<JavaAst> for WhileIteratorToForEach {
1703 fn name(&self) -> &'static str {
1704 "while_iterator_to_for_each"
1705 }
1706 fn kinds(&self) -> &'static [NodeKind] {
1707 &[NodeKind::While]
1708 }
1709 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1710 let parent = ctx.parent(id)?;
1712 let idx = ctx.index(id)?;
1713 let lang = ctx.lang;
1714 if lang.kind(id) != NodeKind::While {
1715 return None;
1716 }
1717 let wch = lang.children(id).to_vec();
1718 let body = wch[1];
1719 if lang.kind(parent) != NodeKind::Block {
1721 return None;
1722 }
1723 if idx == 0 {
1724 return None;
1725 }
1726 let stmts = lang.children(parent).to_vec();
1727 let cond = wch[0];
1730 let it_hint = match lang.kind(cond) {
1731 NodeKind::Call => {
1732 let cc0 = lang.children(cond).to_vec();
1733 match lang.data(*cc0.first()?) {
1734 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1735 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1736 }
1737 _ => None,
1738 }
1739 }
1740 _ => None,
1741 };
1742 let mut decl = None;
1743 if let Some(hint) = it_hint {
1744 let mut up = idx;
1745 for _ in 0..4 {
1746 if up == 0 {
1747 break;
1748 }
1749 up -= 1;
1750 let st = stmts[up];
1751 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1752 decl = Some(st);
1753 break;
1754 }
1755 if subtree_has_var(lang, st, &hint) {
1756 break;
1757 }
1758 }
1759 }
1760 let decl = decl?;
1761 let it_name = lang.var_name(decl)?.to_string();
1762 if stmts[idx + 1..]
1767 .iter()
1768 .any(|&s| subtree_has_var(lang, s, &it_name))
1769 {
1770 return None;
1771 }
1772 let init_call = lang.children(decl).first().copied()?;
1773 if lang.kind(init_call) != NodeKind::Call {
1774 return None;
1775 }
1776 let ic = lang.children(init_call).to_vec();
1777 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1778 return None;
1779 };
1780 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1781 return None;
1782 }
1783 let iterable = lang.children(ic[0])[0];
1784 if subtree_has_var(&*lang, iterable, &it_name) {
1785 return None;
1786 }
1787 {
1797 let ty = lang.var_type(iterable);
1798 let ok = ty.is_some_and(|t| statically_iterable(t));
1799 if !ok {
1800 return None;
1801 }
1802 }
1803 if lang.kind(cond) != NodeKind::Call {
1805 return None;
1806 }
1807 let cc = lang.children(cond).to_vec();
1808 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1809 return None;
1810 };
1811 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1812 return None;
1813 }
1814 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1815 return None;
1816 }
1817 if lang.kind(body) != NodeKind::Block {
1819 return None;
1820 }
1821 let bch = lang.children(body).to_vec();
1822 let first = *bch.first()?;
1823 if lang.kind(first) != NodeKind::VarDecl {
1824 let it_refs_in_body: Vec<JavaId> = {
1827 let mut v = Vec::new();
1828 collect_var_refs(&*lang, body, &it_name, &mut v);
1829 v
1830 };
1831 if it_refs_in_body.len() != 1 {
1832 return None;
1833 }
1834 let next_recv = it_refs_in_body[0];
1835 let member = parent_of_recv(lang, body, next_recv);
1837 if lang.kind(member) != NodeKind::Member {
1838 return None;
1839 }
1840 let NodeData::Member { name: mn } = lang.data(member) else {
1841 return None;
1842 };
1843 if lang.sn(*mn) != "next" {
1844 return None;
1845 }
1846 let next_call = parent_of_recv(lang, body, member);
1847 if lang.kind(next_call) != NodeKind::Call {
1848 return None;
1849 }
1850 if lang.children(next_call).len() != 1 {
1851 return None;
1852 }
1853 let mut wrapped = next_call;
1855 loop {
1856 let par = parent_of_recv(lang, body, wrapped);
1857 match lang.kind(par) {
1858 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1859 _ => break,
1860 }
1861 }
1862 {
1872 let mut cur = wrapped;
1873 while cur != body {
1874 let par = parent_of_recv(lang, body, cur);
1875 if par == body || par == cur {
1876 break; }
1878 let ch = lang.children(par).to_vec();
1879 let idx = ch.iter().position(|&c| c == cur);
1880 match lang.kind(par) {
1881 NodeKind::Block
1882 | NodeKind::ExprStmt
1883 | NodeKind::Label
1884 | NodeKind::Return
1885 | NodeKind::Throw => {}
1886 NodeKind::Ternary => {
1887 if idx.unwrap_or(0) > 0 {
1889 return None;
1890 }
1891 }
1892 NodeKind::Binary => {
1893 if let Some(op) = lang.bin_op(par) {
1894 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1895 return None; }
1897 }
1898 }
1899 NodeKind::If
1900 | NodeKind::While
1901 | NodeKind::DoWhile
1902 | NodeKind::For
1903 | NodeKind::Switch
1904 | NodeKind::Try
1905 | NodeKind::Catch
1906 | NodeKind::Synchronized
1907 | NodeKind::ForEach => return None,
1908 NodeKind::Lambda | NodeKind::Assert => return None,
1913 _ => {} }
1915 cur = par;
1916 }
1917 }
1918 let it_decl_elem = match lang.data(decl) {
1926 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
1927 _ => None,
1928 };
1929 let elem = match it_decl_elem {
1930 Some(t) => t,
1931 None => match lang.var_type(iterable) {
1932 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
1933 None => JType::Ref("Object".into()),
1934 },
1935 };
1936 let elem = if for_each_elem_ok(&elem) {
1937 elem
1938 } else {
1939 JType::Ref("Object".into())
1940 };
1941 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
1942 let (replace_target, e_ty) = match lang.data(wrapped) {
1943 NodeData::Cast { .. } if is_object => (next_call, elem),
1944 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
1945 _ => (wrapped, elem),
1946 };
1947 let taken = |nm: &str| {
1951 subtree_has_var(&*lang, body, nm)
1952 || subtree_has_binding(&*lang, body, nm)
1953 };
1954 let e_name = if taken("e") {
1955 if taken("e2") { "e3" } else { "e2" }
1956 } else {
1957 "e"
1958 };
1959 let e_ref = lang.var(e_name);
1960 let foreach = lang.for_each(e_name, e_ty, iterable, body);
1961 let wrapped_parent = parent_of_recv(lang, body, wrapped);
1965 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
1966 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
1967 {
1968 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
1969 if let Some(si) = lang
1970 .children(stmt_block)
1971 .iter()
1972 .position(|&c| c == wrapped_parent)
1973 {
1974 return Some(Edit::Multi(vec![
1975 Edit::Splice {
1976 node: stmt_block,
1977 index: si,
1978 remove: 1,
1979 insert: vec![],
1980 },
1981 Edit::Splice {
1982 node: parent,
1983 index: idx,
1984 remove: 1,
1985 insert: vec![foreach],
1986 },
1987 Edit::Delete { node: decl },
1988 ]));
1989 }
1990 }
1991 return Some(Edit::Multi(vec![
1992 Edit::Replace {
1993 target: replace_target,
1994 with: e_ref,
1995 },
1996 Edit::Splice {
1997 node: parent,
1998 index: idx,
1999 remove: 1,
2000 insert: vec![foreach],
2001 },
2002 Edit::Delete { node: decl },
2003 ]));
2004 }
2005 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
2006 return None;
2007 };
2008 let (e_name, ty) = (e_name.clone(), ty.clone());
2009 let next_call = lang.children(first).first().copied()?;
2010 let next_call = unwrap_paren_cast(&*lang, next_call);
2011 if lang.kind(next_call) != NodeKind::Call {
2012 return None;
2013 }
2014 let nc = lang.children(next_call).to_vec();
2015 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
2016 return None;
2017 };
2018 if lang.sn(*m2) != "next" || nc.len() != 1 {
2019 return None;
2020 }
2021 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
2022 return None;
2023 }
2024 let elem_is_object = match lang.var_type(iterable) {
2026 Some(it_t) => generic_elem_ty(it_t).is_none(),
2027 None => true,
2028 };
2029 if elem_is_object && ty != JType::Ref("Object".into()) {
2030 return None;
2031 }
2032 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2034 let rest = bch[1..].to_vec();
2035 for &r in &rest {
2036 if subtree_has_var(&*lang, r, &it_name) {
2037 return None;
2038 }
2039 }
2040 let new_body = lang.build_block(rest);
2041 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2042 Some(Edit::Multi(vec![
2045 Edit::Splice {
2046 node: parent,
2047 index: idx,
2048 remove: 1,
2049 insert: vec![foreach],
2050 },
2051 Edit::Delete { node: decl },
2052 ]))
2053 }
2054}
2055
2056pub struct ConcatValueOfDrop;
2065
2066impl Rule<JavaAst> for ConcatValueOfDrop {
2067 fn name(&self) -> &'static str {
2068 "concat_value_of_drop"
2069 }
2070 fn kinds(&self) -> &'static [NodeKind] {
2071 &[NodeKind::Binary]
2072 }
2073 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2074 let root = ctx.root();
2075 let lang = ctx.lang;
2076 if lang.kind(id) != NodeKind::Binary {
2077 return None;
2078 }
2079 if lang.bin_op(id) != Some(BinOp::Add) {
2080 return None;
2081 }
2082 let ch = lang.children(id).to_vec();
2083 let (l, r) = (ch[0], ch[1]);
2084 let becomes_constant = |x: JavaId, other: JavaId| {
2087 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2088 };
2089 let is_value_of = |n: JavaId| -> Option<JavaId> {
2090 if lang.kind(n) != NodeKind::Call {
2091 return None;
2092 }
2093 let c = lang.children(n).to_vec();
2094 if c.len() != 2 {
2095 return None;
2096 }
2097 let callee = c[0];
2098 if let NodeData::Member { name } = lang.data(callee) {
2099 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2100 return Some(c[1]);
2101 }
2102 }
2103 if let NodeData::VarRef { name } = lang.data(callee) {
2104 if lang.sn(*name) == "valueOf" {
2105 return Some(c[1]);
2106 }
2107 }
2108 None
2109 };
2110 let stringy = |n: JavaId| -> bool {
2111 matches!(lang.literal(n), Some(LitRef::Str(_)))
2112 || lang
2113 .var_type(n)
2114 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2115 };
2116 if let Some(x) = is_value_of(l) {
2118 if stringy(r) {
2119 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2120 return None;
2121 }
2122 let with = lang.build_bin(BinOp::Add, x, r);
2123 return Some(Edit::Replace {
2124 target: id,
2125 with,
2126 });
2127 }
2128 }
2129 if let Some(x) = is_value_of(r) {
2131 if stringy(l) {
2132 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2133 return None;
2134 }
2135 let with = lang.build_bin(BinOp::Add, l, x);
2136 return Some(Edit::Replace {
2137 target: id,
2138 with,
2139 });
2140 }
2141 }
2142 None
2143 }
2144}
2145
2146
2147pub struct StringBuilderStatements;
2160
2161fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2162 let mut n = 0;
2165 let mut stack = vec![root];
2166 while let Some(x) = stack.pop() {
2167 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2168 let ch = lang.children(x).to_vec();
2169 if let Some(&t) = ch.first() {
2170 let is_named_target =
2171 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2172 if !is_named_target {
2173 stack.push(t);
2174 }
2175 for &c in &ch[1..] {
2176 stack.push(c);
2177 }
2178 }
2179 continue;
2180 }
2181 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2182 n += 1;
2183 }
2184 for &c in lang.children(x) {
2185 stack.push(c);
2186 }
2187 }
2188 n
2189}
2190
2191impl Rule<JavaAst> for StringBuilderStatements {
2192 fn name(&self) -> &'static str {
2193 "string_builder_statements"
2194 }
2195 fn kinds(&self) -> &'static [NodeKind] {
2196 &[NodeKind::VarDecl]
2197 }
2198 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2199 let parent = ctx.parent(id)?;
2201 let idx = ctx.index(id)?;
2202 let root = ctx.root();
2203 let lang = ctx.lang;
2204
2205 if lang.kind(id) != NodeKind::VarDecl {
2206 return None;
2207 }
2208 if lang.kind(parent) != NodeKind::Block {
2209 return None;
2210 }
2211 let v0 = lang.var_name(id)?.to_string();
2212 let init = *lang.children(id).first()?;
2214 if lang.kind(init) != NodeKind::Call {
2215 return None;
2216 }
2217 let ic = lang.children(init).to_vec();
2218 if ic.len() != 2 {
2219 return None;
2220 }
2221 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2222 return None;
2223 };
2224 if lang.sn(*m0) != "append" {
2225 return None;
2226 }
2227 let sb_new = lang.children(ic[0])[0];
2228 let (ctor_arg, a0) = match lang.data(sb_new) {
2229 NodeData::New { ty, .. } => {
2230 let is_sb = !lang.unit_shadows_lang
2231 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2232 if !is_sb {
2233 return None;
2234 }
2235 let args = lang.children(sb_new).to_vec();
2236 match args.len() {
2237 0 => (None, ic[1]),
2238 1 => {
2239 let a = args[0];
2240 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2241 || lang
2242 .var_type(a)
2243 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2244 if stringy {
2245 (Some(a), ic[1])
2246 } else if matches!(
2247 lang.literal(a),
2248 Some(LitRef::Int(_) | LitRef::Long(_))
2249 ) {
2250 (None, ic[1])
2252 } else {
2253 return None; }
2255 }
2256 _ => return None,
2257 }
2258 }
2259 _ => return None,
2260 };
2261
2262 let stmts = lang.children(parent).to_vec();
2263 let mut parts: Vec<JavaId> = vec![a0];
2265 let mut expected_reads: std::collections::HashMap<String, usize> =
2266 std::collections::HashMap::new();
2267 let mut chain_len = 1usize; let mut cur = v0.clone();
2269 while idx + chain_len < stmts.len() {
2270 let s = stmts[idx + chain_len];
2271 let (recv, arg, new_var, consumed) = match lang.data(s) {
2272 NodeData::Assign { op: None } => {
2274 let ch = lang.children(s).to_vec();
2275 if ch.len() != 2 {
2276 break;
2277 }
2278 let (t, v) = (ch[0], ch[1]);
2279 if lang.kind(t) != NodeKind::VarRef
2280 || lang.var_name(t) != Some(cur.as_str())
2281 || lang.kind(v) != NodeKind::Call
2282 {
2283 break;
2284 }
2285 let vc = lang.children(v).to_vec();
2286 if vc.len() != 2 {
2287 break;
2288 }
2289 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2290 break;
2291 };
2292 if lang.sn(*mn) != "append" {
2293 break;
2294 }
2295 let recv = lang.children(vc[0])[0];
2296 (recv, vc[1], false, s)
2297 }
2298 NodeData::ExprStmt => {
2299 let ch = lang.children(s).to_vec();
2300 if ch.len() != 1 {
2301 break;
2302 }
2303 let inner = ch[0];
2304 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2305 break;
2306 }
2307 let ach = lang.children(inner).to_vec();
2308 if ach.len() != 2 {
2309 break;
2310 }
2311 let (t, v) = (ach[0], ach[1]);
2312 if lang.kind(t) != NodeKind::VarRef
2313 || lang.var_name(t) != Some(cur.as_str())
2314 || lang.kind(v) != NodeKind::Call
2315 {
2316 break;
2317 }
2318 let vc = lang.children(v).to_vec();
2319 if vc.len() != 2 {
2320 break;
2321 }
2322 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2323 break;
2324 };
2325 if lang.sn(*mn) != "append" {
2326 break;
2327 }
2328 let recv = lang.children(vc[0])[0];
2329 (recv, vc[1], false, s)
2330 }
2331 NodeData::VarDecl { .. } => {
2333 let init = match lang.children(s).first() {
2334 Some(&x) => x,
2335 None => break,
2336 };
2337 if lang.kind(init) != NodeKind::Call {
2338 break;
2339 }
2340 let vc = lang.children(init).to_vec();
2341 if vc.len() != 2 {
2342 break;
2343 }
2344 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2345 break;
2346 };
2347 if lang.sn(*mn) != "append" {
2348 break;
2349 }
2350 let recv = lang.children(vc[0])[0];
2351 if lang.var_name(recv) != Some(cur.as_str()) {
2352 break;
2353 }
2354 (recv, vc[1], true, s)
2355 }
2356 _ => break,
2357 };
2358 if lang.var_name(recv) != Some(cur.as_str()) {
2360 break;
2361 }
2362 parts.push(arg);
2363 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2364 if new_var {
2365 cur = lang.var_name(consumed)?.to_string();
2366 }
2367 chain_len += 1;
2368 let _ = consumed;
2369 }
2370
2371 let final_stmt = *stmts.get(idx + chain_len)?;
2373 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2374 return None;
2375 };
2376 let s_init = *lang.children(final_stmt).first()?;
2377 if lang.kind(s_init) != NodeKind::Call {
2378 return None;
2379 }
2380 let sc = lang.children(s_init).to_vec();
2381 if sc.len() != 1 {
2382 return None;
2383 }
2384 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2385 return None;
2386 };
2387 if lang.sn(*mn) != "toString" {
2388 return None;
2389 }
2390 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2391 return None;
2392 }
2393
2394 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2398 if count_var_uses_in(&*lang, parent, v) != *exp {
2399 return None;
2400 }
2401 }
2402
2403 let mut all_parts: Vec<JavaId> = Vec::new();
2405 if let Some(ca) = ctor_arg {
2406 all_parts.push(ca);
2407 }
2408 all_parts.extend(parts);
2409 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2410 && lang.has_string_identity_compare(root)
2411 {
2412 return None;
2413 }
2414 if all_parts.is_empty() {
2415 let empty = lang.build_str("");
2416 return Some(Edit::Multi(vec![
2417 Edit::Replace {
2418 target: s_init,
2419 with: empty,
2420 },
2421 Edit::Splice {
2422 node: parent,
2423 index: idx,
2424 remove: chain_len,
2425 insert: Vec::new(),
2426 },
2427 ]));
2428 }
2429 let first = all_parts[0];
2430 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2431 || lang
2432 .var_type(first)
2433 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2434 let mut acc = if stringy_first {
2435 first
2436 } else {
2437 let empty = lang.build_str("");
2438 lang.build_bin(BinOp::Add, empty, first)
2439 };
2440 for &p in &all_parts[1..] {
2441 acc = lang.build_bin(BinOp::Add, acc, p);
2442 }
2443 Some(Edit::Multi(vec![
2444 Edit::Replace {
2445 target: s_init,
2446 with: acc,
2447 },
2448 Edit::Splice {
2449 node: parent,
2450 index: idx,
2451 remove: chain_len,
2452 insert: Vec::new(),
2453 },
2454 ]))
2455 }
2456}
2457
2458
2459pub struct TrailingContinueJava;
2466
2467impl Rule<JavaAst> for TrailingContinueJava {
2468 fn name(&self) -> &'static str {
2469 "trailing_continue"
2470 }
2471 fn kinds(&self) -> &'static [NodeKind] {
2472 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2473 }
2474 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2475 let parent = ctx.parent(id)?;
2476 let lang = ctx.lang;
2477 let body = match lang.kind(id) {
2478 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2479 *lang.children(id).last()?
2480 }
2481 _ => return None,
2482 };
2483 if lang.kind(body) != NodeKind::Block {
2484 return None;
2485 }
2486 let ch = lang.children(body).to_vec();
2487 let last = *ch.last()?;
2488 if lang.kind(last) != NodeKind::Continue {
2489 return None;
2490 }
2491 let label = match lang.data(last) {
2492 NodeData::Continue { label } => label.clone(),
2493 _ => return None,
2494 };
2495 match label {
2496 None => Some(Edit::Delete { node: last }),
2497 Some(l) => {
2498 if lang.kind(parent) == NodeKind::Label {
2500 if let NodeData::Label { name } = lang.data(parent) {
2501 if *name == l {
2502 return Some(Edit::Delete { node: last });
2503 }
2504 }
2505 }
2506 None
2507 }
2508 }
2509 }
2510}
2511
2512
2513pub struct XorNoise;
2521
2522impl Rule<JavaAst> for XorNoise {
2523 fn name(&self) -> &'static str {
2524 "xor_noise"
2525 }
2526 fn kinds(&self) -> &'static [NodeKind] {
2527 &[NodeKind::Binary]
2528 }
2529 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2530 let lang = ctx.lang;
2531 if lang.kind(id) != NodeKind::Binary {
2532 return None;
2533 }
2534 let op = lang.bin_op(id)?;
2535 if op != BinOp::BitXor {
2536 return None;
2537 }
2538 let ch = lang.children(id).to_vec();
2539 let (l, r) = (ch[0], ch[1]);
2540 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2542 if int_lit(r) == Some(0) {
2543 return Some(Edit::Replace {
2544 target: id,
2545 with: l,
2546 });
2547 }
2548 if int_lit(l) == Some(0) {
2549 return Some(Edit::Replace {
2550 target: id,
2551 with: r,
2552 });
2553 }
2554 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2556 return None;
2557 }
2558 let ich = lang.children(l).to_vec();
2559 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2560 let k2 = int_lit(r)?;
2561 let x = ich[0];
2562 let k = k1 ^ k2;
2563 let with = if k == 0 {
2564 x
2565 } else {
2566 let lit = lang.build_int(k, false);
2567 lang.build_bin(BinOp::BitXor, x, lit)
2568 };
2569 Some(Edit::Replace {
2570 target: id,
2571 with,
2572 })
2573 }
2574}
2575
2576pub struct StrLenFold;
2581
2582impl Rule<JavaAst> for StrLenFold {
2583 fn name(&self) -> &'static str {
2584 "str_len_fold"
2585 }
2586 fn kinds(&self) -> &'static [NodeKind] {
2587 &[NodeKind::Call]
2588 }
2589 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2590 let _at_stmt_pos = ctx
2592 .parent(id)
2593 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2594 .unwrap_or(false);
2595 let lang = ctx.lang;
2596 if lang.kind(id) != NodeKind::Call {
2597 return None;
2598 }
2599 let ch = lang.children(id).to_vec();
2600 if ch.len() != 1 {
2601 return None;
2602 }
2603 let NodeData::Member { name } = lang.data(ch[0]) else {
2604 return None;
2605 };
2606 if lang.sn(*name) != "length" {
2607 return None;
2608 }
2609 let recv = lang.children(ch[0])[0];
2610 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2611 let with = lang.build_int(s.chars().count() as i64, false);
2612 return Some(Edit::Replace {
2613 target: id,
2614 with,
2615 });
2616 }
2617 None
2618 }
2619}
2620
2621
2622pub struct LiteralEval;
2635
2636impl Rule<JavaAst> for LiteralEval {
2637 fn name(&self) -> &'static str {
2638 "literal_eval"
2639 }
2640 fn kinds(&self) -> &'static [NodeKind] {
2641 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2642 }
2643 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2644 let root = ctx.root();
2645 let lang = ctx.lang;
2646 let edit = match lang.kind(id) {
2647 NodeKind::Call => self.eval_call(&mut *lang, id),
2648 NodeKind::Index => self.eval_index(&mut *lang, id),
2649 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2650 _ => None,
2651 };
2652 if let Some(Edit::Replace { with, .. }) = &edit {
2656 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2657 && lang.has_string_identity_compare(root)
2658 {
2659 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2660 eprintln!("[gate] blocked literal_eval at {id:?}");
2661 }
2662 return None;
2663 }
2664 }
2665 edit
2666 }
2667}
2668
2669impl LiteralEval {
2670 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2671 let ch = lang.children(id).to_vec();
2672 let callee = ch[0];
2673 let NodeData::Member { name: method } = lang.data(callee) else {
2674 return None;
2675 };
2676 let method = *method;
2677 let recv = lang.children(callee)[0];
2678 let args: Vec<Lit> = ch[1..]
2680 .iter()
2681 .map(|&a| litref_to_lit(lang.literal(a)))
2682 .collect::<Option<Vec<_>>>()?;
2683
2684 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2685 eval_str_method(s, lang.sn(method), &args)
2686 } else if let Some(cls) = lang.var_name(recv) {
2687 eval_static_method(cls, lang.sn(method), &args)
2688 } else {
2689 None
2690 };
2691 let lit = result?;
2692 let with = lit_to_node(lang, &lit);
2693 Some(Edit::Replace {
2694 target: id,
2695 with,
2696 })
2697 }
2698
2699 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2700 let ch = lang.children(id).to_vec();
2702 let arr = match lang.kind(ch[0]) {
2704 NodeKind::ArrayLit => ch[0],
2705 NodeKind::NewArray => {
2706 let nc = lang.children(ch[0]);
2707 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2708 return None;
2709 };
2710 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2711 return None;
2712 }
2713 nc[0]
2714 }
2715 _ => return None,
2716 };
2717 let idx = lang.literal(ch[1])?.as_int()?;
2718 let elems = lang.children(arr).to_vec();
2719 if idx < 0 || idx >= elems.len() as i64 {
2720 return None;
2721 }
2722 Some(Edit::Replace {
2723 target: id,
2724 with: elems[idx as usize],
2725 })
2726 }
2727}
2728
2729fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2730 Some(match l? {
2731 LitRef::Str(s) => Lit::Str(s.to_string()),
2732 LitRef::Int(v) => Lit::Int(v),
2733 LitRef::Long(v) => Lit::Long(v),
2734 LitRef::Bool(b) => Lit::Bool(b),
2735 LitRef::Char(c) => Lit::Char(c),
2736 LitRef::Float(v) => Lit::Float(v),
2737 LitRef::Double(v) => Lit::Double(v),
2738 LitRef::Null => return None,
2739 })
2740}
2741
2742fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2743 match lit {
2744 Lit::Str(s) => lang.build_str(s),
2745 Lit::Int(v) => lang.build_int(*v, false),
2746 Lit::Long(v) => lang.build_int(*v, true),
2747 Lit::Bool(b) => lang.build_bool(*b),
2748 Lit::Char(c) => lang.build_char(*c),
2749 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2750 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2751 _ => lang.lit(Lit::Null),
2752 }
2753}
2754
2755fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2757 let NodeData::Cast { ty } = lang.data(id) else {
2758 return None;
2759 };
2760 let ty = ty.clone();
2761 let inner = lang.children(id)[0];
2762 let lit = litref_to_lit(lang.literal(inner))?;
2763 let with = match (&ty, &lit) {
2764 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2765 lang.build_char(char::from_u32(*v as u32)?)
2766 }
2767 (JType::Char, Lit::Char(_)) => inner,
2768 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2769 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2770 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2771 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2772 lang.build_int(*v, false)
2773 }
2774 (JType::Int, Lit::Int(_)) => inner,
2775 _ => return None,
2776 };
2777 Some(Edit::Replace {
2778 target: id,
2779 with,
2780 })
2781}
2782
2783fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2784 let chars: Vec<char> = s.chars().collect();
2785 let n = chars.len() as i64;
2786 Some(match (method, args) {
2787 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2788 ("intern", []) => Lit::Str(s.to_string()),
2790 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2791 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2792 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2793 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2794 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2795 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2796 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2797 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2798 }
2799 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2800 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2801 ),
2802 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2803 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2804 ),
2805 ("length", []) => Lit::Int(n),
2806 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2807 Lit::Char(chars[*i as usize])
2808 }
2809 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2810 Lit::Str(s.chars().skip(*b as usize).collect())
2811 }
2812 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2813 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2814 }
2815 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2816 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2817 }
2818 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2819 Lit::Str(s.replace(a.as_str(), b))
2820 }
2821 ("hashCode", []) => Lit::Int(
2823 s.chars()
2824 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2825 as i64,
2826 ),
2827 _ => return None,
2828 })
2829}
2830
2831fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2832 let cls = cls.rsplit('.').next().unwrap_or(cls);
2833 Some(match (cls, method, args) {
2834 ("Integer", "parseInt", [Lit::Str(s)]) => {
2835 Lit::Int(s.trim().parse::<i64>().ok()?)
2836 }
2837 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2838 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2839 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2840 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2841 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2842 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2843 Lit::Bool(s.eq_ignore_ascii_case("true"))
2844 }
2845 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2846 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2847 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2848 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2849 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2850 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2851 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2852 let fmt = match &fmt_args[0] {
2853 Lit::Str(s) => s.clone(),
2854 _ => return None,
2855 };
2856 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2857 Lit::Str(out)
2858 }
2859 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2860 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2861 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2862 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2863 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2864 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2865 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2866 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2867 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2868 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2869 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2870 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2871 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2872 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2873 _ => return None,
2874 })
2875}
2876
2877fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2879 let mut out = String::new();
2880 let mut it = fmt.chars().peekable();
2881 let mut arg_i = 0usize;
2882 while let Some(c) = it.next() {
2883 if c != '%' {
2884 out.push(c);
2885 continue;
2886 }
2887 match it.next()? {
2888 '%' => out.push('%'),
2889 's' => {
2890 let a = args.get(arg_i)?;
2891 arg_i += 1;
2892 match a {
2893 Lit::Str(s) => out.push_str(s),
2894 Lit::Char(ch) => out.push(*ch),
2895 Lit::Bool(b) => out.push_str(&b.to_string()),
2896 _ => out.push_str(&lit_debug_str(a)),
2897 }
2898 }
2899 'd' => {
2900 let a = args.get(arg_i)?;
2901 arg_i += 1;
2902 match a {
2903 Lit::Int(v) => out.push_str(&v.to_string()),
2904 Lit::Long(v) => out.push_str(&v.to_string()),
2905 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
2906 _ => return None,
2907 }
2908 }
2909 _ => return None,
2911 }
2912 }
2913 if arg_i != args.len() {
2914 return None; }
2916 Some(out)
2917}
2918
2919fn lit_debug_str(l: &Lit) -> String {
2920 match l {
2921 Lit::Int(v) => v.to_string(),
2922 Lit::Long(v) => v.to_string(),
2923 _ => "?".into(),
2924 }
2925}
2926
2927fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
2950 match ast.data(m) {
2951 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
2952 _ => None,
2953 }
2954}
2955
2956pub struct UrlDecodeFold;
2957
2958impl Rule<JavaAst> for UrlDecodeFold {
2959 fn name(&self) -> &'static str {
2960 "url_decode_fold"
2961 }
2962 fn kinds(&self) -> &'static [NodeKind] {
2963 &[NodeKind::Call]
2964 }
2965 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2966 let lang = ctx.lang;
2967 let ch = lang.children(id).to_vec();
2970 if ch.len() != 3 {
2971 return None; }
2973 let callee = ch[0];
2974 if lang.kind(callee) != NodeKind::Member {
2975 return None;
2976 }
2977 if member_name(lang, callee).as_deref() != Some("decode") {
2978 return None;
2979 }
2980 let recv = *lang.children(callee).first()?;
2982 let recv_name = match lang.kind(recv) {
2983 NodeKind::Member => member_name(lang, recv)?,
2984 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
2985 _ => return None,
2986 };
2987 if !recv_name.ends_with("URLDecoder") {
2988 return None;
2989 }
2990 let s_lit = match lang.literal(ch[1])? {
2991 LitRef::Str(s) => s.to_string(),
2992 _ => return None,
2993 };
2994 let cs = match lang.literal(ch[2])? {
2996 LitRef::Str(s) => s.to_string(),
2997 _ => return None,
2998 };
2999 if cs != "UTF-8" {
3000 return None;
3001 }
3002 if s_lit.len() > 8192 {
3004 return None;
3005 }
3006 let decoded = url_decode(&s_lit)?;
3007 if decoded.chars().any(|c| {
3009 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
3010 }) {
3011 return None;
3012 }
3013 let lit = lang.lit(Lit::Str(decoded));
3014 Some(Edit::Replace { target: id, with: lit })
3015 }
3016}
3017
3018fn url_decode(s: &str) -> Option<String> {
3021 let b = s.as_bytes();
3022 let mut out: Vec<u8> = Vec::with_capacity(b.len());
3023 let mut i = 0usize;
3024 while i < b.len() {
3025 match b[i] {
3026 b'+' => {
3027 out.push(b' ');
3028 i += 1;
3029 }
3030 b'%' => {
3031 if i + 3 > b.len() {
3032 return None;
3033 }
3034 let hi = hex_val(b[i + 1])?;
3035 let lo = hex_val(b[i + 2])?;
3036 out.push(hi * 16 + lo);
3037 i += 3;
3038 }
3039 c => {
3040 out.push(c);
3041 i += 1;
3042 }
3043 }
3044 }
3045 String::from_utf8(out).ok()
3046}
3047
3048fn hex_val(c: u8) -> Option<u8> {
3049 match c {
3050 b'0'..=b'9' => Some(c - b'0'),
3051 b'a'..=b'f' => Some(c - b'a' + 10),
3052 b'A'..=b'F' => Some(c - b'A' + 10),
3053 _ => None,
3054 }
3055}
3056
3057pub struct Base64NewStringFold;
3058
3059impl Rule<JavaAst> for Base64NewStringFold {
3060 fn name(&self) -> &'static str {
3061 "base64_new_string_fold"
3062 }
3063 fn kinds(&self) -> &'static [NodeKind] {
3064 &[NodeKind::New]
3065 }
3066 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3067 let root = ctx.root();
3068 let lang = ctx.lang;
3069 let NodeData::New { ty, .. } = lang.data(id) else {
3070 return None;
3071 };
3072 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3073 return None;
3074 }
3075 if lang.has_string_identity_compare(root) {
3077 return None;
3078 }
3079 let args = lang.children(id).to_vec();
3080 if args.len() != 1 {
3081 return None;
3082 }
3083 if lang.kind(args[0]) != NodeKind::Call {
3085 return None;
3086 }
3087 let dc = lang.children(args[0]).to_vec();
3088 if dc.len() != 2 {
3089 return None;
3090 }
3091 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3092 return None;
3093 };
3094 if lang.sn(*dn) != "decode" {
3095 return None;
3096 }
3097 let recv = lang.children(dc[0])[0];
3099 if lang.kind(recv) != NodeKind::Call {
3100 return None;
3101 }
3102 let gc = lang.children(recv).to_vec();
3103 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3104 return None;
3105 };
3106 let url_safe = match lang.sn(*gn) {
3107 "getDecoder" => false,
3108 "getUrlDecoder" => true,
3109 _ => return None,
3110 };
3111 let owner = lang.children(gc[0])[0];
3113 let owner_ok = match lang.data(owner) {
3114 NodeData::Member { name } => lang.sn(*name) == "Base64",
3115 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3116 };
3117 if !owner_ok {
3118 return None;
3119 }
3120 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3121 return None;
3122 };
3123 let bytes = base64_decode(b64, url_safe)?;
3124 let text = String::from_utf8(bytes).ok()?;
3125 let with = lang.build_str(&text);
3126 Some(Edit::Replace {
3127 target: id,
3128 with,
3129 })
3130 }
3131}
3132
3133fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3135 let val = |c: char| -> Option<u32> {
3136 match c {
3137 'A'..='Z' => Some(c as u32 - 'A' as u32),
3138 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3139 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3140 '+' if !url_safe => Some(62),
3141 '/' if !url_safe => Some(63),
3142 '-' if url_safe => Some(62),
3143 '_' if url_safe => Some(63),
3144 _ => None,
3145 }
3146 };
3147 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3148 if clean.len() % 4 == 1 {
3149 return None;
3150 }
3151 let mut out = Vec::new();
3152 let mut buf = 0u32;
3153 let mut bits = 0u32;
3154 for c in clean.chars() {
3155 buf = (buf << 6) | val(c)?;
3156 bits += 6;
3157 if bits >= 8 {
3158 bits -= 8;
3159 out.push((buf >> bits) as u8);
3160 buf &= (1 << bits) - 1;
3161 }
3162 }
3163 Some(out)
3164}
3165
3166
3167pub struct CffRecover;
3189
3190struct CffCase {
3191 body: Vec<JavaId>,
3192 tail: CffTail,
3193}
3194
3195enum CffTail {
3196 Goto(i64),
3197 Cond { cond: JavaId, a: i64, b: i64 },
3198 Term(JavaId),
3199 Exit,
3200}
3201
3202#[derive(Clone, Copy, PartialEq, Debug)]
3203enum Flow {
3204 Done,
3205 Stop,
3206 Cycle(usize),
3207 Cond(i64),
3208 External(i64),
3209}
3210
3211struct CffCtx<'a> {
3212 lang: &'a mut JavaAst,
3213 cases: std::collections::HashMap<i64, CffCase>,
3214 default_term: Option<Vec<JavaId>>,
3216 used_default: bool,
3217 reached: std::collections::HashSet<i64>,
3219 emitted: std::collections::HashSet<i64>,
3221}
3222
3223impl Rule<JavaAst> for CffRecover {
3224 fn name(&self) -> &'static str {
3225 "cff_recover"
3226 }
3227 fn kinds(&self) -> &'static [NodeKind] {
3228 &[NodeKind::While]
3229 }
3230 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3231 let parent = ctx.parent(id)?;
3232 let idx = ctx.index(id)?;
3233 let lang = ctx.lang;
3234 if lang.kind(id) != NodeKind::While {
3235 return None;
3236 }
3237 let wch = lang.children(id).to_vec();
3238 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3239 return None;
3240 }
3241 if lang.kind(wch[1]) != NodeKind::Block {
3242 return None;
3243 }
3244 let body_ch = lang.children(wch[1]).to_vec();
3245 let (switch, exit_if) = match body_ch.len() {
3246 1 => (body_ch[0], None),
3247 2 => (body_ch[0], Some(body_ch[1])),
3248 _ => return None,
3249 };
3250 if lang.kind(switch) != NodeKind::Switch {
3251 return None;
3252 }
3253 let sch = lang.children(switch).to_vec();
3254 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3255 return None;
3256 }
3257 let v = lang.var_name(sch[0])?.to_string();
3258
3259 let sentinel = match exit_if {
3261 None => None,
3262 Some(ei) => {
3263 if lang.kind(ei) != NodeKind::If {
3264 return None;
3265 }
3266 let ich = lang.children(ei).to_vec();
3267 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3268 return None;
3269 }
3270 let cch = lang.children(ich[0]).to_vec();
3271 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3272 && lang.var_name(cch[0]) == Some(v.as_str())
3273 {
3274 lang.literal(cch[1]).and_then(|x| x.as_int())
3275 } else if lang.kind(cch[1]) == NodeKind::VarRef
3276 && lang.var_name(cch[1]) == Some(v.as_str())
3277 {
3278 lang.literal(cch[0]).and_then(|x| x.as_int())
3279 } else {
3280 return None;
3281 }?;
3282 let then_ok = match lang.kind(ich[1]) {
3283 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3284 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3285 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3286 }
3287 _ => false,
3288 };
3289 if !then_ok {
3290 return None;
3291 }
3292 Some(sent)
3293 }
3294 };
3295
3296 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3298 let mut default_term: Option<Vec<JavaId>> = None;
3299 for &c in &sch[1..] {
3300 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3301 return None;
3302 };
3303 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3304 let ch = lang.children(c).to_vec();
3305 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3306 if !arrow
3308 && stmts
3309 .last()
3310 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3311 {
3312 stmts.pop();
3313 }
3314 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3315 let mut inner = lang.children(stmts[0]).to_vec();
3316 if !arrow
3317 && inner
3318 .last()
3319 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3320 {
3321 inner.pop();
3322 }
3323 stmts = inner;
3324 }
3325 if is_default {
3326 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3327 default_term = Some(stmts);
3328 continue;
3329 }
3330 return None;
3331 }
3332 if labels != 1 {
3334 return None;
3335 }
3336 let state = match lang.literal(ch[0]) {
3337 Some(LitRef::Int(v)) => v,
3338 _ => return None,
3339 };
3340 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3341 for &st in &stmts {
3343 if subtree_has_var(lang, st, &v) {
3344 return None;
3345 }
3346 }
3347 cases.insert(state, CffCase { body: stmts, tail });
3348 }
3349 if cases.is_empty() {
3350 return None;
3351 }
3352
3353 if idx == 0 {
3356 return None;
3357 }
3358 let block_ch = lang.children(parent).to_vec();
3359 let mut init = None;
3360 let mut init_idx = idx;
3361 {
3362 let mut up = idx;
3363 for _ in 0..8 {
3364 if up == 0 {
3365 break;
3366 }
3367 up -= 1;
3368 let st = block_ch[up];
3369 match scan_cff_event(lang, st, &v) {
3370 Some(CffEvent::Read) => return None,
3371 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3372 init = parse_cff_init(lang, &v, st);
3373 init_idx = up;
3374 break;
3375 }
3376 None => continue,
3377 }
3378 }
3379 }
3380 let init = init?;
3381
3382 for &st in &block_ch[idx + 1..] {
3384 if subtree_has_var(lang, st, &v) {
3385 return None;
3386 }
3387 }
3388
3389 let mut to_exit: Vec<i64> = Vec::new();
3391 for (k, ci) in cases.iter() {
3392 if let CffTail::Goto(g) = ci.tail {
3393 if !cases.contains_key(&g) {
3394 if !(sentinel == Some(g) || default_term.is_some()) {
3395 return None;
3396 }
3397 to_exit.push(*k);
3398 }
3399 }
3400 }
3401 for k in to_exit {
3402 if let Some(ci) = cases.get_mut(&k) {
3403 ci.tail = CffTail::Exit;
3404 }
3405 }
3406
3407 let mut c = CffCtx {
3408 lang,
3409 cases,
3410 default_term,
3411 used_default: false,
3412 reached: Default::default(),
3413 emitted: Default::default(),
3414 };
3415 let stmts = cff_run(&mut c, init)?;
3416
3417 if stmts.is_empty() {
3420 return None;
3421 }
3422
3423 Some(Edit::Multi(vec![
3426 Edit::Splice {
3427 node: parent,
3428 index: idx,
3429 remove: 1,
3430 insert: stmts,
3431 },
3432 Edit::Delete {
3433 node: block_ch[init_idx],
3434 },
3435 ]))
3436 }
3437}
3438
3439fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3440 let last = *stmts.last()?;
3441 if lang.kind(last) == NodeKind::Return {
3442 stmts.pop();
3443 return Some(CffTail::Term(last));
3444 }
3445 let assign_of = |n: JavaId| -> Option<JavaId> {
3446 let inner = match lang.kind(n) {
3447 NodeKind::Assign => n,
3448 NodeKind::ExprStmt => {
3449 let ch = lang.children(n);
3450 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3451 ch[0]
3452 } else {
3453 return None;
3454 }
3455 }
3456 _ => return None,
3457 };
3458 if lang.assign_op(inner).is_some() {
3459 return None;
3460 }
3461 let ch = lang.children(inner).to_vec();
3462 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3463 return None;
3464 }
3465 Some(inner)
3466 };
3467 if let Some(assign) = assign_of(last) {
3468 let value = lang.children(assign)[1];
3469 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3470 stmts.pop();
3471 return Some(CffTail::Goto(k));
3472 }
3473 if lang.kind(value) == NodeKind::Ternary {
3474 let tch = lang.children(value).to_vec();
3475 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3476 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3477 stmts.pop();
3478 return Some(CffTail::Cond { cond: tch[0], a, b });
3479 }
3480 return None;
3481 }
3482 if lang.kind(last) == NodeKind::If {
3483 let ich = lang.children(last).to_vec();
3484 if ich.len() != 3 {
3485 return None;
3486 }
3487 let arm = |n: JavaId| -> Option<i64> {
3488 let target = match lang.kind(n) {
3489 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3490 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3491 _ => return None,
3492 };
3493 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3494 };
3495 let a = arm(ich[1])?;
3496 let b = arm(ich[2])?;
3497 stmts.pop();
3498 return Some(CffTail::Cond { cond: ich[0], a, b });
3499 }
3500 None
3501}
3502
3503
3504enum CffEvent {
3506 Read,
3507 Write,
3508 Decl,
3510}
3511
3512fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3513 match lang.kind(node) {
3514 NodeKind::VarRef => {
3515 if lang.var_name(node) == Some(name) {
3516 Some(CffEvent::Read)
3517 } else {
3518 None
3519 }
3520 }
3521 NodeKind::VarDecl => {
3522 if lang.var_name(node) == Some(name) {
3523 Some(CffEvent::Decl)
3524 } else {
3525 for &c in lang.children(node) {
3526 if let Some(e) = scan_cff_event(lang, c, name) {
3527 return Some(e);
3528 }
3529 }
3530 None
3531 }
3532 }
3533 NodeKind::Assign => {
3534 let ch = lang.children(node);
3535 if let Some(&val) = ch.get(1) {
3536 if let Some(e) = scan_cff_event(lang, val, name) {
3537 if matches!(e, CffEvent::Read) {
3538 return Some(e);
3539 }
3540 }
3541 }
3542 if lang.assign_op(node).is_none() {
3543 if let Some(&t) = ch.first() {
3544 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3545 return Some(CffEvent::Write);
3546 }
3547 }
3548 }
3549 None
3550 }
3551 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3552 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3553 NodeKind::Binary => {
3554 let op = lang.bin_op(node)?;
3555 let ch = lang.children(node);
3556 if op.is_short_circuit() {
3557 scan_cff_event(lang, *ch.first()?, name)
3558 } else {
3559 for &c in ch {
3560 if let Some(e) = scan_cff_event(lang, c, name) {
3561 return Some(e);
3562 }
3563 }
3564 None
3565 }
3566 }
3567 _ => {
3568 for &c in lang.children(node) {
3569 if let Some(e) = scan_cff_event(lang, c, name) {
3570 return Some(e);
3571 }
3572 }
3573 None
3574 }
3575 }
3576}
3577
3578fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3579 match lang.kind(stmt) {
3580 NodeKind::VarDecl => {
3581 let ch = lang.children(stmt).to_vec();
3582 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3583 return None;
3584 }
3585 lang.literal(ch[0]).and_then(|x| x.as_int())
3586 }
3587 NodeKind::Assign => {
3588 if lang.assign_op(stmt).is_some() {
3589 return None;
3590 }
3591 let ch = lang.children(stmt).to_vec();
3592 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3593 return None;
3594 }
3595 lang.literal(ch[1]).and_then(|x| x.as_int())
3596 }
3597 NodeKind::ExprStmt => {
3598 let ch = lang.children(stmt).to_vec();
3599 if ch.len() == 1 {
3600 parse_cff_init(lang, v, ch[0])
3601 } else {
3602 None
3603 }
3604 }
3605 _ => None,
3606 }
3607}
3608
3609fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3610 for (s, stmts) in pairs {
3611 c.emitted.insert(*s);
3612 out.extend(stmts.iter().copied());
3613 }
3614}
3615
3616fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3617 for (s, stmts) in pairs {
3618 if *s == m {
3619 break;
3620 }
3621 out.extend(stmts.iter().copied());
3622 }
3623}
3624
3625fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3626 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3627 pairs[pos..].to_vec()
3628}
3629
3630fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3632 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3633 let mut cur = start;
3634 let mut guard = 0usize;
3635 loop {
3636 guard += 1;
3637 if guard > 10_000 {
3638 return None;
3639 }
3640 if Some(cur) == stop {
3641 return Some((path, Flow::Stop));
3642 }
3643 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3644 return Some((path, Flow::Cycle(i)));
3645 }
3646 if c.emitted.contains(&cur) {
3647 return Some((path, Flow::External(cur)));
3648 }
3649 c.reached.insert(cur);
3650 let ci = c.cases.get(&cur)?;
3651 let mut body = ci.body.clone();
3652 match ci.tail {
3653 CffTail::Goto(k) => {
3654 path.push((cur, body));
3655 cur = k;
3656 }
3657 CffTail::Term(r) => {
3658 body.push(r);
3659 path.push((cur, body));
3660 return Some((path, Flow::Done));
3661 }
3662 CffTail::Exit => {
3663 if c.default_term.is_some() {
3664 if let Some(dt) = c.default_term.clone() {
3665 body.extend(dt);
3666 c.used_default = true;
3667 }
3668 }
3669 path.push((cur, body));
3670 return Some((path, Flow::Done));
3671 }
3672 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3673 }
3674 }
3675}
3676
3677fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3679 let (path, flow) = collect(c, init, None)?;
3680 cff_structure(c, path, flow)
3681}
3682
3683fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3685 let (path, flow) = collect(c, start, None)?;
3686 cff_structure(c, path, flow)
3687}
3688
3689fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3693 let mut stmts: Vec<JavaId> = Vec::new();
3694 loop {
3695 match flow {
3696 Flow::Done => {
3697 flatten(&path, &mut stmts, c);
3698 return Some(stmts);
3699 }
3700 Flow::Cycle(i) => {
3701 flatten(&path[..i], &mut stmts, c);
3702 let mut suffix = Vec::new();
3703 flatten(&path[i..], &mut suffix, c);
3704 let inner = c.lang.build_block(suffix);
3705 let cond = c.lang.build_bool(true);
3706 let w = c.lang.while_(cond, inner);
3707 stmts.push(w);
3708 return Some(stmts);
3709 }
3710 Flow::Stop | Flow::External(_) => return None,
3711 Flow::Cond(state) => {
3712 flatten(&path, &mut stmts, c);
3713 path.clear();
3714 let ci = c.cases.get(&state)?;
3715 stmts.extend(ci.body.iter().copied());
3716 c.emitted.insert(state);
3717 let CffTail::Cond { cond, a, b } = ci.tail else {
3718 return None;
3719 };
3720 let (pa, fa) = collect(c, a, Some(state))?;
3722 if fa == Flow::Stop {
3723 let mut lb = Vec::new();
3724 flatten(&pa, &mut lb, c);
3725 let inner = c.lang.build_block(lb);
3726 let w = c.lang.while_(cond, inner);
3727 stmts.push(w);
3728 let (p2, f2) = collect(c, b, None)?;
3729 path = p2;
3730 flow = f2;
3731 continue;
3732 }
3733 let (pb, fb) = collect(c, b, Some(state))?;
3734 if fb == Flow::Stop {
3735 let mut lb = Vec::new();
3736 flatten(&pb, &mut lb, c);
3737 let inner = c.lang.build_block(lb);
3738 let nc = c.lang.build_unary(UnOp::Not, cond);
3739 let w = c.lang.while_(nc, inner);
3740 stmts.push(w);
3741 let (p2, f2) = collect(c, a, None)?;
3742 path = p2;
3743 flow = f2;
3744 continue;
3745 }
3746 let common = pa
3748 .iter()
3749 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3750 .map(|(s, _)| *s);
3751 let same_end = match (fa, fb) {
3752 (Flow::Done, Flow::Done) => true,
3753 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3754 _ => false,
3755 };
3756 match common {
3757 Some(m) if same_end => {
3758 let mut pre_a = Vec::new();
3759 let mut pre_b = Vec::new();
3760 flatten_prefix(&pa, m, &mut pre_a);
3761 flatten_prefix(&pb, m, &mut pre_b);
3762 let ba = c.lang.build_block(pre_a);
3763 let bb = c.lang.build_block(pre_b);
3764 let ifs = c.lang.if_(cond, ba, Some(bb));
3765 stmts.push(ifs);
3766 path = suffix_pairs(&pa, m);
3767 flow = fa;
3768 continue;
3769 }
3770 Some(_) => return None,
3771 None => {
3772 let mut fa_stmts = Vec::new();
3774 let mut fb_stmts = Vec::new();
3775 flatten(&pa, &mut fa_stmts, c);
3776 flatten(&pb, &mut fb_stmts, c);
3777 match (fa, fb) {
3778 (Flow::Done, Flow::Done) => {
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 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3787 let ba = c.lang.build_block(fa_stmts);
3788 let bb = c.lang.build_block(fb_stmts);
3789 let ifs = c.lang.if_(cond, ba, Some(bb));
3790 stmts.push(ifs);
3791 let (p2, f2) = collect(c, x, None)?;
3792 path = p2;
3793 flow = f2;
3794 continue;
3795 }
3796 (Flow::Done, Flow::Cond(x)) => {
3798 let cont = cff_run_from(c, x)?;
3799 fb_stmts.extend(cont);
3800 let ba = c.lang.build_block(fa_stmts);
3801 let bb = c.lang.build_block(fb_stmts);
3802 let ifs = c.lang.if_(cond, ba, Some(bb));
3803 stmts.push(ifs);
3804 return Some(stmts);
3805 }
3806 (Flow::Cond(x), Flow::Done) => {
3807 let cont = cff_run_from(c, x)?;
3808 fa_stmts.extend(cont);
3809 let ba = c.lang.build_block(fa_stmts);
3810 let bb = c.lang.build_block(fb_stmts);
3811 let ifs = c.lang.if_(cond, ba, Some(bb));
3812 stmts.push(ifs);
3813 return Some(stmts);
3814 }
3815 _ => return None,
3816 }
3817 }
3818 }
3819 }
3820 }
3821 }
3822}
3823
3824
3825
3826pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3836 ast.const_fields.clear();
3837 ast.const_scalars.clear();
3838 ast.inline_methods.clear();
3839 ast.field_types.clear();
3840 ast.final_fields.clear();
3841 ast.raw_generic_names = {
3844 let mut m: std::collections::HashSet<String> = [
3845 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3846 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3847 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3848 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3849 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3850 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3851 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3852 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3853 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3854 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3856 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3857 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3858 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3859 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3860 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3861 ]
3862 .iter()
3863 .map(|x| x.to_string())
3864 .collect();
3865 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3866 if !ty.ty_params.trim().is_empty() {
3867 m.insert(ty.name.clone());
3868 }
3869 for mem in &ty.members {
3870 if let Member::Type(t) = mem {
3871 collect_generics(t, m);
3872 }
3873 }
3874 }
3875 for ty in &unit.types {
3876 collect_generics(ty, &mut m);
3877 }
3878 m
3879 };
3880 ast.unit_shadows_lang = {
3881 fn decl_shadows(ty: &TypeDecl) -> bool {
3882 if ty.name == "String" || ty.name == "StringBuilder" {
3883 return true;
3884 }
3885 ty.members.iter().any(|m| match m {
3886 Member::Type(t) => decl_shadows(t),
3887 _ => false,
3888 })
3889 }
3890 unit.types.iter().any(decl_shadows)
3891 };
3892
3893 let mut shadowed: std::collections::HashSet<String> = Default::default();
3898 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3899 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3900 let mut bodies: Vec<JavaId> = Vec::new();
3901 for ty in &unit.types {
3902 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3903 }
3904 for ty in &unit.types {
3907 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
3908 }
3909 for &root in &bodies {
3913 let mut stack = vec![root];
3914 while let Some(n) = stack.pop() {
3915 if ast.kind(n) == NodeKind::VarDecl {
3916 if let Some(nm) = ast.var_name(n) {
3917 shadowed.insert(nm.to_string());
3918 }
3919 }
3920 match ast.data(n) {
3921 NodeData::Assign { .. } => {
3922 let t = ast.children(n)[0];
3923 if ast.kind(t) == NodeKind::Member {
3924 if let NodeData::Member { name: sym } = ast.data(t) {
3925 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
3926 }
3927 }
3928 let mut ts = vec![t];
3929 while let Some(x) = ts.pop() {
3930 if ast.kind(x) == NodeKind::VarRef {
3931 if let Some(nm) = ast.var_name(x) {
3932 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3933 }
3934 }
3935 for &c in ast.children(x) {
3936 ts.push(c);
3937 }
3938 }
3939 }
3940 NodeData::Unary { op } if op.is_incdec() => {
3941 let t = ast.children(n)[0];
3942 if ast.kind(t) == NodeKind::VarRef {
3943 if let Some(nm) = ast.var_name(t) {
3944 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3945 }
3946 }
3947 }
3948 _ => {}
3949 }
3950 for &c in ast.children(n) {
3951 stack.push(c);
3952 }
3953 }
3954 }
3955
3956 for ty in &unit.types {
3958 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
3960 collect_own_clinit_lits(ast, ty, &mut own_lits);
3961 for m in &ty.members {
3962 if let Member::Field { mods, ty: fty, declarators } = m {
3963 let words: Vec<&str> = mods.split_whitespace().collect();
3964 let is_static = words.contains(&"static");
3965 let is_final = words.contains(&"final");
3966 let is_private = words.contains(&"private");
3967 for d in declarators {
3968 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
3972 continue;
3973 }
3974 let init = match d.init {
3975 Some(init) => {
3976 if !is_final
3978 || !is_const_init(ast, init)
3979 || shadowed.contains(&d.name)
3980 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
3981 {
3982 continue;
3983 }
3984 init
3985 }
3986 None => {
3987 if !is_static
3994 || (!is_final && !is_private)
3995 || shadowed.contains(&d.name)
3996 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
3997 {
3998 continue;
3999 }
4000 match own_lits.get(&d.name) {
4001 Some(&init) if is_const_init(ast, init) => init,
4002 _ => continue,
4003 }
4004 }
4005 };
4006 if ast.const_fields.contains_key(&d.name) {
4008 ast.const_fields.remove(&d.name);
4009 ast.const_scalars.remove(&d.name);
4010 } else {
4011 let scalar_ok = scalar_lit_of(ast, init)
4012 .is_some_and(|l| lit_type_matches_decl(fty, l));
4013 ast.const_fields.insert(d.name.clone(), init);
4014 if scalar_ok {
4015 ast.const_scalars.insert(d.name.clone());
4016 }
4017 }
4018 }
4019 }
4020 }
4021 }
4022
4023 {
4030 let identity_assert = [
4031 "assertSame",
4032 "assertNotSame",
4033 "isSameInstanceAs",
4034 "isNotSameInstanceAs",
4035 "referenceEquals",
4036 "same",
4037 ];
4038 let mut hit = false;
4039 for root in &bodies {
4040 let mut stack = vec![*root];
4041 while let Some(n) = stack.pop() {
4042 if let NodeData::Call = ast.data(n) {
4043 if let Some(&callee) = ast.children(n).first() {
4044 let nm = match ast.data(callee) {
4045 NodeData::Member { name } => ast.sn(*name),
4046 NodeData::VarRef { name } => ast.sn(*name),
4047 _ => "",
4048 };
4049 if identity_assert.contains(&nm) {
4050 hit = true;
4051 break;
4052 }
4053 }
4054 }
4055 for &c in ast.children(n) {
4056 stack.push(c);
4057 }
4058 }
4059 if hit {
4060 break;
4061 }
4062 }
4063 ast.unit_string_identity =
4064 ast.any_string_identity(&bodies) || hit;
4065 }
4066
4067 let mut method_name_count: std::collections::HashMap<String, usize> =
4077 std::collections::HashMap::new();
4078 for_each_type(unit, &mut |ty: &TypeDecl| {
4079 for m in &ty.members {
4080 if let Member::Method { name, .. } = m {
4081 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4082 }
4083 }
4084 });
4085 for ty in &unit.types {
4086 for m in &ty.members {
4087 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4088 let words: Vec<&str> = mods.split_whitespace().collect();
4089 let non_overridable = words.contains(&"private")
4090 || words.contains(&"static")
4091 || words.contains(&"final");
4092 if !non_overridable {
4093 continue;
4094 }
4095 let ch = ast.children(*b).to_vec();
4096 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4097 if let Some(&expr) = ast.children(ch[0]).first() {
4098 if params.len() <= 3
4099 && !subtree_has_anon(ast, expr)
4104 && !params.iter().any(|p| p.varargs)
4108 && !subtree_calls_self(ast, expr, name)
4109 && !subtree_has_type_sensitive(ast, expr)
4115 && method_name_count.get(name).copied() == Some(1)
4116 {
4117 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4118 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4119 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4120 }
4121 }
4122 }
4123 }
4124 }
4125 }
4126}
4127
4128fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4130 match ast.kind(init) {
4131 NodeKind::Literal => true,
4132 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4133 NodeKind::NewArray => {
4134 let ch = ast.children(init).to_vec();
4135 ch.len() == 1
4136 && ast.kind(ch[0]) == NodeKind::ArrayLit
4137 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4138 }
4139 _ => false,
4140 }
4141}
4142
4143fn collect_bodies_recursive(
4147 ty: &TypeDecl,
4148 bodies: &mut Vec<JavaId>,
4149 shadowed: &mut std::collections::HashSet<String>,
4150 field_decls: &mut std::collections::HashMap<String, u32>,
4151) {
4152 if ty.kind == TypeKind::Enum {
4160 for c in &ty.enum_constants {
4161 if c.trim() == "," {
4162 continue;
4163 }
4164 let last = c.split('(').next().unwrap_or(c)
4165 .split_whitespace()
4166 .next_back()
4167 .unwrap_or("");
4168 let name: String = last
4169 .chars()
4170 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4171 .collect();
4172 if !name.is_empty() {
4173 *field_decls.entry(name).or_insert(0) += 1;
4174 }
4175 }
4176 }
4177 for m in &ty.members {
4178 match m {
4179 Member::Method { body: Some(b), params, .. }
4180 | Member::Constructor { body: Some(b), params, .. } => {
4181 bodies.push(*b);
4182 for p in params {
4183 shadowed.insert(p.name.clone());
4184 }
4185 }
4186 Member::Initializer { body, .. } => bodies.push(*body),
4187 Member::Field { declarators, .. } => {
4188 for d in declarators {
4189 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4190 if let Some(init) = d.init {
4191 bodies.push(init);
4192 }
4193 }
4194 }
4195 Member::Type(t) => {
4196 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4197 }
4198 _ => {}
4199 }
4200 }
4201}
4202
4203fn collect_field_types(
4205 ty: &TypeDecl,
4206 out: &mut std::collections::HashMap<String, JType>,
4207 finals: &mut std::collections::HashSet<String>,
4208) {
4209 for m in &ty.members {
4210 match m {
4211 Member::Field { mods, ty: fty, declarators } => {
4212 let is_final = mods.split_whitespace().any(|w| w == "final");
4213 for d in declarators {
4214 if out.contains_key(&d.name) {
4215 out.remove(&d.name);
4216 finals.remove(&d.name);
4217 } else {
4218 out.insert(d.name.clone(), fty.clone());
4219 if is_final {
4220 finals.insert(d.name.clone());
4221 }
4222 }
4223 }
4224 }
4225 Member::Type(t) => collect_field_types(t, out, finals),
4226 _ => {}
4227 }
4228 }
4229}
4230
4231fn collect_own_clinit_lits(
4236 ast: &JavaAst,
4237 ty: &TypeDecl,
4238 out: &mut std::collections::HashMap<String, JavaId>,
4239) {
4240 for m in &ty.members {
4241 if let Member::Initializer { is_static: true, body } = m {
4242 if ast.kind(*body) != NodeKind::Block {
4243 continue;
4244 }
4245 for &st in ast.children(*body) {
4246 let asg = match ast.kind(st) {
4247 NodeKind::ExprStmt => {
4248 let Some(&c) = ast.children(st).first() else {
4252 continue;
4253 };
4254 if ast.kind(c) == NodeKind::Assign {
4255 c
4256 } else {
4257 continue;
4258 }
4259 }
4260 NodeKind::Assign => st,
4261 _ => continue,
4262 };
4263 let ch = ast.children(asg).to_vec();
4264 if ast.kind(ch[0]) != NodeKind::VarRef {
4265 continue;
4266 }
4267 let Some(nm) = ast.var_name(ch[0]) else { continue };
4268 if is_const_init(ast, ch[1]) {
4269 out.insert(nm.to_string(), ch[1]);
4270 }
4271 }
4272 }
4273 }
4274}
4275
4276fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4278 if ast.kind(init) == NodeKind::Literal {
4279 ast.literal(init)
4280 } else {
4281 None
4282 }
4283}
4284
4285fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4290 use cure_engine::kind::LitRef;
4291 match (fty, lit) {
4292 (JType::Ref(r), LitRef::Str(_)) => {
4293 let base = r.split('<').next().unwrap_or(r).trim();
4294 base == "String" || base == "java.lang.String"
4295 }
4296 (JType::Char, LitRef::Char(_)) => true,
4297 (JType::Bool, LitRef::Bool(_)) => true,
4298 (JType::Int, LitRef::Int(_)) => true,
4299 (JType::Long, LitRef::Long(_)) => true,
4300 (JType::Float, LitRef::Float(_)) => true,
4301 (JType::Double, LitRef::Double(_)) => true,
4302 _ => false,
4303 }
4304}
4305
4306fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4310 let mut stack = vec![root];
4311 while let Some(n) = stack.pop() {
4312 match ast.kind(n) {
4313 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4314 _ => {}
4315 }
4316 for &c in ast.children(n) {
4317 stack.push(c);
4318 }
4319 }
4320 false
4321}
4322
4323fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4324 let mut stack = vec![root];
4325 while let Some(n) = stack.pop() {
4326 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4327 return true;
4328 }
4329 for &c in ast.children(n) {
4330 stack.push(c);
4331 }
4332 }
4333 false
4334}
4335
4336fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4337 let mut stack = vec![root];
4338 while let Some(n) = stack.pop() {
4339 if ast.kind(n) == NodeKind::Call {
4340 let callee = ast.children(n)[0];
4341 if ast.var_name(callee) == Some(name) {
4342 return true;
4343 }
4344 if let NodeData::Member { name: m } = ast.data(callee) {
4349 if ast.sn(*m) == name {
4350 return true;
4351 }
4352 }
4353 }
4354 for &c in ast.children(n) {
4355 stack.push(c);
4356 }
4357 }
4358 false
4359}
4360
4361pub struct StaticArrayIndexFold;
4366
4367impl Rule<JavaAst> for StaticArrayIndexFold {
4368 fn name(&self) -> &'static str {
4369 "static_array_index_fold"
4370 }
4371 fn kinds(&self) -> &'static [NodeKind] {
4372 &[NodeKind::Index]
4373 }
4374 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4375 let lang = ctx.lang;
4376 if lang.kind(id) != NodeKind::Index {
4377 return None;
4378 }
4379 let ch = lang.children(id).to_vec();
4380 if lang.kind(ch[0]) != NodeKind::VarRef {
4381 return None;
4382 }
4383 let name = lang.var_name(ch[0])?.to_string();
4384 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4385 let init = *lang.const_fields.get(&name)?;
4386 let arr = match lang.kind(init) {
4387 NodeKind::ArrayLit => init,
4388 NodeKind::NewArray => {
4389 let ic = lang.children(init).to_vec();
4390 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4391 ic[0]
4392 } else {
4393 return None;
4394 }
4395 }
4396 _ => return None,
4397 };
4398 let elems = lang.children(arr).to_vec();
4399 if idx < 0 || idx >= elems.len() as i64 {
4400 return None;
4401 }
4402 let elem = lang.copy_subtree(elems[idx as usize]);
4403 Some(Edit::Replace {
4404 target: id,
4405 with: elem,
4406 })
4407 }
4408}
4409
4410pub struct DeadPureCall;
4490
4491impl Rule<JavaAst> for DeadPureCall {
4492 fn name(&self) -> &'static str {
4493 "dead_pure_call"
4494 }
4495 fn kinds(&self) -> &'static [NodeKind] {
4496 &[NodeKind::ExprStmt]
4497 }
4498 fn structural(&self) -> bool {
4499 true
4500 }
4501 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4502 let lang = ctx.lang;
4503 if lang.kind(id) != NodeKind::ExprStmt {
4504 return None;
4505 }
4506 let Some(&call) = lang.children(id).first() else { return None };
4507 if lang.kind(call) != NodeKind::Call {
4508 return None;
4509 }
4510 let cch = lang.children(call).to_vec();
4511 let Some(&callee) = cch.first() else { return None };
4512 let (recv, name) = match lang.data(callee) {
4515 NodeData::Member { name } => {
4516 let Some(&recv_expr) = lang.children(callee).first() else { return None };
4517 let recv_name = match lang.data(recv_expr) {
4520 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4521 NodeData::Member { name } => lang.sn(*name).to_string(),
4522 _ => return None,
4523 };
4524 if !recv_name
4525 .chars()
4526 .next()
4527 .is_some_and(|c| c.is_uppercase())
4528 {
4529 return None;
4530 }
4531 (recv_name, lang.sn(*name))
4532 }
4533 _ => return None,
4534 };
4535 let args = &cch[1..];
4536 let pure = match (recv.as_str(), name) {
4537 ("Long" | "Integer" | "Float" | "Double" | "Short" | "Byte" | "Character", "compare") => {
4538 args.iter().all(|&a| match lang.data(a) {
4542 NodeData::Literal(
4543 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_),
4544 ) => true,
4545 NodeData::Literal(Lit::NumRaw { .. }) => true,
4547 NodeData::VarRef { .. } => {
4548 let t = lang.var_type(a);
4549 matches!(
4550 t,
4551 Some(JType::Int | JType::Long | JType::Float | JType::Double | JType::Char
4552 | JType::Short | JType::Byte)
4553 )
4554 }
4555 _ => false,
4556 })
4557 }
4558 ("Objects", "toString") => args.len() == 1,
4559 _ => false,
4560 };
4561 if !pure {
4562 return None;
4563 }
4564 Some(Edit::Delete { node: id })
4565 }
4566}
4567
4568pub struct CtorDelegationHoist;
4569
4570fn hoist_resolve_arg(
4575 lang: &mut JavaAst,
4576 assign: &std::collections::HashMap<String, JavaId>,
4577 a: JavaId,
4578 visiting: &mut std::collections::HashSet<String>,
4579) -> Option<JavaId> {
4580 let name = match lang.data(a) {
4581 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4582 _ => return Some(a), };
4584 let Some(&expr) = assign.get(&name) else {
4586 return Some(a);
4587 };
4588 if !visiting.insert(name.clone()) {
4589 return None; }
4591 let r = hoist_subst(lang, assign, expr, visiting);
4592 visiting.remove(&name);
4593 r
4594}
4595
4596fn hoist_subst(
4598 lang: &mut JavaAst,
4599 assign: &std::collections::HashMap<String, JavaId>,
4600 e: JavaId,
4601 visiting: &mut std::collections::HashSet<String>,
4602) -> Option<JavaId> {
4603 match lang.data(e) {
4604 NodeData::VarRef { name } => {
4605 let n = lang.sn(*name).to_string();
4606 if let Some(&src) = assign.get(&n) {
4607 if !visiting.insert(n.clone()) {
4608 return None;
4609 }
4610 let r = hoist_subst(lang, assign, src, visiting);
4611 visiting.remove(&n);
4612 r
4613 } else {
4614 Some(e)
4615 }
4616 }
4617 _ => {
4618 let ch = lang.children(e).to_vec();
4619 let mut new_ch = Vec::with_capacity(ch.len());
4620 for c in ch {
4621 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4622 }
4623 Some(lang.clone_node(e, new_ch))
4624 }
4625 }
4626}
4627
4628impl Rule<JavaAst> for CtorDelegationHoist {
4629 fn name(&self) -> &'static str {
4630 "ctor_delegation_hoist"
4631 }
4632 fn kinds(&self) -> &'static [NodeKind] {
4633 &[NodeKind::Block]
4634 }
4635 fn structural(&self) -> bool {
4636 true
4637 }
4638 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4639 if ctx.parent(id).is_some() {
4642 return None;
4643 }
4644 let lang = ctx.lang;
4645 if lang.kind(id) != NodeKind::Block {
4646 return None;
4647 }
4648 let stmts = lang.children(id).to_vec();
4649 let mut ctor_idx: Option<usize> = None;
4651 for (i, &st) in stmts.iter().enumerate() {
4652 if *lang.data(st) != NodeData::ExprStmt {
4653 continue;
4654 }
4655 let Some(&inner) = lang.children(st).first() else { continue };
4656 if lang.kind(inner) != NodeKind::Call {
4657 continue;
4658 }
4659 let Some(&callee) = lang.children(inner).first() else { continue };
4660 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4661 ctor_idx = Some(i);
4662 break;
4663 }
4664 }
4665 let ci = ctor_idx?;
4666 if ci == 0 {
4667 return None; }
4669 let mut last_assign: std::collections::HashMap<String, JavaId> =
4672 std::collections::HashMap::new();
4673 for &st in &stmts[..ci] {
4674 let (name, value) = match lang.data(st) {
4675 NodeData::VarDecl { name, .. } => {
4676 let init = lang.children(st).first().copied();
4677 match init {
4678 Some(e) => (name.clone(), e),
4679 None => continue, }
4681 }
4682 NodeData::ExprStmt => {
4683 let Some(&a) = lang.children(st).first() else {
4684 return None;
4685 };
4686 if lang.kind(a) != NodeKind::Assign
4689 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4690 {
4691 return None;
4692 }
4693 let ch = lang.children(a).to_vec();
4694 let (t, v) = (*ch.first()?, *ch.get(1)?);
4695 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4696 return None; }
4698 let vn = lang.var_name(t)?;
4699 (vn.to_string(), v)
4700 }
4701 _ => return None, };
4703 last_assign.insert(name, value);
4708 }
4709 let call_stmt = stmts[ci];
4711 let call = *lang.children(call_stmt).first()?;
4712 let cch = lang.children(call).to_vec();
4713 let callee = *cch.first()?;
4714 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4715 for &a in &cch[1..] {
4716 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4717 }
4718 let callee_copy = lang.copy_subtree(callee);
4720 let new_call = lang.call(callee_copy, new_args);
4721 let new_stmt = lang.expr_stmt(new_call);
4722 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4723 insert.push(new_stmt);
4724 insert.extend(stmts[..ci].iter().copied());
4725 Some(Edit::Splice {
4726 node: id,
4727 index: 0,
4728 remove: ci + 1,
4729 insert,
4730 })
4731 }
4732}
4733
4734pub struct TrailingElseFlatten;
4752
4753impl Rule<JavaAst> for TrailingElseFlatten {
4754 fn name(&self) -> &'static str {
4755 "trailing_else_flatten"
4756 }
4757 fn kinds(&self) -> &'static [NodeKind] {
4758 &[NodeKind::If]
4759 }
4760 fn structural(&self) -> bool {
4761 true
4762 }
4763 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4764 let (parent, idx) = (ctx.parent(id), ctx.index(id));
4765 let lang = ctx.lang;
4766 if lang.kind(id) != NodeKind::If {
4767 return None;
4768 }
4769 let ch = lang.children(id).to_vec();
4770 if ch.len() != 3 {
4771 return None;
4772 }
4773 let (_cond, then, els) = (ch[0], ch[1], ch[2]);
4774 if lang.kind(then) != NodeKind::Block {
4776 return None;
4777 }
4778 let then_stmts = lang.children(then).to_vec();
4779 let then_terminal = then_stmts
4780 .last()
4781 .is_some_and(|&s| matches!(lang.kind(s), NodeKind::Return | NodeKind::Throw));
4782 if !then_terminal {
4783 return None;
4784 }
4785 if lang.kind(els) == NodeKind::Block {
4790 let else_stmts = lang.children(els).to_vec();
4791 if else_stmts.len() == 1 && lang.kind(else_stmts[0]) == NodeKind::Return {
4792 if lang.children(else_stmts[0]).is_empty() {
4794 return Some(Edit::Splice {
4796 node: id,
4797 index: 0,
4798 remove: 3,
4799 insert: vec![ch[0], then],
4800 });
4801 }
4802 }
4803 }
4804 let parent = parent?;
4806 if lang.kind(parent) != NodeKind::Block {
4807 return None;
4808 }
4809 let idx = idx?;
4810 let new_if = lang.clone_node(id, vec![ch[0], then]);
4812 let mut insert: Vec<JavaId> = vec![new_if];
4813 if lang.kind(els) == NodeKind::Block {
4815 insert.extend(lang.children(els).to_vec());
4816 } else {
4817 insert.push(els);
4818 }
4819 Some(Edit::Splice {
4820 node: parent,
4821 index: idx,
4822 remove: 1,
4823 insert,
4824 })
4825 }
4826}
4827
4828pub struct TrailingIfContinue;
4829
4830impl Rule<JavaAst> for TrailingIfContinue {
4831 fn name(&self) -> &'static str {
4832 "trailing_if_continue"
4833 }
4834 fn kinds(&self) -> &'static [NodeKind] {
4835 &[NodeKind::If]
4836 }
4837 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4838 let parent = ctx.parent(id);
4839 let grandparent = parent.and_then(|p| ctx.parent(p));
4840 let lang = ctx.lang;
4841 if lang.kind(id) != NodeKind::If {
4842 return None;
4843 }
4844 let ch = lang.children(id).to_vec();
4845 if ch.len() != 2 {
4847 return None;
4848 }
4849 let then = ch[1];
4850 if lang.kind(then) != NodeKind::Block {
4851 return None;
4852 }
4853 let tbody = lang.children(then).to_vec();
4854 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4855 return None;
4856 }
4857 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4859 return None;
4860 }
4861 let p = parent?;
4864 if lang.kind(p) != NodeKind::Block {
4865 return None;
4866 }
4867 let pch = lang.children(p).to_vec();
4868 if pch.last() != Some(&id) {
4869 return None;
4870 }
4871 let gp = grandparent?;
4873 match lang.kind(gp) {
4874 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4875 let gch = lang.children(gp).to_vec();
4876 if gch.last() == Some(&p) {
4877 Some(Edit::Delete { node: id })
4878 } else {
4879 None
4880 }
4881 }
4882 _ => None,
4883 }
4884 }
4885}
4886
4887pub struct TrailingAssignReturn;
4888
4889impl Rule<JavaAst> for TrailingAssignReturn {
4890 fn name(&self) -> &'static str {
4891 "trailing_assign_return"
4892 }
4893 fn kinds(&self) -> &'static [NodeKind] {
4894 &[NodeKind::Assign]
4895 }
4896 fn structural(&self) -> bool {
4899 true
4900 }
4901 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4902 let parent = ctx.parent(id);
4903 let grandparent = parent.and_then(|p| ctx.parent(p));
4904 let lang = ctx.lang;
4905 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4907 return None;
4908 }
4909 let ch = lang.children(id).to_vec();
4910 if ch.len() != 2 {
4911 return None;
4912 }
4913 let (target, value) = (ch[0], ch[1]);
4914 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4916 return None;
4917 }
4918 let name = lang.var_name(target)?.to_string();
4919 let parent = parent?;
4921 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4922 parent
4923 } else if lang.kind(parent) == NodeKind::Block {
4924 id
4925 } else {
4926 return None;
4927 };
4928 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4929 grandparent?
4930 } else {
4931 parent
4932 };
4933 if lang.kind(block) != NodeKind::Block {
4934 return None;
4935 }
4936 let stmts = lang.children(block).to_vec();
4937 let idx = stmts.iter().position(|&x| x == stmt)?;
4938 let next = *stmts.get(idx + 1)?;
4940 let (flow_val, is_return) = match lang.kind(next) {
4941 NodeKind::Return => (lang.children(next).first().copied(), true),
4942 NodeKind::Throw => (lang.children(next).first().copied(), false),
4943 _ => return None,
4944 };
4945 let flow_val = flow_val?;
4946 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4947 return None;
4948 }
4949 for &s in &stmts[idx + 2..] {
4952 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4953 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4954 }) {
4955 return None;
4956 }
4957 }
4958 let new_flow = if is_return {
4960 lang.ret(Some(value))
4961 } else {
4962 lang.throw(value)
4963 };
4964 Some(Edit::Multi(vec![
4965 Edit::Replace { target: next, with: new_flow },
4966 Edit::Delete { node: stmt },
4967 ]))
4968 }
4969}
4970
4971pub struct ConstFieldPropagate;
4972
4973impl Rule<JavaAst> for ConstFieldPropagate {
4974 fn name(&self) -> &'static str {
4975 "const_field_propagate"
4976 }
4977 fn kinds(&self) -> &'static [NodeKind] {
4978 &[NodeKind::VarRef]
4979 }
4980 fn structural(&self) -> bool {
4983 true
4984 }
4985 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4986 let parent = ctx.parent(id);
4987 let _grandparent = parent.and_then(|p| ctx.parent(p));
4988 let lang = ctx.lang;
4989 if lang.kind(id) != NodeKind::VarRef {
4990 return None;
4991 }
4992 let name = lang.var_name(id)?.to_string();
4993 if !lang.const_scalars.contains(&name) {
4994 return None;
4995 }
4996 if let Some(p) = parent {
4997 let is_first = lang.children(p).first() == Some(&id);
4998 match lang.kind(p) {
4999 NodeKind::Assign if is_first => return None,
5001 NodeKind::Unary if is_first => {
5002 if let NodeData::Unary { op } = lang.data(p) {
5003 if op.is_incdec() {
5004 return None;
5005 }
5006 }
5007 }
5008 NodeKind::Call if is_first => return None,
5010 _ => {}
5011 }
5012 }
5013 let init = *lang.const_fields.get(&name)?;
5014 let lit = lang.copy_subtree(init);
5015 Some(Edit::Replace { target: id, with: lit })
5016 }
5017}
5018
5019pub struct NoopPrivateCall;
5020
5021impl Rule<JavaAst> for NoopPrivateCall {
5022 fn name(&self) -> &'static str {
5023 "noop_private_call"
5024 }
5025 fn kinds(&self) -> &'static [NodeKind] {
5026 &[NodeKind::ExprStmt]
5027 }
5028 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5029 let lang = ctx.lang;
5030 if lang.kind(id) != NodeKind::ExprStmt {
5031 return None;
5032 }
5033 let ch = lang.children(id).to_vec();
5034 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
5035 return None;
5036 }
5037 let call = ch[0];
5038 let cch = lang.children(call).to_vec();
5039 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
5040 return None; }
5042 let name = lang.var_name(cch[0])?.to_string();
5043 let arity = cch.len() - 1;
5044 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
5045 if cands.is_empty() {
5046 return None;
5047 }
5048 let arg_types: Vec<Option<JType>> =
5052 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
5053 let mut applicable_noop = false;
5054 let mut applicable_count = 0usize;
5055 for c in cands {
5056 let ok = c
5057 .params
5058 .iter()
5059 .zip(arg_types.iter())
5060 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
5061 if ok {
5062 applicable_count += 1;
5063 applicable_noop = c.noop;
5064 }
5065 }
5066 if applicable_count != 1 || !applicable_noop {
5067 return None;
5068 }
5069 for &arg in &cch[1..] {
5071 if lang.effect(arg) > Effect::MayRead {
5072 return None;
5073 }
5074 }
5075 Some(Edit::Delete { node: id })
5076 }
5077}
5078
5079fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
5083 match lang.kind(arg) {
5084 NodeKind::Literal => match lang.literal(arg)? {
5085 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
5086 LitRef::Char(_) => Some(JType::Char),
5087 LitRef::Bool(_) => Some(JType::Bool),
5088 LitRef::Int(_) => Some(JType::Int),
5089 LitRef::Long(_) => Some(JType::Long),
5090 LitRef::Float(_) => Some(JType::Float),
5091 LitRef::Double(_) => Some(JType::Double),
5092 LitRef::Null => None,
5093 },
5094 NodeKind::VarRef => {
5095 let nm = lang.var_name(arg)?;
5096 lang.var_type(arg)
5097 .cloned()
5098 .or_else(|| lang.field_types.get(nm).cloned())
5099 }
5100 _ => None,
5101 }
5102}
5103
5104fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5109 let Some(a) = at else { return true };
5110 match pt {
5111 JType::Ref(p) => {
5112 let pbase = p.split('<').next().unwrap_or(p).trim();
5113 pbase == "Object"
5114 || pbase == "java.lang.Object"
5115 || match a {
5116 JType::Ref(arend) => {
5117 let abase = arend.split('<').next().unwrap_or(arend).trim();
5118 abase == pbase
5119 }
5120 _ => false,
5121 }
5122 }
5123 JType::Array(_) => false, prim => match a {
5125 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5126 },
5127 }
5128}
5129
5130pub struct ConstMethodInline;
5140
5141impl Rule<JavaAst> for ConstMethodInline {
5142 fn name(&self) -> &'static str {
5143 "const_method_inline"
5144 }
5145 fn kinds(&self) -> &'static [NodeKind] {
5146 &[NodeKind::Call]
5147 }
5148 fn structural(&self) -> bool {
5149 true
5150 }
5151 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5152 let at_stmt_pos = ctx
5154 .parent(id)
5155 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5156 .unwrap_or(false);
5157 let lang = ctx.lang;
5158 if lang.kind(id) != NodeKind::Call {
5159 return None;
5160 }
5161 let ch = lang.children(id).to_vec();
5162 let (method_name, recv_class) = match lang.data(ch[0]) {
5166 NodeData::VarRef { name } => (*name, None),
5167 NodeData::Member { name } => {
5168 let recv = lang.children(ch[0])[0];
5169 match lang.var_name(recv) {
5170 Some(r) if r == "this" => (*name, None),
5171 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5172 (*name, Some(r.to_string()))
5173 }
5174 _ => return None,
5175 }
5176 }
5177 _ => return None,
5178 };
5179 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5180 let (params, body_expr, owner) = entry;
5181 if let Some(recv) = recv_class {
5188 if recv != owner {
5189 return None;
5190 }
5191 }
5192 {
5194 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5195 for nm in &names {
5196 if subtree_calls_self(lang, body_expr, nm) {
5197 return None;
5198 }
5199 }
5200 }
5201 let args = ch[1..].to_vec();
5202 if args.len() != params.len() || params.is_empty() {
5203 return None;
5204 }
5205 for &a in &args {
5206 lang.literal(a)?;
5207 }
5208 let map: std::collections::HashMap<String, JavaId> = params
5209 .iter()
5210 .zip(args.iter())
5211 .map(|(p, &a)| (p.clone(), a))
5212 .collect();
5213 let with = copy_subst(lang, body_expr, &map);
5214 if at_stmt_pos {
5220 match lang.kind(with) {
5221 NodeKind::Call | NodeKind::New => {}
5222 _ => return None,
5223 }
5224 }
5225 Some(Edit::Replace {
5226 target: id,
5227 with,
5228 })
5229 }
5230}
5231
5232fn copy_subst(
5234 lang: &mut JavaAst,
5235 node: JavaId,
5236 map: &std::collections::HashMap<String, JavaId>,
5237) -> JavaId {
5238 if let NodeData::VarRef { name } = lang.data(node) {
5239 if let Some(&repl) = map.get(lang.sn(*name)) {
5240 return lang.copy_subtree(repl);
5241 }
5242 }
5243 let children: Vec<JavaId> = lang.children(node).to_vec();
5244 let new_children: Vec<JavaId> = children
5245 .iter()
5246 .map(|&c| copy_subst(lang, c, map))
5247 .collect();
5248 lang.clone_node(node, new_children)
5249}
5250
5251
5252pub struct TwrRecover;
5285
5286fn twr_is_throwable(ty_raw: &str) -> bool {
5287 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5288}
5289
5290fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5292 if lang.kind(n) == NodeKind::ExprStmt {
5293 if let Some(&e) = lang.children(n).first() {
5294 return e;
5295 }
5296 }
5297 n
5298}
5299
5300fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5302 if lang.kind(n) == NodeKind::Block {
5303 lang.children(n).to_vec()
5304 } else {
5305 vec![n]
5306 }
5307}
5308
5309fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5311 if lang.kind(e) != NodeKind::Call {
5312 return false;
5313 }
5314 let ch = lang.children(e);
5315 if ch.len() != 1 {
5316 return false;
5317 }
5318 match lang.data(ch[0]) {
5319 NodeData::Member { name } if lang.sn(*name) == "close" => {
5320 let Some(&recv) = lang.children(ch[0]).first() else {
5321 return false;
5322 };
5323 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5324 }
5325 _ => false,
5326 }
5327}
5328
5329fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5330 if lang.kind(n) != NodeKind::ExprStmt {
5331 return false;
5332 }
5333 lang.children(n)
5334 .first()
5335 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5336}
5337
5338fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5340 if lang.kind(n) != NodeKind::ExprStmt {
5341 return false;
5342 }
5343 let Some(&e) = lang.children(n).first() else {
5344 return false;
5345 };
5346 if lang.kind(e) != NodeKind::Call {
5347 return false;
5348 }
5349 let ch = lang.children(e);
5350 if ch.len() != 2 {
5351 return false;
5352 }
5353 match lang.data(ch[0]) {
5354 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5355 let Some(&recv) = lang.children(ch[0]).first() else {
5356 return false;
5357 };
5358 lang.kind(recv) == NodeKind::VarRef
5359 && lang.var_name(recv) == Some(primary)
5360 && lang.kind(ch[1]) == NodeKind::VarRef
5361 && lang.var_name(ch[1]) == Some(sup)
5362 }
5363 _ => false,
5364 }
5365}
5366
5367fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5368 let mut stack = vec![root];
5369 while let Some(n) = stack.pop() {
5370 if n == target {
5371 return true;
5372 }
5373 stack.extend(lang.children(n).iter().copied());
5374 }
5375 false
5376}
5377
5378fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5380 let mut stack = vec![root];
5381 while let Some(n) = stack.pop() {
5382 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5383 let ok = allowed
5384 .iter()
5385 .any(|&a| a == n || node_contains(lang, a, n));
5386 if !ok {
5387 return true;
5388 }
5389 }
5390 if lang.kind(n) == NodeKind::VarDecl {
5391 if let Some(nm) = lang.var_name(n) {
5392 if nm == name && !allowed.contains(&n) {
5393 return true;
5395 }
5396 }
5397 }
5398 stack.extend(lang.children(n).iter().copied());
5399 }
5400 false
5401}
5402
5403fn twr_close_with_suppressed(
5407 lang: &JavaAst,
5408 try_node: JavaId,
5409 primary: &str,
5410) -> Option<(String, String)> {
5411 let ch = lang.children(try_node).to_vec();
5412 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5413 return None;
5414 }
5415 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5416 return None;
5417 };
5418 if !twr_is_throwable(ty_raw) {
5419 return None;
5420 }
5421 let sup = sup.clone();
5422 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5423 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5424 return None;
5425 }
5426 let close_stmts = twr_stmts(lang, ch[0]);
5427 if close_stmts.len() != 1 {
5428 return None;
5429 }
5430 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5431 return None;
5432 }
5433 let e = *lang.children(close_stmts[0]).first()?;
5434 let cc = lang.children(e);
5435 if cc.len() != 1 {
5436 return None;
5437 }
5438 let NodeData::Member { name } = lang.data(cc[0]) else {
5439 return None;
5440 };
5441 if lang.sn(*name) != "close" {
5442 return None;
5443 }
5444 let recv = *lang.children(cc[0]).first()?;
5445 if lang.kind(recv) != NodeKind::VarRef {
5446 return None;
5447 }
5448 Some((lang.var_name(recv)?.to_string(), sup))
5449}
5450
5451impl Rule<JavaAst> for TwrRecover {
5452 fn name(&self) -> &'static str {
5453 "twr_recover"
5454 }
5455 fn kinds(&self) -> &'static [NodeKind] {
5456 &[NodeKind::Try]
5457 }
5458 fn structural(&self) -> bool {
5459 true
5460 }
5461 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5462 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5464 let lang = ctx.lang;
5465 if lang.kind(id) != NodeKind::Try {
5466 return None;
5467 }
5468 let ch = lang.children(id).to_vec();
5469 let try_idx = ch
5471 .iter()
5472 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5473 if try_idx != 0 {
5474 return None;
5475 }
5476 let try_block = ch[0];
5477 if lang.kind(parent) != NodeKind::Block {
5478 return None;
5479 }
5480 let pch = lang.children(parent).to_vec();
5481
5482 if ch.len() == 2 {
5484 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5485 if twr_is_throwable(ty_raw) {
5486 let t = t.clone();
5487 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5488 if catch_stmts.len() == 2 {
5489 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5491 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5492 && lang
5493 .children(throw_stmt)
5494 .first()
5495 .is_some_and(|&x| {
5496 lang.kind(x) == NodeKind::VarRef
5497 && lang.var_name(x) == Some(t.as_str())
5498 });
5499 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5500 if let Some((r, sup)) =
5501 twr_close_with_suppressed(lang, inner_try, t.as_str())
5502 {
5503 if idx >= 1 && idx + 1 < pch.len() {
5504 let decl_r = pch[idx - 1];
5505 let trailing = pch[idx + 1];
5506 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5507 if name == &r {
5508 let (name, ty) = (name.clone(), ty.clone());
5509 let init =
5510 lang.children(decl_r).first().copied();
5511 if let Some(init) = init {
5512 let provably_new = lang.kind(init)
5514 == NodeKind::New;
5515 let close_ok =
5516 twr_is_close_stmt(lang, trailing, &r);
5517 if provably_new && close_ok {
5518 let root = walk_root;
5520 let r_ok = !refs_escape(
5521 lang,
5522 root,
5523 &r,
5524 &[
5525 decl_r,
5526 init,
5527 *lang
5528 .children(inner_try)
5529 .first()
5530 .unwrap_or(&inner_try),
5531 trailing,
5532 try_block,
5533 ],
5534 );
5535 let t_ok = !refs_escape(
5536 lang, root, &t,
5537 &[throw_stmt, ch[1]],
5538 );
5539 let sup_ok = !refs_escape(
5540 lang, root, &sup,
5541 &[ch[1]],
5542 );
5543 if r_ok && t_ok && sup_ok {
5544 let init_copy =
5545 lang.copy_subtree(init);
5546 let res = lang.var_decl(
5547 &name, ty,
5548 Some(init_copy),
5549 );
5550 let new_try = lang.try_(
5551 vec![res],
5552 try_block,
5553 Vec::new(),
5554 None,
5555 );
5556 return Some(Edit::Splice {
5557 node: parent,
5558 index: idx - 1,
5559 remove: 3,
5560 insert: vec![new_try],
5561 });
5562 }
5563 }
5564 }
5565 }
5566 }
5567 }
5568 }
5569 }
5570 }
5571 }
5572 }
5573 }
5574
5575 if ch.len() == 3 {
5577 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5578 return None;
5579 };
5580 if !twr_is_throwable(ty_raw) {
5581 return None;
5582 }
5583 let t = t.clone();
5584 if lang.kind(ch[2]) != NodeKind::Block {
5586 return None;
5587 }
5588 let fin_stmts = twr_stmts(lang, ch[2]);
5589 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5590 return None;
5591 }
5592 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5594 if catch_stmts.len() != 2 {
5595 return None;
5596 }
5597 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5598 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5599 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5600 return None;
5601 };
5602 let as_ch = lang.children(assign_expr);
5603 if as_ch.len() != 2 {
5604 return None;
5605 }
5606 let primary = match lang.data(as_ch[0]) {
5607 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5608 _ => return None,
5609 };
5610 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5611 && lang.var_name(as_ch[1]) == Some(t.as_str())
5612 && lang.kind(as_ch[0]) == NodeKind::VarRef
5613 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5614 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5615 && lang.children(throw_stmt)
5616 .first()
5617 .is_some_and(|&x| {
5618 lang.kind(x) == NodeKind::VarRef
5619 && lang.var_name(x) == Some(t.as_str())
5620 });
5621 if !assign_ok || !throw_ok {
5622 return None;
5623 }
5624 let if1 = fin_stmts[0];
5626 let i1 = lang.children(if1).to_vec();
5627 if i1.len() < 2 {
5628 return None;
5629 }
5630 let cond1 = i1[0];
5631 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5632 return None;
5633 };
5634 let c1 = lang.children(cond1);
5635 let r = match lang.data(c1[0]) {
5636 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5637 lang.sn(*name).to_string()
5638 }
5639 _ => {
5640 return None;
5641 }
5642 };
5643 let then1 = i1[1];
5644 let inner_stmts = twr_stmts(lang, then1);
5645 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5646 return None;
5647 }
5648 let if2 = inner_stmts[0];
5649 let i2 = lang.children(if2).to_vec();
5650 if i2.len() != 3 {
5651 return None;
5652 }
5653 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5654 return None;
5655 };
5656 let c2 = lang.children(i2[0]);
5657 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5658 && lang.var_name(c2[0]) == Some(primary.as_str())
5659 && lang.literal(c2[1]) == Some(LitRef::Null);
5660 if !primary_ok {
5661 return None;
5662 }
5663 let (then2, els2) = (i2[1], i2[2]);
5664 let t2_stmts = twr_stmts(lang, then2);
5666 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5667 return None;
5668 }
5669 let Some((r2, sup)) =
5670 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5671 else {
5672 return None;
5673 };
5674 if r2 != r {
5675 return None;
5676 }
5677 let e2_stmts = twr_stmts(lang, els2);
5679 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5680 return None;
5681 }
5682 if idx < 2 {
5684 return None;
5685 }
5686 let decl_r = pch[idx - 2];
5687 let decl_primary = pch[idx - 1];
5688 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5689 (
5690 NodeData::VarDecl { name: rn, .. },
5691 NodeData::VarDecl { name: pn, .. },
5692 ) => (rn.clone(), pn.clone()),
5693 _ => return None,
5694 };
5695 if r_decl != r || primary_decl != primary {
5696 return None;
5697 }
5698 let pinit_null = lang
5700 .children(decl_primary)
5701 .first()
5702 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5703 if !pinit_null {
5704 return None;
5705 }
5706 let r_init = lang.children(decl_r).first().copied()?;
5707 let ty = match lang.data(decl_r) {
5708 NodeData::VarDecl { ty, .. } => ty.clone(),
5709 _ => return None,
5710 };
5711 let root = walk_root;
5713 let r_allowed = [decl_r, r_init, ch[2], try_block];
5715 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5716 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5717 if refs_escape(lang, root, &r, &r_allowed)
5718 || refs_escape(lang, root, &primary, &p_allowed)
5719 || refs_escape(lang, root, &t, &t_allowed)
5720 || refs_escape(lang, root, &sup, &[ch[2]])
5721 {
5722 return None;
5723 }
5724 let init_copy = lang.copy_subtree(r_init);
5725 let res = lang.var_decl(&r, ty, Some(init_copy));
5726 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5727 return Some(Edit::Splice {
5728 node: parent,
5729 index: idx - 2,
5730 remove: 3,
5731 insert: vec![new_try],
5732 });
5733 }
5734 None
5735 }
5736}
5737
5738pub struct StringSwitchRecover;
5763
5764fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5765 out.push_str(&format!("{:?} ", lang.data(n)));
5766 for &c in lang.children(n) {
5767 subtree_sig(lang, c, out);
5768 }
5769}
5770
5771fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5773 let ch = lang.children(sw).to_vec();
5774 let mut out = Vec::new();
5775 for &c in &ch[1..] {
5776 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5777 return None;
5778 };
5779 let is_default = *is_default;
5780 let cc = lang.children(c);
5781 let label = if is_default {
5782 None
5783 } else {
5784 if *labels != 1 {
5785 return None; }
5787 let lab = cc[0];
5788 let lit = lang.literal(lab)?;
5789 let v = match lit {
5790 LitRef::Int(v) | LitRef::Long(v) => v,
5791 _ => return None, };
5793 Some(v)
5794 };
5795 out.push((label, cc[*labels as usize..].to_vec()));
5796 }
5797 Some(out)
5798}
5799
5800fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5801 let Some(&last) = stmts.last() else {
5802 return false; };
5804 let last = if lang.kind(last) == NodeKind::Block {
5805 match lang.children(last).last() {
5806 Some(&l) => l,
5807 None => return false,
5808 }
5809 } else {
5810 last
5811 };
5812 matches!(
5813 lang.kind(last),
5814 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5815 )
5816}
5817
5818impl Rule<JavaAst> for StringSwitchRecover {
5819 fn name(&self) -> &'static str {
5820 "string_switch_recover"
5821 }
5822 fn kinds(&self) -> &'static [NodeKind] {
5823 &[NodeKind::Switch]
5824 }
5825 fn structural(&self) -> bool {
5826 true
5827 }
5828 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5829 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5830 let lang = ctx.lang;
5831 if lang.kind(id) != NodeKind::Switch {
5832 return None;
5833 }
5834 let ch = lang.children(id).to_vec();
5835 if ch.len() < 2 {
5836 return None;
5837 }
5838 let x = match lang.data(ch[0]) {
5840 NodeData::Call => {
5841 let cc = lang.children(ch[0]);
5842 if cc.len() != 1 {
5843 return None;
5844 }
5845 match lang.data(cc[0]) {
5846 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5847 let recv = *lang.children(cc[0]).first()?;
5848 if lang.kind(recv) != NodeKind::VarRef {
5849 return None;
5850 }
5851 lang.var_name(recv)?.to_string()
5852 }
5853 _ => return None,
5854 }
5855 }
5856 _ => {
5857 return None;
5858 }
5859 };
5860 struct CaseInfo {
5862 lit: String,
5863 k: i64,
5864 inner_switch: JavaId,
5865 }
5866 let mut infos: Vec<CaseInfo> = Vec::new();
5867 let mut v_name: Option<String> = None;
5868 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5870 for &c in &ch[1..] {
5871 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5872 return None;
5873 };
5874 let is_default = *is_default;
5875 if is_default {
5876 return None; }
5878 if *labels != 1 {
5879 return None;
5880 }
5881 let cc = lang.children(c);
5882 let h = match lang.literal(cc[0])? {
5883 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5884 _ => return None,
5885 };
5886 let body = cc[1..].to_vec();
5887 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5889 lang.children(body[0]).to_vec()
5890 } else {
5891 body
5892 };
5893 let mut rest = body;
5894 let mut _e_decl: Option<(String, JavaId)> = None;
5896 if let Some(&first) = rest.first() {
5897 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5898 if let Some(&init) = lang.children(first).first() {
5899 if is_x_equals(lang, init, &x) {
5900 _e_decl = Some((name.clone(), first));
5901 rest = rest[1..].to_vec();
5902 }
5903 }
5904 }
5905 }
5906 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5907 return None;
5908 }
5909 let ifn = rest[0];
5910 let ic = lang.children(ifn).to_vec();
5911 if ic.len() != 3 {
5912 return None;
5913 }
5914 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5916 return None;
5917 };
5918 let guard = *lang.children(ic[0]).first()?;
5919 let lit = match lang.data(guard) {
5920 NodeData::VarRef { name } => {
5921 let Some((en, decl)) = &_e_decl else {
5923 return None;
5924 };
5925 if lang.sn(*name) != en {
5926 return None;
5927 }
5928 let init = *lang.children(*decl).first()?;
5929 equals_lit(lang, init, &x)?
5930 }
5931 NodeData::Call => equals_lit(lang, guard, &x)?,
5932 _ => return None,
5933 };
5934 guard_nodes.push(guard);
5935 if let Some((_, decl)) = &_e_decl {
5936 guard_nodes.push(*decl);
5937 }
5938 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5940 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5941 NodeData::Break { label: None });
5942 if !then_ok {
5943 return None;
5944 }
5945 let els = twr_stmts(lang, ic[2]);
5947 let (k, inner_switch, assign_node) = match els.len() {
5948 2 => {
5949 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5950 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5951 return None;
5952 };
5953 let a_ch = lang.children(assign_expr);
5954 let vn = match lang.data(a_ch[0]) {
5955 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5956 _ => return None,
5957 };
5958 let k = match lang.literal(a_ch[1])? {
5959 LitRef::Int(v) | LitRef::Long(v) => v,
5960 _ => return None,
5961 };
5962 if lang.kind(els[1]) != NodeKind::Switch {
5963 return None;
5964 }
5965 let sel = *lang.children(els[1]).first()?;
5967 if lang.kind(sel) != NodeKind::VarRef
5968 || lang.var_name(sel) != Some(vn.as_str())
5969 {
5970 return None;
5971 }
5972 match &v_name {
5973 Some(v) if *v != vn => return None,
5974 _ => v_name = Some(vn),
5975 }
5976 (k, els[1], Some(els[0]))
5977 }
5978 1 => {
5979 if lang.kind(els[0]) != NodeKind::Switch {
5980 return None;
5981 }
5982 let sel = *lang.children(els[0]).first()?;
5984 let k = match lang.literal(sel)? {
5985 LitRef::Int(v) | LitRef::Long(v) => v,
5986 _ => return None,
5987 };
5988 (k, els[0], None)
5989 }
5990 _ => return None,
5991 };
5992 if let Some(a) = assign_node {
5993 v_nodes.push(a);
5994 }
5995 v_nodes.push(*lang.children(inner_switch).first()?);
5996 let hlit: i32 = lit
5998 .chars()
5999 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
6000 if hlit != h {
6001 return None;
6002 }
6003 let _ = assign_node;
6004 infos.push(CaseInfo {
6005 lit,
6006 k,
6007 inner_switch,
6008 });
6009 }
6010 if infos.is_empty() {
6011 return None;
6012 }
6013 if lang.kind(parent) != NodeKind::Block {
6015 return None;
6016 }
6017 let pch = lang.children(parent).to_vec();
6018 let mut k = idx + 1;
6019 if k < pch.len() {
6021 let texpr = unwrap_expr_stmt(lang, pch[k]);
6022 if let NodeData::Assign { op: None } = lang.data(texpr) {
6023 let a_ch = lang.children(texpr);
6024 if a_ch.len() == 2
6025 && lang.kind(a_ch[0]) == NodeKind::VarRef
6026 && lang.var_name(a_ch[0]) == v_name.as_deref()
6027 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
6028 {
6029 v_nodes.push(pch[k]);
6030 k += 1;
6031 }
6032 }
6033 }
6034 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
6035 return None;
6036 }
6037 let trailing = pch[k];
6038 let tsel = *lang.children(trailing).first()?;
6040 match lang.data(tsel) {
6041 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
6042 v_nodes.push(tsel);
6043 }
6044 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
6045 _ => {
6046 lang.literal(tsel)?;
6047 }
6048 }
6049 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
6052 for &c in &lang.children(sw)[1..] {
6053 subtree_sig(lang, c, out);
6054 }
6055 }
6056 let mut sig0 = String::new();
6057 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
6058 for info in &infos[1..] {
6059 let mut sig = String::new();
6060 switch_cases_sig(lang, info.inner_switch, &mut sig);
6061 if sig != sig0 {
6062 return None;
6063 }
6064 }
6065 {
6066 let mut sig = String::new();
6067 switch_cases_sig(lang, trailing, &mut sig);
6068 if sig != sig0 {
6069 return None;
6070 }
6071 }
6072 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
6074 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
6075 std::collections::HashMap::new();
6076 let mut default_body: Option<Vec<JavaId>> = None;
6077 for (lab, stmts) in &bodies {
6078 match lab {
6079 Some(v) => {
6080 body_map.insert(*v, stmts.clone());
6081 }
6082 None => default_body = Some(stmts.clone()),
6083 }
6084 }
6085 let Some(default_body) = default_body else {
6086 return None;
6087 };
6088 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
6090 if !ssw_body_terminal(lang, stmts) {
6091 return None;
6092 }
6093 }
6094 let root = ctx_root;
6096 if let Some(vn) = &v_name {
6097 let mut allowed: Vec<JavaId> = v_nodes.clone();
6098 allowed.push(trailing);
6099 if idx >= 1 {
6100 let cand = pch[idx - 1];
6101 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
6102 if name == vn {
6103 allowed.push(cand);
6104 if idx != 1 {
6106 allowed.pop();
6107 }
6108 }
6109 }
6110 }
6111 if refs_escape(lang, root, vn, &allowed) {
6112 return None;
6113 }
6114 }
6115 let mut new_cases: Vec<JavaId> = Vec::new();
6117 for info in &infos {
6118 let Some(body) = body_map.get(&info.k) else {
6119 return None;
6120 };
6121 let lab = lang.lit(Lit::Str(info.lit.clone()));
6122 let case = lang.case_(vec![lab], false, false, body.clone());
6123 new_cases.push(case);
6124 }
6125 let def_case = lang.case_(Vec::new(), true, false, default_body);
6126 new_cases.push(def_case);
6127 let sel = lang.var(&x);
6128 let new_switch = lang.switch_(sel, new_cases);
6129 let remove = k - idx + 1;
6131 Some(Edit::Splice {
6132 node: parent,
6133 index: idx,
6134 remove,
6135 insert: vec![new_switch],
6136 })
6137 }
6138}
6139
6140fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6142 if lang.kind(e) != NodeKind::Call {
6143 return None;
6144 }
6145 let cc = lang.children(e);
6146 if cc.len() != 2 {
6147 return None;
6148 }
6149 match lang.data(cc[0]) {
6150 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6151 let recv = *lang.children(cc[0]).first()?;
6152 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6153 return None;
6154 }
6155 match lang.literal(cc[1])? {
6156 LitRef::Str(s) => Some(s.to_string()),
6157 _ => None,
6158 }
6159 }
6160 _ => None,
6161 }
6162}
6163
6164fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6165 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6166}
6167
6168pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6175 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6176 rules.push(Box::new(CastSimplify));
6177 rules.push(Box::new(SelfCompare));
6178 rules.push(Box::new(StringBuilderFold));
6179 rules.push(Box::new(BoxUnboxChain));
6180 rules.push(Box::new(IteratorToForEach));
6181 rules.push(Box::new(NewStringFold));
6182 rules.push(Box::new(NewStringCharArrayFold));
6183 rules.push(Box::new(EmptyFinallyStrip));
6184 rules.push(Box::new(TryUnwrapNoCatch));
6185 rules.push(Box::new(TryUnwrapRethrow));
6186 rules.push(Box::new(StaticExec));
6187 rules.push(Box::new(LoopHeadBreak));
6188 rules.push(Box::new(WhileIteratorToForEach));
6189 rules.push(Box::new(ConcatValueOfDrop));
6190 rules.push(Box::new(StringBuilderStatements));
6191 rules.push(Box::new(TrailingContinueJava));
6192 rules.push(Box::new(XorNoise));
6193 rules.push(Box::new(StrLenFold));
6194 rules.push(Box::new(LiteralEval));
6195 rules.push(Box::new(Base64NewStringFold));
6196 rules.push(Box::new(UrlDecodeFold));
6197 rules.push(Box::new(CffRecover));
6198 rules.push(Box::new(StaticArrayIndexFold));
6199 rules.push(Box::new(TrailingAssignReturn));
6200 rules.push(Box::new(DeadPureCall));
6201 rules.push(Box::new(CtorDelegationHoist));
6202 rules.push(Box::new(TrailingElseFlatten));
6203 rules.push(Box::new(TrailingIfContinue));
6204 rules.push(Box::new(ConstFieldPropagate));
6205 rules.push(Box::new(NoopPrivateCall));
6206 rules.push(Box::new(ConstMethodInline));
6207 rules.push(Box::new(TwrRecover));
6208 rules.push(Box::new(StringSwitchRecover));
6209 rules
6210}
6211
6212pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6214 let mut rules = default_java_rules();
6215 let mut engine_all = cure_engine::rules::all_rules();
6216 rules.append(&mut engine_all);
6217 rules
6218}
6219
6220pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6222 let rules = default_java_rules();
6223 cure_engine::simplify(ast, root, &rules, cfg)
6224}
6225
6226pub fn simplify_unit_with_facts(
6231 ast: &mut JavaAst,
6232 unit: &mut CompilationUnit,
6233 cfg: &Config,
6234 facts: &cure_java_ast::JavaFacts,
6235) -> Report {
6236 ast.set_facts(facts.clone());
6237 let r = simplify_unit(ast, unit, cfg);
6238 ast.clear_facts();
6239 r
6240}
6241
6242pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6243 let mut total = Report::default();
6244 for _cycle in 0..6 {
6249 let mut round = Report::default();
6250 collect_unit_consts(ast, unit);
6251 if cfg.remove_dead_methods {
6254 collect_noop_private_methods(ast, unit);
6255 } else {
6256 ast.noop_private_methods.clear();
6257 }
6258 for ty in &mut unit.types {
6259 simplify_type(ast, ty, cfg, &mut round);
6260 }
6261 if cfg.remove_dead_methods {
6262 round.edits += remove_dead_private_fields(ast, unit);
6263 round.edits += remove_dead_private_methods(ast, unit);
6264 round.edits += remove_unused_imports(ast, unit);
6265 }
6266 total.edits += round.edits;
6267 total.iterations += round.iterations;
6268 total.discarded += round.discarded;
6269 for (k, v) in round.by_rule {
6270 *total.by_rule.entry(k).or_insert(0) += v;
6271 }
6272 if round.edits == 0 {
6277 break;
6278 }
6279 }
6280 total
6281}
6282
6283fn collect_anon_and_string_names(
6290 ast: &JavaAst,
6291 unit: &CompilationUnit,
6292) -> std::collections::HashSet<String> {
6293 let mut out: std::collections::HashSet<String> = Default::default();
6294 let mut bodies: Vec<JavaId> = Vec::new();
6295 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6296 for root in bodies {
6297 let mut stack = vec![root];
6298 while let Some(n) = stack.pop() {
6299 match ast.data(n) {
6300 NodeData::New { anon_raw: Some(raw), .. } => {
6301 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6302 if !id.is_empty() {
6303 out.insert(id.to_string());
6304 }
6305 }
6306 }
6307 NodeData::Literal(Lit::Str(text)) => {
6308 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6309 if !id.is_empty() {
6310 out.insert(id.to_string());
6311 }
6312 }
6313 }
6314 _ => {}
6315 }
6316 for &c in ast.children(n) {
6317 stack.push(c);
6318 }
6319 }
6320 }
6321 out
6322}
6323
6324fn collect_all_blind_refs(
6353 ast: &JavaAst,
6354 unit: &CompilationUnit,
6355) -> std::collections::HashSet<String> {
6356 let mut out: std::collections::HashSet<String> = Default::default();
6357 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6358 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6359 if !id.is_empty() {
6360 out.insert(id.to_string());
6361 }
6362 }
6363 };
6364 let mut bodies: Vec<JavaId> = Vec::new();
6366 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6367 for root in bodies {
6368 let mut stack = vec![root];
6369 while let Some(n) = stack.pop() {
6370 match ast.data(n) {
6371 NodeData::Raw { text } => split_into(text, &mut out),
6372 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6373 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6374 NodeData::ForEach { ty, .. } => {
6376 collect_ty_idents(ty, &mut out);
6377 }
6378 NodeData::NewArray { ty, .. } => {
6380 collect_ty_idents(ty, &mut out);
6381 }
6382 NodeData::Lambda { params_raw } => {
6386 split_into(params_raw, &mut out);
6387 }
6388 _ => {}
6389 }
6390 for &c in ast.children(n) {
6391 stack.push(c);
6392 }
6393 }
6394 }
6395 for r in &unit.raws {
6399 split_into(r, &mut out);
6400 }
6401 for_each_type(unit, &mut |ty: &TypeDecl| {
6404 collect_mods_idents_all(&ty.mods, &mut out);
6405 for e in &ty.enum_constants {
6406 split_into(e, &mut out);
6407 }
6408 split_into(&ty.ty_params, &mut out);
6409 split_into(&ty.header, &mut out);
6410 for m in &ty.members {
6411 match m {
6412 Member::Method { mods, name, ty_params, params, .. }
6413 | Member::Constructor { mods, name, ty_params, params, .. } => {
6414 collect_mods_idents_all(mods, &mut out);
6415 split_into(ty_params, &mut out);
6416 for p in params {
6417 collect_mods_idents_all(&p.mods, &mut out);
6418 collect_ty_idents(&p.ty, &mut out);
6419 }
6420 if has_bare_methodsource(mods) {
6425 out.insert(name.clone());
6426 }
6427 }
6428 Member::Field { mods, declarators, .. } => {
6429 collect_mods_idents_all(mods, &mut out);
6430 let _ = declarators;
6432 }
6433 Member::Type(t) => {
6434 let _ = t; }
6436 Member::Raw(text) => {
6443 split_into(text, &mut out);
6444 }
6445 _ => {}
6446 }
6447 }
6448 });
6449 out
6450}
6451
6452fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6456 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6457 if !id.is_empty() {
6458 out.insert(id.to_string());
6459 }
6460 }
6461}
6462
6463fn has_bare_methodsource(mods: &str) -> bool {
6470 let hay = mods.as_bytes();
6471 const NEEDLE: &[u8] = b"MethodSource";
6472 let mut i = 0usize;
6473 while let Some(p) = mods[i..].find("MethodSource") {
6474 let at = i + p;
6475 let end = at + NEEDLE.len();
6476 let prev_ok = at == 0
6479 || !(hay[at - 1].is_ascii_alphanumeric()
6480 || hay[at - 1] == b'_'
6481 || hay[at - 1] == b'$');
6482 let next_ident =
6483 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6484 if prev_ok && !next_ident {
6485 let mut j = end;
6486 while j < hay.len() && hay[j].is_ascii_whitespace() {
6487 j += 1;
6488 }
6489 if j >= hay.len() || hay[j] != b'(' {
6490 return true;
6491 }
6492 }
6493 i = end;
6494 }
6495 false
6496}
6497
6498const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6502 "writeObject",
6503 "readObject",
6504 "readObjectNoData",
6505 "writeReplace",
6506 "readResolve",
6507 "validateObject",
6508];
6509
6510fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6511 use std::collections::HashSet;
6512 let mut removed = 0usize;
6513 let anon_names = collect_anon_and_string_names(ast, unit);
6515 let blind = collect_all_blind_refs(ast, unit);
6520 loop {
6521 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6528 let mut veto: HashSet<String> = HashSet::new();
6529 let mut roots: Vec<JavaId> = Vec::new();
6530 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6531 for body in roots {
6532 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6533 }
6534 for_each_type(unit, &mut |ty| {
6535 for m in &ty.members {
6536 if let Member::Method { name, params, .. } = m {
6537 if params.iter().any(|p| p.varargs) {
6538 veto.insert(name.clone());
6539 }
6540 }
6541 }
6542 });
6543 let mut changed = false;
6545 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6546 let before = ty.members.len();
6547 ty.members.retain(|m| {
6548 if let Member::Method { mods, name, params, .. } = m {
6549 let is_private = mods.split_whitespace().any(|w| w == "private");
6550 let annotated = mods.contains('@');
6551 let keep = !is_private
6552 || annotated
6553 || params.iter().any(|p| p.varargs)
6555 || veto.contains(name)
6556 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6560 || anon_names.contains(name)
6561 || blind.contains(name)
6562 || referenced.contains(&(name.clone(), params.len()));
6563 keep
6564 } else {
6565 true
6566 }
6567 });
6568 if ty.members.len() != before {
6569 removed += before - ty.members.len();
6570 changed = true;
6571 }
6572 });
6573 if !changed {
6574 return removed;
6575 }
6576 }
6577}
6578
6579fn strip_ta_prefix(name: &str) -> &str {
6584 if !name.starts_with('<') {
6585 return name;
6586 }
6587 let b = name.as_bytes();
6588 let mut depth = 0i32;
6589 for (i, &c) in b.iter().enumerate() {
6590 match c {
6591 b'<' => depth += 1,
6592 b'>' => {
6593 depth -= 1;
6594 if depth == 0 {
6595 return &name[i + 1..];
6596 }
6597 }
6598 _ => {}
6599 }
6600 }
6601 name }
6603
6604fn collect_call_arity_refs(
6606 ast: &JavaAst,
6607 root: JavaId,
6608 out: &mut std::collections::HashSet<(String, usize)>,
6609 veto: &mut std::collections::HashSet<String>,
6610) {
6611 let mut stack = vec![root];
6612 while let Some(id) = stack.pop() {
6613 match ast.data(id) {
6614 NodeData::Call => {
6615 let ch = ast.children(id).to_vec();
6616 if let Some(&callee) = ch.first() {
6617 let name = match ast.data(callee) {
6618 NodeData::Member { name } | NodeData::VarRef { name } => {
6619 ast.sn(*name).to_string()
6620 }
6621 _ => String::new(),
6622 };
6623 if !name.is_empty() {
6624 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6625 }
6626 }
6627 }
6628 NodeData::MethodRef { name } => {
6629 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6630 veto.insert(strip_ta_prefix(short).to_string());
6631 }
6632 }
6633 _ => {}
6634 }
6635 for &c in ast.children(id) {
6636 stack.push(c);
6637 }
6638 }
6639}
6640
6641fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6643 for ty in &unit.types {
6644 for_each_type_inner(ty, f);
6645 }
6646}
6647fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6648 f(ty);
6649 for m in &ty.members {
6650 if let Member::Type(t) = m {
6651 for_each_type_inner(t, f);
6652 }
6653 }
6654}
6655
6656fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6658 for ty in &mut unit.types {
6659 for_each_type_mut_inner(ty, f);
6660 }
6661}
6662fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6663 f(ty);
6664 for m in &mut ty.members {
6665 if let Member::Type(t) = m {
6666 for_each_type_mut_inner(t, f);
6667 }
6668 }
6669}
6670
6671fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6673 for m in &ty.members {
6674 match m {
6675 Member::Method { body: Some(b), .. }
6676 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6677 Member::Initializer { body, .. } => out.push(*body),
6678 Member::Field { declarators, .. } => {
6679 for d in declarators {
6680 if let Some(init) = d.init {
6681 out.push(init);
6682 }
6683 }
6684 }
6685 Member::Type(t) => collect_member_roots(t, out),
6686 _ => {}
6687 }
6688 }
6689}
6690
6691fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6706 use std::collections::{HashMap, HashSet};
6707 ast.noop_private_methods.clear();
6708 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6709 let mut veto: HashSet<String> = HashSet::new();
6710 for_each_type(unit, &mut |ty: &TypeDecl| {
6711 for m in &ty.members {
6712 if let Member::Method { mods, name, params, body, throws, .. } = m {
6713 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6714 veto.insert(name.clone());
6715 continue;
6716 }
6717 let is_private = mods.split_whitespace().any(|w| w == "private");
6718 let empty = match body {
6719 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6720 None => false,
6721 };
6722 let no_throws = throws.is_empty();
6726 let cand = NoopCandidate {
6727 params: params.iter().map(|p| p.ty.clone()).collect(),
6728 noop: is_private && empty && no_throws,
6729 };
6730 by_name
6731 .entry(name.clone())
6732 .or_default()
6733 .entry(params.len())
6734 .or_default()
6735 .push(cand);
6736 }
6737 }
6738 });
6739 let blind = collect_all_blind_refs(ast, unit);
6742 let mut roots: Vec<JavaId> = Vec::new();
6745 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6746 for root in roots {
6747 let mut stack = vec![root];
6748 while let Some(n) = stack.pop() {
6749 if let NodeData::MethodRef { name } = ast.data(n) {
6750 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6751 veto.insert(strip_ta_prefix(short).to_string());
6752 }
6753 }
6754 for &c in ast.children(n) {
6755 stack.push(c);
6756 }
6757 }
6758 }
6759 for (name, arities) in by_name {
6760 if veto.contains(&name) || blind.contains(&name) {
6761 continue;
6762 }
6763 ast.noop_private_methods.insert(name, arities);
6764 }
6765}
6766
6767fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6772 use std::collections::HashSet;
6773 let mut idents: HashSet<String> = HashSet::new();
6774 let mut roots: Vec<JavaId> = Vec::new();
6776 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6777 for root in roots {
6778 let mut stack = vec![root];
6779 while let Some(n) = stack.pop() {
6780 match ast.data(n) {
6781 NodeData::VarDecl { name, ty, .. } => {
6782 idents.insert(name.clone());
6783 collect_ty_idents(ty, &mut idents);
6784 }
6785 NodeData::VarRef { name } => {
6786 let n = ast.sn(*name);
6790 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6791 if !id.is_empty() {
6792 idents.insert(id.to_string());
6793 }
6794 }
6795 }
6796 NodeData::Member { name } => {
6797 idents.insert(ast.sn(*name).to_string());
6798 }
6799 NodeData::MethodRef { name } => {
6800 let n = ast.sn(*name);
6801 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6802 if !id.is_empty() {
6803 idents.insert(id.to_string());
6804 }
6805 }
6806 }
6807 NodeData::Label { name } => {
6808 idents.insert(name.clone());
6809 }
6810 NodeData::New { ty, anon_raw, .. } => {
6811 collect_ty_idents(ty, &mut idents);
6812 if let Some(raw) = anon_raw {
6815 for id in
6816 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6817 {
6818 if !id.is_empty() {
6819 idents.insert(id.to_string());
6820 }
6821 }
6822 }
6823 }
6824 NodeData::Cast { ty } => {
6825 collect_ty_idents(ty, &mut idents);
6826 }
6827 NodeData::InstanceOf { ty, .. } => {
6828 collect_ty_idents(ty, &mut idents);
6829 }
6830 NodeData::Catch { ty_raw, .. } => {
6831 for id in
6833 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6834 {
6835 if !id.is_empty() {
6836 idents.insert(id.to_string());
6837 }
6838 }
6839 }
6840 NodeData::Raw { text } => {
6841 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6842 if !id.is_empty() {
6843 idents.insert(id.to_string());
6844 }
6845 }
6846 }
6847 _ => {}
6848 }
6849 for &c in ast.children(n) {
6850 stack.push(c);
6851 }
6852 }
6853 }
6854 for name in collect_all_blind_refs(ast, unit) {
6858 idents.insert(name);
6859 }
6860 for r in &unit.raws {
6863 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6864 if !id.is_empty() {
6865 idents.insert(id.to_string());
6866 }
6867 }
6868 }
6869 for_each_type(unit, &mut |ty| {
6872 collect_mods_idents(&ty.mods, &mut idents);
6873 idents.insert(ty.name.clone());
6874 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6875 if !id.is_empty() {
6876 idents.insert(id.to_string());
6877 }
6878 }
6879 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6883 if !id.is_empty() {
6884 idents.insert(id.to_string());
6885 }
6886 }
6887 for e in ty.extends.iter().chain(ty.implements.iter()) {
6888 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6889 if !id.is_empty() {
6890 idents.insert(id.to_string());
6891 }
6892 }
6893 }
6894 for m in &ty.members {
6895 match m {
6896 Member::Field { mods, ty, declarators } => {
6897 collect_mods_idents(mods, &mut idents);
6898 collect_ty_idents(ty, &mut idents);
6899 for d in declarators {
6900 idents.insert(d.name.clone());
6901 }
6902 }
6903 Member::Method { mods, ret, name, params, throws, .. } => {
6904 collect_mods_idents(mods, &mut idents);
6905 collect_ty_idents(ret, &mut idents);
6906 idents.insert(name.clone());
6907 for p in params {
6908 collect_mods_idents(&p.mods, &mut idents);
6909 collect_ty_idents(&p.ty, &mut idents);
6910 idents.insert(p.name.clone());
6911 }
6912 for t in throws {
6913 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6914 if !id.is_empty() {
6915 idents.insert(id.to_string());
6916 }
6917 }
6918 }
6919 }
6920 Member::Constructor { mods, name, params, throws, .. } => {
6921 collect_mods_idents(mods, &mut idents);
6922 idents.insert(name.clone());
6923 for p in params {
6924 collect_mods_idents(&p.mods, &mut idents);
6925 collect_ty_idents(&p.ty, &mut idents);
6926 idents.insert(p.name.clone());
6927 }
6928 for t in throws {
6929 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6930 if !id.is_empty() {
6931 idents.insert(id.to_string());
6932 }
6933 }
6934 }
6935 }
6936 Member::Type(t) => {
6937 let _ = t;
6939 }
6940 _ => {}
6941 }
6942 }
6943 });
6944 let before = unit.imports.len();
6946 unit.imports.retain(|imp| {
6947 if imp.contains('*') {
6948 return true; }
6950 let path = imp.trim_start_matches("static ").trim();
6952 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6953 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6954 return true; }
6956 idents.contains(simple)
6957 });
6958 before - unit.imports.len()
6959}
6960
6961fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6962 match t {
6963 JType::Ref(r) => {
6964 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6965 if !id.is_empty() {
6966 out.insert(id.to_string());
6967 }
6968 }
6969 }
6970 JType::Array(inner) => collect_ty_idents(inner, out),
6971 _ => {}
6972 }
6973}
6974
6975fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6976 if mods.contains('@') {
6978 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6979 if !id.is_empty() {
6980 out.insert(id.to_string());
6981 }
6982 }
6983 }
6984}
6985
6986fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6992 use std::collections::HashSet;
6993 let mut removed = 0usize;
6994 let anon_names = collect_anon_and_string_names(ast, unit);
6996 let blind = collect_all_blind_refs(ast, unit);
6999 let mut referenced: HashSet<String> = HashSet::new();
7001 let mut bodies: Vec<JavaId> = Vec::new();
7002 for_each_type(unit, &mut |ty: &TypeDecl| {
7003 for m in &ty.members {
7004 match m {
7005 Member::Method { body: Some(b), .. }
7006 | Member::Constructor { body: Some(b), .. }
7007 | Member::Initializer { body: b, .. } => bodies.push(*b),
7008 Member::Field { declarators, .. } => {
7009 for d in declarators {
7010 if let Some(init) = d.init {
7011 bodies.push(init);
7012 }
7013 }
7014 }
7015 _ => {}
7016 }
7017 }
7018 });
7019 for root in bodies {
7020 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
7022 while let Some((n, is_callee)) = stack.pop() {
7023 match ast.kind(n) {
7024 NodeKind::VarRef if !is_callee => {
7025 if let Some(nm) = ast.var_name(n) {
7026 referenced.insert(nm.to_string());
7027 }
7028 }
7029 NodeKind::Member => {
7030 if let NodeData::Member { name: sym } = ast.data(n) {
7031 referenced.insert(ast.sn(*sym).to_string());
7032 }
7033 }
7034 _ => {}
7035 }
7036 if ast.kind(n) == NodeKind::Call {
7037 let ch = ast.children(n).to_vec();
7038 if let Some(&first) = ch.first() {
7039 stack.push((first, true));
7040 }
7041 for &c in &ch[1..] {
7042 stack.push((c, false));
7043 }
7044 continue;
7045 }
7046 for &c in ast.children(n) {
7047 stack.push((c, false));
7048 }
7049 }
7050 }
7051 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
7057 for m in ty.members.iter_mut() {
7058 if let Member::Field { mods, declarators, .. } = m {
7059 let is_private = mods.split_whitespace().any(|w| w == "private");
7060 let annotated = mods.contains('@');
7061 if !is_private || annotated {
7062 continue;
7063 }
7064 let dead: Vec<bool> = declarators
7065 .iter()
7066 .map(|d| {
7067 let reflective =
7068 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
7069 let init_ok =
7070 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
7071 !reflective
7072 && init_ok
7073 && !referenced.contains(&d.name)
7074 && !anon_names.contains(&d.name)
7075 && !blind.contains(&d.name)
7076 })
7077 .collect();
7078 let before = declarators.len();
7079 let mut idx = 0usize;
7080 declarators.retain(|_| {
7081 let keep = !dead[idx];
7082 idx += 1;
7083 keep
7084 });
7085 removed += before - declarators.len();
7086 }
7087 }
7088 let before = ty.members.len();
7089 ty.members
7090 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
7091 removed += before - ty.members.len();
7092 });
7093 removed
7094}
7095
7096fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
7097 for m in &mut ty.members {
7098 simplify_member(ast, m, cfg, total);
7099 }
7100}
7101
7102fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
7103 let params: Vec<(String, JType)> = match m {
7105 Member::Method { params, .. } | Member::Constructor { params, .. } => params
7106 .iter()
7107 .map(|p| (p.name.clone(), p.ty.clone()))
7108 .collect(),
7109 _ => Vec::new(),
7110 };
7111 ast.set_param_scope(¶ms);
7112 match m {
7113 Member::Method { body: Some(b), .. }
7114 | Member::Constructor { body: Some(b), .. } => {
7115 let r = simplify(ast, *b, cfg);
7116 merge_report(total, r);
7117 }
7118 Member::Initializer { body, .. } => {
7119 let r = simplify(ast, *body, cfg);
7120 merge_report(total, r);
7121 }
7122 Member::Field { declarators, .. } => {
7123 for d in declarators.iter_mut() {
7124 if let Some(init) = d.init {
7125 let r = simplify(ast, init, cfg);
7126 merge_report(total, r);
7127 let rules = default_java_rules();
7131 let mut cur: JavaId = init;
7132 for _ in 0..8 {
7133 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7134 Some((new_root, name)) => {
7135 d.init = Some(new_root);
7136 cur = new_root;
7137 *total.by_rule.entry(name).or_insert(0) += 1;
7138 total.edits += 1;
7139 let r = simplify(ast, new_root, cfg);
7141 merge_report(total, r);
7142 }
7143 None => break,
7144 }
7145 }
7146 }
7147 }
7148 }
7149 Member::Type(t) => simplify_type(ast, t, cfg, total),
7150 Member::Raw(_) => {}
7151 _ => {}
7152 }
7153}
7154
7155fn merge_report(total: &mut Report, r: Report) {
7156 total.edits += r.edits;
7157 total.iterations += r.iterations;
7158 for (k, v) in r.by_rule {
7159 *total.by_rule.entry(k).or_insert(0) += v;
7160 }
7161}
7162
7163pub fn default_config() -> Config {
7165 Config::default()
7166}