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 map_view_synthetic_ty(lang: &JavaAst, call: JavaId) -> Option<JType> {
595 if lang.kind(call) != NodeKind::Call {
596 return None;
597 }
598 let ch = lang.children(call).to_vec();
599 if ch.len() != 1 {
600 return None; }
602 let callee = *ch.first()?;
603 let NodeData::Member { name } = lang.data(callee) else { return None };
604 let view = lang.sn(*name);
605 if !matches!(view, "keySet" | "values") {
606 return None;
607 }
608 let recv = lang.children(callee).first().copied()?;
609 let NodeData::VarRef { name } = lang.data(recv) else { return None };
610 let recv_ty = lang
614 .var_type(recv)
615 .cloned()
616 .or_else(|| lang.field_types.get(lang.sn(*name)).cloned())?;
617 let JType::Ref(n) = recv_ty else { return None };
618 let lt = n.find('<')?;
619 let gt = n.rfind('>')?;
620 if gt <= lt {
621 return None; }
623 let args = n[lt + 1..gt].trim();
625 let depth = args.chars().fold(0i32, |d, c| match c {
626 '<' => d + 1,
627 '>' => d - 1,
628 _ => d,
629 });
630 let top = args.find(',').filter(|_| depth == 0)?;
631 let k = args[..top].trim();
632 let v = args[top + 1..].trim();
633 let base = n[..lt].trim();
634 let last = base.rsplit('.').next().unwrap_or(base);
635 let map_family = matches!(
636 last,
637 "Map"
638 | "SortedMap"
639 | "NavigableMap"
640 | "HashMap"
641 | "LinkedHashMap"
642 | "TreeMap"
643 | "Hashtable"
644 | "Properties"
645 | "ConcurrentMap"
646 | "ConcurrentHashMap"
647 | "ConcurrentSkipListMap"
648 | "ConcurrentNavigableMap"
649 | "EnumMap"
650 | "WeakHashMap"
651 | "IdentityHashMap"
652 | "AbstractMap"
653 );
654 if !map_family || k.is_empty() || v.is_empty() {
655 return None;
656 }
657 match view {
658 "keySet" => Some(JType::Ref(format!("java.util.Set<{k}>"))),
659 _ => Some(JType::Ref(format!("java.util.Collection<{v}>"))),
660 }
661}
662
663fn for_each_elem_ok(ty: &JType) -> bool {
667 match ty {
668 JType::Ref(n) => !n.trim_start().starts_with('?'),
669 _ => true,
670 }
671}
672
673fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
675 let mut stack = vec![root];
676 while let Some(n) = stack.pop() {
677 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
678 out.push(n);
679 }
680 for &c in lang.children(n) {
681 stack.push(c);
682 }
683 }
684}
685
686fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
688 let mut stack = vec![root];
689 while let Some(n) = stack.pop() {
690 for &c in lang.children(n) {
691 if c == target {
692 return n;
693 }
694 stack.push(c);
695 }
696 }
697 root
698}
699
700fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
701 let mut stack = vec![id];
702 while let Some(n) = stack.pop() {
703 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
704 return true;
705 }
706 for &c in lang.children(n) {
707 stack.push(c);
708 }
709 }
710 false
711}
712
713fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
715 let mut stack = vec![id];
716 while let Some(n) = stack.pop() {
717 match lang.data(n) {
718 NodeData::Catch { name: cn, .. } if cn == name => return true,
719 NodeData::ForEach { name: vn, .. } if vn == name => return true,
720 NodeData::Lambda { params_raw } => {
723 for part in
724 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
725 {
726 if part == name {
727 return true;
728 }
729 }
730 }
731 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
733 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
736 _ => {}
737 }
738 for &c in lang.children(n) {
739 stack.push(c);
740 }
741 }
742 false
743}
744
745pub struct NewStringFold;
751
752impl Rule<JavaAst> for NewStringFold {
753 fn name(&self) -> &'static str {
754 "new_string_fold"
755 }
756 fn kinds(&self) -> &'static [NodeKind] {
757 &[NodeKind::New]
758 }
759 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
760 let root = ctx.root();
761 let lang = ctx.lang;
762 let NodeData::New { ty, .. } = lang.data(id) else {
763 return None;
764 };
765 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
766 return None;
767 }
768 if lang.unit_shadows_lang {
772 return None;
773 }
774 let _ = root;
777 if lang.unit_string_identity {
778 return None;
779 }
780 let args = lang.children(id).to_vec();
781 match args.len() {
782 0 => {
783 let empty = lang.build_str("");
784 Some(Edit::Replace {
785 target: id,
786 with: empty,
787 })
788 }
789 1 => {
790 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
791 Some(Edit::Replace {
792 target: id,
793 with: args[0],
794 })
795 } else {
796 None
797 }
798 }
799 _ => None,
800 }
801 }
802}
803
804
805pub struct NewStringCharArrayFold;
811
812impl Rule<JavaAst> for NewStringCharArrayFold {
813 fn name(&self) -> &'static str {
814 "new_string_char_array_fold"
815 }
816 fn kinds(&self) -> &'static [NodeKind] {
817 &[NodeKind::New]
818 }
819 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
820 let root = ctx.root();
821 let lang = ctx.lang;
822 let NodeData::New { ty, .. } = lang.data(id) else {
823 return None;
824 };
825 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
826 return None;
827 }
828 let _ = root;
829 if lang.unit_shadows_lang || lang.unit_string_identity {
830 return None;
831 }
832 let args = lang.children(id).to_vec();
834 if args.len() != 1 {
835 return None;
836 }
837 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
838 return None;
839 };
840 if *sized != 0 {
841 return None;
842 }
843 let arr_children = lang.children(args[0]).to_vec();
844 let lit = *arr_children.first()?;
845 if lang.kind(lit) != NodeKind::ArrayLit {
846 return None;
847 }
848 let mut text = String::new();
849 for &c in lang.children(lit) {
850 match lang.literal(c) {
851 Some(LitRef::Char(ch)) => text.push(ch),
852 _ => return None, }
854 }
855 let with = lang.build_str(&text);
856 Some(Edit::Replace {
857 target: id,
858 with,
859 })
860 }
861}
862
863pub struct EmptyFinallyStrip;
871
872impl Rule<JavaAst> for EmptyFinallyStrip {
873 fn name(&self) -> &'static str {
874 "empty_finally_strip"
875 }
876 fn kinds(&self) -> &'static [NodeKind] {
877 &[NodeKind::Try]
878 }
879 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
880 let lang = ctx.lang;
881 if lang.kind(id) != NodeKind::Try {
882 return None;
883 }
884 let ch = lang.children(id).to_vec();
885 let is_empty_block = |n: JavaId| -> bool {
892 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
893 };
894 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
895 if ch.len() < try_idx + 2 {
896 return None; }
898 let last = *ch.last()?;
900 if lang.kind(last) == NodeKind::Catch {
901 return None;
902 }
903 if !is_empty_block(last) {
904 return None;
905 }
906 Some(Edit::Splice {
909 node: id,
910 index: ch.len() - 1,
911 remove: 1,
912 insert: Vec::new(),
913 })
914 }
915}
916
917fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
963 if lang.kind(a) != lang.kind(b) {
964 let (ia, ib) = (lang.kind(a), lang.kind(b));
966 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
967 if k == NodeKind::ExprStmt {
968 lang.children(x).first().copied()
969 } else {
970 Some(x)
971 }
972 };
973 return match (unwrap(ia, a), unwrap(ib, b)) {
974 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
975 _ => false,
976 };
977 }
978 print_equiv_inner(lang, a, b)
979}
980
981fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
982 if lang.kind(a) != lang.kind(b) {
983 return false;
984 }
985 match (lang.literal(a), lang.literal(b)) {
987 (Some(x), Some(y)) => {
988 if x != y {
989 return false;
990 }
991 }
992 (None, None) => {}
993 _ => return false,
994 }
995 let (ca, cb) = (lang.children(a), lang.children(b));
996 if ca.len() != cb.len() {
997 return false;
998 }
999 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
1000}
1001
1002fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
1006 for &root in rest {
1007 let mut stack = vec![root];
1008 while let Some(n) = stack.pop() {
1009 match lang.kind(n) {
1010 NodeKind::Assign => {
1011 let t = lang.children(n)[0];
1012 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
1013 return true;
1014 }
1015 }
1016 NodeKind::Unary => {
1017 if let NodeData::Unary { op } = lang.data(n) {
1018 if op.is_incdec() {
1019 let t = lang.children(n)[0];
1020 if lang.kind(t) == NodeKind::VarRef
1021 && lang.var_name(t) == Some(name)
1022 {
1023 return true;
1024 }
1025 }
1026 }
1027 }
1028 _ => {}
1029 }
1030 for &c in lang.children(n) {
1031 stack.push(c);
1032 }
1033 }
1034 }
1035 false
1036}
1037
1038pub struct StaticExec;
1039
1040impl Rule<JavaAst> for StaticExec {
1041 fn name(&self) -> &'static str {
1042 "static_exec"
1043 }
1044 fn kinds(&self) -> &'static [NodeKind] {
1045 &[NodeKind::Block]
1046 }
1047 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1048 if ctx.parent(id).is_some() {
1051 return None;
1052 }
1053 let lang = ctx.lang;
1054 if lang.kind(id) != NodeKind::Block {
1055 return None;
1056 }
1057 let stmts = lang.children(id).to_vec();
1058 if stmts.len() < 3 {
1059 return None; }
1061 let mut ex = vexec::Exec::new(lang);
1067 let run = ex.run_prefix(&stmts).ok()?;
1068 if std::env::var("CURE_DBG_P1").is_ok() {
1069 eprintln!(
1070 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
1071 run.cuts.len(),
1072 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
1073 ex.budget_exhausted(),
1074 ex.effect_log().len(),
1075 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
1076 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
1077 ex.effect_log().iter().map(|e| match e {
1078 vexec::EffectEvent::FieldWrite(_) => "FW",
1079 vexec::EffectEvent::ClassLoad { .. } => "CL",
1080 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
1081 }).collect::<Vec<_>>()
1082 );
1083 }
1084 if ex.budget_exhausted() {
1086 return None;
1087 }
1088 let root_idx = run.root_cut_index();
1089 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
1091 if total_completed < 3 {
1092 return None;
1093 }
1094 let true_rest = run.true_rest();
1098 if !true_rest.is_empty() {
1099 for ev in ex.effect_log().iter() {
1100 if let vexec::EffectEvent::FieldWrite(k) = ev {
1101 if let Some(name) = lang.name_of_key(*k) {
1102 if lang.final_fields.contains(&name)
1107 && rest_reassigns_field(lang, &true_rest, &name)
1108 {
1109 return None;
1110 }
1111 }
1112 }
1113 }
1114 }
1115 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1117 let field_finals: Vec<(u32, vexec::VVal)> =
1118 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1119 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1120 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1123 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1124 _ => None,
1125 };
1126 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1129 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1135 for (j, cut) in run.cuts.iter().enumerate() {
1136 let range_end = if j == 0 {
1137 decl_order.len()
1138 } else {
1139 run.cuts[j - 1].decl_start
1140 };
1141 let rest_j = run.rest_for_cut(j);
1142 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1143 let mut live_names: std::collections::HashSet<String> =
1144 std::collections::HashSet::new();
1145 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1146 if ex.var_value(k).is_none() {
1147 continue; }
1149 let name = lang.name_of_key(k)?;
1150 live_names.insert(name.clone());
1151 let referenced = rest_j.iter().any(|&st| {
1152 cure_engine::analysis::subtree_contains(lang, st, |n| {
1153 lang.kind(n) == NodeKind::VarRef
1154 && lang.var_name(n) == Some(name.as_str())
1155 })
1156 });
1157 if referenced {
1158 esc.push((
1159 k,
1160 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1161 ex.var_domain(k),
1162 ex.var_decl_ty(k).unwrap_or(JType::Var),
1165 ));
1166 }
1167 }
1168 escaped_by_cut.push(esc);
1169 cut_live_names.push(live_names);
1170 }
1171 drop(ex);
1172 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1174 for (j, cut) in run.cuts.iter().enumerate() {
1175 let block_id = if j == root_idx {
1176 id
1177 } else {
1178 cut.block_hint?
1179 };
1180 let mut insert: Vec<JavaId> = Vec::new();
1181 for pos in cut.log_start..cut.log_end {
1183 match &log[pos] {
1184 vexec::EffectEvent::FieldWrite(k) => {
1185 let is_last = !log[pos + 1..]
1187 .iter()
1188 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1189 if !is_last {
1190 continue;
1191 }
1192 let name = lang.name_of_key(*k)?;
1193 if lang.is_param_name(&name) {
1195 return None;
1196 }
1197 let v = field_finals
1198 .iter()
1199 .find(|(fk, _)| fk == k)
1200 .map(|(_, v)| v.clone())?;
1201 let v = match (&v, lang.field_types.get(&name)) {
1206 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1207 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1208 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1209 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1210 _ => v,
1211 };
1212 let (val, mut extra) = materialize(lang, &v)?;
1213 let tgt = lang.var(&name);
1214 insert.push(lang.assign(tgt, val));
1215 insert.append(&mut extra);
1216 }
1217 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1218 if std::env::var("CURE_DBG_P1").is_ok() {
1219 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1220 }
1221 let name = lang.name_of_key(*k)?;
1226 if lang.is_param_name(&name) {
1228 return None;
1229 }
1230 let cch = lang.children(*call).to_vec();
1231 let mut new_args: Vec<JavaId> = Vec::new();
1232 let mut kept_original: Vec<bool> = Vec::new();
1233 for (i, &arg) in cch.iter().enumerate().skip(1) {
1234 let val = args.get(i - 1).cloned().flatten();
1235 let replaced = match val {
1236 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1237 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1238 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1239 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1240 _ => None,
1241 };
1242 kept_original.push(replaced.is_none());
1243 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1244 }
1245 let names_j = cut_live_names.get(j)?;
1249 for ((i, &arg), &kept) in
1250 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1251 {
1252 let _ = i;
1253 if kept
1254 && !names_j.is_empty()
1255 && cure_engine::analysis::subtree_contains(
1256 lang,
1257 arg,
1258 |n| {
1259 lang.kind(n) == NodeKind::VarRef
1260 && lang
1261 .var_name(n)
1262 .is_some_and(|nm| names_j.contains(nm))
1263 },
1264 )
1265 {
1266 return None;
1267 }
1268 }
1269 let callee = *lang.children(*call).first()?;
1270 let callee_copy = lang.copy_subtree(callee);
1271 let mut ch = vec![callee_copy];
1272 ch.extend(new_args);
1273 let new_call = lang.clone_node(*call, ch);
1274 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1275 lang.clone_node(*stmt, vec![new_call])
1276 } else {
1277 new_call
1278 };
1279 let tgt = lang.var(&name);
1281 let assign = lang.assign(tgt, new_stmt);
1282 insert.push(assign);
1283 }
1284 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1285 let lit = lang.lit(Lit::Str(name.clone()));
1293 let callee = *lang.children(*call).first()?;
1294 let callee_copy = lang.copy_subtree(callee);
1295 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1296 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1297 lang.clone_node(*stmt, vec![new_call])
1298 } else {
1299 new_call
1300 };
1301 let try_block = lang.block(vec![new_stmt]);
1302 let catch_block = lang.block(vec![]);
1303 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1304 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1305 insert.push(wrapped);
1306 }
1307 }
1308 }
1309 let mut alias_first: std::collections::HashMap<usize, String> =
1314 std::collections::HashMap::new();
1315 for (k, v, dom, dty) in &escaped_by_cut[j] {
1316 let name = lang.name_of_key(*k)?;
1317 let (val, mut extra) = match v {
1318 vexec::VVal::CA(a) => {
1319 let addr = std::rc::Rc::as_ptr(a) as usize;
1320 match alias_first.get(&addr) {
1321 Some(first) => (lang.var(first), vec![]),
1322 None => {
1323 alias_first.insert(addr, name.clone());
1324 materialize(lang, v)?
1325 }
1326 }
1327 }
1328 vexec::VVal::SA(a) => {
1329 let addr = std::rc::Rc::as_ptr(a) as usize;
1330 match alias_first.get(&addr) {
1331 Some(first) => (lang.var(first), vec![]),
1332 None => {
1333 alias_first.insert(addr, name.clone());
1334 materialize(lang, v)?
1335 }
1336 }
1337 }
1338 _ => materialize(lang, v)?,
1339 };
1340 let (ty, val) = match (*dom, val) {
1345 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1346 match lang.literal(lit) {
1349 Some(LitRef::Int(v)) => {
1350 match u32::try_from(v).ok().and_then(char::from_u32) {
1351 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1352 None => (JType::Char, lit),
1353 }
1354 }
1355 Some(LitRef::Long(v)) => {
1356 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1357 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1358 None => (JType::Char, lit),
1359 }
1360 }
1361 _ => (JType::Char, lit),
1362 }
1363 }
1364 (3, lit) => (JType::Byte, lit),
1365 (4, lit) => (JType::Short, lit),
1366 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1367 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1368 (JType::Long, lang.build_int(v, true))
1369 } else {
1370 (JType::Long, lit)
1371 }
1372 }
1373 (_, lit) => {
1378 if matches!(dty, JType::Ref(_)) {
1379 (dty.clone(), lit)
1380 } else {
1381 (JType::Var, lit)
1382 }
1383 }
1384 };
1385 let stmt = lang.var_decl(&name, ty, Some(val));
1386 insert.push(stmt);
1387 insert.append(&mut extra);
1388 }
1389 if j == root_idx {
1391 if let Some(v) = ret_payload.clone().flatten() {
1392 let (val, mut extra) = materialize(lang, &v)?;
1393 let stmt = lang.ret(Some(val));
1394 insert.push(stmt);
1395 insert.append(&mut extra);
1396 }
1397 }
1398 if insert.is_empty() && cut.completed == 0 {
1400 continue;
1401 }
1402 {
1405 let removed = lang.children(block_id).to_vec();
1406 if insert.len() == cut.completed
1407 && cut.completed <= removed.len()
1408 && insert
1409 .iter()
1410 .zip(removed[..cut.completed].iter())
1411 .all(|(&n, &o)| print_equiv(lang, n, o))
1412 {
1413 continue;
1414 }
1415 }
1416 edits.push(Edit::Splice {
1417 node: block_id,
1418 index: 0,
1419 remove: cut.completed,
1420 insert,
1421 });
1422 }
1423 if std::env::var("CURE_DBG_P1").is_ok() {
1424 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1425 }
1426 if edits.is_empty() {
1427 return None;
1428 }
1429 let candidate = if edits.len() == 1 {
1431 edits.pop().unwrap()
1432 } else {
1433 Edit::Multi(edits)
1434 };
1435 if candidate.cost_reduction(lang) <= 0 {
1438 return None;
1439 }
1440 Some(candidate)
1441 }
1442}
1443
1444fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1447 use vexec::VVal;
1448 match v {
1449 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1450 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1451 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1452 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1453 VVal::CA(a) => {
1454 let chars: Vec<char> = a.borrow().clone();
1456 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1457 let lit_arr = lang.array_lit(lits);
1458 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1459 Some((node, vec![]))
1460 }
1461 VVal::SA(elems) => {
1462 let arr = elems.borrow().clone();
1465 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1466 let lits: Vec<JavaId> = arr
1467 .iter()
1468 .map(|e| match e {
1469 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1470 _ => unreachable!(),
1471 })
1472 .collect();
1473 let lit_arr = lang.array_lit(lits);
1474 let node =
1475 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1476 Some((node, vec![]))
1477 } else {
1478 None
1483 }
1484 }
1485 VVal::Undef => None,
1487 }
1488}
1489
1490pub struct TryUnwrapRethrow;
1491
1492impl Rule<JavaAst> for TryUnwrapRethrow {
1493 fn name(&self) -> &'static str {
1494 "try_unwrap_rethrow"
1495 }
1496 fn kinds(&self) -> &'static [NodeKind] {
1497 &[NodeKind::Try]
1498 }
1499 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1500 let lang = ctx.lang;
1501 if lang.kind(id) != NodeKind::Try {
1502 return None;
1503 }
1504 let ch = lang.children(id).to_vec();
1505 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1507 let catches: Vec<JavaId> =
1508 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1509 if catches.is_empty() {
1510 return None;
1511 }
1512 let rethrow_only = |c: JavaId| -> bool {
1513 let block = match lang.children(c).first() {
1515 Some(&b) => b,
1516 None => return false,
1517 };
1518 let stmts = lang.children(block);
1519 if stmts.len() != 1 {
1520 return false;
1521 }
1522 let stmt = stmts[0];
1523 if lang.kind(stmt) != NodeKind::Throw {
1524 return false;
1525 }
1526 let Some(&thrown) = lang.children(stmt).first() else { return false };
1527 if lang.kind(thrown) != NodeKind::VarRef {
1529 return false;
1530 }
1531 match &lang.node(c).data {
1532 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1533 _ => false,
1534 }
1535 };
1536 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1537 if all_rethrow {
1538 let extras: Vec<JavaId> = ch
1540 .iter()
1541 .copied()
1542 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1543 .collect();
1544 if extras.is_empty() {
1545 return Some(Edit::Replace {
1546 target: id,
1547 with: try_block,
1548 });
1549 }
1550 return None;
1551 }
1552 let catch_slots: Vec<usize> = ch
1558 .iter()
1559 .enumerate()
1560 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1561 .map(|(i, _)| i)
1562 .collect();
1563 for &slot in &catch_slots {
1564 let c = ch[slot];
1565 if !rethrow_only(c) {
1566 continue;
1567 }
1568 let later_all_rethrow = catch_slots
1569 .iter()
1570 .filter(|&&s2| s2 > slot)
1571 .all(|&s2| rethrow_only(ch[s2]));
1572 if !later_all_rethrow {
1573 continue;
1574 }
1575 return Some(Edit::Splice {
1576 node: id,
1577 index: slot,
1578 remove: 1,
1579 insert: vec![],
1580 });
1581 }
1582 None
1583 }
1584}
1585
1586pub struct TryUnwrapNoCatch;
1587
1588impl Rule<JavaAst> for TryUnwrapNoCatch {
1589 fn name(&self) -> &'static str {
1590 "try_unwrap_no_catch"
1591 }
1592 fn kinds(&self) -> &'static [NodeKind] {
1593 &[NodeKind::Try]
1594 }
1595 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1596 let lang = ctx.lang;
1597 if lang.kind(id) != NodeKind::Try {
1598 return None;
1599 }
1600 let ch = lang.children(id).to_vec();
1601 if ch.is_empty() {
1602 return None;
1603 }
1604 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1607 if has_catch {
1608 return None;
1609 }
1610 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1614 let rest: Vec<JavaId> = ch
1615 .iter()
1616 .copied()
1617 .filter(|&c| c != try_block)
1618 .collect();
1619 for &r in &rest {
1622 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1623 if !is_empty_block {
1624 return None; }
1626 }
1627 Some(Edit::Replace {
1628 target: id,
1629 with: try_block,
1630 })
1631 }
1632}
1633
1634pub struct LoopHeadBreak;
1642
1643impl Rule<JavaAst> for LoopHeadBreak {
1644 fn name(&self) -> &'static str {
1645 "loop_head_break"
1646 }
1647 fn kinds(&self) -> &'static [NodeKind] {
1648 &[NodeKind::While, NodeKind::DoWhile]
1649 }
1650 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1651 let lang = ctx.lang;
1652 let body = match lang.kind(id) {
1653 NodeKind::While => {
1654 let ch = lang.children(id).to_vec();
1655 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1656 return None;
1657 }
1658 ch[1]
1659 }
1660 NodeKind::DoWhile => {
1661 let ch = lang.children(id).to_vec();
1662 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1663 return None;
1664 }
1665 ch[0]
1666 }
1667 _ => return None,
1668 };
1669 if lang.kind(body) != NodeKind::Block {
1670 return None;
1671 }
1672 let bch = lang.children(body).to_vec();
1673 let is_do = lang.kind(id) == NodeKind::DoWhile;
1674 if is_do && bch.len() != 1 {
1676 return None;
1677 }
1678 let first = *bch.first()?;
1679 if lang.kind(first) != NodeKind::If {
1680 return None;
1681 }
1682 let ich = lang.children(first).to_vec();
1683 let c = ich[0];
1684
1685 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1686 let is_break = |n: JavaId| match lang.kind(n) {
1687 NodeKind::Break => bare_break(n),
1688 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1689 _ => false,
1690 };
1691 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1692 let ends_with_continue = |n: JavaId| -> bool {
1693 match lang.kind(n) {
1694 NodeKind::Continue => bare_continue(n),
1695 NodeKind::Block => lang
1696 .children(n)
1697 .last()
1698 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1699 _ => false,
1700 }
1701 };
1702 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1703 match lang.kind(n) {
1704 NodeKind::Continue => vec![],
1705 NodeKind::Block => {
1706 let ch = lang.children(n).to_vec();
1707 if ch
1708 .last()
1709 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1710 {
1711 ch[..ch.len() - 1].to_vec()
1712 } else {
1713 ch
1714 }
1715 }
1716 _ => vec![n],
1717 }
1718 };
1719
1720 match ich.len() {
1721 2 => {
1722 if is_do || !is_break(ich[1]) {
1724 return None;
1725 }
1726 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1727 if let Some(&last) = rest.last() {
1728 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1729 rest.pop();
1730 }
1731 }
1732 let nc = lang.build_unary(UnOp::Not, c);
1733 let inner = lang.build_block(rest);
1734 let w = lang.while_(nc, inner);
1735 Some(Edit::Replace {
1736 target: id,
1737 with: w,
1738 })
1739 }
1740 3 => {
1741 let (then, els) = (ich[1], ich[2]);
1742 if ends_with_continue(then) && is_break(els) {
1744 let rest = strip_tail_continue(then);
1745 let inner = lang.build_block(rest);
1746 let w = lang.while_(c, inner);
1747 return Some(Edit::Replace {
1748 target: id,
1749 with: w,
1750 });
1751 }
1752 if is_break(then) && ends_with_continue(els) {
1754 let rest = strip_tail_continue(els);
1755 let inner = lang.build_block(rest);
1756 let nc = lang.build_unary(UnOp::Not, c);
1757 let w = lang.while_(nc, inner);
1758 return Some(Edit::Replace {
1759 target: id,
1760 with: w,
1761 });
1762 }
1763 if !is_do && is_break(then) {
1765 let mut all = strip_tail_continue(els);
1766 all.extend(bch[1..].iter().copied());
1767 let inner = lang.build_block(all);
1768 let nc = lang.build_unary(UnOp::Not, c);
1769 let w = lang.while_(nc, inner);
1770 return Some(Edit::Replace {
1771 target: id,
1772 with: w,
1773 });
1774 }
1775 None
1776 }
1777 _ => None,
1778 }
1779 }
1780}
1781
1782pub struct WhileIteratorToForEach;
1783
1784impl Rule<JavaAst> for WhileIteratorToForEach {
1785 fn name(&self) -> &'static str {
1786 "while_iterator_to_for_each"
1787 }
1788 fn kinds(&self) -> &'static [NodeKind] {
1789 &[NodeKind::While]
1790 }
1791 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1792 let parent = ctx.parent(id)?;
1794 let idx = ctx.index(id)?;
1795 let lang = ctx.lang;
1796 if lang.kind(id) != NodeKind::While {
1797 return None;
1798 }
1799 let wch = lang.children(id).to_vec();
1800 let body = wch[1];
1801 if lang.kind(parent) != NodeKind::Block {
1803 return None;
1804 }
1805 if idx == 0 {
1806 return None;
1807 }
1808 let stmts = lang.children(parent).to_vec();
1809 let cond = wch[0];
1812 let it_hint = match lang.kind(cond) {
1813 NodeKind::Call => {
1814 let cc0 = lang.children(cond).to_vec();
1815 match lang.data(*cc0.first()?) {
1816 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1817 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1818 }
1819 _ => None,
1820 }
1821 }
1822 _ => None,
1823 };
1824 let mut decl = None;
1825 if let Some(hint) = it_hint {
1826 let mut up = idx;
1827 for _ in 0..4 {
1828 if up == 0 {
1829 break;
1830 }
1831 up -= 1;
1832 let st = stmts[up];
1833 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1834 decl = Some(st);
1835 break;
1836 }
1837 if subtree_has_var(lang, st, &hint) {
1838 break;
1839 }
1840 }
1841 }
1842 let decl = decl?;
1843 let it_name = lang.var_name(decl)?.to_string();
1844 if stmts[idx + 1..]
1849 .iter()
1850 .any(|&s| subtree_has_var(lang, s, &it_name))
1851 {
1852 return None;
1853 }
1854 let init_call = lang.children(decl).first().copied()?;
1855 if lang.kind(init_call) != NodeKind::Call {
1856 return None;
1857 }
1858 let ic = lang.children(init_call).to_vec();
1859 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1860 return None;
1861 };
1862 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1863 return None;
1864 }
1865 let iterable = lang.children(ic[0])[0];
1866 if subtree_has_var(&*lang, iterable, &it_name) {
1867 return None;
1868 }
1869 {
1879 let ok = match lang.var_type(iterable) {
1884 Some(t) => statically_iterable(t),
1885 None => map_view_synthetic_ty(lang, iterable).is_some(),
1886 };
1887 if !ok {
1888 return None;
1889 }
1890 }
1891 if lang.kind(cond) != NodeKind::Call {
1893 return None;
1894 }
1895 let cc = lang.children(cond).to_vec();
1896 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1897 return None;
1898 };
1899 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1900 return None;
1901 }
1902 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1903 return None;
1904 }
1905 if lang.kind(body) != NodeKind::Block {
1907 return None;
1908 }
1909 let bch = lang.children(body).to_vec();
1910 let first = *bch.first()?;
1911 if lang.kind(first) != NodeKind::VarDecl {
1912 let it_refs_in_body: Vec<JavaId> = {
1915 let mut v = Vec::new();
1916 collect_var_refs(&*lang, body, &it_name, &mut v);
1917 v
1918 };
1919 if it_refs_in_body.len() != 1 {
1920 return None;
1921 }
1922 let next_recv = it_refs_in_body[0];
1923 let member = parent_of_recv(lang, body, next_recv);
1925 if lang.kind(member) != NodeKind::Member {
1926 return None;
1927 }
1928 let NodeData::Member { name: mn } = lang.data(member) else {
1929 return None;
1930 };
1931 if lang.sn(*mn) != "next" {
1932 return None;
1933 }
1934 let next_call = parent_of_recv(lang, body, member);
1935 if lang.kind(next_call) != NodeKind::Call {
1936 return None;
1937 }
1938 if lang.children(next_call).len() != 1 {
1939 return None;
1940 }
1941 let mut wrapped = next_call;
1943 loop {
1944 let par = parent_of_recv(lang, body, wrapped);
1945 match lang.kind(par) {
1946 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1947 _ => break,
1948 }
1949 }
1950 {
1960 let mut cur = wrapped;
1961 while cur != body {
1962 let par = parent_of_recv(lang, body, cur);
1963 if par == body || par == cur {
1964 break; }
1966 let ch = lang.children(par).to_vec();
1967 let idx = ch.iter().position(|&c| c == cur);
1968 match lang.kind(par) {
1969 NodeKind::Block
1970 | NodeKind::ExprStmt
1971 | NodeKind::Label
1972 | NodeKind::Return
1973 | NodeKind::Throw => {}
1974 NodeKind::Ternary => {
1975 if idx.unwrap_or(0) > 0 {
1977 return None;
1978 }
1979 }
1980 NodeKind::Binary => {
1981 if let Some(op) = lang.bin_op(par) {
1982 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1983 return None; }
1985 }
1986 }
1987 NodeKind::If
1988 | NodeKind::While
1989 | NodeKind::DoWhile
1990 | NodeKind::For
1991 | NodeKind::Switch
1992 | NodeKind::Try
1993 | NodeKind::Catch
1994 | NodeKind::Synchronized
1995 | NodeKind::ForEach => {
1996 return None;
1997 }
1998 NodeKind::Lambda | NodeKind::Assert => {
2003 return None;
2004 }
2005 _ => {} }
2007 cur = par;
2008 }
2009 }
2010 let it_decl_elem = match lang.data(decl) {
2018 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
2019 _ => None,
2020 };
2021 let elem = match it_decl_elem {
2022 Some(t) => t,
2023 None => match lang.var_type(iterable) {
2024 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
2025 None => JType::Ref("Object".into()),
2026 },
2027 };
2028 let elem = if for_each_elem_ok(&elem) {
2029 elem
2030 } else {
2031 JType::Ref("Object".into())
2032 };
2033 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
2034 let (replace_target, e_ty) = match lang.data(wrapped) {
2035 NodeData::Cast { .. } if is_object => (next_call, elem),
2036 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
2037 _ => (wrapped, elem),
2038 };
2039 let taken = |nm: &str| {
2043 subtree_has_var(&*lang, body, nm)
2044 || subtree_has_binding(&*lang, body, nm)
2045 };
2046 let e_name = if taken("e") {
2047 if taken("e2") { "e3" } else { "e2" }
2048 } else {
2049 "e"
2050 };
2051 let e_ref = lang.var(e_name);
2052 let foreach = lang.for_each(e_name, e_ty, iterable, body);
2053 let wrapped_parent = parent_of_recv(lang, body, wrapped);
2057 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
2058 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
2059 {
2060 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
2061 if let Some(si) = lang
2062 .children(stmt_block)
2063 .iter()
2064 .position(|&c| c == wrapped_parent)
2065 {
2066 return Some(Edit::Multi(vec![
2067 Edit::Splice {
2068 node: stmt_block,
2069 index: si,
2070 remove: 1,
2071 insert: vec![],
2072 },
2073 Edit::Splice {
2074 node: parent,
2075 index: idx,
2076 remove: 1,
2077 insert: vec![foreach],
2078 },
2079 Edit::Delete { node: decl },
2080 ]));
2081 }
2082 }
2083 return Some(Edit::Multi(vec![
2084 Edit::Replace {
2085 target: replace_target,
2086 with: e_ref,
2087 },
2088 Edit::Splice {
2089 node: parent,
2090 index: idx,
2091 remove: 1,
2092 insert: vec![foreach],
2093 },
2094 Edit::Delete { node: decl },
2095 ]));
2096 }
2097 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
2098 return None;
2099 };
2100 let (e_name, ty) = (e_name.clone(), ty.clone());
2101 let next_call = lang.children(first).first().copied()?;
2102 let next_call = unwrap_paren_cast(&*lang, next_call);
2103 if lang.kind(next_call) != NodeKind::Call {
2104 return None;
2105 }
2106 let nc = lang.children(next_call).to_vec();
2107 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
2108 return None;
2109 };
2110 if lang.sn(*m2) != "next" || nc.len() != 1 {
2111 return None;
2112 }
2113 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
2114 return None;
2115 }
2116 let elem_is_object = match lang.var_type(iterable) {
2118 Some(it_t) => generic_elem_ty(it_t).is_none(),
2119 None => map_view_synthetic_ty(lang, iterable)
2120 .as_ref()
2121 .and_then(generic_elem_ty)
2122 .is_none(),
2123 };
2124 if elem_is_object && ty != JType::Ref("Object".into()) {
2125 return None;
2126 }
2127 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2129 let rest = bch[1..].to_vec();
2130 for &r in &rest {
2131 if subtree_has_var(&*lang, r, &it_name) {
2132 return None;
2133 }
2134 }
2135 let new_body = lang.build_block(rest);
2136 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2137 Some(Edit::Multi(vec![
2140 Edit::Splice {
2141 node: parent,
2142 index: idx,
2143 remove: 1,
2144 insert: vec![foreach],
2145 },
2146 Edit::Delete { node: decl },
2147 ]))
2148 }
2149}
2150
2151pub struct ConcatValueOfDrop;
2160
2161impl Rule<JavaAst> for ConcatValueOfDrop {
2162 fn name(&self) -> &'static str {
2163 "concat_value_of_drop"
2164 }
2165 fn kinds(&self) -> &'static [NodeKind] {
2166 &[NodeKind::Binary]
2167 }
2168 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2169 let root = ctx.root();
2170 let lang = ctx.lang;
2171 if lang.kind(id) != NodeKind::Binary {
2172 return None;
2173 }
2174 if lang.bin_op(id) != Some(BinOp::Add) {
2175 return None;
2176 }
2177 let ch = lang.children(id).to_vec();
2178 let (l, r) = (ch[0], ch[1]);
2179 let becomes_constant = |x: JavaId, other: JavaId| {
2182 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2183 };
2184 let is_value_of = |n: JavaId| -> Option<JavaId> {
2185 if lang.kind(n) != NodeKind::Call {
2186 return None;
2187 }
2188 let c = lang.children(n).to_vec();
2189 if c.len() != 2 {
2190 return None;
2191 }
2192 let callee = c[0];
2193 if let NodeData::Member { name } = lang.data(callee) {
2194 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2195 return Some(c[1]);
2196 }
2197 }
2198 if let NodeData::VarRef { name } = lang.data(callee) {
2199 if lang.sn(*name) == "valueOf" {
2200 return Some(c[1]);
2201 }
2202 }
2203 None
2204 };
2205 let stringy = |n: JavaId| -> bool {
2206 matches!(lang.literal(n), Some(LitRef::Str(_)))
2207 || lang
2208 .var_type(n)
2209 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2210 };
2211 if let Some(x) = is_value_of(l) {
2213 if stringy(r) {
2214 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2215 return None;
2216 }
2217 let with = lang.build_bin(BinOp::Add, x, r);
2218 return Some(Edit::Replace {
2219 target: id,
2220 with,
2221 });
2222 }
2223 }
2224 if let Some(x) = is_value_of(r) {
2226 if stringy(l) {
2227 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2228 return None;
2229 }
2230 let with = lang.build_bin(BinOp::Add, l, x);
2231 return Some(Edit::Replace {
2232 target: id,
2233 with,
2234 });
2235 }
2236 }
2237 None
2238 }
2239}
2240
2241
2242pub struct StringBuilderStatements;
2255
2256fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2257 let mut n = 0;
2260 let mut stack = vec![root];
2261 while let Some(x) = stack.pop() {
2262 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2263 let ch = lang.children(x).to_vec();
2264 if let Some(&t) = ch.first() {
2265 let is_named_target =
2266 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2267 if !is_named_target {
2268 stack.push(t);
2269 }
2270 for &c in &ch[1..] {
2271 stack.push(c);
2272 }
2273 }
2274 continue;
2275 }
2276 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2277 n += 1;
2278 }
2279 for &c in lang.children(x) {
2280 stack.push(c);
2281 }
2282 }
2283 n
2284}
2285
2286impl Rule<JavaAst> for StringBuilderStatements {
2287 fn name(&self) -> &'static str {
2288 "string_builder_statements"
2289 }
2290 fn kinds(&self) -> &'static [NodeKind] {
2291 &[NodeKind::VarDecl]
2292 }
2293 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2294 let parent = ctx.parent(id)?;
2296 let idx = ctx.index(id)?;
2297 let root = ctx.root();
2298 let lang = ctx.lang;
2299
2300 if lang.kind(id) != NodeKind::VarDecl {
2301 return None;
2302 }
2303 if lang.kind(parent) != NodeKind::Block {
2304 return None;
2305 }
2306 let v0 = lang.var_name(id)?.to_string();
2307 let init = *lang.children(id).first()?;
2309 if lang.kind(init) != NodeKind::Call {
2310 return None;
2311 }
2312 let ic = lang.children(init).to_vec();
2313 if ic.len() != 2 {
2314 return None;
2315 }
2316 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2317 return None;
2318 };
2319 if lang.sn(*m0) != "append" {
2320 return None;
2321 }
2322 let sb_new = lang.children(ic[0])[0];
2323 let (ctor_arg, a0) = match lang.data(sb_new) {
2324 NodeData::New { ty, .. } => {
2325 let is_sb = !lang.unit_shadows_lang
2326 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2327 if !is_sb {
2328 return None;
2329 }
2330 let args = lang.children(sb_new).to_vec();
2331 match args.len() {
2332 0 => (None, ic[1]),
2333 1 => {
2334 let a = args[0];
2335 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2336 || lang
2337 .var_type(a)
2338 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2339 if stringy {
2340 (Some(a), ic[1])
2341 } else if matches!(
2342 lang.literal(a),
2343 Some(LitRef::Int(_) | LitRef::Long(_))
2344 ) {
2345 (None, ic[1])
2347 } else {
2348 return None; }
2350 }
2351 _ => return None,
2352 }
2353 }
2354 _ => return None,
2355 };
2356
2357 let stmts = lang.children(parent).to_vec();
2358 let mut parts: Vec<JavaId> = vec![a0];
2360 let mut expected_reads: std::collections::HashMap<String, usize> =
2361 std::collections::HashMap::new();
2362 let mut chain_len = 1usize; let mut cur = v0.clone();
2364 while idx + chain_len < stmts.len() {
2365 let s = stmts[idx + chain_len];
2366 let (recv, arg, new_var, consumed) = match lang.data(s) {
2367 NodeData::Assign { op: None } => {
2369 let ch = lang.children(s).to_vec();
2370 if ch.len() != 2 {
2371 break;
2372 }
2373 let (t, v) = (ch[0], ch[1]);
2374 if lang.kind(t) != NodeKind::VarRef
2375 || lang.var_name(t) != Some(cur.as_str())
2376 || lang.kind(v) != NodeKind::Call
2377 {
2378 break;
2379 }
2380 let vc = lang.children(v).to_vec();
2381 if vc.len() != 2 {
2382 break;
2383 }
2384 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2385 break;
2386 };
2387 if lang.sn(*mn) != "append" {
2388 break;
2389 }
2390 let recv = lang.children(vc[0])[0];
2391 (recv, vc[1], false, s)
2392 }
2393 NodeData::ExprStmt => {
2394 let ch = lang.children(s).to_vec();
2395 if ch.len() != 1 {
2396 break;
2397 }
2398 let inner = ch[0];
2399 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2400 break;
2401 }
2402 let ach = lang.children(inner).to_vec();
2403 if ach.len() != 2 {
2404 break;
2405 }
2406 let (t, v) = (ach[0], ach[1]);
2407 if lang.kind(t) != NodeKind::VarRef
2408 || lang.var_name(t) != Some(cur.as_str())
2409 || lang.kind(v) != NodeKind::Call
2410 {
2411 break;
2412 }
2413 let vc = lang.children(v).to_vec();
2414 if vc.len() != 2 {
2415 break;
2416 }
2417 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2418 break;
2419 };
2420 if lang.sn(*mn) != "append" {
2421 break;
2422 }
2423 let recv = lang.children(vc[0])[0];
2424 (recv, vc[1], false, s)
2425 }
2426 NodeData::VarDecl { .. } => {
2428 let init = match lang.children(s).first() {
2429 Some(&x) => x,
2430 None => break,
2431 };
2432 if lang.kind(init) != NodeKind::Call {
2433 break;
2434 }
2435 let vc = lang.children(init).to_vec();
2436 if vc.len() != 2 {
2437 break;
2438 }
2439 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2440 break;
2441 };
2442 if lang.sn(*mn) != "append" {
2443 break;
2444 }
2445 let recv = lang.children(vc[0])[0];
2446 if lang.var_name(recv) != Some(cur.as_str()) {
2447 break;
2448 }
2449 (recv, vc[1], true, s)
2450 }
2451 _ => break,
2452 };
2453 if lang.var_name(recv) != Some(cur.as_str()) {
2455 break;
2456 }
2457 parts.push(arg);
2458 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2459 if new_var {
2460 cur = lang.var_name(consumed)?.to_string();
2461 }
2462 chain_len += 1;
2463 let _ = consumed;
2464 }
2465
2466 let final_stmt = *stmts.get(idx + chain_len)?;
2468 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2469 return None;
2470 };
2471 let s_init = *lang.children(final_stmt).first()?;
2472 if lang.kind(s_init) != NodeKind::Call {
2473 return None;
2474 }
2475 let sc = lang.children(s_init).to_vec();
2476 if sc.len() != 1 {
2477 return None;
2478 }
2479 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2480 return None;
2481 };
2482 if lang.sn(*mn) != "toString" {
2483 return None;
2484 }
2485 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2486 return None;
2487 }
2488
2489 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2493 if count_var_uses_in(&*lang, parent, v) != *exp {
2494 return None;
2495 }
2496 }
2497
2498 let mut all_parts: Vec<JavaId> = Vec::new();
2500 if let Some(ca) = ctor_arg {
2501 all_parts.push(ca);
2502 }
2503 all_parts.extend(parts);
2504 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2505 && lang.has_string_identity_compare(root)
2506 {
2507 return None;
2508 }
2509 if all_parts.is_empty() {
2510 let empty = lang.build_str("");
2511 return Some(Edit::Multi(vec![
2512 Edit::Replace {
2513 target: s_init,
2514 with: empty,
2515 },
2516 Edit::Splice {
2517 node: parent,
2518 index: idx,
2519 remove: chain_len,
2520 insert: Vec::new(),
2521 },
2522 ]));
2523 }
2524 let first = all_parts[0];
2525 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2526 || lang
2527 .var_type(first)
2528 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2529 let mut acc = if stringy_first {
2530 first
2531 } else {
2532 let empty = lang.build_str("");
2533 lang.build_bin(BinOp::Add, empty, first)
2534 };
2535 for &p in &all_parts[1..] {
2536 acc = lang.build_bin(BinOp::Add, acc, p);
2537 }
2538 Some(Edit::Multi(vec![
2539 Edit::Replace {
2540 target: s_init,
2541 with: acc,
2542 },
2543 Edit::Splice {
2544 node: parent,
2545 index: idx,
2546 remove: chain_len,
2547 insert: Vec::new(),
2548 },
2549 ]))
2550 }
2551}
2552
2553
2554pub struct TrailingContinueJava;
2561
2562impl Rule<JavaAst> for TrailingContinueJava {
2563 fn name(&self) -> &'static str {
2564 "trailing_continue"
2565 }
2566 fn kinds(&self) -> &'static [NodeKind] {
2567 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2568 }
2569 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2570 let parent = ctx.parent(id)?;
2571 let lang = ctx.lang;
2572 let body = match lang.kind(id) {
2573 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2574 *lang.children(id).last()?
2575 }
2576 _ => return None,
2577 };
2578 if lang.kind(body) != NodeKind::Block {
2579 return None;
2580 }
2581 let ch = lang.children(body).to_vec();
2582 let last = *ch.last()?;
2583 if lang.kind(last) != NodeKind::Continue {
2584 return None;
2585 }
2586 let label = match lang.data(last) {
2587 NodeData::Continue { label } => label.clone(),
2588 _ => return None,
2589 };
2590 match label {
2591 None => Some(Edit::Delete { node: last }),
2592 Some(l) => {
2593 if lang.kind(parent) == NodeKind::Label {
2595 if let NodeData::Label { name } = lang.data(parent) {
2596 if *name == l {
2597 return Some(Edit::Delete { node: last });
2598 }
2599 }
2600 }
2601 None
2602 }
2603 }
2604 }
2605}
2606
2607
2608pub struct XorNoise;
2616
2617impl Rule<JavaAst> for XorNoise {
2618 fn name(&self) -> &'static str {
2619 "xor_noise"
2620 }
2621 fn kinds(&self) -> &'static [NodeKind] {
2622 &[NodeKind::Binary]
2623 }
2624 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2625 let lang = ctx.lang;
2626 if lang.kind(id) != NodeKind::Binary {
2627 return None;
2628 }
2629 let op = lang.bin_op(id)?;
2630 if op != BinOp::BitXor {
2631 return None;
2632 }
2633 let ch = lang.children(id).to_vec();
2634 let (l, r) = (ch[0], ch[1]);
2635 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2637 if int_lit(r) == Some(0) {
2638 return Some(Edit::Replace {
2639 target: id,
2640 with: l,
2641 });
2642 }
2643 if int_lit(l) == Some(0) {
2644 return Some(Edit::Replace {
2645 target: id,
2646 with: r,
2647 });
2648 }
2649 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2651 return None;
2652 }
2653 let ich = lang.children(l).to_vec();
2654 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2655 let k2 = int_lit(r)?;
2656 let x = ich[0];
2657 let k = k1 ^ k2;
2658 let with = if k == 0 {
2659 x
2660 } else {
2661 let lit = lang.build_int(k, false);
2662 lang.build_bin(BinOp::BitXor, x, lit)
2663 };
2664 Some(Edit::Replace {
2665 target: id,
2666 with,
2667 })
2668 }
2669}
2670
2671pub struct StrLenFold;
2676
2677impl Rule<JavaAst> for StrLenFold {
2678 fn name(&self) -> &'static str {
2679 "str_len_fold"
2680 }
2681 fn kinds(&self) -> &'static [NodeKind] {
2682 &[NodeKind::Call]
2683 }
2684 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2685 let _at_stmt_pos = ctx
2687 .parent(id)
2688 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2689 .unwrap_or(false);
2690 let lang = ctx.lang;
2691 if lang.kind(id) != NodeKind::Call {
2692 return None;
2693 }
2694 let ch = lang.children(id).to_vec();
2695 if ch.len() != 1 {
2696 return None;
2697 }
2698 let NodeData::Member { name } = lang.data(ch[0]) else {
2699 return None;
2700 };
2701 if lang.sn(*name) != "length" {
2702 return None;
2703 }
2704 let recv = lang.children(ch[0])[0];
2705 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2706 let with = lang.build_int(s.chars().count() as i64, false);
2707 return Some(Edit::Replace {
2708 target: id,
2709 with,
2710 });
2711 }
2712 None
2713 }
2714}
2715
2716
2717pub struct LiteralEval;
2730
2731impl Rule<JavaAst> for LiteralEval {
2732 fn name(&self) -> &'static str {
2733 "literal_eval"
2734 }
2735 fn kinds(&self) -> &'static [NodeKind] {
2736 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2737 }
2738 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2739 let root = ctx.root();
2740 let lang = ctx.lang;
2741 let edit = match lang.kind(id) {
2742 NodeKind::Call => self.eval_call(&mut *lang, id),
2743 NodeKind::Index => self.eval_index(&mut *lang, id),
2744 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2745 _ => None,
2746 };
2747 if let Some(Edit::Replace { with, .. }) = &edit {
2751 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2752 && lang.has_string_identity_compare(root)
2753 {
2754 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2755 eprintln!("[gate] blocked literal_eval at {id:?}");
2756 }
2757 return None;
2758 }
2759 }
2760 edit
2761 }
2762}
2763
2764impl LiteralEval {
2765 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2766 let ch = lang.children(id).to_vec();
2767 let callee = ch[0];
2768 let NodeData::Member { name: method } = lang.data(callee) else {
2769 return None;
2770 };
2771 let method = *method;
2772 let recv = lang.children(callee)[0];
2773 let args: Vec<Lit> = ch[1..]
2775 .iter()
2776 .map(|&a| litref_to_lit(lang.literal(a)))
2777 .collect::<Option<Vec<_>>>()?;
2778
2779 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2780 eval_str_method(s, lang.sn(method), &args)
2781 } else if let Some(cls) = lang.var_name(recv) {
2782 eval_static_method(cls, lang.sn(method), &args)
2783 } else {
2784 None
2785 };
2786 let lit = result?;
2787 let with = lit_to_node(lang, &lit);
2788 Some(Edit::Replace {
2789 target: id,
2790 with,
2791 })
2792 }
2793
2794 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2795 let ch = lang.children(id).to_vec();
2797 let arr = match lang.kind(ch[0]) {
2799 NodeKind::ArrayLit => ch[0],
2800 NodeKind::NewArray => {
2801 let nc = lang.children(ch[0]);
2802 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2803 return None;
2804 };
2805 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2806 return None;
2807 }
2808 nc[0]
2809 }
2810 _ => return None,
2811 };
2812 let idx = lang.literal(ch[1])?.as_int()?;
2813 let elems = lang.children(arr).to_vec();
2814 if idx < 0 || idx >= elems.len() as i64 {
2815 return None;
2816 }
2817 Some(Edit::Replace {
2818 target: id,
2819 with: elems[idx as usize],
2820 })
2821 }
2822}
2823
2824fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2825 Some(match l? {
2826 LitRef::Str(s) => Lit::Str(s.to_string()),
2827 LitRef::Int(v) => Lit::Int(v),
2828 LitRef::Long(v) => Lit::Long(v),
2829 LitRef::Bool(b) => Lit::Bool(b),
2830 LitRef::Char(c) => Lit::Char(c),
2831 LitRef::Float(v) => Lit::Float(v),
2832 LitRef::Double(v) => Lit::Double(v),
2833 LitRef::Null => return None,
2834 })
2835}
2836
2837fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2838 match lit {
2839 Lit::Str(s) => lang.build_str(s),
2840 Lit::Int(v) => lang.build_int(*v, false),
2841 Lit::Long(v) => lang.build_int(*v, true),
2842 Lit::Bool(b) => lang.build_bool(*b),
2843 Lit::Char(c) => lang.build_char(*c),
2844 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2845 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2846 _ => lang.lit(Lit::Null),
2847 }
2848}
2849
2850fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2852 let NodeData::Cast { ty } = lang.data(id) else {
2853 return None;
2854 };
2855 let ty = ty.clone();
2856 let inner = lang.children(id)[0];
2857 let lit = litref_to_lit(lang.literal(inner))?;
2858 let with = match (&ty, &lit) {
2859 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2860 lang.build_char(char::from_u32(*v as u32)?)
2861 }
2862 (JType::Char, Lit::Char(_)) => inner,
2863 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2864 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2865 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2866 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2867 lang.build_int(*v, false)
2868 }
2869 (JType::Int, Lit::Int(_)) => inner,
2870 _ => return None,
2871 };
2872 Some(Edit::Replace {
2873 target: id,
2874 with,
2875 })
2876}
2877
2878fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2879 let chars: Vec<char> = s.chars().collect();
2880 let n = chars.len() as i64;
2881 Some(match (method, args) {
2882 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2883 ("intern", []) => Lit::Str(s.to_string()),
2885 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2886 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2887 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2888 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2889 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2890 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2891 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2892 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2893 }
2894 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2895 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2896 ),
2897 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2898 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2899 ),
2900 ("length", []) => Lit::Int(n),
2901 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2902 Lit::Char(chars[*i as usize])
2903 }
2904 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2905 Lit::Str(s.chars().skip(*b as usize).collect())
2906 }
2907 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2908 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2909 }
2910 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2911 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2912 }
2913 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2914 Lit::Str(s.replace(a.as_str(), b))
2915 }
2916 ("hashCode", []) => Lit::Int(
2918 s.chars()
2919 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2920 as i64,
2921 ),
2922 _ => return None,
2923 })
2924}
2925
2926fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2927 let cls = cls.rsplit('.').next().unwrap_or(cls);
2928 Some(match (cls, method, args) {
2929 ("Integer", "parseInt", [Lit::Str(s)]) => {
2930 Lit::Int(s.trim().parse::<i64>().ok()?)
2931 }
2932 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2933 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2934 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2935 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2936 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2937 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2938 Lit::Bool(s.eq_ignore_ascii_case("true"))
2939 }
2940 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2941 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2942 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2943 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2944 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2945 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2946 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2947 let fmt = match &fmt_args[0] {
2948 Lit::Str(s) => s.clone(),
2949 _ => return None,
2950 };
2951 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2952 Lit::Str(out)
2953 }
2954 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2955 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2956 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2957 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2958 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2959 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2960 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2961 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2962 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2963 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2964 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2965 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2966 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2967 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2968 _ => return None,
2969 })
2970}
2971
2972fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2974 let mut out = String::new();
2975 let mut it = fmt.chars().peekable();
2976 let mut arg_i = 0usize;
2977 while let Some(c) = it.next() {
2978 if c != '%' {
2979 out.push(c);
2980 continue;
2981 }
2982 match it.next()? {
2983 '%' => out.push('%'),
2984 's' => {
2985 let a = args.get(arg_i)?;
2986 arg_i += 1;
2987 match a {
2988 Lit::Str(s) => out.push_str(s),
2989 Lit::Char(ch) => out.push(*ch),
2990 Lit::Bool(b) => out.push_str(&b.to_string()),
2991 _ => out.push_str(&lit_debug_str(a)),
2992 }
2993 }
2994 'd' => {
2995 let a = args.get(arg_i)?;
2996 arg_i += 1;
2997 match a {
2998 Lit::Int(v) => out.push_str(&v.to_string()),
2999 Lit::Long(v) => out.push_str(&v.to_string()),
3000 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
3001 _ => return None,
3002 }
3003 }
3004 _ => return None,
3006 }
3007 }
3008 if arg_i != args.len() {
3009 return None; }
3011 Some(out)
3012}
3013
3014fn lit_debug_str(l: &Lit) -> String {
3015 match l {
3016 Lit::Int(v) => v.to_string(),
3017 Lit::Long(v) => v.to_string(),
3018 _ => "?".into(),
3019 }
3020}
3021
3022fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
3045 match ast.data(m) {
3046 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
3047 _ => None,
3048 }
3049}
3050
3051pub struct UrlDecodeFold;
3052
3053impl Rule<JavaAst> for UrlDecodeFold {
3054 fn name(&self) -> &'static str {
3055 "url_decode_fold"
3056 }
3057 fn kinds(&self) -> &'static [NodeKind] {
3058 &[NodeKind::Call]
3059 }
3060 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3061 let lang = ctx.lang;
3062 let ch = lang.children(id).to_vec();
3065 if ch.len() != 3 {
3066 return None; }
3068 let callee = ch[0];
3069 if lang.kind(callee) != NodeKind::Member {
3070 return None;
3071 }
3072 if member_name(lang, callee).as_deref() != Some("decode") {
3073 return None;
3074 }
3075 let recv = *lang.children(callee).first()?;
3077 let recv_name = match lang.kind(recv) {
3078 NodeKind::Member => member_name(lang, recv)?,
3079 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
3080 _ => return None,
3081 };
3082 if !recv_name.ends_with("URLDecoder") {
3083 return None;
3084 }
3085 let s_lit = match lang.literal(ch[1])? {
3086 LitRef::Str(s) => s.to_string(),
3087 _ => return None,
3088 };
3089 let cs = match lang.literal(ch[2])? {
3091 LitRef::Str(s) => s.to_string(),
3092 _ => return None,
3093 };
3094 if cs != "UTF-8" {
3095 return None;
3096 }
3097 if s_lit.len() > 8192 {
3099 return None;
3100 }
3101 let decoded = url_decode(&s_lit)?;
3102 if decoded.chars().any(|c| {
3104 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
3105 }) {
3106 return None;
3107 }
3108 let lit = lang.lit(Lit::Str(decoded));
3109 Some(Edit::Replace { target: id, with: lit })
3110 }
3111}
3112
3113fn url_decode(s: &str) -> Option<String> {
3116 let b = s.as_bytes();
3117 let mut out: Vec<u8> = Vec::with_capacity(b.len());
3118 let mut i = 0usize;
3119 while i < b.len() {
3120 match b[i] {
3121 b'+' => {
3122 out.push(b' ');
3123 i += 1;
3124 }
3125 b'%' => {
3126 if i + 3 > b.len() {
3127 return None;
3128 }
3129 let hi = hex_val(b[i + 1])?;
3130 let lo = hex_val(b[i + 2])?;
3131 out.push(hi * 16 + lo);
3132 i += 3;
3133 }
3134 c => {
3135 out.push(c);
3136 i += 1;
3137 }
3138 }
3139 }
3140 String::from_utf8(out).ok()
3141}
3142
3143fn hex_val(c: u8) -> Option<u8> {
3144 match c {
3145 b'0'..=b'9' => Some(c - b'0'),
3146 b'a'..=b'f' => Some(c - b'a' + 10),
3147 b'A'..=b'F' => Some(c - b'A' + 10),
3148 _ => None,
3149 }
3150}
3151
3152pub struct Base64NewStringFold;
3153
3154impl Rule<JavaAst> for Base64NewStringFold {
3155 fn name(&self) -> &'static str {
3156 "base64_new_string_fold"
3157 }
3158 fn kinds(&self) -> &'static [NodeKind] {
3159 &[NodeKind::New]
3160 }
3161 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3162 let root = ctx.root();
3163 let lang = ctx.lang;
3164 let NodeData::New { ty, .. } = lang.data(id) else {
3165 return None;
3166 };
3167 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3168 return None;
3169 }
3170 if lang.has_string_identity_compare(root) {
3172 return None;
3173 }
3174 let args = lang.children(id).to_vec();
3175 if args.len() != 1 {
3176 return None;
3177 }
3178 if lang.kind(args[0]) != NodeKind::Call {
3180 return None;
3181 }
3182 let dc = lang.children(args[0]).to_vec();
3183 if dc.len() != 2 {
3184 return None;
3185 }
3186 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3187 return None;
3188 };
3189 if lang.sn(*dn) != "decode" {
3190 return None;
3191 }
3192 let recv = lang.children(dc[0])[0];
3194 if lang.kind(recv) != NodeKind::Call {
3195 return None;
3196 }
3197 let gc = lang.children(recv).to_vec();
3198 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3199 return None;
3200 };
3201 let url_safe = match lang.sn(*gn) {
3202 "getDecoder" => false,
3203 "getUrlDecoder" => true,
3204 _ => return None,
3205 };
3206 let owner = lang.children(gc[0])[0];
3208 let owner_ok = match lang.data(owner) {
3209 NodeData::Member { name } => lang.sn(*name) == "Base64",
3210 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3211 };
3212 if !owner_ok {
3213 return None;
3214 }
3215 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3216 return None;
3217 };
3218 let bytes = base64_decode(b64, url_safe)?;
3219 let text = String::from_utf8(bytes).ok()?;
3220 let with = lang.build_str(&text);
3221 Some(Edit::Replace {
3222 target: id,
3223 with,
3224 })
3225 }
3226}
3227
3228fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3230 let val = |c: char| -> Option<u32> {
3231 match c {
3232 'A'..='Z' => Some(c as u32 - 'A' as u32),
3233 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3234 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3235 '+' if !url_safe => Some(62),
3236 '/' if !url_safe => Some(63),
3237 '-' if url_safe => Some(62),
3238 '_' if url_safe => Some(63),
3239 _ => None,
3240 }
3241 };
3242 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3243 if clean.len() % 4 == 1 {
3244 return None;
3245 }
3246 let mut out = Vec::new();
3247 let mut buf = 0u32;
3248 let mut bits = 0u32;
3249 for c in clean.chars() {
3250 buf = (buf << 6) | val(c)?;
3251 bits += 6;
3252 if bits >= 8 {
3253 bits -= 8;
3254 out.push((buf >> bits) as u8);
3255 buf &= (1 << bits) - 1;
3256 }
3257 }
3258 Some(out)
3259}
3260
3261
3262pub struct CffRecover;
3284
3285struct CffCase {
3286 body: Vec<JavaId>,
3287 tail: CffTail,
3288}
3289
3290enum CffTail {
3291 Goto(i64),
3292 Cond { cond: JavaId, a: i64, b: i64 },
3293 Term(JavaId),
3294 Exit,
3295}
3296
3297#[derive(Clone, Copy, PartialEq, Debug)]
3298enum Flow {
3299 Done,
3300 Stop,
3301 Cycle(usize),
3302 Cond(i64),
3303 External(i64),
3304}
3305
3306struct CffCtx<'a> {
3307 lang: &'a mut JavaAst,
3308 cases: std::collections::HashMap<i64, CffCase>,
3309 default_term: Option<Vec<JavaId>>,
3311 used_default: bool,
3312 reached: std::collections::HashSet<i64>,
3314 emitted: std::collections::HashSet<i64>,
3316}
3317
3318impl Rule<JavaAst> for CffRecover {
3319 fn name(&self) -> &'static str {
3320 "cff_recover"
3321 }
3322 fn kinds(&self) -> &'static [NodeKind] {
3323 &[NodeKind::While]
3324 }
3325 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3326 let parent = ctx.parent(id)?;
3327 let idx = ctx.index(id)?;
3328 let lang = ctx.lang;
3329 if lang.kind(id) != NodeKind::While {
3330 return None;
3331 }
3332 let wch = lang.children(id).to_vec();
3333 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3334 return None;
3335 }
3336 if lang.kind(wch[1]) != NodeKind::Block {
3337 return None;
3338 }
3339 let body_ch = lang.children(wch[1]).to_vec();
3340 let (switch, exit_if) = match body_ch.len() {
3341 1 => (body_ch[0], None),
3342 2 => (body_ch[0], Some(body_ch[1])),
3343 _ => return None,
3344 };
3345 if lang.kind(switch) != NodeKind::Switch {
3346 return None;
3347 }
3348 let sch = lang.children(switch).to_vec();
3349 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3350 return None;
3351 }
3352 let v = lang.var_name(sch[0])?.to_string();
3353
3354 let sentinel = match exit_if {
3356 None => None,
3357 Some(ei) => {
3358 if lang.kind(ei) != NodeKind::If {
3359 return None;
3360 }
3361 let ich = lang.children(ei).to_vec();
3362 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3363 return None;
3364 }
3365 let cch = lang.children(ich[0]).to_vec();
3366 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3367 && lang.var_name(cch[0]) == Some(v.as_str())
3368 {
3369 lang.literal(cch[1]).and_then(|x| x.as_int())
3370 } else if lang.kind(cch[1]) == NodeKind::VarRef
3371 && lang.var_name(cch[1]) == Some(v.as_str())
3372 {
3373 lang.literal(cch[0]).and_then(|x| x.as_int())
3374 } else {
3375 return None;
3376 }?;
3377 let then_ok = match lang.kind(ich[1]) {
3378 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3379 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3380 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3381 }
3382 _ => false,
3383 };
3384 if !then_ok {
3385 return None;
3386 }
3387 Some(sent)
3388 }
3389 };
3390
3391 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3393 let mut default_term: Option<Vec<JavaId>> = None;
3394 for &c in &sch[1..] {
3395 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3396 return None;
3397 };
3398 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3399 let ch = lang.children(c).to_vec();
3400 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3401 if !arrow
3403 && stmts
3404 .last()
3405 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3406 {
3407 stmts.pop();
3408 }
3409 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3410 let mut inner = lang.children(stmts[0]).to_vec();
3411 if !arrow
3412 && inner
3413 .last()
3414 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3415 {
3416 inner.pop();
3417 }
3418 stmts = inner;
3419 }
3420 if is_default {
3421 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3422 default_term = Some(stmts);
3423 continue;
3424 }
3425 return None;
3426 }
3427 if labels != 1 {
3429 return None;
3430 }
3431 let state = match lang.literal(ch[0]) {
3432 Some(LitRef::Int(v)) => v,
3433 _ => return None,
3434 };
3435 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3436 for &st in &stmts {
3438 if subtree_has_var(lang, st, &v) {
3439 return None;
3440 }
3441 }
3442 cases.insert(state, CffCase { body: stmts, tail });
3443 }
3444 if cases.is_empty() {
3445 return None;
3446 }
3447
3448 if idx == 0 {
3451 return None;
3452 }
3453 let block_ch = lang.children(parent).to_vec();
3454 let mut init = None;
3455 let mut init_idx = idx;
3456 {
3457 let mut up = idx;
3458 for _ in 0..8 {
3459 if up == 0 {
3460 break;
3461 }
3462 up -= 1;
3463 let st = block_ch[up];
3464 match scan_cff_event(lang, st, &v) {
3465 Some(CffEvent::Read) => return None,
3466 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3467 init = parse_cff_init(lang, &v, st);
3468 init_idx = up;
3469 break;
3470 }
3471 None => continue,
3472 }
3473 }
3474 }
3475 let init = init?;
3476
3477 for &st in &block_ch[idx + 1..] {
3479 if subtree_has_var(lang, st, &v) {
3480 return None;
3481 }
3482 }
3483
3484 let mut to_exit: Vec<i64> = Vec::new();
3486 for (k, ci) in cases.iter() {
3487 if let CffTail::Goto(g) = ci.tail {
3488 if !cases.contains_key(&g) {
3489 if !(sentinel == Some(g) || default_term.is_some()) {
3490 return None;
3491 }
3492 to_exit.push(*k);
3493 }
3494 }
3495 }
3496 for k in to_exit {
3497 if let Some(ci) = cases.get_mut(&k) {
3498 ci.tail = CffTail::Exit;
3499 }
3500 }
3501
3502 let mut c = CffCtx {
3503 lang,
3504 cases,
3505 default_term,
3506 used_default: false,
3507 reached: Default::default(),
3508 emitted: Default::default(),
3509 };
3510 let stmts = cff_run(&mut c, init)?;
3511
3512 if stmts.is_empty() {
3515 return None;
3516 }
3517
3518 Some(Edit::Multi(vec![
3521 Edit::Splice {
3522 node: parent,
3523 index: idx,
3524 remove: 1,
3525 insert: stmts,
3526 },
3527 Edit::Delete {
3528 node: block_ch[init_idx],
3529 },
3530 ]))
3531 }
3532}
3533
3534fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3535 let last = *stmts.last()?;
3536 if lang.kind(last) == NodeKind::Return {
3537 stmts.pop();
3538 return Some(CffTail::Term(last));
3539 }
3540 let assign_of = |n: JavaId| -> Option<JavaId> {
3541 let inner = match lang.kind(n) {
3542 NodeKind::Assign => n,
3543 NodeKind::ExprStmt => {
3544 let ch = lang.children(n);
3545 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3546 ch[0]
3547 } else {
3548 return None;
3549 }
3550 }
3551 _ => return None,
3552 };
3553 if lang.assign_op(inner).is_some() {
3554 return None;
3555 }
3556 let ch = lang.children(inner).to_vec();
3557 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3558 return None;
3559 }
3560 Some(inner)
3561 };
3562 if let Some(assign) = assign_of(last) {
3563 let value = lang.children(assign)[1];
3564 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3565 stmts.pop();
3566 return Some(CffTail::Goto(k));
3567 }
3568 if lang.kind(value) == NodeKind::Ternary {
3569 let tch = lang.children(value).to_vec();
3570 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3571 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3572 stmts.pop();
3573 return Some(CffTail::Cond { cond: tch[0], a, b });
3574 }
3575 return None;
3576 }
3577 if lang.kind(last) == NodeKind::If {
3578 let ich = lang.children(last).to_vec();
3579 if ich.len() != 3 {
3580 return None;
3581 }
3582 let arm = |n: JavaId| -> Option<i64> {
3583 let target = match lang.kind(n) {
3584 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3585 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3586 _ => return None,
3587 };
3588 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3589 };
3590 let a = arm(ich[1])?;
3591 let b = arm(ich[2])?;
3592 stmts.pop();
3593 return Some(CffTail::Cond { cond: ich[0], a, b });
3594 }
3595 None
3596}
3597
3598
3599enum CffEvent {
3601 Read,
3602 Write,
3603 Decl,
3605}
3606
3607fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3608 match lang.kind(node) {
3609 NodeKind::VarRef => {
3610 if lang.var_name(node) == Some(name) {
3611 Some(CffEvent::Read)
3612 } else {
3613 None
3614 }
3615 }
3616 NodeKind::VarDecl => {
3617 if lang.var_name(node) == Some(name) {
3618 Some(CffEvent::Decl)
3619 } else {
3620 for &c in lang.children(node) {
3621 if let Some(e) = scan_cff_event(lang, c, name) {
3622 return Some(e);
3623 }
3624 }
3625 None
3626 }
3627 }
3628 NodeKind::Assign => {
3629 let ch = lang.children(node);
3630 if let Some(&val) = ch.get(1) {
3631 if let Some(e) = scan_cff_event(lang, val, name) {
3632 if matches!(e, CffEvent::Read) {
3633 return Some(e);
3634 }
3635 }
3636 }
3637 if lang.assign_op(node).is_none() {
3638 if let Some(&t) = ch.first() {
3639 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3640 return Some(CffEvent::Write);
3641 }
3642 }
3643 }
3644 None
3645 }
3646 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3647 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3648 NodeKind::Binary => {
3649 let op = lang.bin_op(node)?;
3650 let ch = lang.children(node);
3651 if op.is_short_circuit() {
3652 scan_cff_event(lang, *ch.first()?, name)
3653 } else {
3654 for &c in ch {
3655 if let Some(e) = scan_cff_event(lang, c, name) {
3656 return Some(e);
3657 }
3658 }
3659 None
3660 }
3661 }
3662 _ => {
3663 for &c in lang.children(node) {
3664 if let Some(e) = scan_cff_event(lang, c, name) {
3665 return Some(e);
3666 }
3667 }
3668 None
3669 }
3670 }
3671}
3672
3673fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3674 match lang.kind(stmt) {
3675 NodeKind::VarDecl => {
3676 let ch = lang.children(stmt).to_vec();
3677 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3678 return None;
3679 }
3680 lang.literal(ch[0]).and_then(|x| x.as_int())
3681 }
3682 NodeKind::Assign => {
3683 if lang.assign_op(stmt).is_some() {
3684 return None;
3685 }
3686 let ch = lang.children(stmt).to_vec();
3687 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3688 return None;
3689 }
3690 lang.literal(ch[1]).and_then(|x| x.as_int())
3691 }
3692 NodeKind::ExprStmt => {
3693 let ch = lang.children(stmt).to_vec();
3694 if ch.len() == 1 {
3695 parse_cff_init(lang, v, ch[0])
3696 } else {
3697 None
3698 }
3699 }
3700 _ => None,
3701 }
3702}
3703
3704fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3705 for (s, stmts) in pairs {
3706 c.emitted.insert(*s);
3707 out.extend(stmts.iter().copied());
3708 }
3709}
3710
3711fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3712 for (s, stmts) in pairs {
3713 if *s == m {
3714 break;
3715 }
3716 out.extend(stmts.iter().copied());
3717 }
3718}
3719
3720fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3721 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3722 pairs[pos..].to_vec()
3723}
3724
3725fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3727 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3728 let mut cur = start;
3729 let mut guard = 0usize;
3730 loop {
3731 guard += 1;
3732 if guard > 10_000 {
3733 return None;
3734 }
3735 if Some(cur) == stop {
3736 return Some((path, Flow::Stop));
3737 }
3738 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3739 return Some((path, Flow::Cycle(i)));
3740 }
3741 if c.emitted.contains(&cur) {
3742 return Some((path, Flow::External(cur)));
3743 }
3744 c.reached.insert(cur);
3745 let ci = c.cases.get(&cur)?;
3746 let mut body = ci.body.clone();
3747 match ci.tail {
3748 CffTail::Goto(k) => {
3749 path.push((cur, body));
3750 cur = k;
3751 }
3752 CffTail::Term(r) => {
3753 body.push(r);
3754 path.push((cur, body));
3755 return Some((path, Flow::Done));
3756 }
3757 CffTail::Exit => {
3758 if c.default_term.is_some() {
3759 if let Some(dt) = c.default_term.clone() {
3760 body.extend(dt);
3761 c.used_default = true;
3762 }
3763 }
3764 path.push((cur, body));
3765 return Some((path, Flow::Done));
3766 }
3767 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3768 }
3769 }
3770}
3771
3772fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3774 let (path, flow) = collect(c, init, None)?;
3775 cff_structure(c, path, flow)
3776}
3777
3778fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3780 let (path, flow) = collect(c, start, None)?;
3781 cff_structure(c, path, flow)
3782}
3783
3784fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3788 let mut stmts: Vec<JavaId> = Vec::new();
3789 loop {
3790 match flow {
3791 Flow::Done => {
3792 flatten(&path, &mut stmts, c);
3793 return Some(stmts);
3794 }
3795 Flow::Cycle(i) => {
3796 flatten(&path[..i], &mut stmts, c);
3797 let mut suffix = Vec::new();
3798 flatten(&path[i..], &mut suffix, c);
3799 let inner = c.lang.build_block(suffix);
3800 let cond = c.lang.build_bool(true);
3801 let w = c.lang.while_(cond, inner);
3802 stmts.push(w);
3803 return Some(stmts);
3804 }
3805 Flow::Stop | Flow::External(_) => return None,
3806 Flow::Cond(state) => {
3807 flatten(&path, &mut stmts, c);
3808 path.clear();
3809 let ci = c.cases.get(&state)?;
3810 stmts.extend(ci.body.iter().copied());
3811 c.emitted.insert(state);
3812 let CffTail::Cond { cond, a, b } = ci.tail else {
3813 return None;
3814 };
3815 let (pa, fa) = collect(c, a, Some(state))?;
3817 if fa == Flow::Stop {
3818 let mut lb = Vec::new();
3819 flatten(&pa, &mut lb, c);
3820 let inner = c.lang.build_block(lb);
3821 let w = c.lang.while_(cond, inner);
3822 stmts.push(w);
3823 let (p2, f2) = collect(c, b, None)?;
3824 path = p2;
3825 flow = f2;
3826 continue;
3827 }
3828 let (pb, fb) = collect(c, b, Some(state))?;
3829 if fb == Flow::Stop {
3830 let mut lb = Vec::new();
3831 flatten(&pb, &mut lb, c);
3832 let inner = c.lang.build_block(lb);
3833 let nc = c.lang.build_unary(UnOp::Not, cond);
3834 let w = c.lang.while_(nc, inner);
3835 stmts.push(w);
3836 let (p2, f2) = collect(c, a, None)?;
3837 path = p2;
3838 flow = f2;
3839 continue;
3840 }
3841 let common = pa
3843 .iter()
3844 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3845 .map(|(s, _)| *s);
3846 let same_end = match (fa, fb) {
3847 (Flow::Done, Flow::Done) => true,
3848 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3849 _ => false,
3850 };
3851 match common {
3852 Some(m) if same_end => {
3853 let mut pre_a = Vec::new();
3854 let mut pre_b = Vec::new();
3855 flatten_prefix(&pa, m, &mut pre_a);
3856 flatten_prefix(&pb, m, &mut pre_b);
3857 let ba = c.lang.build_block(pre_a);
3858 let bb = c.lang.build_block(pre_b);
3859 let ifs = c.lang.if_(cond, ba, Some(bb));
3860 stmts.push(ifs);
3861 path = suffix_pairs(&pa, m);
3862 flow = fa;
3863 continue;
3864 }
3865 Some(_) => return None,
3866 None => {
3867 let mut fa_stmts = Vec::new();
3869 let mut fb_stmts = Vec::new();
3870 flatten(&pa, &mut fa_stmts, c);
3871 flatten(&pb, &mut fb_stmts, c);
3872 match (fa, fb) {
3873 (Flow::Done, Flow::Done) => {
3874 let ba = c.lang.build_block(fa_stmts);
3875 let bb = c.lang.build_block(fb_stmts);
3876 let ifs = c.lang.if_(cond, ba, Some(bb));
3877 stmts.push(ifs);
3878 return Some(stmts);
3879 }
3880 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3882 let ba = c.lang.build_block(fa_stmts);
3883 let bb = c.lang.build_block(fb_stmts);
3884 let ifs = c.lang.if_(cond, ba, Some(bb));
3885 stmts.push(ifs);
3886 let (p2, f2) = collect(c, x, None)?;
3887 path = p2;
3888 flow = f2;
3889 continue;
3890 }
3891 (Flow::Done, Flow::Cond(x)) => {
3893 let cont = cff_run_from(c, x)?;
3894 fb_stmts.extend(cont);
3895 let ba = c.lang.build_block(fa_stmts);
3896 let bb = c.lang.build_block(fb_stmts);
3897 let ifs = c.lang.if_(cond, ba, Some(bb));
3898 stmts.push(ifs);
3899 return Some(stmts);
3900 }
3901 (Flow::Cond(x), Flow::Done) => {
3902 let cont = cff_run_from(c, x)?;
3903 fa_stmts.extend(cont);
3904 let ba = c.lang.build_block(fa_stmts);
3905 let bb = c.lang.build_block(fb_stmts);
3906 let ifs = c.lang.if_(cond, ba, Some(bb));
3907 stmts.push(ifs);
3908 return Some(stmts);
3909 }
3910 _ => return None,
3911 }
3912 }
3913 }
3914 }
3915 }
3916 }
3917}
3918
3919
3920
3921pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3931 ast.const_fields.clear();
3932 ast.const_scalars.clear();
3933 ast.inline_methods.clear();
3934 ast.field_types.clear();
3935 ast.final_fields.clear();
3936 ast.raw_generic_names = {
3939 let mut m: std::collections::HashSet<String> = [
3940 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3941 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3942 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3943 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3944 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3945 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3946 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3947 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3948 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3949 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3951 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3952 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3953 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3954 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3955 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3956 ]
3957 .iter()
3958 .map(|x| x.to_string())
3959 .collect();
3960 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3961 if !ty.ty_params.trim().is_empty() {
3962 m.insert(ty.name.clone());
3963 }
3964 for mem in &ty.members {
3965 if let Member::Type(t) = mem {
3966 collect_generics(t, m);
3967 }
3968 }
3969 }
3970 for ty in &unit.types {
3971 collect_generics(ty, &mut m);
3972 }
3973 m
3974 };
3975 ast.unit_shadows_lang = {
3976 fn decl_shadows(ty: &TypeDecl) -> bool {
3977 if ty.name == "String" || ty.name == "StringBuilder" {
3978 return true;
3979 }
3980 ty.members.iter().any(|m| match m {
3981 Member::Type(t) => decl_shadows(t),
3982 _ => false,
3983 })
3984 }
3985 unit.types.iter().any(decl_shadows)
3986 };
3987
3988 let mut shadowed: std::collections::HashSet<String> = Default::default();
3993 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3994 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3995 let mut bodies: Vec<JavaId> = Vec::new();
3996 for ty in &unit.types {
3997 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3998 }
3999 for ty in &unit.types {
4002 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
4003 }
4004 for &root in &bodies {
4008 let mut stack = vec![root];
4009 while let Some(n) = stack.pop() {
4010 if ast.kind(n) == NodeKind::VarDecl {
4011 if let Some(nm) = ast.var_name(n) {
4012 shadowed.insert(nm.to_string());
4013 }
4014 }
4015 match ast.data(n) {
4016 NodeData::Assign { .. } => {
4017 let t = ast.children(n)[0];
4018 if ast.kind(t) == NodeKind::Member {
4019 if let NodeData::Member { name: sym } = ast.data(t) {
4020 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
4021 }
4022 }
4023 let mut ts = vec![t];
4024 while let Some(x) = ts.pop() {
4025 if ast.kind(x) == NodeKind::VarRef {
4026 if let Some(nm) = ast.var_name(x) {
4027 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
4028 }
4029 }
4030 for &c in ast.children(x) {
4031 ts.push(c);
4032 }
4033 }
4034 }
4035 NodeData::Unary { op } if op.is_incdec() => {
4036 let t = ast.children(n)[0];
4037 if ast.kind(t) == NodeKind::VarRef {
4038 if let Some(nm) = ast.var_name(t) {
4039 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
4040 }
4041 }
4042 }
4043 _ => {}
4044 }
4045 for &c in ast.children(n) {
4046 stack.push(c);
4047 }
4048 }
4049 }
4050
4051 for ty in &unit.types {
4053 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
4055 collect_own_clinit_lits(ast, ty, &mut own_lits);
4056 for m in &ty.members {
4057 if let Member::Field { mods, ty: fty, declarators } = m {
4058 let words: Vec<&str> = mods.split_whitespace().collect();
4059 let is_static = words.contains(&"static");
4060 let is_final = words.contains(&"final");
4061 let is_private = words.contains(&"private");
4062 for d in declarators {
4063 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
4067 continue;
4068 }
4069 let init = match d.init {
4070 Some(init) => {
4071 if !is_final
4073 || !is_const_init(ast, init)
4074 || shadowed.contains(&d.name)
4075 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
4076 {
4077 continue;
4078 }
4079 init
4080 }
4081 None => {
4082 if !is_static
4089 || (!is_final && !is_private)
4090 || shadowed.contains(&d.name)
4091 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
4092 {
4093 continue;
4094 }
4095 match own_lits.get(&d.name) {
4096 Some(&init) if is_const_init(ast, init) => init,
4097 _ => continue,
4098 }
4099 }
4100 };
4101 if ast.const_fields.contains_key(&d.name) {
4103 ast.const_fields.remove(&d.name);
4104 ast.const_scalars.remove(&d.name);
4105 } else {
4106 let scalar_ok = scalar_lit_of(ast, init)
4107 .is_some_and(|l| lit_type_matches_decl(fty, l));
4108 ast.const_fields.insert(d.name.clone(), init);
4109 if scalar_ok {
4110 ast.const_scalars.insert(d.name.clone());
4111 }
4112 }
4113 }
4114 }
4115 }
4116 }
4117
4118 {
4125 let identity_assert = [
4126 "assertSame",
4127 "assertNotSame",
4128 "isSameInstanceAs",
4129 "isNotSameInstanceAs",
4130 "referenceEquals",
4131 "same",
4132 ];
4133 let mut hit = false;
4134 for root in &bodies {
4135 let mut stack = vec![*root];
4136 while let Some(n) = stack.pop() {
4137 if let NodeData::Call = ast.data(n) {
4138 if let Some(&callee) = ast.children(n).first() {
4139 let nm = match ast.data(callee) {
4140 NodeData::Member { name } => ast.sn(*name),
4141 NodeData::VarRef { name } => ast.sn(*name),
4142 _ => "",
4143 };
4144 if identity_assert.contains(&nm) {
4145 hit = true;
4146 break;
4147 }
4148 }
4149 }
4150 for &c in ast.children(n) {
4151 stack.push(c);
4152 }
4153 }
4154 if hit {
4155 break;
4156 }
4157 }
4158 ast.unit_string_identity =
4159 ast.any_string_identity(&bodies) || hit;
4160 }
4161
4162 let mut method_name_count: std::collections::HashMap<String, usize> =
4172 std::collections::HashMap::new();
4173 for_each_type(unit, &mut |ty: &TypeDecl| {
4174 for m in &ty.members {
4175 if let Member::Method { name, .. } = m {
4176 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4177 }
4178 }
4179 });
4180 for ty in &unit.types {
4181 for m in &ty.members {
4182 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4183 let words: Vec<&str> = mods.split_whitespace().collect();
4184 let non_overridable = words.contains(&"private")
4185 || words.contains(&"static")
4186 || words.contains(&"final");
4187 if !non_overridable {
4188 continue;
4189 }
4190 let ch = ast.children(*b).to_vec();
4191 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4192 if let Some(&expr) = ast.children(ch[0]).first() {
4193 if params.len() <= 3
4194 && !subtree_has_anon(ast, expr)
4199 && !params.iter().any(|p| p.varargs)
4203 && !subtree_calls_self(ast, expr, name)
4204 && !subtree_has_type_sensitive(ast, expr)
4210 && method_name_count.get(name).copied() == Some(1)
4211 {
4212 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4213 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4214 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4215 }
4216 }
4217 }
4218 }
4219 }
4220 }
4221}
4222
4223fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4225 match ast.kind(init) {
4226 NodeKind::Literal => true,
4227 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4228 NodeKind::NewArray => {
4229 let ch = ast.children(init).to_vec();
4230 ch.len() == 1
4231 && ast.kind(ch[0]) == NodeKind::ArrayLit
4232 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4233 }
4234 _ => false,
4235 }
4236}
4237
4238fn collect_bodies_recursive(
4242 ty: &TypeDecl,
4243 bodies: &mut Vec<JavaId>,
4244 shadowed: &mut std::collections::HashSet<String>,
4245 field_decls: &mut std::collections::HashMap<String, u32>,
4246) {
4247 if ty.kind == TypeKind::Enum {
4255 for c in &ty.enum_constants {
4256 if c.trim() == "," {
4257 continue;
4258 }
4259 let last = c.split('(').next().unwrap_or(c)
4260 .split_whitespace()
4261 .next_back()
4262 .unwrap_or("");
4263 let name: String = last
4264 .chars()
4265 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4266 .collect();
4267 if !name.is_empty() {
4268 *field_decls.entry(name).or_insert(0) += 1;
4269 }
4270 }
4271 }
4272 for m in &ty.members {
4273 match m {
4274 Member::Method { body: Some(b), params, .. }
4275 | Member::Constructor { body: Some(b), params, .. } => {
4276 bodies.push(*b);
4277 for p in params {
4278 shadowed.insert(p.name.clone());
4279 }
4280 }
4281 Member::Initializer { body, .. } => bodies.push(*body),
4282 Member::Field { declarators, .. } => {
4283 for d in declarators {
4284 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4285 if let Some(init) = d.init {
4286 bodies.push(init);
4287 }
4288 }
4289 }
4290 Member::Type(t) => {
4291 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4292 }
4293 _ => {}
4294 }
4295 }
4296}
4297
4298fn collect_field_types(
4300 ty: &TypeDecl,
4301 out: &mut std::collections::HashMap<String, JType>,
4302 finals: &mut std::collections::HashSet<String>,
4303) {
4304 for m in &ty.members {
4305 match m {
4306 Member::Field { mods, ty: fty, declarators } => {
4307 let is_final = mods.split_whitespace().any(|w| w == "final");
4308 for d in declarators {
4309 if out.contains_key(&d.name) {
4310 out.remove(&d.name);
4311 finals.remove(&d.name);
4312 } else {
4313 out.insert(d.name.clone(), fty.clone());
4314 if is_final {
4315 finals.insert(d.name.clone());
4316 }
4317 }
4318 }
4319 }
4320 Member::Type(t) => collect_field_types(t, out, finals),
4321 _ => {}
4322 }
4323 }
4324}
4325
4326fn collect_own_clinit_lits(
4331 ast: &JavaAst,
4332 ty: &TypeDecl,
4333 out: &mut std::collections::HashMap<String, JavaId>,
4334) {
4335 for m in &ty.members {
4336 if let Member::Initializer { is_static: true, body } = m {
4337 if ast.kind(*body) != NodeKind::Block {
4338 continue;
4339 }
4340 for &st in ast.children(*body) {
4341 let asg = match ast.kind(st) {
4342 NodeKind::ExprStmt => {
4343 let Some(&c) = ast.children(st).first() else {
4347 continue;
4348 };
4349 if ast.kind(c) == NodeKind::Assign {
4350 c
4351 } else {
4352 continue;
4353 }
4354 }
4355 NodeKind::Assign => st,
4356 _ => continue,
4357 };
4358 let ch = ast.children(asg).to_vec();
4359 if ast.kind(ch[0]) != NodeKind::VarRef {
4360 continue;
4361 }
4362 let Some(nm) = ast.var_name(ch[0]) else { continue };
4363 if is_const_init(ast, ch[1]) {
4364 out.insert(nm.to_string(), ch[1]);
4365 }
4366 }
4367 }
4368 }
4369}
4370
4371fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4373 if ast.kind(init) == NodeKind::Literal {
4374 ast.literal(init)
4375 } else {
4376 None
4377 }
4378}
4379
4380fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4385 use cure_engine::kind::LitRef;
4386 match (fty, lit) {
4387 (JType::Ref(r), LitRef::Str(_)) => {
4388 let base = r.split('<').next().unwrap_or(r).trim();
4389 base == "String" || base == "java.lang.String"
4390 }
4391 (JType::Char, LitRef::Char(_)) => true,
4392 (JType::Bool, LitRef::Bool(_)) => true,
4393 (JType::Int, LitRef::Int(_)) => true,
4394 (JType::Long, LitRef::Long(_)) => true,
4395 (JType::Float, LitRef::Float(_)) => true,
4396 (JType::Double, LitRef::Double(_)) => true,
4397 _ => false,
4398 }
4399}
4400
4401fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4405 let mut stack = vec![root];
4406 while let Some(n) = stack.pop() {
4407 match ast.kind(n) {
4408 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4409 _ => {}
4410 }
4411 for &c in ast.children(n) {
4412 stack.push(c);
4413 }
4414 }
4415 false
4416}
4417
4418fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4419 let mut stack = vec![root];
4420 while let Some(n) = stack.pop() {
4421 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4422 return true;
4423 }
4424 for &c in ast.children(n) {
4425 stack.push(c);
4426 }
4427 }
4428 false
4429}
4430
4431fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4432 let mut stack = vec![root];
4433 while let Some(n) = stack.pop() {
4434 if ast.kind(n) == NodeKind::Call {
4435 let callee = ast.children(n)[0];
4436 if ast.var_name(callee) == Some(name) {
4437 return true;
4438 }
4439 if let NodeData::Member { name: m } = ast.data(callee) {
4444 if ast.sn(*m) == name {
4445 return true;
4446 }
4447 }
4448 }
4449 for &c in ast.children(n) {
4450 stack.push(c);
4451 }
4452 }
4453 false
4454}
4455
4456pub struct StaticArrayIndexFold;
4461
4462impl Rule<JavaAst> for StaticArrayIndexFold {
4463 fn name(&self) -> &'static str {
4464 "static_array_index_fold"
4465 }
4466 fn kinds(&self) -> &'static [NodeKind] {
4467 &[NodeKind::Index]
4468 }
4469 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4470 let lang = ctx.lang;
4471 if lang.kind(id) != NodeKind::Index {
4472 return None;
4473 }
4474 let ch = lang.children(id).to_vec();
4475 if lang.kind(ch[0]) != NodeKind::VarRef {
4476 return None;
4477 }
4478 let name = lang.var_name(ch[0])?.to_string();
4479 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4480 let init = *lang.const_fields.get(&name)?;
4481 let arr = match lang.kind(init) {
4482 NodeKind::ArrayLit => init,
4483 NodeKind::NewArray => {
4484 let ic = lang.children(init).to_vec();
4485 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4486 ic[0]
4487 } else {
4488 return None;
4489 }
4490 }
4491 _ => return None,
4492 };
4493 let elems = lang.children(arr).to_vec();
4494 if idx < 0 || idx >= elems.len() as i64 {
4495 return None;
4496 }
4497 let elem = lang.copy_subtree(elems[idx as usize]);
4498 Some(Edit::Replace {
4499 target: id,
4500 with: elem,
4501 })
4502 }
4503}
4504
4505pub struct DeadPureCall;
4585
4586impl Rule<JavaAst> for DeadPureCall {
4587 fn name(&self) -> &'static str {
4588 "dead_pure_call"
4589 }
4590 fn kinds(&self) -> &'static [NodeKind] {
4591 &[NodeKind::ExprStmt]
4592 }
4593 fn structural(&self) -> bool {
4594 true
4595 }
4596 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4597 let lang = ctx.lang;
4598 if lang.kind(id) != NodeKind::ExprStmt {
4599 return None;
4600 }
4601 let Some(&call) = lang.children(id).first() else { return None };
4602 if lang.kind(call) != NodeKind::Call {
4603 return None;
4604 }
4605 let cch = lang.children(call).to_vec();
4606 let Some(&callee) = cch.first() else { return None };
4607 let (recv, name) = match lang.data(callee) {
4610 NodeData::Member { name } => {
4611 let Some(&recv_expr) = lang.children(callee).first() else { return None };
4612 let recv_name = match lang.data(recv_expr) {
4615 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4616 NodeData::Member { name } => lang.sn(*name).to_string(),
4617 _ => return None,
4618 };
4619 if !recv_name
4620 .chars()
4621 .next()
4622 .is_some_and(|c| c.is_uppercase())
4623 {
4624 return None;
4625 }
4626 (recv_name, lang.sn(*name))
4627 }
4628 _ => return None,
4629 };
4630 let args = &cch[1..];
4631 let pure = match (recv.as_str(), name) {
4632 ("Long" | "Integer" | "Float" | "Double" | "Short" | "Byte" | "Character", "compare") => {
4633 args.iter().all(|&a| match lang.data(a) {
4637 NodeData::Literal(
4638 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_),
4639 ) => true,
4640 NodeData::Literal(Lit::NumRaw { .. }) => true,
4642 NodeData::VarRef { .. } => {
4643 let t = lang.var_type(a);
4644 matches!(
4645 t,
4646 Some(JType::Int | JType::Long | JType::Float | JType::Double | JType::Char
4647 | JType::Short | JType::Byte)
4648 )
4649 }
4650 _ => false,
4651 })
4652 }
4653 ("Objects", "toString") => args.len() == 1,
4654 _ => false,
4655 };
4656 if !pure {
4657 return None;
4658 }
4659 Some(Edit::Delete { node: id })
4660 }
4661}
4662
4663pub struct CtorDelegationHoist;
4664
4665fn hoist_resolve_arg(
4670 lang: &mut JavaAst,
4671 assign: &std::collections::HashMap<String, JavaId>,
4672 a: JavaId,
4673 visiting: &mut std::collections::HashSet<String>,
4674) -> Option<JavaId> {
4675 let name = match lang.data(a) {
4676 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4677 _ => return Some(a), };
4679 let Some(&expr) = assign.get(&name) else {
4681 return Some(a);
4682 };
4683 if !visiting.insert(name.clone()) {
4684 return None; }
4686 let r = hoist_subst(lang, assign, expr, visiting);
4687 visiting.remove(&name);
4688 r
4689}
4690
4691fn hoist_subst(
4693 lang: &mut JavaAst,
4694 assign: &std::collections::HashMap<String, JavaId>,
4695 e: JavaId,
4696 visiting: &mut std::collections::HashSet<String>,
4697) -> Option<JavaId> {
4698 match lang.data(e) {
4699 NodeData::VarRef { name } => {
4700 let n = lang.sn(*name).to_string();
4701 if let Some(&src) = assign.get(&n) {
4702 if !visiting.insert(n.clone()) {
4703 return None;
4704 }
4705 let r = hoist_subst(lang, assign, src, visiting);
4706 visiting.remove(&n);
4707 r
4708 } else {
4709 Some(e)
4710 }
4711 }
4712 _ => {
4713 let ch = lang.children(e).to_vec();
4714 let mut new_ch = Vec::with_capacity(ch.len());
4715 for c in ch {
4716 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4717 }
4718 Some(lang.clone_node(e, new_ch))
4719 }
4720 }
4721}
4722
4723impl Rule<JavaAst> for CtorDelegationHoist {
4724 fn name(&self) -> &'static str {
4725 "ctor_delegation_hoist"
4726 }
4727 fn kinds(&self) -> &'static [NodeKind] {
4728 &[NodeKind::Block]
4729 }
4730 fn structural(&self) -> bool {
4731 true
4732 }
4733 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4734 if ctx.parent(id).is_some() {
4737 return None;
4738 }
4739 let lang = ctx.lang;
4740 if lang.kind(id) != NodeKind::Block {
4741 return None;
4742 }
4743 let stmts = lang.children(id).to_vec();
4744 let mut ctor_idx: Option<usize> = None;
4746 for (i, &st) in stmts.iter().enumerate() {
4747 if *lang.data(st) != NodeData::ExprStmt {
4748 continue;
4749 }
4750 let Some(&inner) = lang.children(st).first() else { continue };
4751 if lang.kind(inner) != NodeKind::Call {
4752 continue;
4753 }
4754 let Some(&callee) = lang.children(inner).first() else { continue };
4755 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4756 ctor_idx = Some(i);
4757 break;
4758 }
4759 }
4760 let ci = ctor_idx?;
4761 if ci == 0 {
4762 return None; }
4764 let mut last_assign: std::collections::HashMap<String, JavaId> =
4767 std::collections::HashMap::new();
4768 for &st in &stmts[..ci] {
4769 let (name, value) = match lang.data(st) {
4770 NodeData::VarDecl { name, .. } => {
4771 let init = lang.children(st).first().copied();
4772 match init {
4773 Some(e) => (name.clone(), e),
4774 None => continue, }
4776 }
4777 NodeData::ExprStmt => {
4778 let Some(&a) = lang.children(st).first() else {
4779 return None;
4780 };
4781 if lang.kind(a) != NodeKind::Assign
4784 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4785 {
4786 return None;
4787 }
4788 let ch = lang.children(a).to_vec();
4789 let (t, v) = (*ch.first()?, *ch.get(1)?);
4790 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4791 return None; }
4793 let vn = lang.var_name(t)?;
4794 (vn.to_string(), v)
4795 }
4796 _ => return None, };
4798 last_assign.insert(name, value);
4803 }
4804 let call_stmt = stmts[ci];
4806 let call = *lang.children(call_stmt).first()?;
4807 let cch = lang.children(call).to_vec();
4808 let callee = *cch.first()?;
4809 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4810 for &a in &cch[1..] {
4811 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4812 }
4813 let callee_copy = lang.copy_subtree(callee);
4815 let new_call = lang.call(callee_copy, new_args);
4816 let new_stmt = lang.expr_stmt(new_call);
4817 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4818 insert.push(new_stmt);
4819 insert.extend(stmts[..ci].iter().copied());
4820 Some(Edit::Splice {
4821 node: id,
4822 index: 0,
4823 remove: ci + 1,
4824 insert,
4825 })
4826 }
4827}
4828
4829pub struct TrailingElseFlatten;
4847
4848impl Rule<JavaAst> for TrailingElseFlatten {
4849 fn name(&self) -> &'static str {
4850 "trailing_else_flatten"
4851 }
4852 fn kinds(&self) -> &'static [NodeKind] {
4853 &[NodeKind::If]
4854 }
4855 fn structural(&self) -> bool {
4856 true
4857 }
4858 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4859 let (parent, idx) = (ctx.parent(id), ctx.index(id));
4860 let lang = ctx.lang;
4861 if lang.kind(id) != NodeKind::If {
4862 return None;
4863 }
4864 let ch = lang.children(id).to_vec();
4865 if ch.len() != 3 {
4866 return None;
4867 }
4868 let (_cond, then, els) = (ch[0], ch[1], ch[2]);
4869 if lang.kind(then) != NodeKind::Block {
4871 return None;
4872 }
4873 let then_stmts = lang.children(then).to_vec();
4874 let then_terminal = then_stmts
4875 .last()
4876 .is_some_and(|&s| matches!(lang.kind(s), NodeKind::Return | NodeKind::Throw));
4877 if !then_terminal {
4878 return None;
4879 }
4880 if lang.kind(els) == NodeKind::Block {
4885 let else_stmts = lang.children(els).to_vec();
4886 if else_stmts.len() == 1 && lang.kind(else_stmts[0]) == NodeKind::Return {
4887 if lang.children(else_stmts[0]).is_empty() {
4889 return Some(Edit::Splice {
4891 node: id,
4892 index: 0,
4893 remove: 3,
4894 insert: vec![ch[0], then],
4895 });
4896 }
4897 }
4898 }
4899 let parent = parent?;
4901 if lang.kind(parent) != NodeKind::Block {
4902 return None;
4903 }
4904 let idx = idx?;
4905 let new_if = lang.clone_node(id, vec![ch[0], then]);
4907 let mut insert: Vec<JavaId> = vec![new_if];
4908 if lang.kind(els) == NodeKind::Block {
4910 insert.extend(lang.children(els).to_vec());
4911 } else {
4912 insert.push(els);
4913 }
4914 Some(Edit::Splice {
4915 node: parent,
4916 index: idx,
4917 remove: 1,
4918 insert,
4919 })
4920 }
4921}
4922
4923pub struct TrailingIfContinue;
4924
4925impl Rule<JavaAst> for TrailingIfContinue {
4926 fn name(&self) -> &'static str {
4927 "trailing_if_continue"
4928 }
4929 fn kinds(&self) -> &'static [NodeKind] {
4930 &[NodeKind::If]
4931 }
4932 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4933 let parent = ctx.parent(id);
4934 let grandparent = parent.and_then(|p| ctx.parent(p));
4935 let lang = ctx.lang;
4936 if lang.kind(id) != NodeKind::If {
4937 return None;
4938 }
4939 let ch = lang.children(id).to_vec();
4940 if ch.len() != 2 {
4942 return None;
4943 }
4944 let then = ch[1];
4945 if lang.kind(then) != NodeKind::Block {
4946 return None;
4947 }
4948 let tbody = lang.children(then).to_vec();
4949 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4950 return None;
4951 }
4952 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4954 return None;
4955 }
4956 let p = parent?;
4959 if lang.kind(p) != NodeKind::Block {
4960 return None;
4961 }
4962 let pch = lang.children(p).to_vec();
4963 if pch.last() != Some(&id) {
4964 return None;
4965 }
4966 let gp = grandparent?;
4968 match lang.kind(gp) {
4969 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4970 let gch = lang.children(gp).to_vec();
4971 if gch.last() == Some(&p) {
4972 Some(Edit::Delete { node: id })
4973 } else {
4974 None
4975 }
4976 }
4977 _ => None,
4978 }
4979 }
4980}
4981
4982pub struct TrailingAssignReturn;
4983
4984impl Rule<JavaAst> for TrailingAssignReturn {
4985 fn name(&self) -> &'static str {
4986 "trailing_assign_return"
4987 }
4988 fn kinds(&self) -> &'static [NodeKind] {
4989 &[NodeKind::Assign]
4990 }
4991 fn structural(&self) -> bool {
4994 true
4995 }
4996 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4997 let parent = ctx.parent(id);
4998 let grandparent = parent.and_then(|p| ctx.parent(p));
4999 let lang = ctx.lang;
5000 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
5002 return None;
5003 }
5004 let ch = lang.children(id).to_vec();
5005 if ch.len() != 2 {
5006 return None;
5007 }
5008 let (target, value) = (ch[0], ch[1]);
5009 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
5011 return None;
5012 }
5013 let name = lang.var_name(target)?.to_string();
5014 let parent = parent?;
5016 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
5017 parent
5018 } else if lang.kind(parent) == NodeKind::Block {
5019 id
5020 } else {
5021 return None;
5022 };
5023 let block = if lang.kind(parent) == NodeKind::ExprStmt {
5024 grandparent?
5025 } else {
5026 parent
5027 };
5028 if lang.kind(block) != NodeKind::Block {
5029 return None;
5030 }
5031 let stmts = lang.children(block).to_vec();
5032 let idx = stmts.iter().position(|&x| x == stmt)?;
5033 let next = *stmts.get(idx + 1)?;
5035 let (flow_val, is_return) = match lang.kind(next) {
5036 NodeKind::Return => (lang.children(next).first().copied(), true),
5037 NodeKind::Throw => (lang.children(next).first().copied(), false),
5038 _ => return None,
5039 };
5040 let flow_val = flow_val?;
5041 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
5042 return None;
5043 }
5044 for &s in &stmts[idx + 2..] {
5047 if cure_engine::analysis::subtree_contains(lang, s, |n| {
5048 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
5049 }) {
5050 return None;
5051 }
5052 }
5053 let new_flow = if is_return {
5055 lang.ret(Some(value))
5056 } else {
5057 lang.throw(value)
5058 };
5059 Some(Edit::Multi(vec![
5060 Edit::Replace { target: next, with: new_flow },
5061 Edit::Delete { node: stmt },
5062 ]))
5063 }
5064}
5065
5066pub struct ConstFieldPropagate;
5067
5068impl Rule<JavaAst> for ConstFieldPropagate {
5069 fn name(&self) -> &'static str {
5070 "const_field_propagate"
5071 }
5072 fn kinds(&self) -> &'static [NodeKind] {
5073 &[NodeKind::VarRef]
5074 }
5075 fn structural(&self) -> bool {
5078 true
5079 }
5080 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5081 let parent = ctx.parent(id);
5082 let _grandparent = parent.and_then(|p| ctx.parent(p));
5083 let lang = ctx.lang;
5084 if lang.kind(id) != NodeKind::VarRef {
5085 return None;
5086 }
5087 let name = lang.var_name(id)?.to_string();
5088 if !lang.const_scalars.contains(&name) {
5089 return None;
5090 }
5091 if let Some(p) = parent {
5092 let is_first = lang.children(p).first() == Some(&id);
5093 match lang.kind(p) {
5094 NodeKind::Assign if is_first => return None,
5096 NodeKind::Unary if is_first => {
5097 if let NodeData::Unary { op } = lang.data(p) {
5098 if op.is_incdec() {
5099 return None;
5100 }
5101 }
5102 }
5103 NodeKind::Call if is_first => return None,
5105 _ => {}
5106 }
5107 }
5108 let init = *lang.const_fields.get(&name)?;
5109 let lit = lang.copy_subtree(init);
5110 Some(Edit::Replace { target: id, with: lit })
5111 }
5112}
5113
5114pub struct NoopPrivateCall;
5115
5116impl Rule<JavaAst> for NoopPrivateCall {
5117 fn name(&self) -> &'static str {
5118 "noop_private_call"
5119 }
5120 fn kinds(&self) -> &'static [NodeKind] {
5121 &[NodeKind::ExprStmt]
5122 }
5123 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5124 let lang = ctx.lang;
5125 if lang.kind(id) != NodeKind::ExprStmt {
5126 return None;
5127 }
5128 let ch = lang.children(id).to_vec();
5129 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
5130 return None;
5131 }
5132 let call = ch[0];
5133 let cch = lang.children(call).to_vec();
5134 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
5135 return None; }
5137 let name = lang.var_name(cch[0])?.to_string();
5138 let arity = cch.len() - 1;
5139 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
5140 if cands.is_empty() {
5141 return None;
5142 }
5143 let arg_types: Vec<Option<JType>> =
5147 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
5148 let mut applicable_noop = false;
5149 let mut applicable_count = 0usize;
5150 for c in cands {
5151 let ok = c
5152 .params
5153 .iter()
5154 .zip(arg_types.iter())
5155 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
5156 if ok {
5157 applicable_count += 1;
5158 applicable_noop = c.noop;
5159 }
5160 }
5161 if applicable_count != 1 || !applicable_noop {
5162 return None;
5163 }
5164 for &arg in &cch[1..] {
5166 if lang.effect(arg) > Effect::MayRead {
5167 return None;
5168 }
5169 }
5170 Some(Edit::Delete { node: id })
5171 }
5172}
5173
5174fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
5178 match lang.kind(arg) {
5179 NodeKind::Literal => match lang.literal(arg)? {
5180 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
5181 LitRef::Char(_) => Some(JType::Char),
5182 LitRef::Bool(_) => Some(JType::Bool),
5183 LitRef::Int(_) => Some(JType::Int),
5184 LitRef::Long(_) => Some(JType::Long),
5185 LitRef::Float(_) => Some(JType::Float),
5186 LitRef::Double(_) => Some(JType::Double),
5187 LitRef::Null => None,
5188 },
5189 NodeKind::VarRef => {
5190 let nm = lang.var_name(arg)?;
5191 lang.var_type(arg)
5192 .cloned()
5193 .or_else(|| lang.field_types.get(nm).cloned())
5194 }
5195 _ => None,
5196 }
5197}
5198
5199fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5204 let Some(a) = at else { return true };
5205 match pt {
5206 JType::Ref(p) => {
5207 let pbase = p.split('<').next().unwrap_or(p).trim();
5208 pbase == "Object"
5209 || pbase == "java.lang.Object"
5210 || match a {
5211 JType::Ref(arend) => {
5212 let abase = arend.split('<').next().unwrap_or(arend).trim();
5213 abase == pbase
5214 }
5215 _ => false,
5216 }
5217 }
5218 JType::Array(_) => false, prim => match a {
5220 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5221 },
5222 }
5223}
5224
5225pub struct ConstMethodInline;
5235
5236impl Rule<JavaAst> for ConstMethodInline {
5237 fn name(&self) -> &'static str {
5238 "const_method_inline"
5239 }
5240 fn kinds(&self) -> &'static [NodeKind] {
5241 &[NodeKind::Call]
5242 }
5243 fn structural(&self) -> bool {
5244 true
5245 }
5246 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5247 let at_stmt_pos = ctx
5249 .parent(id)
5250 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5251 .unwrap_or(false);
5252 let lang = ctx.lang;
5253 if lang.kind(id) != NodeKind::Call {
5254 return None;
5255 }
5256 let ch = lang.children(id).to_vec();
5257 let (method_name, recv_class) = match lang.data(ch[0]) {
5261 NodeData::VarRef { name } => (*name, None),
5262 NodeData::Member { name } => {
5263 let recv = lang.children(ch[0])[0];
5264 match lang.var_name(recv) {
5265 Some(r) if r == "this" => (*name, None),
5266 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5267 (*name, Some(r.to_string()))
5268 }
5269 _ => return None,
5270 }
5271 }
5272 _ => return None,
5273 };
5274 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5275 let (params, body_expr, owner) = entry;
5276 if let Some(recv) = recv_class {
5283 if recv != owner {
5284 return None;
5285 }
5286 }
5287 {
5289 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5290 for nm in &names {
5291 if subtree_calls_self(lang, body_expr, nm) {
5292 return None;
5293 }
5294 }
5295 }
5296 let args = ch[1..].to_vec();
5297 if args.len() != params.len() || params.is_empty() {
5298 return None;
5299 }
5300 for &a in &args {
5301 lang.literal(a)?;
5302 }
5303 let map: std::collections::HashMap<String, JavaId> = params
5304 .iter()
5305 .zip(args.iter())
5306 .map(|(p, &a)| (p.clone(), a))
5307 .collect();
5308 let with = copy_subst(lang, body_expr, &map);
5309 if at_stmt_pos {
5315 match lang.kind(with) {
5316 NodeKind::Call | NodeKind::New => {}
5317 _ => return None,
5318 }
5319 }
5320 Some(Edit::Replace {
5321 target: id,
5322 with,
5323 })
5324 }
5325}
5326
5327fn copy_subst(
5329 lang: &mut JavaAst,
5330 node: JavaId,
5331 map: &std::collections::HashMap<String, JavaId>,
5332) -> JavaId {
5333 if let NodeData::VarRef { name } = lang.data(node) {
5334 if let Some(&repl) = map.get(lang.sn(*name)) {
5335 return lang.copy_subtree(repl);
5336 }
5337 }
5338 let children: Vec<JavaId> = lang.children(node).to_vec();
5339 let new_children: Vec<JavaId> = children
5340 .iter()
5341 .map(|&c| copy_subst(lang, c, map))
5342 .collect();
5343 lang.clone_node(node, new_children)
5344}
5345
5346
5347pub struct TwrRecover;
5380
5381fn twr_is_throwable(ty_raw: &str) -> bool {
5382 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5383}
5384
5385fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5387 if lang.kind(n) == NodeKind::ExprStmt {
5388 if let Some(&e) = lang.children(n).first() {
5389 return e;
5390 }
5391 }
5392 n
5393}
5394
5395fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5397 if lang.kind(n) == NodeKind::Block {
5398 lang.children(n).to_vec()
5399 } else {
5400 vec![n]
5401 }
5402}
5403
5404fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5406 if lang.kind(e) != NodeKind::Call {
5407 return false;
5408 }
5409 let ch = lang.children(e);
5410 if ch.len() != 1 {
5411 return false;
5412 }
5413 match lang.data(ch[0]) {
5414 NodeData::Member { name } if lang.sn(*name) == "close" => {
5415 let Some(&recv) = lang.children(ch[0]).first() else {
5416 return false;
5417 };
5418 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5419 }
5420 _ => false,
5421 }
5422}
5423
5424fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5425 if lang.kind(n) != NodeKind::ExprStmt {
5426 return false;
5427 }
5428 lang.children(n)
5429 .first()
5430 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5431}
5432
5433fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5435 if lang.kind(n) != NodeKind::ExprStmt {
5436 return false;
5437 }
5438 let Some(&e) = lang.children(n).first() else {
5439 return false;
5440 };
5441 if lang.kind(e) != NodeKind::Call {
5442 return false;
5443 }
5444 let ch = lang.children(e);
5445 if ch.len() != 2 {
5446 return false;
5447 }
5448 match lang.data(ch[0]) {
5449 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5450 let Some(&recv) = lang.children(ch[0]).first() else {
5451 return false;
5452 };
5453 lang.kind(recv) == NodeKind::VarRef
5454 && lang.var_name(recv) == Some(primary)
5455 && lang.kind(ch[1]) == NodeKind::VarRef
5456 && lang.var_name(ch[1]) == Some(sup)
5457 }
5458 _ => false,
5459 }
5460}
5461
5462fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5463 let mut stack = vec![root];
5464 while let Some(n) = stack.pop() {
5465 if n == target {
5466 return true;
5467 }
5468 stack.extend(lang.children(n).iter().copied());
5469 }
5470 false
5471}
5472
5473fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5475 let mut stack = vec![root];
5476 while let Some(n) = stack.pop() {
5477 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5478 let ok = allowed
5479 .iter()
5480 .any(|&a| a == n || node_contains(lang, a, n));
5481 if !ok {
5482 return true;
5483 }
5484 }
5485 if lang.kind(n) == NodeKind::VarDecl {
5486 if let Some(nm) = lang.var_name(n) {
5487 if nm == name && !allowed.contains(&n) {
5488 return true;
5490 }
5491 }
5492 }
5493 stack.extend(lang.children(n).iter().copied());
5494 }
5495 false
5496}
5497
5498fn twr_close_with_suppressed(
5502 lang: &JavaAst,
5503 try_node: JavaId,
5504 primary: &str,
5505) -> Option<(String, String)> {
5506 let ch = lang.children(try_node).to_vec();
5507 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5508 return None;
5509 }
5510 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5511 return None;
5512 };
5513 if !twr_is_throwable(ty_raw) {
5514 return None;
5515 }
5516 let sup = sup.clone();
5517 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5518 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5519 return None;
5520 }
5521 let close_stmts = twr_stmts(lang, ch[0]);
5522 if close_stmts.len() != 1 {
5523 return None;
5524 }
5525 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5526 return None;
5527 }
5528 let e = *lang.children(close_stmts[0]).first()?;
5529 let cc = lang.children(e);
5530 if cc.len() != 1 {
5531 return None;
5532 }
5533 let NodeData::Member { name } = lang.data(cc[0]) else {
5534 return None;
5535 };
5536 if lang.sn(*name) != "close" {
5537 return None;
5538 }
5539 let recv = *lang.children(cc[0]).first()?;
5540 if lang.kind(recv) != NodeKind::VarRef {
5541 return None;
5542 }
5543 Some((lang.var_name(recv)?.to_string(), sup))
5544}
5545
5546impl Rule<JavaAst> for TwrRecover {
5547 fn name(&self) -> &'static str {
5548 "twr_recover"
5549 }
5550 fn kinds(&self) -> &'static [NodeKind] {
5551 &[NodeKind::Try]
5552 }
5553 fn structural(&self) -> bool {
5554 true
5555 }
5556 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5557 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5559 let lang = ctx.lang;
5560 if lang.kind(id) != NodeKind::Try {
5561 return None;
5562 }
5563 let ch = lang.children(id).to_vec();
5564 let try_idx = ch
5566 .iter()
5567 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5568 if try_idx != 0 {
5569 return None;
5570 }
5571 let try_block = ch[0];
5572 if lang.kind(parent) != NodeKind::Block {
5573 return None;
5574 }
5575 let pch = lang.children(parent).to_vec();
5576
5577 if ch.len() == 2 {
5579 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5580 if twr_is_throwable(ty_raw) {
5581 let t = t.clone();
5582 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5583 if catch_stmts.len() == 2 {
5584 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5586 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5587 && lang
5588 .children(throw_stmt)
5589 .first()
5590 .is_some_and(|&x| {
5591 lang.kind(x) == NodeKind::VarRef
5592 && lang.var_name(x) == Some(t.as_str())
5593 });
5594 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5595 if let Some((r, sup)) =
5596 twr_close_with_suppressed(lang, inner_try, t.as_str())
5597 {
5598 if idx >= 1 && idx + 1 < pch.len() {
5599 let decl_r = pch[idx - 1];
5600 let trailing = pch[idx + 1];
5601 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5602 if name == &r {
5603 let (name, ty) = (name.clone(), ty.clone());
5604 let init =
5605 lang.children(decl_r).first().copied();
5606 if let Some(init) = init {
5607 let provably_new = lang.kind(init)
5609 == NodeKind::New;
5610 let close_ok =
5611 twr_is_close_stmt(lang, trailing, &r);
5612 if provably_new && close_ok {
5613 let root = walk_root;
5615 let r_ok = !refs_escape(
5616 lang,
5617 root,
5618 &r,
5619 &[
5620 decl_r,
5621 init,
5622 *lang
5623 .children(inner_try)
5624 .first()
5625 .unwrap_or(&inner_try),
5626 trailing,
5627 try_block,
5628 ],
5629 );
5630 let t_ok = !refs_escape(
5631 lang, root, &t,
5632 &[throw_stmt, ch[1]],
5633 );
5634 let sup_ok = !refs_escape(
5635 lang, root, &sup,
5636 &[ch[1]],
5637 );
5638 if r_ok && t_ok && sup_ok {
5639 let init_copy =
5640 lang.copy_subtree(init);
5641 let res = lang.var_decl(
5642 &name, ty,
5643 Some(init_copy),
5644 );
5645 let new_try = lang.try_(
5646 vec![res],
5647 try_block,
5648 Vec::new(),
5649 None,
5650 );
5651 return Some(Edit::Splice {
5652 node: parent,
5653 index: idx - 1,
5654 remove: 3,
5655 insert: vec![new_try],
5656 });
5657 }
5658 }
5659 }
5660 }
5661 }
5662 }
5663 }
5664 }
5665 }
5666 }
5667 }
5668 }
5669
5670 if ch.len() == 3 {
5672 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5673 return None;
5674 };
5675 if !twr_is_throwable(ty_raw) {
5676 return None;
5677 }
5678 let t = t.clone();
5679 if lang.kind(ch[2]) != NodeKind::Block {
5681 return None;
5682 }
5683 let fin_stmts = twr_stmts(lang, ch[2]);
5684 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5685 return None;
5686 }
5687 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5689 if catch_stmts.len() != 2 {
5690 return None;
5691 }
5692 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5693 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5694 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5695 return None;
5696 };
5697 let as_ch = lang.children(assign_expr);
5698 if as_ch.len() != 2 {
5699 return None;
5700 }
5701 let primary = match lang.data(as_ch[0]) {
5702 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5703 _ => return None,
5704 };
5705 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5706 && lang.var_name(as_ch[1]) == Some(t.as_str())
5707 && lang.kind(as_ch[0]) == NodeKind::VarRef
5708 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5709 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5710 && lang.children(throw_stmt)
5711 .first()
5712 .is_some_and(|&x| {
5713 lang.kind(x) == NodeKind::VarRef
5714 && lang.var_name(x) == Some(t.as_str())
5715 });
5716 if !assign_ok || !throw_ok {
5717 return None;
5718 }
5719 let if1 = fin_stmts[0];
5721 let i1 = lang.children(if1).to_vec();
5722 if i1.len() < 2 {
5723 return None;
5724 }
5725 let cond1 = i1[0];
5726 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5727 return None;
5728 };
5729 let c1 = lang.children(cond1);
5730 let r = match lang.data(c1[0]) {
5731 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5732 lang.sn(*name).to_string()
5733 }
5734 _ => {
5735 return None;
5736 }
5737 };
5738 let then1 = i1[1];
5739 let inner_stmts = twr_stmts(lang, then1);
5740 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5741 return None;
5742 }
5743 let if2 = inner_stmts[0];
5744 let i2 = lang.children(if2).to_vec();
5745 if i2.len() != 3 {
5746 return None;
5747 }
5748 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5749 return None;
5750 };
5751 let c2 = lang.children(i2[0]);
5752 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5753 && lang.var_name(c2[0]) == Some(primary.as_str())
5754 && lang.literal(c2[1]) == Some(LitRef::Null);
5755 if !primary_ok {
5756 return None;
5757 }
5758 let (then2, els2) = (i2[1], i2[2]);
5759 let t2_stmts = twr_stmts(lang, then2);
5761 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5762 return None;
5763 }
5764 let Some((r2, sup)) =
5765 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5766 else {
5767 return None;
5768 };
5769 if r2 != r {
5770 return None;
5771 }
5772 let e2_stmts = twr_stmts(lang, els2);
5774 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5775 return None;
5776 }
5777 if idx < 2 {
5779 return None;
5780 }
5781 let decl_r = pch[idx - 2];
5782 let decl_primary = pch[idx - 1];
5783 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5784 (
5785 NodeData::VarDecl { name: rn, .. },
5786 NodeData::VarDecl { name: pn, .. },
5787 ) => (rn.clone(), pn.clone()),
5788 _ => return None,
5789 };
5790 if r_decl != r || primary_decl != primary {
5791 return None;
5792 }
5793 let pinit_null = lang
5795 .children(decl_primary)
5796 .first()
5797 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5798 if !pinit_null {
5799 return None;
5800 }
5801 let r_init = lang.children(decl_r).first().copied()?;
5802 let ty = match lang.data(decl_r) {
5803 NodeData::VarDecl { ty, .. } => ty.clone(),
5804 _ => return None,
5805 };
5806 let root = walk_root;
5808 let r_allowed = [decl_r, r_init, ch[2], try_block];
5810 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5811 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5812 if refs_escape(lang, root, &r, &r_allowed)
5813 || refs_escape(lang, root, &primary, &p_allowed)
5814 || refs_escape(lang, root, &t, &t_allowed)
5815 || refs_escape(lang, root, &sup, &[ch[2]])
5816 {
5817 return None;
5818 }
5819 let init_copy = lang.copy_subtree(r_init);
5820 let res = lang.var_decl(&r, ty, Some(init_copy));
5821 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5822 return Some(Edit::Splice {
5823 node: parent,
5824 index: idx - 2,
5825 remove: 3,
5826 insert: vec![new_try],
5827 });
5828 }
5829 None
5830 }
5831}
5832
5833pub struct StringSwitchRecover;
5858
5859fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5860 out.push_str(&format!("{:?} ", lang.data(n)));
5861 for &c in lang.children(n) {
5862 subtree_sig(lang, c, out);
5863 }
5864}
5865
5866fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5868 let ch = lang.children(sw).to_vec();
5869 let mut out = 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 let cc = lang.children(c);
5876 let label = if is_default {
5877 None
5878 } else {
5879 if *labels != 1 {
5880 return None; }
5882 let lab = cc[0];
5883 let lit = lang.literal(lab)?;
5884 let v = match lit {
5885 LitRef::Int(v) | LitRef::Long(v) => v,
5886 _ => return None, };
5888 Some(v)
5889 };
5890 out.push((label, cc[*labels as usize..].to_vec()));
5891 }
5892 Some(out)
5893}
5894
5895fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5896 let Some(&last) = stmts.last() else {
5897 return false; };
5899 let last = if lang.kind(last) == NodeKind::Block {
5900 match lang.children(last).last() {
5901 Some(&l) => l,
5902 None => return false,
5903 }
5904 } else {
5905 last
5906 };
5907 matches!(
5908 lang.kind(last),
5909 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5910 )
5911}
5912
5913impl Rule<JavaAst> for StringSwitchRecover {
5914 fn name(&self) -> &'static str {
5915 "string_switch_recover"
5916 }
5917 fn kinds(&self) -> &'static [NodeKind] {
5918 &[NodeKind::Switch]
5919 }
5920 fn structural(&self) -> bool {
5921 true
5922 }
5923 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5924 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5925 let lang = ctx.lang;
5926 if lang.kind(id) != NodeKind::Switch {
5927 return None;
5928 }
5929 let ch = lang.children(id).to_vec();
5930 if ch.len() < 2 {
5931 return None;
5932 }
5933 let x = match lang.data(ch[0]) {
5935 NodeData::Call => {
5936 let cc = lang.children(ch[0]);
5937 if cc.len() != 1 {
5938 return None;
5939 }
5940 match lang.data(cc[0]) {
5941 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5942 let recv = *lang.children(cc[0]).first()?;
5943 if lang.kind(recv) != NodeKind::VarRef {
5944 return None;
5945 }
5946 lang.var_name(recv)?.to_string()
5947 }
5948 _ => return None,
5949 }
5950 }
5951 _ => {
5952 return None;
5953 }
5954 };
5955 struct CaseInfo {
5957 lit: String,
5958 k: i64,
5959 inner_switch: JavaId,
5960 }
5961 let mut infos: Vec<CaseInfo> = Vec::new();
5962 let mut v_name: Option<String> = None;
5963 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5965 for &c in &ch[1..] {
5966 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5967 return None;
5968 };
5969 let is_default = *is_default;
5970 if is_default {
5971 return None; }
5973 if *labels != 1 {
5974 return None;
5975 }
5976 let cc = lang.children(c);
5977 let h = match lang.literal(cc[0])? {
5978 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5979 _ => return None,
5980 };
5981 let body = cc[1..].to_vec();
5982 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5984 lang.children(body[0]).to_vec()
5985 } else {
5986 body
5987 };
5988 let mut rest = body;
5989 let mut _e_decl: Option<(String, JavaId)> = None;
5991 if let Some(&first) = rest.first() {
5992 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5993 if let Some(&init) = lang.children(first).first() {
5994 if is_x_equals(lang, init, &x) {
5995 _e_decl = Some((name.clone(), first));
5996 rest = rest[1..].to_vec();
5997 }
5998 }
5999 }
6000 }
6001 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
6002 return None;
6003 }
6004 let ifn = rest[0];
6005 let ic = lang.children(ifn).to_vec();
6006 if ic.len() != 3 {
6007 return None;
6008 }
6009 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
6011 return None;
6012 };
6013 let guard = *lang.children(ic[0]).first()?;
6014 let lit = match lang.data(guard) {
6015 NodeData::VarRef { name } => {
6016 let Some((en, decl)) = &_e_decl else {
6018 return None;
6019 };
6020 if lang.sn(*name) != en {
6021 return None;
6022 }
6023 let init = *lang.children(*decl).first()?;
6024 equals_lit(lang, init, &x)?
6025 }
6026 NodeData::Call => equals_lit(lang, guard, &x)?,
6027 _ => return None,
6028 };
6029 guard_nodes.push(guard);
6030 if let Some((_, decl)) = &_e_decl {
6031 guard_nodes.push(*decl);
6032 }
6033 let then_ok = twr_stmts(lang, ic[1]).len() == 1
6035 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
6036 NodeData::Break { label: None });
6037 if !then_ok {
6038 return None;
6039 }
6040 let els = twr_stmts(lang, ic[2]);
6042 let (k, inner_switch, assign_node) = match els.len() {
6043 2 => {
6044 let assign_expr = unwrap_expr_stmt(lang, els[0]);
6045 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
6046 return None;
6047 };
6048 let a_ch = lang.children(assign_expr);
6049 let vn = match lang.data(a_ch[0]) {
6050 NodeData::VarRef { name } => lang.sn(*name).to_string(),
6051 _ => return None,
6052 };
6053 let k = match lang.literal(a_ch[1])? {
6054 LitRef::Int(v) | LitRef::Long(v) => v,
6055 _ => return None,
6056 };
6057 if lang.kind(els[1]) != NodeKind::Switch {
6058 return None;
6059 }
6060 let sel = *lang.children(els[1]).first()?;
6062 if lang.kind(sel) != NodeKind::VarRef
6063 || lang.var_name(sel) != Some(vn.as_str())
6064 {
6065 return None;
6066 }
6067 match &v_name {
6068 Some(v) if *v != vn => return None,
6069 _ => v_name = Some(vn),
6070 }
6071 (k, els[1], Some(els[0]))
6072 }
6073 1 => {
6074 if lang.kind(els[0]) != NodeKind::Switch {
6075 return None;
6076 }
6077 let sel = *lang.children(els[0]).first()?;
6079 let k = match lang.literal(sel)? {
6080 LitRef::Int(v) | LitRef::Long(v) => v,
6081 _ => return None,
6082 };
6083 (k, els[0], None)
6084 }
6085 _ => return None,
6086 };
6087 if let Some(a) = assign_node {
6088 v_nodes.push(a);
6089 }
6090 v_nodes.push(*lang.children(inner_switch).first()?);
6091 let hlit: i32 = lit
6093 .chars()
6094 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
6095 if hlit != h {
6096 return None;
6097 }
6098 let _ = assign_node;
6099 infos.push(CaseInfo {
6100 lit,
6101 k,
6102 inner_switch,
6103 });
6104 }
6105 if infos.is_empty() {
6106 return None;
6107 }
6108 if lang.kind(parent) != NodeKind::Block {
6110 return None;
6111 }
6112 let pch = lang.children(parent).to_vec();
6113 let mut k = idx + 1;
6114 if k < pch.len() {
6116 let texpr = unwrap_expr_stmt(lang, pch[k]);
6117 if let NodeData::Assign { op: None } = lang.data(texpr) {
6118 let a_ch = lang.children(texpr);
6119 if a_ch.len() == 2
6120 && lang.kind(a_ch[0]) == NodeKind::VarRef
6121 && lang.var_name(a_ch[0]) == v_name.as_deref()
6122 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
6123 {
6124 v_nodes.push(pch[k]);
6125 k += 1;
6126 }
6127 }
6128 }
6129 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
6130 return None;
6131 }
6132 let trailing = pch[k];
6133 let tsel = *lang.children(trailing).first()?;
6135 match lang.data(tsel) {
6136 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
6137 v_nodes.push(tsel);
6138 }
6139 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
6140 _ => {
6141 lang.literal(tsel)?;
6142 }
6143 }
6144 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
6147 for &c in &lang.children(sw)[1..] {
6148 subtree_sig(lang, c, out);
6149 }
6150 }
6151 let mut sig0 = String::new();
6152 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
6153 for info in &infos[1..] {
6154 let mut sig = String::new();
6155 switch_cases_sig(lang, info.inner_switch, &mut sig);
6156 if sig != sig0 {
6157 return None;
6158 }
6159 }
6160 {
6161 let mut sig = String::new();
6162 switch_cases_sig(lang, trailing, &mut sig);
6163 if sig != sig0 {
6164 return None;
6165 }
6166 }
6167 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
6169 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
6170 std::collections::HashMap::new();
6171 let mut default_body: Option<Vec<JavaId>> = None;
6172 for (lab, stmts) in &bodies {
6173 match lab {
6174 Some(v) => {
6175 body_map.insert(*v, stmts.clone());
6176 }
6177 None => default_body = Some(stmts.clone()),
6178 }
6179 }
6180 let Some(default_body) = default_body else {
6181 return None;
6182 };
6183 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
6185 if !ssw_body_terminal(lang, stmts) {
6186 return None;
6187 }
6188 }
6189 let root = ctx_root;
6191 if let Some(vn) = &v_name {
6192 let mut allowed: Vec<JavaId> = v_nodes.clone();
6193 allowed.push(trailing);
6194 if idx >= 1 {
6195 let cand = pch[idx - 1];
6196 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
6197 if name == vn {
6198 allowed.push(cand);
6199 if idx != 1 {
6201 allowed.pop();
6202 }
6203 }
6204 }
6205 }
6206 if refs_escape(lang, root, vn, &allowed) {
6207 return None;
6208 }
6209 }
6210 let mut new_cases: Vec<JavaId> = Vec::new();
6212 for info in &infos {
6213 let Some(body) = body_map.get(&info.k) else {
6214 return None;
6215 };
6216 let lab = lang.lit(Lit::Str(info.lit.clone()));
6217 let case = lang.case_(vec![lab], false, false, body.clone());
6218 new_cases.push(case);
6219 }
6220 let def_case = lang.case_(Vec::new(), true, false, default_body);
6221 new_cases.push(def_case);
6222 let sel = lang.var(&x);
6223 let new_switch = lang.switch_(sel, new_cases);
6224 let remove = k - idx + 1;
6226 Some(Edit::Splice {
6227 node: parent,
6228 index: idx,
6229 remove,
6230 insert: vec![new_switch],
6231 })
6232 }
6233}
6234
6235fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6237 if lang.kind(e) != NodeKind::Call {
6238 return None;
6239 }
6240 let cc = lang.children(e);
6241 if cc.len() != 2 {
6242 return None;
6243 }
6244 match lang.data(cc[0]) {
6245 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6246 let recv = *lang.children(cc[0]).first()?;
6247 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6248 return None;
6249 }
6250 match lang.literal(cc[1])? {
6251 LitRef::Str(s) => Some(s.to_string()),
6252 _ => None,
6253 }
6254 }
6255 _ => None,
6256 }
6257}
6258
6259fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6260 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6261}
6262
6263pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6270 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6271 rules.push(Box::new(CastSimplify));
6272 rules.push(Box::new(SelfCompare));
6273 rules.push(Box::new(StringBuilderFold));
6274 rules.push(Box::new(BoxUnboxChain));
6275 rules.push(Box::new(IteratorToForEach));
6276 rules.push(Box::new(NewStringFold));
6277 rules.push(Box::new(NewStringCharArrayFold));
6278 rules.push(Box::new(EmptyFinallyStrip));
6279 rules.push(Box::new(TryUnwrapNoCatch));
6280 rules.push(Box::new(TryUnwrapRethrow));
6281 rules.push(Box::new(StaticExec));
6282 rules.push(Box::new(LoopHeadBreak));
6283 rules.push(Box::new(WhileIteratorToForEach));
6284 rules.push(Box::new(ConcatValueOfDrop));
6285 rules.push(Box::new(StringBuilderStatements));
6286 rules.push(Box::new(TrailingContinueJava));
6287 rules.push(Box::new(XorNoise));
6288 rules.push(Box::new(StrLenFold));
6289 rules.push(Box::new(LiteralEval));
6290 rules.push(Box::new(Base64NewStringFold));
6291 rules.push(Box::new(UrlDecodeFold));
6292 rules.push(Box::new(CffRecover));
6293 rules.push(Box::new(StaticArrayIndexFold));
6294 rules.push(Box::new(TrailingAssignReturn));
6295 rules.push(Box::new(DeadPureCall));
6296 rules.push(Box::new(CtorDelegationHoist));
6297 rules.push(Box::new(TrailingElseFlatten));
6298 rules.push(Box::new(TrailingIfContinue));
6299 rules.push(Box::new(ConstFieldPropagate));
6300 rules.push(Box::new(NoopPrivateCall));
6301 rules.push(Box::new(ConstMethodInline));
6302 rules.push(Box::new(TwrRecover));
6303 rules.push(Box::new(StringSwitchRecover));
6304 rules
6305}
6306
6307pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6309 let mut rules = default_java_rules();
6310 rules.push(Box::new(cure_engine::rules::UnreachableAfterTerminal));
6313 rules
6314}
6315
6316pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6318 let rules = default_java_rules();
6319 cure_engine::simplify(ast, root, &rules, cfg)
6320}
6321
6322pub fn simplify_unit_with_facts(
6327 ast: &mut JavaAst,
6328 unit: &mut CompilationUnit,
6329 cfg: &Config,
6330 facts: &cure_java_ast::JavaFacts,
6331) -> Report {
6332 ast.set_facts(facts.clone());
6333 let r = simplify_unit(ast, unit, cfg);
6334 ast.clear_facts();
6335 r
6336}
6337
6338pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6339 let mut total = Report::default();
6340 for _cycle in 0..6 {
6345 let mut round = Report::default();
6346 collect_unit_consts(ast, unit);
6347 if cfg.remove_dead_methods {
6350 collect_noop_private_methods(ast, unit);
6351 } else {
6352 ast.noop_private_methods.clear();
6353 }
6354 for ty in &mut unit.types {
6355 simplify_type(ast, ty, cfg, &mut round);
6356 }
6357 if cfg.remove_dead_methods {
6358 round.edits += remove_dead_private_fields(ast, unit);
6359 round.edits += remove_dead_private_methods(ast, unit);
6360 round.edits += remove_unused_imports(ast, unit);
6361 }
6362 total.edits += round.edits;
6363 total.iterations += round.iterations;
6364 total.discarded += round.discarded;
6365 for (k, v) in round.by_rule {
6366 *total.by_rule.entry(k).or_insert(0) += v;
6367 }
6368 if round.edits == 0 {
6373 break;
6374 }
6375 }
6376 total
6377}
6378
6379fn collect_anon_and_string_names(
6386 ast: &JavaAst,
6387 unit: &CompilationUnit,
6388) -> std::collections::HashSet<String> {
6389 let mut out: std::collections::HashSet<String> = Default::default();
6390 let mut bodies: Vec<JavaId> = Vec::new();
6391 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6392 for root in bodies {
6393 let mut stack = vec![root];
6394 while let Some(n) = stack.pop() {
6395 match ast.data(n) {
6396 NodeData::New { anon_raw: Some(raw), .. } => {
6397 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6398 if !id.is_empty() {
6399 out.insert(id.to_string());
6400 }
6401 }
6402 }
6403 NodeData::Literal(Lit::Str(text)) => {
6404 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6405 if !id.is_empty() {
6406 out.insert(id.to_string());
6407 }
6408 }
6409 }
6410 _ => {}
6411 }
6412 for &c in ast.children(n) {
6413 stack.push(c);
6414 }
6415 }
6416 }
6417 out
6418}
6419
6420fn collect_all_blind_refs(
6449 ast: &JavaAst,
6450 unit: &CompilationUnit,
6451) -> std::collections::HashSet<String> {
6452 let mut out: std::collections::HashSet<String> = Default::default();
6453 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6454 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6455 if !id.is_empty() {
6456 out.insert(id.to_string());
6457 }
6458 }
6459 };
6460 let mut bodies: Vec<JavaId> = Vec::new();
6462 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6463 for root in bodies {
6464 let mut stack = vec![root];
6465 while let Some(n) = stack.pop() {
6466 match ast.data(n) {
6467 NodeData::Raw { text } => split_into(text, &mut out),
6468 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6469 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6470 NodeData::ForEach { ty, .. } => {
6472 collect_ty_idents(ty, &mut out);
6473 }
6474 NodeData::NewArray { ty, .. } => {
6476 collect_ty_idents(ty, &mut out);
6477 }
6478 NodeData::Lambda { params_raw } => {
6482 split_into(params_raw, &mut out);
6483 }
6484 _ => {}
6485 }
6486 for &c in ast.children(n) {
6487 stack.push(c);
6488 }
6489 }
6490 }
6491 for r in &unit.raws {
6495 split_into(r, &mut out);
6496 }
6497 for_each_type(unit, &mut |ty: &TypeDecl| {
6500 collect_mods_idents_all(&ty.mods, &mut out);
6501 for e in &ty.enum_constants {
6502 split_into(e, &mut out);
6503 }
6504 split_into(&ty.ty_params, &mut out);
6505 split_into(&ty.header, &mut out);
6506 for m in &ty.members {
6507 match m {
6508 Member::Method { mods, name, ty_params, params, .. }
6509 | Member::Constructor { mods, name, ty_params, params, .. } => {
6510 collect_mods_idents_all(mods, &mut out);
6511 split_into(ty_params, &mut out);
6512 for p in params {
6513 collect_mods_idents_all(&p.mods, &mut out);
6514 collect_ty_idents(&p.ty, &mut out);
6515 }
6516 if has_bare_methodsource(mods) {
6521 out.insert(name.clone());
6522 }
6523 }
6524 Member::Field { mods, declarators, .. } => {
6525 collect_mods_idents_all(mods, &mut out);
6526 let _ = declarators;
6528 }
6529 Member::Type(t) => {
6530 let _ = t; }
6532 Member::Raw(text) => {
6539 split_into(text, &mut out);
6540 }
6541 _ => {}
6542 }
6543 }
6544 });
6545 out
6546}
6547
6548fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6552 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6553 if !id.is_empty() {
6554 out.insert(id.to_string());
6555 }
6556 }
6557}
6558
6559fn has_bare_methodsource(mods: &str) -> bool {
6566 let hay = mods.as_bytes();
6567 const NEEDLE: &[u8] = b"MethodSource";
6568 let mut i = 0usize;
6569 while let Some(p) = mods[i..].find("MethodSource") {
6570 let at = i + p;
6571 let end = at + NEEDLE.len();
6572 let prev_ok = at == 0
6575 || !(hay[at - 1].is_ascii_alphanumeric()
6576 || hay[at - 1] == b'_'
6577 || hay[at - 1] == b'$');
6578 let next_ident =
6579 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6580 if prev_ok && !next_ident {
6581 let mut j = end;
6582 while j < hay.len() && hay[j].is_ascii_whitespace() {
6583 j += 1;
6584 }
6585 if j >= hay.len() || hay[j] != b'(' {
6586 return true;
6587 }
6588 }
6589 i = end;
6590 }
6591 false
6592}
6593
6594const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6598 "writeObject",
6599 "readObject",
6600 "readObjectNoData",
6601 "writeReplace",
6602 "readResolve",
6603 "validateObject",
6604];
6605
6606fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6607 use std::collections::HashSet;
6608 let mut removed = 0usize;
6609 let anon_names = collect_anon_and_string_names(ast, unit);
6611 let blind = collect_all_blind_refs(ast, unit);
6616 loop {
6617 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6624 let mut veto: HashSet<String> = HashSet::new();
6625 let mut roots: Vec<JavaId> = Vec::new();
6626 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6627 for body in roots {
6628 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6629 }
6630 for_each_type(unit, &mut |ty| {
6631 for m in &ty.members {
6632 if let Member::Method { name, params, .. } = m {
6633 if params.iter().any(|p| p.varargs) {
6634 veto.insert(name.clone());
6635 }
6636 }
6637 }
6638 });
6639 let mut changed = false;
6641 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6642 let before = ty.members.len();
6643 ty.members.retain(|m| {
6644 if let Member::Method { mods, name, params, .. } = m {
6645 let is_private = mods.split_whitespace().any(|w| w == "private");
6646 let annotated = mods.contains('@');
6647 let keep = !is_private
6648 || annotated
6649 || params.iter().any(|p| p.varargs)
6651 || veto.contains(name)
6652 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6656 || anon_names.contains(name)
6657 || blind.contains(name)
6658 || referenced.contains(&(name.clone(), params.len()));
6659 keep
6660 } else {
6661 true
6662 }
6663 });
6664 if ty.members.len() != before {
6665 removed += before - ty.members.len();
6666 changed = true;
6667 }
6668 });
6669 if !changed {
6670 return removed;
6671 }
6672 }
6673}
6674
6675fn strip_ta_prefix(name: &str) -> &str {
6680 if !name.starts_with('<') {
6681 return name;
6682 }
6683 let b = name.as_bytes();
6684 let mut depth = 0i32;
6685 for (i, &c) in b.iter().enumerate() {
6686 match c {
6687 b'<' => depth += 1,
6688 b'>' => {
6689 depth -= 1;
6690 if depth == 0 {
6691 return &name[i + 1..];
6692 }
6693 }
6694 _ => {}
6695 }
6696 }
6697 name }
6699
6700fn collect_call_arity_refs(
6702 ast: &JavaAst,
6703 root: JavaId,
6704 out: &mut std::collections::HashSet<(String, usize)>,
6705 veto: &mut std::collections::HashSet<String>,
6706) {
6707 let mut stack = vec![root];
6708 while let Some(id) = stack.pop() {
6709 match ast.data(id) {
6710 NodeData::Call => {
6711 let ch = ast.children(id).to_vec();
6712 if let Some(&callee) = ch.first() {
6713 let name = match ast.data(callee) {
6714 NodeData::Member { name } | NodeData::VarRef { name } => {
6715 ast.sn(*name).to_string()
6716 }
6717 _ => String::new(),
6718 };
6719 if !name.is_empty() {
6720 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6721 }
6722 }
6723 }
6724 NodeData::MethodRef { name } => {
6725 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6726 veto.insert(strip_ta_prefix(short).to_string());
6727 }
6728 }
6729 _ => {}
6730 }
6731 for &c in ast.children(id) {
6732 stack.push(c);
6733 }
6734 }
6735}
6736
6737fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6739 for ty in &unit.types {
6740 for_each_type_inner(ty, f);
6741 }
6742}
6743fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6744 f(ty);
6745 for m in &ty.members {
6746 if let Member::Type(t) = m {
6747 for_each_type_inner(t, f);
6748 }
6749 }
6750}
6751
6752fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6754 for ty in &mut unit.types {
6755 for_each_type_mut_inner(ty, f);
6756 }
6757}
6758fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6759 f(ty);
6760 for m in &mut ty.members {
6761 if let Member::Type(t) = m {
6762 for_each_type_mut_inner(t, f);
6763 }
6764 }
6765}
6766
6767fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6769 for m in &ty.members {
6770 match m {
6771 Member::Method { body: Some(b), .. }
6772 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6773 Member::Initializer { body, .. } => out.push(*body),
6774 Member::Field { declarators, .. } => {
6775 for d in declarators {
6776 if let Some(init) = d.init {
6777 out.push(init);
6778 }
6779 }
6780 }
6781 Member::Type(t) => collect_member_roots(t, out),
6782 _ => {}
6783 }
6784 }
6785}
6786
6787fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6802 use std::collections::{HashMap, HashSet};
6803 ast.noop_private_methods.clear();
6804 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6805 let mut veto: HashSet<String> = HashSet::new();
6806 for_each_type(unit, &mut |ty: &TypeDecl| {
6807 for m in &ty.members {
6808 if let Member::Method { mods, name, params, body, throws, .. } = m {
6809 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6810 veto.insert(name.clone());
6811 continue;
6812 }
6813 let is_private = mods.split_whitespace().any(|w| w == "private");
6814 let empty = match body {
6815 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6816 None => false,
6817 };
6818 let no_throws = throws.is_empty();
6822 let cand = NoopCandidate {
6823 params: params.iter().map(|p| p.ty.clone()).collect(),
6824 noop: is_private && empty && no_throws,
6825 };
6826 by_name
6827 .entry(name.clone())
6828 .or_default()
6829 .entry(params.len())
6830 .or_default()
6831 .push(cand);
6832 }
6833 }
6834 });
6835 let blind = collect_all_blind_refs(ast, unit);
6838 let mut roots: Vec<JavaId> = Vec::new();
6841 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6842 for root in roots {
6843 let mut stack = vec![root];
6844 while let Some(n) = stack.pop() {
6845 if let NodeData::MethodRef { name } = ast.data(n) {
6846 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6847 veto.insert(strip_ta_prefix(short).to_string());
6848 }
6849 }
6850 for &c in ast.children(n) {
6851 stack.push(c);
6852 }
6853 }
6854 }
6855 for (name, arities) in by_name {
6856 if veto.contains(&name) || blind.contains(&name) {
6857 continue;
6858 }
6859 ast.noop_private_methods.insert(name, arities);
6860 }
6861}
6862
6863fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6868 use std::collections::HashSet;
6869 let mut idents: HashSet<String> = HashSet::new();
6870 let mut roots: Vec<JavaId> = Vec::new();
6872 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6873 for root in roots {
6874 let mut stack = vec![root];
6875 while let Some(n) = stack.pop() {
6876 match ast.data(n) {
6877 NodeData::VarDecl { name, ty, .. } => {
6878 idents.insert(name.clone());
6879 collect_ty_idents(ty, &mut idents);
6880 }
6881 NodeData::VarRef { name } => {
6882 let n = ast.sn(*name);
6886 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6887 if !id.is_empty() {
6888 idents.insert(id.to_string());
6889 }
6890 }
6891 }
6892 NodeData::Member { name } => {
6893 idents.insert(ast.sn(*name).to_string());
6894 }
6895 NodeData::MethodRef { name } => {
6896 let n = ast.sn(*name);
6897 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6898 if !id.is_empty() {
6899 idents.insert(id.to_string());
6900 }
6901 }
6902 }
6903 NodeData::Label { name } => {
6904 idents.insert(name.clone());
6905 }
6906 NodeData::New { ty, anon_raw, .. } => {
6907 collect_ty_idents(ty, &mut idents);
6908 if let Some(raw) = anon_raw {
6911 for id in
6912 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6913 {
6914 if !id.is_empty() {
6915 idents.insert(id.to_string());
6916 }
6917 }
6918 }
6919 }
6920 NodeData::Cast { ty } => {
6921 collect_ty_idents(ty, &mut idents);
6922 }
6923 NodeData::InstanceOf { ty, .. } => {
6924 collect_ty_idents(ty, &mut idents);
6925 }
6926 NodeData::Catch { ty_raw, .. } => {
6927 for id in
6929 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6930 {
6931 if !id.is_empty() {
6932 idents.insert(id.to_string());
6933 }
6934 }
6935 }
6936 NodeData::Raw { text } => {
6937 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6938 if !id.is_empty() {
6939 idents.insert(id.to_string());
6940 }
6941 }
6942 }
6943 _ => {}
6944 }
6945 for &c in ast.children(n) {
6946 stack.push(c);
6947 }
6948 }
6949 }
6950 for name in collect_all_blind_refs(ast, unit) {
6954 idents.insert(name);
6955 }
6956 for r in &unit.raws {
6959 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6960 if !id.is_empty() {
6961 idents.insert(id.to_string());
6962 }
6963 }
6964 }
6965 for_each_type(unit, &mut |ty| {
6968 collect_mods_idents(&ty.mods, &mut idents);
6969 idents.insert(ty.name.clone());
6970 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6971 if !id.is_empty() {
6972 idents.insert(id.to_string());
6973 }
6974 }
6975 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6979 if !id.is_empty() {
6980 idents.insert(id.to_string());
6981 }
6982 }
6983 for e in ty.extends.iter().chain(ty.implements.iter()) {
6984 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6985 if !id.is_empty() {
6986 idents.insert(id.to_string());
6987 }
6988 }
6989 }
6990 for m in &ty.members {
6991 match m {
6992 Member::Field { mods, ty, declarators } => {
6993 collect_mods_idents(mods, &mut idents);
6994 collect_ty_idents(ty, &mut idents);
6995 for d in declarators {
6996 idents.insert(d.name.clone());
6997 }
6998 }
6999 Member::Method { mods, ret, name, params, throws, .. } => {
7000 collect_mods_idents(mods, &mut idents);
7001 collect_ty_idents(ret, &mut idents);
7002 idents.insert(name.clone());
7003 for p in params {
7004 collect_mods_idents(&p.mods, &mut idents);
7005 collect_ty_idents(&p.ty, &mut idents);
7006 idents.insert(p.name.clone());
7007 }
7008 for t in throws {
7009 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7010 if !id.is_empty() {
7011 idents.insert(id.to_string());
7012 }
7013 }
7014 }
7015 }
7016 Member::Constructor { mods, name, params, throws, .. } => {
7017 collect_mods_idents(mods, &mut idents);
7018 idents.insert(name.clone());
7019 for p in params {
7020 collect_mods_idents(&p.mods, &mut idents);
7021 collect_ty_idents(&p.ty, &mut idents);
7022 idents.insert(p.name.clone());
7023 }
7024 for t in throws {
7025 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7026 if !id.is_empty() {
7027 idents.insert(id.to_string());
7028 }
7029 }
7030 }
7031 }
7032 Member::Type(t) => {
7033 let _ = t;
7035 }
7036 _ => {}
7037 }
7038 }
7039 });
7040 let before = unit.imports.len();
7042 unit.imports.retain(|imp| {
7043 if imp.contains('*') {
7044 return true; }
7046 let path = imp.trim_start_matches("static ").trim();
7048 let simple = path.rsplit('.').next().unwrap_or(path).trim();
7049 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
7050 return true; }
7052 idents.contains(simple)
7053 });
7054 before - unit.imports.len()
7055}
7056
7057fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
7058 match t {
7059 JType::Ref(r) => {
7060 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7061 if !id.is_empty() {
7062 out.insert(id.to_string());
7063 }
7064 }
7065 }
7066 JType::Array(inner) => collect_ty_idents(inner, out),
7067 _ => {}
7068 }
7069}
7070
7071fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
7072 if mods.contains('@') {
7074 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
7075 if !id.is_empty() {
7076 out.insert(id.to_string());
7077 }
7078 }
7079 }
7080}
7081
7082fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
7088 use std::collections::HashSet;
7089 let mut removed = 0usize;
7090 let anon_names = collect_anon_and_string_names(ast, unit);
7092 let blind = collect_all_blind_refs(ast, unit);
7095 let mut referenced: HashSet<String> = HashSet::new();
7097 let mut bodies: Vec<JavaId> = Vec::new();
7098 for_each_type(unit, &mut |ty: &TypeDecl| {
7099 for m in &ty.members {
7100 match m {
7101 Member::Method { body: Some(b), .. }
7102 | Member::Constructor { body: Some(b), .. }
7103 | Member::Initializer { body: b, .. } => bodies.push(*b),
7104 Member::Field { declarators, .. } => {
7105 for d in declarators {
7106 if let Some(init) = d.init {
7107 bodies.push(init);
7108 }
7109 }
7110 }
7111 _ => {}
7112 }
7113 }
7114 });
7115 for root in bodies {
7116 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
7118 while let Some((n, is_callee)) = stack.pop() {
7119 match ast.kind(n) {
7120 NodeKind::VarRef if !is_callee => {
7121 if let Some(nm) = ast.var_name(n) {
7122 referenced.insert(nm.to_string());
7123 }
7124 }
7125 NodeKind::Member => {
7126 if let NodeData::Member { name: sym } = ast.data(n) {
7127 referenced.insert(ast.sn(*sym).to_string());
7128 }
7129 }
7130 _ => {}
7131 }
7132 if ast.kind(n) == NodeKind::Call {
7133 let ch = ast.children(n).to_vec();
7134 if let Some(&first) = ch.first() {
7135 stack.push((first, true));
7136 }
7137 for &c in &ch[1..] {
7138 stack.push((c, false));
7139 }
7140 continue;
7141 }
7142 for &c in ast.children(n) {
7143 stack.push((c, false));
7144 }
7145 }
7146 }
7147 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
7153 for m in ty.members.iter_mut() {
7154 if let Member::Field { mods, declarators, .. } = m {
7155 let is_private = mods.split_whitespace().any(|w| w == "private");
7156 let annotated = mods.contains('@');
7157 if !is_private || annotated {
7158 continue;
7159 }
7160 let dead: Vec<bool> = declarators
7161 .iter()
7162 .map(|d| {
7163 let reflective =
7164 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
7165 let init_ok =
7166 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
7167 !reflective
7168 && init_ok
7169 && !referenced.contains(&d.name)
7170 && !anon_names.contains(&d.name)
7171 && !blind.contains(&d.name)
7172 })
7173 .collect();
7174 let before = declarators.len();
7175 let mut idx = 0usize;
7176 declarators.retain(|_| {
7177 let keep = !dead[idx];
7178 idx += 1;
7179 keep
7180 });
7181 removed += before - declarators.len();
7182 }
7183 }
7184 let before = ty.members.len();
7185 ty.members
7186 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
7187 removed += before - ty.members.len();
7188 });
7189 removed
7190}
7191
7192fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
7193 for m in &mut ty.members {
7194 simplify_member(ast, m, cfg, total);
7195 }
7196}
7197
7198fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
7199 let params: Vec<(String, JType)> = match m {
7201 Member::Method { params, .. } | Member::Constructor { params, .. } => params
7202 .iter()
7203 .map(|p| (p.name.clone(), p.ty.clone()))
7204 .collect(),
7205 _ => Vec::new(),
7206 };
7207 ast.set_param_scope(¶ms);
7208 match m {
7209 Member::Method { body: Some(b), .. }
7210 | Member::Constructor { body: Some(b), .. } => {
7211 let r = simplify(ast, *b, cfg);
7212 merge_report(total, r);
7213 }
7214 Member::Initializer { body, .. } => {
7215 let r = simplify(ast, *body, cfg);
7216 merge_report(total, r);
7217 }
7218 Member::Field { declarators, .. } => {
7219 for d in declarators.iter_mut() {
7220 if let Some(init) = d.init {
7221 let r = simplify(ast, init, cfg);
7222 merge_report(total, r);
7223 let rules = default_java_rules();
7227 let mut cur: JavaId = init;
7228 for _ in 0..8 {
7229 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7230 Some((new_root, name)) => {
7231 d.init = Some(new_root);
7232 cur = new_root;
7233 *total.by_rule.entry(name).or_insert(0) += 1;
7234 total.edits += 1;
7235 let r = simplify(ast, new_root, cfg);
7237 merge_report(total, r);
7238 }
7239 None => break,
7240 }
7241 }
7242 }
7243 }
7244 }
7245 Member::Type(t) => simplify_type(ast, t, cfg, total),
7246 Member::Raw(_) => {}
7247 _ => {}
7248 }
7249}
7250
7251fn merge_report(total: &mut Report, r: Report) {
7252 total.edits += r.edits;
7253 total.iterations += r.iterations;
7254 for (k, v) in r.by_rule {
7255 *total.by_rule.entry(k).or_insert(0) += v;
7256 }
7257}
7258
7259pub fn default_config() -> Config {
7261 Config::default()
7262}