1mod jdk;
9pub use jdk::set_jdk_home;
10mod vexec;
11
12use cure_engine::kind::{BinOp, UnOp};
13use cure_engine::{Config, Edit, Effect, Lang, LitRef, NodeKind, Report, RewriteCtx, Rule};
14use cure_java_ast::{CompilationUnit, JavaAst, JavaId, JType, Lit, Member, NoopCandidate, NodeData, TypeDecl, TypeKind};
15
16pub struct CastSimplify;
24
25impl Rule<JavaAst> for CastSimplify {
26 fn name(&self) -> &'static str {
27 "cast_simplify"
28 }
29 fn kinds(&self) -> &'static [NodeKind] {
30 &[NodeKind::Cast]
31 }
32 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
33 let lang = ctx.lang;
34 let NodeData::Cast { ty } = lang.data(id) else {
35 return None;
36 };
37 let ty = ty.clone();
38 let inner = lang.children(id)[0];
39
40 if let NodeData::Cast { ty: inner_ty } = lang.data(inner) {
42 if *inner_ty == ty {
43 return Some(Edit::Replace {
44 target: id,
45 with: inner,
46 });
47 }
48 }
49
50 if let NodeData::VarRef { .. } = lang.data(inner) {
52 if lang.var_type(inner) == Some(&ty) {
53 return Some(Edit::Replace {
54 target: id,
55 with: inner,
56 });
57 }
58 }
59 if let NodeData::Cast { ty: inner_cast_ty } = lang.data(inner) {
66 let is_obj = matches!(inner_cast_ty,
67 JType::Ref(r) if r == "java.lang.Object" || r == "Object");
68 if is_obj {
69 let operand = lang.children(inner).first().copied()?;
70 return Some(Edit::Replace {
71 target: inner,
72 with: operand,
73 });
74 }
75 }
76 None
77 }
78}
79
80pub struct SelfCompare;
83
84impl Rule<JavaAst> for SelfCompare {
85 fn name(&self) -> &'static str {
86 "self_compare"
87 }
88 fn kinds(&self) -> &'static [NodeKind] {
89 &[NodeKind::Binary]
90 }
91 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
92 use cure_engine::kind::BinOp::{Eq, Ne};
93 let lang = ctx.lang;
94 let NodeData::Binary { op } = lang.data(id) else {
95 return None;
96 };
97 if !matches!(op, Eq | Ne) {
98 return None;
99 }
100 let ch = lang.children(id);
101 let (NodeData::VarRef { name: n1 }, NodeData::VarRef { name: n2 }) =
102 (lang.data(ch[0]), lang.data(ch[1]))
103 else {
104 return None;
105 };
106 if n1 != n2 || !lang.is_exact_int(ch[0]) {
107 return None;
108 }
109 let val = lang.build_bool(*op == Eq);
110 Some(Edit::Replace {
111 target: id,
112 with: val,
113 })
114 }
115}
116
117
118pub struct StringBuilderFold;
129
130impl Rule<JavaAst> for StringBuilderFold {
131 fn name(&self) -> &'static str {
132 "string_builder_fold"
133 }
134 fn kinds(&self) -> &'static [NodeKind] {
135 &[NodeKind::Call]
136 }
137 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
138 let root = ctx.root();
139 let lang = ctx.lang;
140 if lang.kind(id) != NodeKind::Call {
142 return None;
143 }
144 let callee = lang.children(id)[0];
145 if let NodeData::Member { name } = lang.data(callee) {
146 if lang.sn(*name) != "toString" || !lang.children(id)[1..].is_empty() {
147 return None;
148 }
149 } else {
150 return None;
151 }
152 let mut node = lang.children(callee)[0]; let mut parts: Vec<JavaId> = Vec::new();
154 loop {
156 match lang.data(node) {
157 NodeData::Call => {
158 let c = lang.children(node);
159 if let NodeData::Member { name } = lang.data(c[0]) {
160 if lang.sn(*name) == "append" && c.len() == 2 {
161 parts.push(c[1]);
162 node = lang.children(c[0])[0];
163 continue;
164 }
165 }
166 return None;
167 }
168 NodeData::New { ty, .. } => {
169 let is_sb = !lang.unit_shadows_lang
170 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
171 if !is_sb {
172 return None;
173 }
174 let args = lang.children(node).to_vec();
175 match args.len() {
176 0 => {} 1 => {
178 let a = args[0];
183 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
184 || lang
185 .var_type(a)
186 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
187 if stringy {
188 parts.push(a);
189 } else if !matches!(
190 lang.literal(a),
191 Some(LitRef::Int(_) | LitRef::Long(_))
192 ) {
193 return None;
194 }
195 }
196 _ => return None,
197 }
198 break;
199 }
200 _ => return None,
201 }
202 }
203 if parts.iter().all(|&p| lang.literal(p).is_some())
207 && lang.has_string_identity_compare(root)
208 {
209 return None;
210 }
211 if parts.is_empty() {
212 let empty = lang.build_str("");
214 return Some(Edit::Replace {
215 target: id,
216 with: empty,
217 });
218 }
219 parts.reverse();
221 let first = parts[0];
222 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
223 || lang
224 .var_type(first)
225 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
226 let mut acc = if stringy_first {
227 first
228 } else {
229 let empty = lang.build_str("");
230 lang.build_bin(BinOp::Add, empty, first)
231 };
232 for &p in &parts[1..] {
233 acc = lang.build_bin(BinOp::Add, acc, p);
234 }
235 Some(Edit::Replace {
236 target: id,
237 with: acc,
238 })
239 }
240}
241
242pub struct BoxUnboxChain;
250
251const BOXERS: &[(&str, &str)] = &[
252 ("Integer", "intValue"),
253 ("java.lang.Integer", "intValue"),
254 ("Long", "longValue"),
255 ("java.lang.Long", "longValue"),
256 ("Boolean", "booleanValue"),
257 ("java.lang.Boolean", "booleanValue"),
258 ("Double", "doubleValue"),
259 ("java.lang.Double", "doubleValue"),
260 ("Float", "floatValue"),
261 ("java.lang.Float", "floatValue"),
262 ("Character", "charValue"),
263 ("java.lang.Character", "charValue"),
264 ("Short", "shortValue"),
265 ("java.lang.Short", "shortValue"),
266 ("Byte", "byteValue"),
267 ("java.lang.Byte", "byteValue"),
268];
269
270impl Rule<JavaAst> for BoxUnboxChain {
271 fn name(&self) -> &'static str {
272 "box_unbox_chain"
273 }
274 fn kinds(&self) -> &'static [NodeKind] {
275 &[NodeKind::Call]
276 }
277 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
278 let lang = ctx.lang;
279 if lang.kind(id) != NodeKind::Call {
281 return None;
282 }
283 let callee = lang.children(id)[0];
284 let NodeData::Member { name: unbox } = lang.data(callee) else {
285 return None;
286 };
287 if !lang.children(id)[1..].is_empty() {
288 return None;
289 }
290 let inner = lang.children(callee)[0];
291 if lang.kind(inner) != NodeKind::Call {
292 return None;
293 }
294 let ich = lang.children(inner).to_vec();
296 let (box_name, arg): (String, JavaId) = if let NodeData::New { ty, .. } = lang.data(inner)
297 {
298 let n = match ty {
299 JType::Ref(r) => r.clone(),
300 _ => return None,
301 };
302 if ich.len() != 1 {
303 return None;
304 }
305 (n, ich[0])
306 } else {
307 let icallee = ich.first().copied()?;
309 if let NodeData::Member { name } = lang.data(icallee) {
310 let recv = lang.children(icallee)[0];
312 if lang.sn(*name) != "valueOf" {
313 return None;
314 }
315 {
316 let n = lang.var_name(recv)?;
317 (n.to_string(), *ich.get(1)?)
318 }
319 } else if let NodeData::VarRef { name } = lang.data(icallee) {
320 let _ = name;
323 return None;
324 } else {
325 return None;
326 }
327 };
328 let unbox_name = *unbox;
329 let expected = BOXERS
330 .iter()
331 .find(|(b, _)| *b == box_name.as_str())
332 .map(|(_, u)| *u)?;
333 if expected != lang.sn(unbox_name) {
334 return None;
335 }
336 let var_is = |t: fn(&JType) -> bool| lang.var_type(arg).is_some_and(t);
339 let lit_is_num = lang.literal(arg).is_some_and(|l| {
340 matches!(
341 l,
342 LitRef::Int(_) | LitRef::Long(_) | LitRef::Float(_) | LitRef::Double(_)
343 )
344 });
345 let ok = match box_name.trim_start_matches("java.lang.") {
346 "Integer" | "Long" | "Short" | "Byte" => lang.is_exact_int(arg),
347 "Double" | "Float" => {
348 lit_is_num
349 || var_is(|t| matches!(t, JType::Double | JType::Float | JType::Int | JType::Long))
350 }
351 "Boolean" => lang.is_bool(arg),
352 "Character" => {
353 matches!(lang.literal(arg), Some(LitRef::Char(_))) || var_is(|t| *t == JType::Char)
354 }
355 _ => false,
356 };
357 if !ok {
358 return None;
359 }
360 Some(Edit::Replace {
361 target: id,
362 with: arg,
363 })
364 }
365}
366
367pub struct IteratorToForEach;
379
380impl Rule<JavaAst> for IteratorToForEach {
381 fn name(&self) -> &'static str {
382 "iterator_to_for_each"
383 }
384 fn kinds(&self) -> &'static [NodeKind] {
385 &[NodeKind::For]
386 }
387 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
388 let lang = ctx.lang;
389 let NodeData::For {
390 inits,
391 has_cond,
392 steps,
393 } = lang.data(id)
394 else {
395 return None;
396 };
397 if *inits != 1 || !*has_cond || *steps != 0 {
398 return None;
399 }
400 let ch = lang.children(id).to_vec();
401 let (init, cond, body) = (ch[0], ch[1], ch[2]);
403
404 let NodeData::VarDecl { name: it_name, .. } = lang.data(init) else {
406 return None;
407 };
408 let it_name = it_name.clone();
409 let init_call = lang.children(init).first().copied()?;
410 if lang.kind(init_call) != NodeKind::Call {
411 return None;
412 }
413 let ic = lang.children(init_call).to_vec();
414 let icallee = *ic.first()?;
415 let NodeData::Member { name: m0 } = lang.data(icallee) else {
416 return None;
417 };
418 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
419 return None;
420 }
421 let iterable = lang.children(icallee)[0];
422 if subtree_has_var(&*lang, iterable, &it_name) {
424 return None;
425 }
426
427 if lang.kind(cond) != NodeKind::Call {
429 return None;
430 }
431 let cc = lang.children(cond).to_vec();
432 let ccallee = *cc.first()?;
433 let NodeData::Member { name: m1 } = lang.data(ccallee) else {
434 return None;
435 };
436 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
437 return None;
438 }
439 if lang.var_name(lang.children(ccallee)[0]) != Some(it_name.as_str()) {
440 return None;
441 }
442
443 if lang.kind(body) != NodeKind::Block {
445 return None;
446 }
447 let bch = lang.children(body).to_vec();
448 let first = *bch.first()?;
449 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
450 return None;
451 };
452 let (e_name, ty) = (e_name.clone(), ty.clone());
453 let next_call = lang.children(first).first().copied()?;
454 if lang.kind(next_call) != NodeKind::Call {
455 return None;
456 }
457 let nc = lang.children(next_call).to_vec();
458 let ncallee = *nc.first()?;
459 let NodeData::Member { name: m2 } = lang.data(ncallee) else {
460 return None;
461 };
462 if lang.sn(*m2) != "next" || nc.len() != 1 {
463 return None;
464 }
465 if lang.var_name(lang.children(ncallee)[0]) != Some(it_name.as_str()) {
466 return None;
467 }
468 let rest = bch[1..].to_vec();
470 for &r in &rest {
471 if subtree_has_var(&*lang, r, &it_name) {
472 return None;
473 }
474 }
475 let new_body = lang.build_block(rest);
476 let with = lang.for_each(&e_name, ty, iterable, new_body);
477 Some(Edit::Replace {
478 target: id,
479 with,
480 })
481 }
482}
483
484fn unwrap_paren_cast(lang: &JavaAst, mut n: JavaId) -> JavaId {
486 loop {
487 match lang.kind(n) {
488 NodeKind::Paren | NodeKind::Cast => {
489 let ch = lang.children(n);
490 if ch.is_empty() {
491 return n;
492 }
493 n = ch[0];
494 }
495 _ => return n,
496 }
497 }
498}
499
500fn generic_elem_ty(ty: &JType) -> Option<JType> {
502 let name = match ty {
503 JType::Ref(n) => n.as_str(),
504 _ => return None,
505 };
506 let lt = name.find('<')?;
507 let gt = name.rfind('>')?;
508 if gt <= lt {
509 return None;
510 }
511 let inner = name[lt + 1..gt].trim();
512 if inner.is_empty() || inner.contains('<') {
513 return None; }
515 Some(JType::Ref(inner.to_string()))
516}
517
518fn statically_iterable(ty: &JType) -> bool {
525 match ty {
526 JType::Array(_) => true,
527 JType::Ref(name) => {
528 let base = name.split('<').next().unwrap_or(name).trim();
529 let last = base.rsplit('.').next().unwrap_or(base);
530 matches!(
531 last,
532 "Iterable"
533 | "Collection"
534 | "List"
535 | "ArrayList"
536 | "LinkedList"
537 | "Vector"
538 | "Stack"
539 | "AbstractList"
540 | "AbstractCollection"
541 | "Set"
542 | "HashSet"
543 | "LinkedHashSet"
544 | "TreeSet"
545 | "SortedSet"
546 | "NavigableSet"
547 | "AbstractSet"
548 | "Queue"
549 | "Deque"
550 | "ArrayDeque"
551 | "PriorityQueue"
552 | "BlockingQueue"
553 | "BlockingDeque"
554 | "ConcurrentLinkedQueue"
555 | "ConcurrentLinkedDeque"
556 | "ConcurrentSkipListSet"
557 | "CopyOnWriteArrayList"
558 | "CopyOnWriteArraySet"
559 | "EnumSet"
560 | "LinkedBlockingQueue"
561 | "LinkedBlockingDeque"
562 | "ArrayBlockingQueue"
563 | "PriorityBlockingQueue"
564 | "DelayQueue"
565 | "SynchronousQueue"
566 | "LinkedTransferQueue"
567 | "TransferQueue"
568 )
569 }
570 _ => false,
571 }
572}
573
574fn for_each_elem_ok(ty: &JType) -> bool {
578 match ty {
579 JType::Ref(n) => !n.trim_start().starts_with('?'),
580 _ => true,
581 }
582}
583
584fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
586 let mut stack = vec![root];
587 while let Some(n) = stack.pop() {
588 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
589 out.push(n);
590 }
591 for &c in lang.children(n) {
592 stack.push(c);
593 }
594 }
595}
596
597fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
599 let mut stack = vec![root];
600 while let Some(n) = stack.pop() {
601 for &c in lang.children(n) {
602 if c == target {
603 return n;
604 }
605 stack.push(c);
606 }
607 }
608 root
609}
610
611fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
612 let mut stack = vec![id];
613 while let Some(n) = stack.pop() {
614 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
615 return true;
616 }
617 for &c in lang.children(n) {
618 stack.push(c);
619 }
620 }
621 false
622}
623
624fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
626 let mut stack = vec![id];
627 while let Some(n) = stack.pop() {
628 match lang.data(n) {
629 NodeData::Catch { name: cn, .. } if cn == name => return true,
630 NodeData::ForEach { name: vn, .. } if vn == name => return true,
631 NodeData::Lambda { params_raw } => {
634 for part in
635 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
636 {
637 if part == name {
638 return true;
639 }
640 }
641 }
642 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
644 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
647 _ => {}
648 }
649 for &c in lang.children(n) {
650 stack.push(c);
651 }
652 }
653 false
654}
655
656pub struct NewStringFold;
662
663impl Rule<JavaAst> for NewStringFold {
664 fn name(&self) -> &'static str {
665 "new_string_fold"
666 }
667 fn kinds(&self) -> &'static [NodeKind] {
668 &[NodeKind::New]
669 }
670 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
671 let root = ctx.root();
672 let lang = ctx.lang;
673 let NodeData::New { ty, .. } = lang.data(id) else {
674 return None;
675 };
676 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
677 return None;
678 }
679 if lang.unit_shadows_lang {
683 return None;
684 }
685 let _ = root;
688 if lang.unit_string_identity {
689 return None;
690 }
691 let args = lang.children(id).to_vec();
692 match args.len() {
693 0 => {
694 let empty = lang.build_str("");
695 Some(Edit::Replace {
696 target: id,
697 with: empty,
698 })
699 }
700 1 => {
701 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
702 Some(Edit::Replace {
703 target: id,
704 with: args[0],
705 })
706 } else {
707 None
708 }
709 }
710 _ => None,
711 }
712 }
713}
714
715
716pub struct NewStringCharArrayFold;
722
723impl Rule<JavaAst> for NewStringCharArrayFold {
724 fn name(&self) -> &'static str {
725 "new_string_char_array_fold"
726 }
727 fn kinds(&self) -> &'static [NodeKind] {
728 &[NodeKind::New]
729 }
730 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
731 let root = ctx.root();
732 let lang = ctx.lang;
733 let NodeData::New { ty, .. } = lang.data(id) else {
734 return None;
735 };
736 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
737 return None;
738 }
739 let _ = root;
740 if lang.unit_shadows_lang || lang.unit_string_identity {
741 return None;
742 }
743 let args = lang.children(id).to_vec();
745 if args.len() != 1 {
746 return None;
747 }
748 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
749 return None;
750 };
751 if *sized != 0 {
752 return None;
753 }
754 let arr_children = lang.children(args[0]).to_vec();
755 let lit = *arr_children.first()?;
756 if lang.kind(lit) != NodeKind::ArrayLit {
757 return None;
758 }
759 let mut text = String::new();
760 for &c in lang.children(lit) {
761 match lang.literal(c) {
762 Some(LitRef::Char(ch)) => text.push(ch),
763 _ => return None, }
765 }
766 let with = lang.build_str(&text);
767 Some(Edit::Replace {
768 target: id,
769 with,
770 })
771 }
772}
773
774pub struct EmptyFinallyStrip;
782
783impl Rule<JavaAst> for EmptyFinallyStrip {
784 fn name(&self) -> &'static str {
785 "empty_finally_strip"
786 }
787 fn kinds(&self) -> &'static [NodeKind] {
788 &[NodeKind::Try]
789 }
790 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
791 let lang = ctx.lang;
792 if lang.kind(id) != NodeKind::Try {
793 return None;
794 }
795 let ch = lang.children(id).to_vec();
796 let is_empty_block = |n: JavaId| -> bool {
803 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
804 };
805 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
806 if ch.len() < try_idx + 2 {
807 return None; }
809 let last = *ch.last()?;
811 if lang.kind(last) == NodeKind::Catch {
812 return None;
813 }
814 if !is_empty_block(last) {
815 return None;
816 }
817 Some(Edit::Splice {
820 node: id,
821 index: ch.len() - 1,
822 remove: 1,
823 insert: Vec::new(),
824 })
825 }
826}
827
828fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
874 if lang.kind(a) != lang.kind(b) {
875 let (ia, ib) = (lang.kind(a), lang.kind(b));
877 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
878 if k == NodeKind::ExprStmt {
879 lang.children(x).first().copied()
880 } else {
881 Some(x)
882 }
883 };
884 return match (unwrap(ia, a), unwrap(ib, b)) {
885 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
886 _ => false,
887 };
888 }
889 print_equiv_inner(lang, a, b)
890}
891
892fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
893 if lang.kind(a) != lang.kind(b) {
894 return false;
895 }
896 match (lang.literal(a), lang.literal(b)) {
898 (Some(x), Some(y)) => {
899 if x != y {
900 return false;
901 }
902 }
903 (None, None) => {}
904 _ => return false,
905 }
906 let (ca, cb) = (lang.children(a), lang.children(b));
907 if ca.len() != cb.len() {
908 return false;
909 }
910 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
911}
912
913fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
917 for &root in rest {
918 let mut stack = vec![root];
919 while let Some(n) = stack.pop() {
920 match lang.kind(n) {
921 NodeKind::Assign => {
922 let t = lang.children(n)[0];
923 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
924 return true;
925 }
926 }
927 NodeKind::Unary => {
928 if let NodeData::Unary { op } = lang.data(n) {
929 if op.is_incdec() {
930 let t = lang.children(n)[0];
931 if lang.kind(t) == NodeKind::VarRef
932 && lang.var_name(t) == Some(name)
933 {
934 return true;
935 }
936 }
937 }
938 }
939 _ => {}
940 }
941 for &c in lang.children(n) {
942 stack.push(c);
943 }
944 }
945 }
946 false
947}
948
949pub struct StaticExec;
950
951impl Rule<JavaAst> for StaticExec {
952 fn name(&self) -> &'static str {
953 "static_exec"
954 }
955 fn kinds(&self) -> &'static [NodeKind] {
956 &[NodeKind::Block]
957 }
958 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
959 if ctx.parent(id).is_some() {
962 return None;
963 }
964 let lang = ctx.lang;
965 if lang.kind(id) != NodeKind::Block {
966 return None;
967 }
968 let stmts = lang.children(id).to_vec();
969 if stmts.len() < 3 {
970 return None; }
972 let mut ex = vexec::Exec::new(lang);
978 let run = ex.run_prefix(&stmts).ok()?;
979 if std::env::var("CURE_DBG_P1").is_ok() {
980 eprintln!(
981 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
982 run.cuts.len(),
983 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
984 ex.budget_exhausted(),
985 ex.effect_log().len(),
986 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
987 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
988 ex.effect_log().iter().map(|e| match e {
989 vexec::EffectEvent::FieldWrite(_) => "FW",
990 vexec::EffectEvent::ClassLoad { .. } => "CL",
991 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
992 }).collect::<Vec<_>>()
993 );
994 }
995 if ex.budget_exhausted() {
997 return None;
998 }
999 let root_idx = run.root_cut_index();
1000 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
1002 if total_completed < 3 {
1003 return None;
1004 }
1005 let true_rest = run.true_rest();
1009 if !true_rest.is_empty() {
1010 for ev in ex.effect_log().iter() {
1011 if let vexec::EffectEvent::FieldWrite(k) = ev {
1012 if let Some(name) = lang.name_of_key(*k) {
1013 if lang.final_fields.contains(&name)
1018 && rest_reassigns_field(lang, &true_rest, &name)
1019 {
1020 return None;
1021 }
1022 }
1023 }
1024 }
1025 }
1026 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
1028 let field_finals: Vec<(u32, vexec::VVal)> =
1029 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
1030 let decl_order: Vec<u32> = ex.decl_order().to_vec();
1031 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
1034 Some(vexec::Flow::Return(v)) => Some(v.clone()),
1035 _ => None,
1036 };
1037 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
1040 let mut cut_live_names: Vec<std::collections::HashSet<String>> = Vec::new();
1046 for (j, cut) in run.cuts.iter().enumerate() {
1047 let range_end = if j == 0 {
1048 decl_order.len()
1049 } else {
1050 run.cuts[j - 1].decl_start
1051 };
1052 let rest_j = run.rest_for_cut(j);
1053 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
1054 let mut live_names: std::collections::HashSet<String> =
1055 std::collections::HashSet::new();
1056 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
1057 if ex.var_value(k).is_none() {
1058 continue; }
1060 let name = lang.name_of_key(k)?;
1061 live_names.insert(name.clone());
1062 let referenced = rest_j.iter().any(|&st| {
1063 cure_engine::analysis::subtree_contains(lang, st, |n| {
1064 lang.kind(n) == NodeKind::VarRef
1065 && lang.var_name(n) == Some(name.as_str())
1066 })
1067 });
1068 if referenced {
1069 esc.push((
1070 k,
1071 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
1072 ex.var_domain(k),
1073 ex.var_decl_ty(k).unwrap_or(JType::Var),
1076 ));
1077 }
1078 }
1079 escaped_by_cut.push(esc);
1080 cut_live_names.push(live_names);
1081 }
1082 drop(ex);
1083 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1085 for (j, cut) in run.cuts.iter().enumerate() {
1086 let block_id = if j == root_idx {
1087 id
1088 } else {
1089 cut.block_hint?
1090 };
1091 let mut insert: Vec<JavaId> = Vec::new();
1092 for pos in cut.log_start..cut.log_end {
1094 match &log[pos] {
1095 vexec::EffectEvent::FieldWrite(k) => {
1096 let is_last = !log[pos + 1..]
1098 .iter()
1099 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1100 if !is_last {
1101 continue;
1102 }
1103 let name = lang.name_of_key(*k)?;
1104 if lang.is_param_name(&name) {
1106 return None;
1107 }
1108 let v = field_finals
1109 .iter()
1110 .find(|(fk, _)| fk == k)
1111 .map(|(_, v)| v.clone())?;
1112 let v = match (&v, lang.field_types.get(&name)) {
1117 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1118 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1119 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1120 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1121 _ => v,
1122 };
1123 let (val, mut extra) = materialize(lang, &v)?;
1124 let tgt = lang.var(&name);
1125 insert.push(lang.assign(tgt, val));
1126 insert.append(&mut extra);
1127 }
1128 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1129 if std::env::var("CURE_DBG_P1").is_ok() {
1130 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1131 }
1132 let name = lang.name_of_key(*k)?;
1137 if lang.is_param_name(&name) {
1139 return None;
1140 }
1141 let cch = lang.children(*call).to_vec();
1142 let mut new_args: Vec<JavaId> = Vec::new();
1143 let mut kept_original: Vec<bool> = Vec::new();
1144 for (i, &arg) in cch.iter().enumerate().skip(1) {
1145 let val = args.get(i - 1).cloned().flatten();
1146 let replaced = match val {
1147 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1148 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1149 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1150 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1151 _ => None,
1152 };
1153 kept_original.push(replaced.is_none());
1154 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1155 }
1156 let names_j = cut_live_names.get(j)?;
1160 for ((i, &arg), &kept) in
1161 cch.iter().enumerate().skip(1).zip(kept_original.iter())
1162 {
1163 let _ = i;
1164 if kept
1165 && !names_j.is_empty()
1166 && cure_engine::analysis::subtree_contains(
1167 lang,
1168 arg,
1169 |n| {
1170 lang.kind(n) == NodeKind::VarRef
1171 && lang
1172 .var_name(n)
1173 .is_some_and(|nm| names_j.contains(nm))
1174 },
1175 )
1176 {
1177 return None;
1178 }
1179 }
1180 let callee = *lang.children(*call).first()?;
1181 let callee_copy = lang.copy_subtree(callee);
1182 let mut ch = vec![callee_copy];
1183 ch.extend(new_args);
1184 let new_call = lang.clone_node(*call, ch);
1185 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1186 lang.clone_node(*stmt, vec![new_call])
1187 } else {
1188 new_call
1189 };
1190 let tgt = lang.var(&name);
1192 let assign = lang.assign(tgt, new_stmt);
1193 insert.push(assign);
1194 }
1195 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1196 let lit = lang.lit(Lit::Str(name.clone()));
1204 let callee = *lang.children(*call).first()?;
1205 let callee_copy = lang.copy_subtree(callee);
1206 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1207 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1208 lang.clone_node(*stmt, vec![new_call])
1209 } else {
1210 new_call
1211 };
1212 let try_block = lang.block(vec![new_stmt]);
1213 let catch_block = lang.block(vec![]);
1214 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1215 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1216 insert.push(wrapped);
1217 }
1218 }
1219 }
1220 let mut alias_first: std::collections::HashMap<usize, String> =
1225 std::collections::HashMap::new();
1226 for (k, v, dom, dty) in &escaped_by_cut[j] {
1227 let name = lang.name_of_key(*k)?;
1228 let (val, mut extra) = match v {
1229 vexec::VVal::CA(a) => {
1230 let addr = std::rc::Rc::as_ptr(a) as usize;
1231 match alias_first.get(&addr) {
1232 Some(first) => (lang.var(first), vec![]),
1233 None => {
1234 alias_first.insert(addr, name.clone());
1235 materialize(lang, v)?
1236 }
1237 }
1238 }
1239 vexec::VVal::SA(a) => {
1240 let addr = std::rc::Rc::as_ptr(a) as usize;
1241 match alias_first.get(&addr) {
1242 Some(first) => (lang.var(first), vec![]),
1243 None => {
1244 alias_first.insert(addr, name.clone());
1245 materialize(lang, v)?
1246 }
1247 }
1248 }
1249 _ => materialize(lang, v)?,
1250 };
1251 let (ty, val) = match (*dom, val) {
1256 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1257 match lang.literal(lit) {
1260 Some(LitRef::Int(v)) => {
1261 match u32::try_from(v).ok().and_then(char::from_u32) {
1262 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1263 None => (JType::Char, lit),
1264 }
1265 }
1266 Some(LitRef::Long(v)) => {
1267 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1268 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1269 None => (JType::Char, lit),
1270 }
1271 }
1272 _ => (JType::Char, lit),
1273 }
1274 }
1275 (3, lit) => (JType::Byte, lit),
1276 (4, lit) => (JType::Short, lit),
1277 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1278 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1279 (JType::Long, lang.build_int(v, true))
1280 } else {
1281 (JType::Long, lit)
1282 }
1283 }
1284 (_, lit) => {
1289 if matches!(dty, JType::Ref(_)) {
1290 (dty.clone(), lit)
1291 } else {
1292 (JType::Var, lit)
1293 }
1294 }
1295 };
1296 let stmt = lang.var_decl(&name, ty, Some(val));
1297 insert.push(stmt);
1298 insert.append(&mut extra);
1299 }
1300 if j == root_idx {
1302 if let Some(v) = ret_payload.clone().flatten() {
1303 let (val, mut extra) = materialize(lang, &v)?;
1304 let stmt = lang.ret(Some(val));
1305 insert.push(stmt);
1306 insert.append(&mut extra);
1307 }
1308 }
1309 if insert.is_empty() && cut.completed == 0 {
1311 continue;
1312 }
1313 {
1316 let removed = lang.children(block_id).to_vec();
1317 if insert.len() == cut.completed
1318 && cut.completed <= removed.len()
1319 && insert
1320 .iter()
1321 .zip(removed[..cut.completed].iter())
1322 .all(|(&n, &o)| print_equiv(lang, n, o))
1323 {
1324 continue;
1325 }
1326 }
1327 edits.push(Edit::Splice {
1328 node: block_id,
1329 index: 0,
1330 remove: cut.completed,
1331 insert,
1332 });
1333 }
1334 if std::env::var("CURE_DBG_P1").is_ok() {
1335 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1336 }
1337 if edits.is_empty() {
1338 return None;
1339 }
1340 if edits.len() == 1 {
1342 Some(edits.pop().unwrap())
1343 } else {
1344 Some(Edit::Multi(edits))
1345 }
1346 }
1347}
1348
1349fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1352 use vexec::VVal;
1353 match v {
1354 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1355 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1356 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1357 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1358 VVal::CA(a) => {
1359 let chars: Vec<char> = a.borrow().clone();
1361 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1362 let lit_arr = lang.array_lit(lits);
1363 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1364 Some((node, vec![]))
1365 }
1366 VVal::SA(elems) => {
1367 let arr = elems.borrow().clone();
1370 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1371 let lits: Vec<JavaId> = arr
1372 .iter()
1373 .map(|e| match e {
1374 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1375 _ => unreachable!(),
1376 })
1377 .collect();
1378 let lit_arr = lang.array_lit(lits);
1379 let node =
1380 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1381 Some((node, vec![]))
1382 } else {
1383 None
1388 }
1389 }
1390 VVal::Undef => None,
1392 }
1393}
1394
1395pub struct TryUnwrapRethrow;
1396
1397impl Rule<JavaAst> for TryUnwrapRethrow {
1398 fn name(&self) -> &'static str {
1399 "try_unwrap_rethrow"
1400 }
1401 fn kinds(&self) -> &'static [NodeKind] {
1402 &[NodeKind::Try]
1403 }
1404 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1405 let lang = ctx.lang;
1406 if lang.kind(id) != NodeKind::Try {
1407 return None;
1408 }
1409 let ch = lang.children(id).to_vec();
1410 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1412 let catches: Vec<JavaId> =
1413 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1414 if catches.is_empty() {
1415 return None;
1416 }
1417 let rethrow_only = |c: JavaId| -> bool {
1418 let block = match lang.children(c).first() {
1420 Some(&b) => b,
1421 None => return false,
1422 };
1423 let stmts = lang.children(block);
1424 if stmts.len() != 1 {
1425 return false;
1426 }
1427 let stmt = stmts[0];
1428 if lang.kind(stmt) != NodeKind::Throw {
1429 return false;
1430 }
1431 let Some(&thrown) = lang.children(stmt).first() else { return false };
1432 if lang.kind(thrown) != NodeKind::VarRef {
1434 return false;
1435 }
1436 match &lang.node(c).data {
1437 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1438 _ => false,
1439 }
1440 };
1441 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1442 if all_rethrow {
1443 let extras: Vec<JavaId> = ch
1445 .iter()
1446 .copied()
1447 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1448 .collect();
1449 if extras.is_empty() {
1450 return Some(Edit::Replace {
1451 target: id,
1452 with: try_block,
1453 });
1454 }
1455 return None;
1456 }
1457 let catch_slots: Vec<usize> = ch
1463 .iter()
1464 .enumerate()
1465 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1466 .map(|(i, _)| i)
1467 .collect();
1468 for &slot in &catch_slots {
1469 let c = ch[slot];
1470 if !rethrow_only(c) {
1471 continue;
1472 }
1473 let later_all_rethrow = catch_slots
1474 .iter()
1475 .filter(|&&s2| s2 > slot)
1476 .all(|&s2| rethrow_only(ch[s2]));
1477 if !later_all_rethrow {
1478 continue;
1479 }
1480 return Some(Edit::Splice {
1481 node: id,
1482 index: slot,
1483 remove: 1,
1484 insert: vec![],
1485 });
1486 }
1487 None
1488 }
1489}
1490
1491pub struct TryUnwrapNoCatch;
1492
1493impl Rule<JavaAst> for TryUnwrapNoCatch {
1494 fn name(&self) -> &'static str {
1495 "try_unwrap_no_catch"
1496 }
1497 fn kinds(&self) -> &'static [NodeKind] {
1498 &[NodeKind::Try]
1499 }
1500 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1501 let lang = ctx.lang;
1502 if lang.kind(id) != NodeKind::Try {
1503 return None;
1504 }
1505 let ch = lang.children(id).to_vec();
1506 if ch.is_empty() {
1507 return None;
1508 }
1509 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1512 if has_catch {
1513 return None;
1514 }
1515 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1519 let rest: Vec<JavaId> = ch
1520 .iter()
1521 .copied()
1522 .filter(|&c| c != try_block)
1523 .collect();
1524 for &r in &rest {
1527 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1528 if !is_empty_block {
1529 return None; }
1531 }
1532 Some(Edit::Replace {
1533 target: id,
1534 with: try_block,
1535 })
1536 }
1537}
1538
1539pub struct LoopHeadBreak;
1547
1548impl Rule<JavaAst> for LoopHeadBreak {
1549 fn name(&self) -> &'static str {
1550 "loop_head_break"
1551 }
1552 fn kinds(&self) -> &'static [NodeKind] {
1553 &[NodeKind::While, NodeKind::DoWhile]
1554 }
1555 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1556 let lang = ctx.lang;
1557 let body = match lang.kind(id) {
1558 NodeKind::While => {
1559 let ch = lang.children(id).to_vec();
1560 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1561 return None;
1562 }
1563 ch[1]
1564 }
1565 NodeKind::DoWhile => {
1566 let ch = lang.children(id).to_vec();
1567 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1568 return None;
1569 }
1570 ch[0]
1571 }
1572 _ => return None,
1573 };
1574 if lang.kind(body) != NodeKind::Block {
1575 return None;
1576 }
1577 let bch = lang.children(body).to_vec();
1578 let is_do = lang.kind(id) == NodeKind::DoWhile;
1579 if is_do && bch.len() != 1 {
1581 return None;
1582 }
1583 let first = *bch.first()?;
1584 if lang.kind(first) != NodeKind::If {
1585 return None;
1586 }
1587 let ich = lang.children(first).to_vec();
1588 let c = ich[0];
1589
1590 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1591 let is_break = |n: JavaId| match lang.kind(n) {
1592 NodeKind::Break => bare_break(n),
1593 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1594 _ => false,
1595 };
1596 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1597 let ends_with_continue = |n: JavaId| -> bool {
1598 match lang.kind(n) {
1599 NodeKind::Continue => bare_continue(n),
1600 NodeKind::Block => lang
1601 .children(n)
1602 .last()
1603 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1604 _ => false,
1605 }
1606 };
1607 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1608 match lang.kind(n) {
1609 NodeKind::Continue => vec![],
1610 NodeKind::Block => {
1611 let ch = lang.children(n).to_vec();
1612 if ch
1613 .last()
1614 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1615 {
1616 ch[..ch.len() - 1].to_vec()
1617 } else {
1618 ch
1619 }
1620 }
1621 _ => vec![n],
1622 }
1623 };
1624
1625 match ich.len() {
1626 2 => {
1627 if is_do || !is_break(ich[1]) {
1629 return None;
1630 }
1631 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1632 if let Some(&last) = rest.last() {
1633 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1634 rest.pop();
1635 }
1636 }
1637 let nc = lang.build_unary(UnOp::Not, c);
1638 let inner = lang.build_block(rest);
1639 let w = lang.while_(nc, inner);
1640 Some(Edit::Replace {
1641 target: id,
1642 with: w,
1643 })
1644 }
1645 3 => {
1646 let (then, els) = (ich[1], ich[2]);
1647 if ends_with_continue(then) && is_break(els) {
1649 let rest = strip_tail_continue(then);
1650 let inner = lang.build_block(rest);
1651 let w = lang.while_(c, inner);
1652 return Some(Edit::Replace {
1653 target: id,
1654 with: w,
1655 });
1656 }
1657 if is_break(then) && ends_with_continue(els) {
1659 let rest = strip_tail_continue(els);
1660 let inner = lang.build_block(rest);
1661 let nc = lang.build_unary(UnOp::Not, c);
1662 let w = lang.while_(nc, inner);
1663 return Some(Edit::Replace {
1664 target: id,
1665 with: w,
1666 });
1667 }
1668 if !is_do && is_break(then) {
1670 let mut all = strip_tail_continue(els);
1671 all.extend(bch[1..].iter().copied());
1672 let inner = lang.build_block(all);
1673 let nc = lang.build_unary(UnOp::Not, c);
1674 let w = lang.while_(nc, inner);
1675 return Some(Edit::Replace {
1676 target: id,
1677 with: w,
1678 });
1679 }
1680 None
1681 }
1682 _ => None,
1683 }
1684 }
1685}
1686
1687pub struct WhileIteratorToForEach;
1688
1689impl Rule<JavaAst> for WhileIteratorToForEach {
1690 fn name(&self) -> &'static str {
1691 "while_iterator_to_for_each"
1692 }
1693 fn kinds(&self) -> &'static [NodeKind] {
1694 &[NodeKind::While]
1695 }
1696 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1697 let parent = ctx.parent(id)?;
1699 let idx = ctx.index(id)?;
1700 let lang = ctx.lang;
1701 if lang.kind(id) != NodeKind::While {
1702 return None;
1703 }
1704 let wch = lang.children(id).to_vec();
1705 let body = wch[1];
1706 if lang.kind(parent) != NodeKind::Block {
1708 return None;
1709 }
1710 if idx == 0 {
1711 return None;
1712 }
1713 let stmts = lang.children(parent).to_vec();
1714 let cond = wch[0];
1717 let it_hint = match lang.kind(cond) {
1718 NodeKind::Call => {
1719 let cc0 = lang.children(cond).to_vec();
1720 match lang.data(*cc0.first()?) {
1721 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1722 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1723 }
1724 _ => None,
1725 }
1726 }
1727 _ => None,
1728 };
1729 let mut decl = None;
1730 if let Some(hint) = it_hint {
1731 let mut up = idx;
1732 for _ in 0..4 {
1733 if up == 0 {
1734 break;
1735 }
1736 up -= 1;
1737 let st = stmts[up];
1738 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1739 decl = Some(st);
1740 break;
1741 }
1742 if subtree_has_var(lang, st, &hint) {
1743 break;
1744 }
1745 }
1746 }
1747 let decl = decl?;
1748 let it_name = lang.var_name(decl)?.to_string();
1749 if stmts[idx + 1..]
1754 .iter()
1755 .any(|&s| subtree_has_var(lang, s, &it_name))
1756 {
1757 return None;
1758 }
1759 let init_call = lang.children(decl).first().copied()?;
1760 if lang.kind(init_call) != NodeKind::Call {
1761 return None;
1762 }
1763 let ic = lang.children(init_call).to_vec();
1764 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1765 return None;
1766 };
1767 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1768 return None;
1769 }
1770 let iterable = lang.children(ic[0])[0];
1771 if subtree_has_var(&*lang, iterable, &it_name) {
1772 return None;
1773 }
1774 {
1784 let ty = lang.var_type(iterable);
1785 let ok = ty.is_some_and(|t| statically_iterable(t));
1786 if !ok {
1787 return None;
1788 }
1789 }
1790 if lang.kind(cond) != NodeKind::Call {
1792 return None;
1793 }
1794 let cc = lang.children(cond).to_vec();
1795 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1796 return None;
1797 };
1798 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1799 return None;
1800 }
1801 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1802 return None;
1803 }
1804 if lang.kind(body) != NodeKind::Block {
1806 return None;
1807 }
1808 let bch = lang.children(body).to_vec();
1809 let first = *bch.first()?;
1810 if lang.kind(first) != NodeKind::VarDecl {
1811 let it_refs_in_body: Vec<JavaId> = {
1814 let mut v = Vec::new();
1815 collect_var_refs(&*lang, body, &it_name, &mut v);
1816 v
1817 };
1818 if it_refs_in_body.len() != 1 {
1819 return None;
1820 }
1821 let next_recv = it_refs_in_body[0];
1822 let member = parent_of_recv(lang, body, next_recv);
1824 if lang.kind(member) != NodeKind::Member {
1825 return None;
1826 }
1827 let NodeData::Member { name: mn } = lang.data(member) else {
1828 return None;
1829 };
1830 if lang.sn(*mn) != "next" {
1831 return None;
1832 }
1833 let next_call = parent_of_recv(lang, body, member);
1834 if lang.kind(next_call) != NodeKind::Call {
1835 return None;
1836 }
1837 if lang.children(next_call).len() != 1 {
1838 return None;
1839 }
1840 let mut wrapped = next_call;
1842 loop {
1843 let par = parent_of_recv(lang, body, wrapped);
1844 match lang.kind(par) {
1845 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1846 _ => break,
1847 }
1848 }
1849 {
1859 let mut cur = wrapped;
1860 while cur != body {
1861 let par = parent_of_recv(lang, body, cur);
1862 if par == body || par == cur {
1863 break; }
1865 let ch = lang.children(par).to_vec();
1866 let idx = ch.iter().position(|&c| c == cur);
1867 match lang.kind(par) {
1868 NodeKind::Block
1869 | NodeKind::ExprStmt
1870 | NodeKind::Label
1871 | NodeKind::Return
1872 | NodeKind::Throw => {}
1873 NodeKind::Ternary => {
1874 if idx.unwrap_or(0) > 0 {
1876 return None;
1877 }
1878 }
1879 NodeKind::Binary => {
1880 if let Some(op) = lang.bin_op(par) {
1881 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1882 return None; }
1884 }
1885 }
1886 NodeKind::If
1887 | NodeKind::While
1888 | NodeKind::DoWhile
1889 | NodeKind::For
1890 | NodeKind::Switch
1891 | NodeKind::Try
1892 | NodeKind::Catch
1893 | NodeKind::Synchronized
1894 | NodeKind::ForEach => return None,
1895 NodeKind::Lambda | NodeKind::Assert => return None,
1900 _ => {} }
1902 cur = par;
1903 }
1904 }
1905 let it_decl_elem = match lang.data(decl) {
1913 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
1914 _ => None,
1915 };
1916 let elem = match it_decl_elem {
1917 Some(t) => t,
1918 None => match lang.var_type(iterable) {
1919 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
1920 None => JType::Ref("Object".into()),
1921 },
1922 };
1923 let elem = if for_each_elem_ok(&elem) {
1924 elem
1925 } else {
1926 JType::Ref("Object".into())
1927 };
1928 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
1929 let (replace_target, e_ty) = match lang.data(wrapped) {
1930 NodeData::Cast { .. } if is_object => (next_call, elem),
1931 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
1932 _ => (wrapped, elem),
1933 };
1934 let taken = |nm: &str| {
1938 subtree_has_var(&*lang, body, nm)
1939 || subtree_has_binding(&*lang, body, nm)
1940 };
1941 let e_name = if taken("e") {
1942 if taken("e2") { "e3" } else { "e2" }
1943 } else {
1944 "e"
1945 };
1946 let e_ref = lang.var(e_name);
1947 let foreach = lang.for_each(e_name, e_ty, iterable, body);
1948 let wrapped_parent = parent_of_recv(lang, body, wrapped);
1952 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
1953 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
1954 {
1955 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
1956 if let Some(si) = lang
1957 .children(stmt_block)
1958 .iter()
1959 .position(|&c| c == wrapped_parent)
1960 {
1961 return Some(Edit::Multi(vec![
1962 Edit::Splice {
1963 node: stmt_block,
1964 index: si,
1965 remove: 1,
1966 insert: vec![],
1967 },
1968 Edit::Splice {
1969 node: parent,
1970 index: idx,
1971 remove: 1,
1972 insert: vec![foreach],
1973 },
1974 Edit::Delete { node: decl },
1975 ]));
1976 }
1977 }
1978 return Some(Edit::Multi(vec![
1979 Edit::Replace {
1980 target: replace_target,
1981 with: e_ref,
1982 },
1983 Edit::Splice {
1984 node: parent,
1985 index: idx,
1986 remove: 1,
1987 insert: vec![foreach],
1988 },
1989 Edit::Delete { node: decl },
1990 ]));
1991 }
1992 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
1993 return None;
1994 };
1995 let (e_name, ty) = (e_name.clone(), ty.clone());
1996 let next_call = lang.children(first).first().copied()?;
1997 let next_call = unwrap_paren_cast(&*lang, next_call);
1998 if lang.kind(next_call) != NodeKind::Call {
1999 return None;
2000 }
2001 let nc = lang.children(next_call).to_vec();
2002 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
2003 return None;
2004 };
2005 if lang.sn(*m2) != "next" || nc.len() != 1 {
2006 return None;
2007 }
2008 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
2009 return None;
2010 }
2011 let elem_is_object = match lang.var_type(iterable) {
2013 Some(it_t) => generic_elem_ty(it_t).is_none(),
2014 None => true,
2015 };
2016 if elem_is_object && ty != JType::Ref("Object".into()) {
2017 return None;
2018 }
2019 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
2021 let rest = bch[1..].to_vec();
2022 for &r in &rest {
2023 if subtree_has_var(&*lang, r, &it_name) {
2024 return None;
2025 }
2026 }
2027 let new_body = lang.build_block(rest);
2028 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
2029 Some(Edit::Multi(vec![
2032 Edit::Splice {
2033 node: parent,
2034 index: idx,
2035 remove: 1,
2036 insert: vec![foreach],
2037 },
2038 Edit::Delete { node: decl },
2039 ]))
2040 }
2041}
2042
2043pub struct ConcatValueOfDrop;
2052
2053impl Rule<JavaAst> for ConcatValueOfDrop {
2054 fn name(&self) -> &'static str {
2055 "concat_value_of_drop"
2056 }
2057 fn kinds(&self) -> &'static [NodeKind] {
2058 &[NodeKind::Binary]
2059 }
2060 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2061 let root = ctx.root();
2062 let lang = ctx.lang;
2063 if lang.kind(id) != NodeKind::Binary {
2064 return None;
2065 }
2066 if lang.bin_op(id) != Some(BinOp::Add) {
2067 return None;
2068 }
2069 let ch = lang.children(id).to_vec();
2070 let (l, r) = (ch[0], ch[1]);
2071 let becomes_constant = |x: JavaId, other: JavaId| {
2074 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
2075 };
2076 let is_value_of = |n: JavaId| -> Option<JavaId> {
2077 if lang.kind(n) != NodeKind::Call {
2078 return None;
2079 }
2080 let c = lang.children(n).to_vec();
2081 if c.len() != 2 {
2082 return None;
2083 }
2084 let callee = c[0];
2085 if let NodeData::Member { name } = lang.data(callee) {
2086 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
2087 return Some(c[1]);
2088 }
2089 }
2090 if let NodeData::VarRef { name } = lang.data(callee) {
2091 if lang.sn(*name) == "valueOf" {
2092 return Some(c[1]);
2093 }
2094 }
2095 None
2096 };
2097 let stringy = |n: JavaId| -> bool {
2098 matches!(lang.literal(n), Some(LitRef::Str(_)))
2099 || lang
2100 .var_type(n)
2101 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
2102 };
2103 if let Some(x) = is_value_of(l) {
2105 if stringy(r) {
2106 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
2107 return None;
2108 }
2109 let with = lang.build_bin(BinOp::Add, x, r);
2110 return Some(Edit::Replace {
2111 target: id,
2112 with,
2113 });
2114 }
2115 }
2116 if let Some(x) = is_value_of(r) {
2118 if stringy(l) {
2119 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
2120 return None;
2121 }
2122 let with = lang.build_bin(BinOp::Add, l, x);
2123 return Some(Edit::Replace {
2124 target: id,
2125 with,
2126 });
2127 }
2128 }
2129 None
2130 }
2131}
2132
2133
2134pub struct StringBuilderStatements;
2147
2148fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2149 let mut n = 0;
2152 let mut stack = vec![root];
2153 while let Some(x) = stack.pop() {
2154 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2155 let ch = lang.children(x).to_vec();
2156 if let Some(&t) = ch.first() {
2157 let is_named_target =
2158 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2159 if !is_named_target {
2160 stack.push(t);
2161 }
2162 for &c in &ch[1..] {
2163 stack.push(c);
2164 }
2165 }
2166 continue;
2167 }
2168 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2169 n += 1;
2170 }
2171 for &c in lang.children(x) {
2172 stack.push(c);
2173 }
2174 }
2175 n
2176}
2177
2178impl Rule<JavaAst> for StringBuilderStatements {
2179 fn name(&self) -> &'static str {
2180 "string_builder_statements"
2181 }
2182 fn kinds(&self) -> &'static [NodeKind] {
2183 &[NodeKind::VarDecl]
2184 }
2185 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2186 let parent = ctx.parent(id)?;
2188 let idx = ctx.index(id)?;
2189 let root = ctx.root();
2190 let lang = ctx.lang;
2191
2192 if lang.kind(id) != NodeKind::VarDecl {
2193 return None;
2194 }
2195 if lang.kind(parent) != NodeKind::Block {
2196 return None;
2197 }
2198 let v0 = lang.var_name(id)?.to_string();
2199 let init = *lang.children(id).first()?;
2201 if lang.kind(init) != NodeKind::Call {
2202 return None;
2203 }
2204 let ic = lang.children(init).to_vec();
2205 if ic.len() != 2 {
2206 return None;
2207 }
2208 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2209 return None;
2210 };
2211 if lang.sn(*m0) != "append" {
2212 return None;
2213 }
2214 let sb_new = lang.children(ic[0])[0];
2215 let (ctor_arg, a0) = match lang.data(sb_new) {
2216 NodeData::New { ty, .. } => {
2217 let is_sb = !lang.unit_shadows_lang
2218 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2219 if !is_sb {
2220 return None;
2221 }
2222 let args = lang.children(sb_new).to_vec();
2223 match args.len() {
2224 0 => (None, ic[1]),
2225 1 => {
2226 let a = args[0];
2227 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2228 || lang
2229 .var_type(a)
2230 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2231 if stringy {
2232 (Some(a), ic[1])
2233 } else if matches!(
2234 lang.literal(a),
2235 Some(LitRef::Int(_) | LitRef::Long(_))
2236 ) {
2237 (None, ic[1])
2239 } else {
2240 return None; }
2242 }
2243 _ => return None,
2244 }
2245 }
2246 _ => return None,
2247 };
2248
2249 let stmts = lang.children(parent).to_vec();
2250 let mut parts: Vec<JavaId> = vec![a0];
2252 let mut expected_reads: std::collections::HashMap<String, usize> =
2253 std::collections::HashMap::new();
2254 let mut chain_len = 1usize; let mut cur = v0.clone();
2256 while idx + chain_len < stmts.len() {
2257 let s = stmts[idx + chain_len];
2258 let (recv, arg, new_var, consumed) = match lang.data(s) {
2259 NodeData::Assign { op: None } => {
2261 let ch = lang.children(s).to_vec();
2262 if ch.len() != 2 {
2263 break;
2264 }
2265 let (t, v) = (ch[0], ch[1]);
2266 if lang.kind(t) != NodeKind::VarRef
2267 || lang.var_name(t) != Some(cur.as_str())
2268 || lang.kind(v) != NodeKind::Call
2269 {
2270 break;
2271 }
2272 let vc = lang.children(v).to_vec();
2273 if vc.len() != 2 {
2274 break;
2275 }
2276 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2277 break;
2278 };
2279 if lang.sn(*mn) != "append" {
2280 break;
2281 }
2282 let recv = lang.children(vc[0])[0];
2283 (recv, vc[1], false, s)
2284 }
2285 NodeData::ExprStmt => {
2286 let ch = lang.children(s).to_vec();
2287 if ch.len() != 1 {
2288 break;
2289 }
2290 let inner = ch[0];
2291 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2292 break;
2293 }
2294 let ach = lang.children(inner).to_vec();
2295 if ach.len() != 2 {
2296 break;
2297 }
2298 let (t, v) = (ach[0], ach[1]);
2299 if lang.kind(t) != NodeKind::VarRef
2300 || lang.var_name(t) != Some(cur.as_str())
2301 || lang.kind(v) != NodeKind::Call
2302 {
2303 break;
2304 }
2305 let vc = lang.children(v).to_vec();
2306 if vc.len() != 2 {
2307 break;
2308 }
2309 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2310 break;
2311 };
2312 if lang.sn(*mn) != "append" {
2313 break;
2314 }
2315 let recv = lang.children(vc[0])[0];
2316 (recv, vc[1], false, s)
2317 }
2318 NodeData::VarDecl { .. } => {
2320 let init = match lang.children(s).first() {
2321 Some(&x) => x,
2322 None => break,
2323 };
2324 if lang.kind(init) != NodeKind::Call {
2325 break;
2326 }
2327 let vc = lang.children(init).to_vec();
2328 if vc.len() != 2 {
2329 break;
2330 }
2331 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2332 break;
2333 };
2334 if lang.sn(*mn) != "append" {
2335 break;
2336 }
2337 let recv = lang.children(vc[0])[0];
2338 if lang.var_name(recv) != Some(cur.as_str()) {
2339 break;
2340 }
2341 (recv, vc[1], true, s)
2342 }
2343 _ => break,
2344 };
2345 if lang.var_name(recv) != Some(cur.as_str()) {
2347 break;
2348 }
2349 parts.push(arg);
2350 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2351 if new_var {
2352 cur = lang.var_name(consumed)?.to_string();
2353 }
2354 chain_len += 1;
2355 let _ = consumed;
2356 }
2357
2358 let final_stmt = *stmts.get(idx + chain_len)?;
2360 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2361 return None;
2362 };
2363 let s_init = *lang.children(final_stmt).first()?;
2364 if lang.kind(s_init) != NodeKind::Call {
2365 return None;
2366 }
2367 let sc = lang.children(s_init).to_vec();
2368 if sc.len() != 1 {
2369 return None;
2370 }
2371 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2372 return None;
2373 };
2374 if lang.sn(*mn) != "toString" {
2375 return None;
2376 }
2377 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2378 return None;
2379 }
2380
2381 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2385 if count_var_uses_in(&*lang, parent, v) != *exp {
2386 return None;
2387 }
2388 }
2389
2390 let mut all_parts: Vec<JavaId> = Vec::new();
2392 if let Some(ca) = ctor_arg {
2393 all_parts.push(ca);
2394 }
2395 all_parts.extend(parts);
2396 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2397 && lang.has_string_identity_compare(root)
2398 {
2399 return None;
2400 }
2401 if all_parts.is_empty() {
2402 let empty = lang.build_str("");
2403 return Some(Edit::Multi(vec![
2404 Edit::Replace {
2405 target: s_init,
2406 with: empty,
2407 },
2408 Edit::Splice {
2409 node: parent,
2410 index: idx,
2411 remove: chain_len,
2412 insert: Vec::new(),
2413 },
2414 ]));
2415 }
2416 let first = all_parts[0];
2417 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2418 || lang
2419 .var_type(first)
2420 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2421 let mut acc = if stringy_first {
2422 first
2423 } else {
2424 let empty = lang.build_str("");
2425 lang.build_bin(BinOp::Add, empty, first)
2426 };
2427 for &p in &all_parts[1..] {
2428 acc = lang.build_bin(BinOp::Add, acc, p);
2429 }
2430 Some(Edit::Multi(vec![
2431 Edit::Replace {
2432 target: s_init,
2433 with: acc,
2434 },
2435 Edit::Splice {
2436 node: parent,
2437 index: idx,
2438 remove: chain_len,
2439 insert: Vec::new(),
2440 },
2441 ]))
2442 }
2443}
2444
2445
2446pub struct TrailingContinueJava;
2453
2454impl Rule<JavaAst> for TrailingContinueJava {
2455 fn name(&self) -> &'static str {
2456 "trailing_continue"
2457 }
2458 fn kinds(&self) -> &'static [NodeKind] {
2459 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2460 }
2461 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2462 let parent = ctx.parent(id)?;
2463 let lang = ctx.lang;
2464 let body = match lang.kind(id) {
2465 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2466 *lang.children(id).last()?
2467 }
2468 _ => return None,
2469 };
2470 if lang.kind(body) != NodeKind::Block {
2471 return None;
2472 }
2473 let ch = lang.children(body).to_vec();
2474 let last = *ch.last()?;
2475 if lang.kind(last) != NodeKind::Continue {
2476 return None;
2477 }
2478 let label = match lang.data(last) {
2479 NodeData::Continue { label } => label.clone(),
2480 _ => return None,
2481 };
2482 match label {
2483 None => Some(Edit::Delete { node: last }),
2484 Some(l) => {
2485 if lang.kind(parent) == NodeKind::Label {
2487 if let NodeData::Label { name } = lang.data(parent) {
2488 if *name == l {
2489 return Some(Edit::Delete { node: last });
2490 }
2491 }
2492 }
2493 None
2494 }
2495 }
2496 }
2497}
2498
2499
2500pub struct XorNoise;
2508
2509impl Rule<JavaAst> for XorNoise {
2510 fn name(&self) -> &'static str {
2511 "xor_noise"
2512 }
2513 fn kinds(&self) -> &'static [NodeKind] {
2514 &[NodeKind::Binary]
2515 }
2516 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2517 let lang = ctx.lang;
2518 if lang.kind(id) != NodeKind::Binary {
2519 return None;
2520 }
2521 let op = lang.bin_op(id)?;
2522 if op != BinOp::BitXor {
2523 return None;
2524 }
2525 let ch = lang.children(id).to_vec();
2526 let (l, r) = (ch[0], ch[1]);
2527 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2529 if int_lit(r) == Some(0) {
2530 return Some(Edit::Replace {
2531 target: id,
2532 with: l,
2533 });
2534 }
2535 if int_lit(l) == Some(0) {
2536 return Some(Edit::Replace {
2537 target: id,
2538 with: r,
2539 });
2540 }
2541 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2543 return None;
2544 }
2545 let ich = lang.children(l).to_vec();
2546 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2547 let k2 = int_lit(r)?;
2548 let x = ich[0];
2549 let k = k1 ^ k2;
2550 let with = if k == 0 {
2551 x
2552 } else {
2553 let lit = lang.build_int(k, false);
2554 lang.build_bin(BinOp::BitXor, x, lit)
2555 };
2556 Some(Edit::Replace {
2557 target: id,
2558 with,
2559 })
2560 }
2561}
2562
2563pub struct StrLenFold;
2568
2569impl Rule<JavaAst> for StrLenFold {
2570 fn name(&self) -> &'static str {
2571 "str_len_fold"
2572 }
2573 fn kinds(&self) -> &'static [NodeKind] {
2574 &[NodeKind::Call]
2575 }
2576 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2577 let _at_stmt_pos = ctx
2579 .parent(id)
2580 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2581 .unwrap_or(false);
2582 let lang = ctx.lang;
2583 if lang.kind(id) != NodeKind::Call {
2584 return None;
2585 }
2586 let ch = lang.children(id).to_vec();
2587 if ch.len() != 1 {
2588 return None;
2589 }
2590 let NodeData::Member { name } = lang.data(ch[0]) else {
2591 return None;
2592 };
2593 if lang.sn(*name) != "length" {
2594 return None;
2595 }
2596 let recv = lang.children(ch[0])[0];
2597 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2598 let with = lang.build_int(s.chars().count() as i64, false);
2599 return Some(Edit::Replace {
2600 target: id,
2601 with,
2602 });
2603 }
2604 None
2605 }
2606}
2607
2608
2609pub struct LiteralEval;
2622
2623impl Rule<JavaAst> for LiteralEval {
2624 fn name(&self) -> &'static str {
2625 "literal_eval"
2626 }
2627 fn kinds(&self) -> &'static [NodeKind] {
2628 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2629 }
2630 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2631 let root = ctx.root();
2632 let lang = ctx.lang;
2633 let edit = match lang.kind(id) {
2634 NodeKind::Call => self.eval_call(&mut *lang, id),
2635 NodeKind::Index => self.eval_index(&mut *lang, id),
2636 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2637 _ => None,
2638 };
2639 if let Some(Edit::Replace { with, .. }) = &edit {
2643 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2644 && lang.has_string_identity_compare(root)
2645 {
2646 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2647 eprintln!("[gate] blocked literal_eval at {id:?}");
2648 }
2649 return None;
2650 }
2651 }
2652 edit
2653 }
2654}
2655
2656impl LiteralEval {
2657 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2658 let ch = lang.children(id).to_vec();
2659 let callee = ch[0];
2660 let NodeData::Member { name: method } = lang.data(callee) else {
2661 return None;
2662 };
2663 let method = *method;
2664 let recv = lang.children(callee)[0];
2665 let args: Vec<Lit> = ch[1..]
2667 .iter()
2668 .map(|&a| litref_to_lit(lang.literal(a)))
2669 .collect::<Option<Vec<_>>>()?;
2670
2671 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2672 eval_str_method(s, lang.sn(method), &args)
2673 } else if let Some(cls) = lang.var_name(recv) {
2674 eval_static_method(cls, lang.sn(method), &args)
2675 } else {
2676 None
2677 };
2678 let lit = result?;
2679 let with = lit_to_node(lang, &lit);
2680 Some(Edit::Replace {
2681 target: id,
2682 with,
2683 })
2684 }
2685
2686 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2687 let ch = lang.children(id).to_vec();
2689 let arr = match lang.kind(ch[0]) {
2691 NodeKind::ArrayLit => ch[0],
2692 NodeKind::NewArray => {
2693 let nc = lang.children(ch[0]);
2694 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2695 return None;
2696 };
2697 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2698 return None;
2699 }
2700 nc[0]
2701 }
2702 _ => return None,
2703 };
2704 let idx = lang.literal(ch[1])?.as_int()?;
2705 let elems = lang.children(arr).to_vec();
2706 if idx < 0 || idx >= elems.len() as i64 {
2707 return None;
2708 }
2709 Some(Edit::Replace {
2710 target: id,
2711 with: elems[idx as usize],
2712 })
2713 }
2714}
2715
2716fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2717 Some(match l? {
2718 LitRef::Str(s) => Lit::Str(s.to_string()),
2719 LitRef::Int(v) => Lit::Int(v),
2720 LitRef::Long(v) => Lit::Long(v),
2721 LitRef::Bool(b) => Lit::Bool(b),
2722 LitRef::Char(c) => Lit::Char(c),
2723 LitRef::Float(v) => Lit::Float(v),
2724 LitRef::Double(v) => Lit::Double(v),
2725 LitRef::Null => return None,
2726 })
2727}
2728
2729fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2730 match lit {
2731 Lit::Str(s) => lang.build_str(s),
2732 Lit::Int(v) => lang.build_int(*v, false),
2733 Lit::Long(v) => lang.build_int(*v, true),
2734 Lit::Bool(b) => lang.build_bool(*b),
2735 Lit::Char(c) => lang.build_char(*c),
2736 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2737 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2738 _ => lang.lit(Lit::Null),
2739 }
2740}
2741
2742fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2744 let NodeData::Cast { ty } = lang.data(id) else {
2745 return None;
2746 };
2747 let ty = ty.clone();
2748 let inner = lang.children(id)[0];
2749 let lit = litref_to_lit(lang.literal(inner))?;
2750 let with = match (&ty, &lit) {
2751 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2752 lang.build_char(char::from_u32(*v as u32)?)
2753 }
2754 (JType::Char, Lit::Char(_)) => inner,
2755 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2756 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2757 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2758 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2759 lang.build_int(*v, false)
2760 }
2761 (JType::Int, Lit::Int(_)) => inner,
2762 _ => return None,
2763 };
2764 Some(Edit::Replace {
2765 target: id,
2766 with,
2767 })
2768}
2769
2770fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2771 let chars: Vec<char> = s.chars().collect();
2772 let n = chars.len() as i64;
2773 Some(match (method, args) {
2774 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2775 ("intern", []) => Lit::Str(s.to_string()),
2777 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2778 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2779 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2780 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2781 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2782 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2783 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2784 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2785 }
2786 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2787 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2788 ),
2789 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2790 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2791 ),
2792 ("length", []) => Lit::Int(n),
2793 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2794 Lit::Char(chars[*i as usize])
2795 }
2796 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2797 Lit::Str(s.chars().skip(*b as usize).collect())
2798 }
2799 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2800 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2801 }
2802 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2803 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2804 }
2805 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2806 Lit::Str(s.replace(a.as_str(), b))
2807 }
2808 ("hashCode", []) => Lit::Int(
2810 s.chars()
2811 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2812 as i64,
2813 ),
2814 _ => return None,
2815 })
2816}
2817
2818fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2819 let cls = cls.rsplit('.').next().unwrap_or(cls);
2820 Some(match (cls, method, args) {
2821 ("Integer", "parseInt", [Lit::Str(s)]) => {
2822 Lit::Int(s.trim().parse::<i64>().ok()?)
2823 }
2824 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2825 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2826 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2827 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2828 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2829 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2830 Lit::Bool(s.eq_ignore_ascii_case("true"))
2831 }
2832 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2833 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2834 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2835 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2836 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2837 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2838 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2839 let fmt = match &fmt_args[0] {
2840 Lit::Str(s) => s.clone(),
2841 _ => return None,
2842 };
2843 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2844 Lit::Str(out)
2845 }
2846 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2847 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2848 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2849 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2850 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2851 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2852 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2853 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2854 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2855 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2856 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2857 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2858 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2859 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2860 _ => return None,
2861 })
2862}
2863
2864fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2866 let mut out = String::new();
2867 let mut it = fmt.chars().peekable();
2868 let mut arg_i = 0usize;
2869 while let Some(c) = it.next() {
2870 if c != '%' {
2871 out.push(c);
2872 continue;
2873 }
2874 match it.next()? {
2875 '%' => out.push('%'),
2876 's' => {
2877 let a = args.get(arg_i)?;
2878 arg_i += 1;
2879 match a {
2880 Lit::Str(s) => out.push_str(s),
2881 Lit::Char(ch) => out.push(*ch),
2882 Lit::Bool(b) => out.push_str(&b.to_string()),
2883 _ => out.push_str(&lit_debug_str(a)),
2884 }
2885 }
2886 'd' => {
2887 let a = args.get(arg_i)?;
2888 arg_i += 1;
2889 match a {
2890 Lit::Int(v) => out.push_str(&v.to_string()),
2891 Lit::Long(v) => out.push_str(&v.to_string()),
2892 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
2893 _ => return None,
2894 }
2895 }
2896 _ => return None,
2898 }
2899 }
2900 if arg_i != args.len() {
2901 return None; }
2903 Some(out)
2904}
2905
2906fn lit_debug_str(l: &Lit) -> String {
2907 match l {
2908 Lit::Int(v) => v.to_string(),
2909 Lit::Long(v) => v.to_string(),
2910 _ => "?".into(),
2911 }
2912}
2913
2914fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
2937 match ast.data(m) {
2938 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
2939 _ => None,
2940 }
2941}
2942
2943pub struct UrlDecodeFold;
2944
2945impl Rule<JavaAst> for UrlDecodeFold {
2946 fn name(&self) -> &'static str {
2947 "url_decode_fold"
2948 }
2949 fn kinds(&self) -> &'static [NodeKind] {
2950 &[NodeKind::Call]
2951 }
2952 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2953 let lang = ctx.lang;
2954 let ch = lang.children(id).to_vec();
2957 if ch.len() != 3 {
2958 return None; }
2960 let callee = ch[0];
2961 if lang.kind(callee) != NodeKind::Member {
2962 return None;
2963 }
2964 if member_name(lang, callee).as_deref() != Some("decode") {
2965 return None;
2966 }
2967 let recv = *lang.children(callee).first()?;
2969 let recv_name = match lang.kind(recv) {
2970 NodeKind::Member => member_name(lang, recv)?,
2971 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
2972 _ => return None,
2973 };
2974 if !recv_name.ends_with("URLDecoder") {
2975 return None;
2976 }
2977 let s_lit = match lang.literal(ch[1])? {
2978 LitRef::Str(s) => s.to_string(),
2979 _ => return None,
2980 };
2981 let cs = match lang.literal(ch[2])? {
2983 LitRef::Str(s) => s.to_string(),
2984 _ => return None,
2985 };
2986 if cs != "UTF-8" {
2987 return None;
2988 }
2989 if s_lit.len() > 8192 {
2991 return None;
2992 }
2993 let decoded = url_decode(&s_lit)?;
2994 if decoded.chars().any(|c| {
2996 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
2997 }) {
2998 return None;
2999 }
3000 let lit = lang.lit(Lit::Str(decoded));
3001 Some(Edit::Replace { target: id, with: lit })
3002 }
3003}
3004
3005fn url_decode(s: &str) -> Option<String> {
3008 let b = s.as_bytes();
3009 let mut out: Vec<u8> = Vec::with_capacity(b.len());
3010 let mut i = 0usize;
3011 while i < b.len() {
3012 match b[i] {
3013 b'+' => {
3014 out.push(b' ');
3015 i += 1;
3016 }
3017 b'%' => {
3018 if i + 3 > b.len() {
3019 return None;
3020 }
3021 let hi = hex_val(b[i + 1])?;
3022 let lo = hex_val(b[i + 2])?;
3023 out.push(hi * 16 + lo);
3024 i += 3;
3025 }
3026 c => {
3027 out.push(c);
3028 i += 1;
3029 }
3030 }
3031 }
3032 String::from_utf8(out).ok()
3033}
3034
3035fn hex_val(c: u8) -> Option<u8> {
3036 match c {
3037 b'0'..=b'9' => Some(c - b'0'),
3038 b'a'..=b'f' => Some(c - b'a' + 10),
3039 b'A'..=b'F' => Some(c - b'A' + 10),
3040 _ => None,
3041 }
3042}
3043
3044pub struct Base64NewStringFold;
3045
3046impl Rule<JavaAst> for Base64NewStringFold {
3047 fn name(&self) -> &'static str {
3048 "base64_new_string_fold"
3049 }
3050 fn kinds(&self) -> &'static [NodeKind] {
3051 &[NodeKind::New]
3052 }
3053 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3054 let root = ctx.root();
3055 let lang = ctx.lang;
3056 let NodeData::New { ty, .. } = lang.data(id) else {
3057 return None;
3058 };
3059 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
3060 return None;
3061 }
3062 if lang.has_string_identity_compare(root) {
3064 return None;
3065 }
3066 let args = lang.children(id).to_vec();
3067 if args.len() != 1 {
3068 return None;
3069 }
3070 if lang.kind(args[0]) != NodeKind::Call {
3072 return None;
3073 }
3074 let dc = lang.children(args[0]).to_vec();
3075 if dc.len() != 2 {
3076 return None;
3077 }
3078 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
3079 return None;
3080 };
3081 if lang.sn(*dn) != "decode" {
3082 return None;
3083 }
3084 let recv = lang.children(dc[0])[0];
3086 if lang.kind(recv) != NodeKind::Call {
3087 return None;
3088 }
3089 let gc = lang.children(recv).to_vec();
3090 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
3091 return None;
3092 };
3093 let url_safe = match lang.sn(*gn) {
3094 "getDecoder" => false,
3095 "getUrlDecoder" => true,
3096 _ => return None,
3097 };
3098 let owner = lang.children(gc[0])[0];
3100 let owner_ok = match lang.data(owner) {
3101 NodeData::Member { name } => lang.sn(*name) == "Base64",
3102 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
3103 };
3104 if !owner_ok {
3105 return None;
3106 }
3107 let LitRef::Str(b64) = lang.literal(dc[1])? else {
3108 return None;
3109 };
3110 let bytes = base64_decode(b64, url_safe)?;
3111 let text = String::from_utf8(bytes).ok()?;
3112 let with = lang.build_str(&text);
3113 Some(Edit::Replace {
3114 target: id,
3115 with,
3116 })
3117 }
3118}
3119
3120fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
3122 let val = |c: char| -> Option<u32> {
3123 match c {
3124 'A'..='Z' => Some(c as u32 - 'A' as u32),
3125 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
3126 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
3127 '+' if !url_safe => Some(62),
3128 '/' if !url_safe => Some(63),
3129 '-' if url_safe => Some(62),
3130 '_' if url_safe => Some(63),
3131 _ => None,
3132 }
3133 };
3134 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3135 if clean.len() % 4 == 1 {
3136 return None;
3137 }
3138 let mut out = Vec::new();
3139 let mut buf = 0u32;
3140 let mut bits = 0u32;
3141 for c in clean.chars() {
3142 buf = (buf << 6) | val(c)?;
3143 bits += 6;
3144 if bits >= 8 {
3145 bits -= 8;
3146 out.push((buf >> bits) as u8);
3147 buf &= (1 << bits) - 1;
3148 }
3149 }
3150 Some(out)
3151}
3152
3153
3154pub struct CffRecover;
3176
3177struct CffCase {
3178 body: Vec<JavaId>,
3179 tail: CffTail,
3180}
3181
3182enum CffTail {
3183 Goto(i64),
3184 Cond { cond: JavaId, a: i64, b: i64 },
3185 Term(JavaId),
3186 Exit,
3187}
3188
3189#[derive(Clone, Copy, PartialEq, Debug)]
3190enum Flow {
3191 Done,
3192 Stop,
3193 Cycle(usize),
3194 Cond(i64),
3195 External(i64),
3196}
3197
3198struct CffCtx<'a> {
3199 lang: &'a mut JavaAst,
3200 cases: std::collections::HashMap<i64, CffCase>,
3201 default_term: Option<Vec<JavaId>>,
3203 used_default: bool,
3204 reached: std::collections::HashSet<i64>,
3206 emitted: std::collections::HashSet<i64>,
3208}
3209
3210impl Rule<JavaAst> for CffRecover {
3211 fn name(&self) -> &'static str {
3212 "cff_recover"
3213 }
3214 fn kinds(&self) -> &'static [NodeKind] {
3215 &[NodeKind::While]
3216 }
3217 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3218 let parent = ctx.parent(id)?;
3219 let idx = ctx.index(id)?;
3220 let lang = ctx.lang;
3221 if lang.kind(id) != NodeKind::While {
3222 return None;
3223 }
3224 let wch = lang.children(id).to_vec();
3225 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3226 return None;
3227 }
3228 if lang.kind(wch[1]) != NodeKind::Block {
3229 return None;
3230 }
3231 let body_ch = lang.children(wch[1]).to_vec();
3232 let (switch, exit_if) = match body_ch.len() {
3233 1 => (body_ch[0], None),
3234 2 => (body_ch[0], Some(body_ch[1])),
3235 _ => return None,
3236 };
3237 if lang.kind(switch) != NodeKind::Switch {
3238 return None;
3239 }
3240 let sch = lang.children(switch).to_vec();
3241 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3242 return None;
3243 }
3244 let v = lang.var_name(sch[0])?.to_string();
3245
3246 let sentinel = match exit_if {
3248 None => None,
3249 Some(ei) => {
3250 if lang.kind(ei) != NodeKind::If {
3251 return None;
3252 }
3253 let ich = lang.children(ei).to_vec();
3254 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3255 return None;
3256 }
3257 let cch = lang.children(ich[0]).to_vec();
3258 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3259 && lang.var_name(cch[0]) == Some(v.as_str())
3260 {
3261 lang.literal(cch[1]).and_then(|x| x.as_int())
3262 } else if lang.kind(cch[1]) == NodeKind::VarRef
3263 && lang.var_name(cch[1]) == Some(v.as_str())
3264 {
3265 lang.literal(cch[0]).and_then(|x| x.as_int())
3266 } else {
3267 return None;
3268 }?;
3269 let then_ok = match lang.kind(ich[1]) {
3270 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3271 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3272 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3273 }
3274 _ => false,
3275 };
3276 if !then_ok {
3277 return None;
3278 }
3279 Some(sent)
3280 }
3281 };
3282
3283 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3285 let mut default_term: Option<Vec<JavaId>> = None;
3286 for &c in &sch[1..] {
3287 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3288 return None;
3289 };
3290 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3291 let ch = lang.children(c).to_vec();
3292 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3293 if !arrow
3295 && stmts
3296 .last()
3297 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3298 {
3299 stmts.pop();
3300 }
3301 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3302 let mut inner = lang.children(stmts[0]).to_vec();
3303 if !arrow
3304 && inner
3305 .last()
3306 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3307 {
3308 inner.pop();
3309 }
3310 stmts = inner;
3311 }
3312 if is_default {
3313 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3314 default_term = Some(stmts);
3315 continue;
3316 }
3317 return None;
3318 }
3319 if labels != 1 {
3321 return None;
3322 }
3323 let state = match lang.literal(ch[0]) {
3324 Some(LitRef::Int(v)) => v,
3325 _ => return None,
3326 };
3327 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3328 for &st in &stmts {
3330 if subtree_has_var(lang, st, &v) {
3331 return None;
3332 }
3333 }
3334 cases.insert(state, CffCase { body: stmts, tail });
3335 }
3336 if cases.is_empty() {
3337 return None;
3338 }
3339
3340 if idx == 0 {
3343 return None;
3344 }
3345 let block_ch = lang.children(parent).to_vec();
3346 let mut init = None;
3347 let mut init_idx = idx;
3348 {
3349 let mut up = idx;
3350 for _ in 0..8 {
3351 if up == 0 {
3352 break;
3353 }
3354 up -= 1;
3355 let st = block_ch[up];
3356 match scan_cff_event(lang, st, &v) {
3357 Some(CffEvent::Read) => return None,
3358 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3359 init = parse_cff_init(lang, &v, st);
3360 init_idx = up;
3361 break;
3362 }
3363 None => continue,
3364 }
3365 }
3366 }
3367 let init = init?;
3368
3369 for &st in &block_ch[idx + 1..] {
3371 if subtree_has_var(lang, st, &v) {
3372 return None;
3373 }
3374 }
3375
3376 let mut to_exit: Vec<i64> = Vec::new();
3378 for (k, ci) in cases.iter() {
3379 if let CffTail::Goto(g) = ci.tail {
3380 if !cases.contains_key(&g) {
3381 if !(sentinel == Some(g) || default_term.is_some()) {
3382 return None;
3383 }
3384 to_exit.push(*k);
3385 }
3386 }
3387 }
3388 for k in to_exit {
3389 if let Some(ci) = cases.get_mut(&k) {
3390 ci.tail = CffTail::Exit;
3391 }
3392 }
3393
3394 let mut c = CffCtx {
3395 lang,
3396 cases,
3397 default_term,
3398 used_default: false,
3399 reached: Default::default(),
3400 emitted: Default::default(),
3401 };
3402 let stmts = cff_run(&mut c, init)?;
3403
3404 if stmts.is_empty() {
3407 return None;
3408 }
3409
3410 Some(Edit::Multi(vec![
3413 Edit::Splice {
3414 node: parent,
3415 index: idx,
3416 remove: 1,
3417 insert: stmts,
3418 },
3419 Edit::Delete {
3420 node: block_ch[init_idx],
3421 },
3422 ]))
3423 }
3424}
3425
3426fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3427 let last = *stmts.last()?;
3428 if lang.kind(last) == NodeKind::Return {
3429 stmts.pop();
3430 return Some(CffTail::Term(last));
3431 }
3432 let assign_of = |n: JavaId| -> Option<JavaId> {
3433 let inner = match lang.kind(n) {
3434 NodeKind::Assign => n,
3435 NodeKind::ExprStmt => {
3436 let ch = lang.children(n);
3437 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3438 ch[0]
3439 } else {
3440 return None;
3441 }
3442 }
3443 _ => return None,
3444 };
3445 if lang.assign_op(inner).is_some() {
3446 return None;
3447 }
3448 let ch = lang.children(inner).to_vec();
3449 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3450 return None;
3451 }
3452 Some(inner)
3453 };
3454 if let Some(assign) = assign_of(last) {
3455 let value = lang.children(assign)[1];
3456 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3457 stmts.pop();
3458 return Some(CffTail::Goto(k));
3459 }
3460 if lang.kind(value) == NodeKind::Ternary {
3461 let tch = lang.children(value).to_vec();
3462 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3463 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3464 stmts.pop();
3465 return Some(CffTail::Cond { cond: tch[0], a, b });
3466 }
3467 return None;
3468 }
3469 if lang.kind(last) == NodeKind::If {
3470 let ich = lang.children(last).to_vec();
3471 if ich.len() != 3 {
3472 return None;
3473 }
3474 let arm = |n: JavaId| -> Option<i64> {
3475 let target = match lang.kind(n) {
3476 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3477 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3478 _ => return None,
3479 };
3480 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3481 };
3482 let a = arm(ich[1])?;
3483 let b = arm(ich[2])?;
3484 stmts.pop();
3485 return Some(CffTail::Cond { cond: ich[0], a, b });
3486 }
3487 None
3488}
3489
3490
3491enum CffEvent {
3493 Read,
3494 Write,
3495 Decl,
3497}
3498
3499fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3500 match lang.kind(node) {
3501 NodeKind::VarRef => {
3502 if lang.var_name(node) == Some(name) {
3503 Some(CffEvent::Read)
3504 } else {
3505 None
3506 }
3507 }
3508 NodeKind::VarDecl => {
3509 if lang.var_name(node) == Some(name) {
3510 Some(CffEvent::Decl)
3511 } else {
3512 for &c in lang.children(node) {
3513 if let Some(e) = scan_cff_event(lang, c, name) {
3514 return Some(e);
3515 }
3516 }
3517 None
3518 }
3519 }
3520 NodeKind::Assign => {
3521 let ch = lang.children(node);
3522 if let Some(&val) = ch.get(1) {
3523 if let Some(e) = scan_cff_event(lang, val, name) {
3524 if matches!(e, CffEvent::Read) {
3525 return Some(e);
3526 }
3527 }
3528 }
3529 if lang.assign_op(node).is_none() {
3530 if let Some(&t) = ch.first() {
3531 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3532 return Some(CffEvent::Write);
3533 }
3534 }
3535 }
3536 None
3537 }
3538 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3539 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3540 NodeKind::Binary => {
3541 let op = lang.bin_op(node)?;
3542 let ch = lang.children(node);
3543 if op.is_short_circuit() {
3544 scan_cff_event(lang, *ch.first()?, name)
3545 } else {
3546 for &c in ch {
3547 if let Some(e) = scan_cff_event(lang, c, name) {
3548 return Some(e);
3549 }
3550 }
3551 None
3552 }
3553 }
3554 _ => {
3555 for &c in lang.children(node) {
3556 if let Some(e) = scan_cff_event(lang, c, name) {
3557 return Some(e);
3558 }
3559 }
3560 None
3561 }
3562 }
3563}
3564
3565fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3566 match lang.kind(stmt) {
3567 NodeKind::VarDecl => {
3568 let ch = lang.children(stmt).to_vec();
3569 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3570 return None;
3571 }
3572 lang.literal(ch[0]).and_then(|x| x.as_int())
3573 }
3574 NodeKind::Assign => {
3575 if lang.assign_op(stmt).is_some() {
3576 return None;
3577 }
3578 let ch = lang.children(stmt).to_vec();
3579 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3580 return None;
3581 }
3582 lang.literal(ch[1]).and_then(|x| x.as_int())
3583 }
3584 NodeKind::ExprStmt => {
3585 let ch = lang.children(stmt).to_vec();
3586 if ch.len() == 1 {
3587 parse_cff_init(lang, v, ch[0])
3588 } else {
3589 None
3590 }
3591 }
3592 _ => None,
3593 }
3594}
3595
3596fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3597 for (s, stmts) in pairs {
3598 c.emitted.insert(*s);
3599 out.extend(stmts.iter().copied());
3600 }
3601}
3602
3603fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3604 for (s, stmts) in pairs {
3605 if *s == m {
3606 break;
3607 }
3608 out.extend(stmts.iter().copied());
3609 }
3610}
3611
3612fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3613 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3614 pairs[pos..].to_vec()
3615}
3616
3617fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3619 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3620 let mut cur = start;
3621 let mut guard = 0usize;
3622 loop {
3623 guard += 1;
3624 if guard > 10_000 {
3625 return None;
3626 }
3627 if Some(cur) == stop {
3628 return Some((path, Flow::Stop));
3629 }
3630 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3631 return Some((path, Flow::Cycle(i)));
3632 }
3633 if c.emitted.contains(&cur) {
3634 return Some((path, Flow::External(cur)));
3635 }
3636 c.reached.insert(cur);
3637 let ci = c.cases.get(&cur)?;
3638 let mut body = ci.body.clone();
3639 match ci.tail {
3640 CffTail::Goto(k) => {
3641 path.push((cur, body));
3642 cur = k;
3643 }
3644 CffTail::Term(r) => {
3645 body.push(r);
3646 path.push((cur, body));
3647 return Some((path, Flow::Done));
3648 }
3649 CffTail::Exit => {
3650 if c.default_term.is_some() {
3651 if let Some(dt) = c.default_term.clone() {
3652 body.extend(dt);
3653 c.used_default = true;
3654 }
3655 }
3656 path.push((cur, body));
3657 return Some((path, Flow::Done));
3658 }
3659 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3660 }
3661 }
3662}
3663
3664fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3666 let (path, flow) = collect(c, init, None)?;
3667 cff_structure(c, path, flow)
3668}
3669
3670fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3672 let (path, flow) = collect(c, start, None)?;
3673 cff_structure(c, path, flow)
3674}
3675
3676fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3680 let mut stmts: Vec<JavaId> = Vec::new();
3681 loop {
3682 match flow {
3683 Flow::Done => {
3684 flatten(&path, &mut stmts, c);
3685 return Some(stmts);
3686 }
3687 Flow::Cycle(i) => {
3688 flatten(&path[..i], &mut stmts, c);
3689 let mut suffix = Vec::new();
3690 flatten(&path[i..], &mut suffix, c);
3691 let inner = c.lang.build_block(suffix);
3692 let cond = c.lang.build_bool(true);
3693 let w = c.lang.while_(cond, inner);
3694 stmts.push(w);
3695 return Some(stmts);
3696 }
3697 Flow::Stop | Flow::External(_) => return None,
3698 Flow::Cond(state) => {
3699 flatten(&path, &mut stmts, c);
3700 path.clear();
3701 let ci = c.cases.get(&state)?;
3702 stmts.extend(ci.body.iter().copied());
3703 c.emitted.insert(state);
3704 let CffTail::Cond { cond, a, b } = ci.tail else {
3705 return None;
3706 };
3707 let (pa, fa) = collect(c, a, Some(state))?;
3709 if fa == Flow::Stop {
3710 let mut lb = Vec::new();
3711 flatten(&pa, &mut lb, c);
3712 let inner = c.lang.build_block(lb);
3713 let w = c.lang.while_(cond, inner);
3714 stmts.push(w);
3715 let (p2, f2) = collect(c, b, None)?;
3716 path = p2;
3717 flow = f2;
3718 continue;
3719 }
3720 let (pb, fb) = collect(c, b, Some(state))?;
3721 if fb == Flow::Stop {
3722 let mut lb = Vec::new();
3723 flatten(&pb, &mut lb, c);
3724 let inner = c.lang.build_block(lb);
3725 let nc = c.lang.build_unary(UnOp::Not, cond);
3726 let w = c.lang.while_(nc, inner);
3727 stmts.push(w);
3728 let (p2, f2) = collect(c, a, None)?;
3729 path = p2;
3730 flow = f2;
3731 continue;
3732 }
3733 let common = pa
3735 .iter()
3736 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3737 .map(|(s, _)| *s);
3738 let same_end = match (fa, fb) {
3739 (Flow::Done, Flow::Done) => true,
3740 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3741 _ => false,
3742 };
3743 match common {
3744 Some(m) if same_end => {
3745 let mut pre_a = Vec::new();
3746 let mut pre_b = Vec::new();
3747 flatten_prefix(&pa, m, &mut pre_a);
3748 flatten_prefix(&pb, m, &mut pre_b);
3749 let ba = c.lang.build_block(pre_a);
3750 let bb = c.lang.build_block(pre_b);
3751 let ifs = c.lang.if_(cond, ba, Some(bb));
3752 stmts.push(ifs);
3753 path = suffix_pairs(&pa, m);
3754 flow = fa;
3755 continue;
3756 }
3757 Some(_) => return None,
3758 None => {
3759 let mut fa_stmts = Vec::new();
3761 let mut fb_stmts = Vec::new();
3762 flatten(&pa, &mut fa_stmts, c);
3763 flatten(&pb, &mut fb_stmts, c);
3764 match (fa, fb) {
3765 (Flow::Done, Flow::Done) => {
3766 let ba = c.lang.build_block(fa_stmts);
3767 let bb = c.lang.build_block(fb_stmts);
3768 let ifs = c.lang.if_(cond, ba, Some(bb));
3769 stmts.push(ifs);
3770 return Some(stmts);
3771 }
3772 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3774 let ba = c.lang.build_block(fa_stmts);
3775 let bb = c.lang.build_block(fb_stmts);
3776 let ifs = c.lang.if_(cond, ba, Some(bb));
3777 stmts.push(ifs);
3778 let (p2, f2) = collect(c, x, None)?;
3779 path = p2;
3780 flow = f2;
3781 continue;
3782 }
3783 (Flow::Done, Flow::Cond(x)) => {
3785 let cont = cff_run_from(c, x)?;
3786 fb_stmts.extend(cont);
3787 let ba = c.lang.build_block(fa_stmts);
3788 let bb = c.lang.build_block(fb_stmts);
3789 let ifs = c.lang.if_(cond, ba, Some(bb));
3790 stmts.push(ifs);
3791 return Some(stmts);
3792 }
3793 (Flow::Cond(x), Flow::Done) => {
3794 let cont = cff_run_from(c, x)?;
3795 fa_stmts.extend(cont);
3796 let ba = c.lang.build_block(fa_stmts);
3797 let bb = c.lang.build_block(fb_stmts);
3798 let ifs = c.lang.if_(cond, ba, Some(bb));
3799 stmts.push(ifs);
3800 return Some(stmts);
3801 }
3802 _ => return None,
3803 }
3804 }
3805 }
3806 }
3807 }
3808 }
3809}
3810
3811
3812
3813pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3823 ast.const_fields.clear();
3824 ast.const_scalars.clear();
3825 ast.inline_methods.clear();
3826 ast.field_types.clear();
3827 ast.final_fields.clear();
3828 ast.raw_generic_names = {
3831 let mut m: std::collections::HashSet<String> = [
3832 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3833 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3834 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3835 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3836 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3837 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3838 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3839 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3840 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3841 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3843 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3844 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3845 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3846 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3847 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3848 ]
3849 .iter()
3850 .map(|x| x.to_string())
3851 .collect();
3852 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3853 if !ty.ty_params.trim().is_empty() {
3854 m.insert(ty.name.clone());
3855 }
3856 for mem in &ty.members {
3857 if let Member::Type(t) = mem {
3858 collect_generics(t, m);
3859 }
3860 }
3861 }
3862 for ty in &unit.types {
3863 collect_generics(ty, &mut m);
3864 }
3865 m
3866 };
3867 ast.unit_shadows_lang = {
3868 fn decl_shadows(ty: &TypeDecl) -> bool {
3869 if ty.name == "String" || ty.name == "StringBuilder" {
3870 return true;
3871 }
3872 ty.members.iter().any(|m| match m {
3873 Member::Type(t) => decl_shadows(t),
3874 _ => false,
3875 })
3876 }
3877 unit.types.iter().any(decl_shadows)
3878 };
3879
3880 let mut shadowed: std::collections::HashSet<String> = Default::default();
3885 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3886 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3887 let mut bodies: Vec<JavaId> = Vec::new();
3888 for ty in &unit.types {
3889 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3890 }
3891 for ty in &unit.types {
3894 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
3895 }
3896 for &root in &bodies {
3900 let mut stack = vec![root];
3901 while let Some(n) = stack.pop() {
3902 if ast.kind(n) == NodeKind::VarDecl {
3903 if let Some(nm) = ast.var_name(n) {
3904 shadowed.insert(nm.to_string());
3905 }
3906 }
3907 match ast.data(n) {
3908 NodeData::Assign { .. } => {
3909 let t = ast.children(n)[0];
3910 if ast.kind(t) == NodeKind::Member {
3911 if let NodeData::Member { name: sym } = ast.data(t) {
3912 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
3913 }
3914 }
3915 let mut ts = vec![t];
3916 while let Some(x) = ts.pop() {
3917 if ast.kind(x) == NodeKind::VarRef {
3918 if let Some(nm) = ast.var_name(x) {
3919 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3920 }
3921 }
3922 for &c in ast.children(x) {
3923 ts.push(c);
3924 }
3925 }
3926 }
3927 NodeData::Unary { op } if op.is_incdec() => {
3928 let t = ast.children(n)[0];
3929 if ast.kind(t) == NodeKind::VarRef {
3930 if let Some(nm) = ast.var_name(t) {
3931 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3932 }
3933 }
3934 }
3935 _ => {}
3936 }
3937 for &c in ast.children(n) {
3938 stack.push(c);
3939 }
3940 }
3941 }
3942
3943 for ty in &unit.types {
3945 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
3947 collect_own_clinit_lits(ast, ty, &mut own_lits);
3948 for m in &ty.members {
3949 if let Member::Field { mods, ty: fty, declarators } = m {
3950 let words: Vec<&str> = mods.split_whitespace().collect();
3951 let is_static = words.contains(&"static");
3952 let is_final = words.contains(&"final");
3953 let is_private = words.contains(&"private");
3954 for d in declarators {
3955 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
3959 continue;
3960 }
3961 let init = match d.init {
3962 Some(init) => {
3963 if !is_final
3965 || !is_const_init(ast, init)
3966 || shadowed.contains(&d.name)
3967 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
3968 {
3969 continue;
3970 }
3971 init
3972 }
3973 None => {
3974 if !is_static
3981 || (!is_final && !is_private)
3982 || shadowed.contains(&d.name)
3983 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
3984 {
3985 continue;
3986 }
3987 match own_lits.get(&d.name) {
3988 Some(&init) if is_const_init(ast, init) => init,
3989 _ => continue,
3990 }
3991 }
3992 };
3993 if ast.const_fields.contains_key(&d.name) {
3995 ast.const_fields.remove(&d.name);
3996 ast.const_scalars.remove(&d.name);
3997 } else {
3998 let scalar_ok = scalar_lit_of(ast, init)
3999 .is_some_and(|l| lit_type_matches_decl(fty, l));
4000 ast.const_fields.insert(d.name.clone(), init);
4001 if scalar_ok {
4002 ast.const_scalars.insert(d.name.clone());
4003 }
4004 }
4005 }
4006 }
4007 }
4008 }
4009
4010 {
4017 let identity_assert = [
4018 "assertSame",
4019 "assertNotSame",
4020 "isSameInstanceAs",
4021 "isNotSameInstanceAs",
4022 "referenceEquals",
4023 "same",
4024 ];
4025 let mut hit = false;
4026 for root in &bodies {
4027 let mut stack = vec![*root];
4028 while let Some(n) = stack.pop() {
4029 if let NodeData::Call = ast.data(n) {
4030 if let Some(&callee) = ast.children(n).first() {
4031 let nm = match ast.data(callee) {
4032 NodeData::Member { name } => ast.sn(*name),
4033 NodeData::VarRef { name } => ast.sn(*name),
4034 _ => "",
4035 };
4036 if identity_assert.contains(&nm) {
4037 hit = true;
4038 break;
4039 }
4040 }
4041 }
4042 for &c in ast.children(n) {
4043 stack.push(c);
4044 }
4045 }
4046 if hit {
4047 break;
4048 }
4049 }
4050 ast.unit_string_identity =
4051 ast.any_string_identity(&bodies) || hit;
4052 }
4053
4054 let mut method_name_count: std::collections::HashMap<String, usize> =
4064 std::collections::HashMap::new();
4065 for_each_type(unit, &mut |ty: &TypeDecl| {
4066 for m in &ty.members {
4067 if let Member::Method { name, .. } = m {
4068 *method_name_count.entry(name.clone()).or_insert(0) += 1;
4069 }
4070 }
4071 });
4072 for ty in &unit.types {
4073 for m in &ty.members {
4074 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
4075 let words: Vec<&str> = mods.split_whitespace().collect();
4076 let non_overridable = words.contains(&"private")
4077 || words.contains(&"static")
4078 || words.contains(&"final");
4079 if !non_overridable {
4080 continue;
4081 }
4082 let ch = ast.children(*b).to_vec();
4083 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
4084 if let Some(&expr) = ast.children(ch[0]).first() {
4085 if params.len() <= 3
4086 && !subtree_has_anon(ast, expr)
4091 && !params.iter().any(|p| p.varargs)
4095 && !subtree_calls_self(ast, expr, name)
4096 && !subtree_has_type_sensitive(ast, expr)
4102 && method_name_count.get(name).copied() == Some(1)
4103 {
4104 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
4105 let owner = ty.name.rsplit('.').next().unwrap_or(&ty.name).to_string();
4106 ast.inline_methods.insert(name.clone(), (ps, expr, owner));
4107 }
4108 }
4109 }
4110 }
4111 }
4112 }
4113}
4114
4115fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
4117 match ast.kind(init) {
4118 NodeKind::Literal => true,
4119 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
4120 NodeKind::NewArray => {
4121 let ch = ast.children(init).to_vec();
4122 ch.len() == 1
4123 && ast.kind(ch[0]) == NodeKind::ArrayLit
4124 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
4125 }
4126 _ => false,
4127 }
4128}
4129
4130fn collect_bodies_recursive(
4134 ty: &TypeDecl,
4135 bodies: &mut Vec<JavaId>,
4136 shadowed: &mut std::collections::HashSet<String>,
4137 field_decls: &mut std::collections::HashMap<String, u32>,
4138) {
4139 if ty.kind == TypeKind::Enum {
4147 for c in &ty.enum_constants {
4148 if c.trim() == "," {
4149 continue;
4150 }
4151 let last = c.split('(').next().unwrap_or(c)
4152 .split_whitespace()
4153 .next_back()
4154 .unwrap_or("");
4155 let name: String = last
4156 .chars()
4157 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '$')
4158 .collect();
4159 if !name.is_empty() {
4160 *field_decls.entry(name).or_insert(0) += 1;
4161 }
4162 }
4163 }
4164 for m in &ty.members {
4165 match m {
4166 Member::Method { body: Some(b), params, .. }
4167 | Member::Constructor { body: Some(b), params, .. } => {
4168 bodies.push(*b);
4169 for p in params {
4170 shadowed.insert(p.name.clone());
4171 }
4172 }
4173 Member::Initializer { body, .. } => bodies.push(*body),
4174 Member::Field { declarators, .. } => {
4175 for d in declarators {
4176 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4177 if let Some(init) = d.init {
4178 bodies.push(init);
4179 }
4180 }
4181 }
4182 Member::Type(t) => {
4183 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4184 }
4185 _ => {}
4186 }
4187 }
4188}
4189
4190fn collect_field_types(
4192 ty: &TypeDecl,
4193 out: &mut std::collections::HashMap<String, JType>,
4194 finals: &mut std::collections::HashSet<String>,
4195) {
4196 for m in &ty.members {
4197 match m {
4198 Member::Field { mods, ty: fty, declarators } => {
4199 let is_final = mods.split_whitespace().any(|w| w == "final");
4200 for d in declarators {
4201 if out.contains_key(&d.name) {
4202 out.remove(&d.name);
4203 finals.remove(&d.name);
4204 } else {
4205 out.insert(d.name.clone(), fty.clone());
4206 if is_final {
4207 finals.insert(d.name.clone());
4208 }
4209 }
4210 }
4211 }
4212 Member::Type(t) => collect_field_types(t, out, finals),
4213 _ => {}
4214 }
4215 }
4216}
4217
4218fn collect_own_clinit_lits(
4223 ast: &JavaAst,
4224 ty: &TypeDecl,
4225 out: &mut std::collections::HashMap<String, JavaId>,
4226) {
4227 for m in &ty.members {
4228 if let Member::Initializer { is_static: true, body } = m {
4229 if ast.kind(*body) != NodeKind::Block {
4230 continue;
4231 }
4232 for &st in ast.children(*body) {
4233 let asg = match ast.kind(st) {
4234 NodeKind::ExprStmt => {
4235 let Some(&c) = ast.children(st).first() else {
4239 continue;
4240 };
4241 if ast.kind(c) == NodeKind::Assign {
4242 c
4243 } else {
4244 continue;
4245 }
4246 }
4247 NodeKind::Assign => st,
4248 _ => continue,
4249 };
4250 let ch = ast.children(asg).to_vec();
4251 if ast.kind(ch[0]) != NodeKind::VarRef {
4252 continue;
4253 }
4254 let Some(nm) = ast.var_name(ch[0]) else { continue };
4255 if is_const_init(ast, ch[1]) {
4256 out.insert(nm.to_string(), ch[1]);
4257 }
4258 }
4259 }
4260 }
4261}
4262
4263fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4265 if ast.kind(init) == NodeKind::Literal {
4266 ast.literal(init)
4267 } else {
4268 None
4269 }
4270}
4271
4272fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4277 use cure_engine::kind::LitRef;
4278 match (fty, lit) {
4279 (JType::Ref(r), LitRef::Str(_)) => {
4280 let base = r.split('<').next().unwrap_or(r).trim();
4281 base == "String" || base == "java.lang.String"
4282 }
4283 (JType::Char, LitRef::Char(_)) => true,
4284 (JType::Bool, LitRef::Bool(_)) => true,
4285 (JType::Int, LitRef::Int(_)) => true,
4286 (JType::Long, LitRef::Long(_)) => true,
4287 (JType::Float, LitRef::Float(_)) => true,
4288 (JType::Double, LitRef::Double(_)) => true,
4289 _ => false,
4290 }
4291}
4292
4293fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4297 let mut stack = vec![root];
4298 while let Some(n) = stack.pop() {
4299 match ast.kind(n) {
4300 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4301 _ => {}
4302 }
4303 for &c in ast.children(n) {
4304 stack.push(c);
4305 }
4306 }
4307 false
4308}
4309
4310fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4311 let mut stack = vec![root];
4312 while let Some(n) = stack.pop() {
4313 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4314 return true;
4315 }
4316 for &c in ast.children(n) {
4317 stack.push(c);
4318 }
4319 }
4320 false
4321}
4322
4323fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4324 let mut stack = vec![root];
4325 while let Some(n) = stack.pop() {
4326 if ast.kind(n) == NodeKind::Call {
4327 let callee = ast.children(n)[0];
4328 if ast.var_name(callee) == Some(name) {
4329 return true;
4330 }
4331 if let NodeData::Member { name: m } = ast.data(callee) {
4336 if ast.sn(*m) == name {
4337 return true;
4338 }
4339 }
4340 }
4341 for &c in ast.children(n) {
4342 stack.push(c);
4343 }
4344 }
4345 false
4346}
4347
4348pub struct StaticArrayIndexFold;
4353
4354impl Rule<JavaAst> for StaticArrayIndexFold {
4355 fn name(&self) -> &'static str {
4356 "static_array_index_fold"
4357 }
4358 fn kinds(&self) -> &'static [NodeKind] {
4359 &[NodeKind::Index]
4360 }
4361 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4362 let lang = ctx.lang;
4363 if lang.kind(id) != NodeKind::Index {
4364 return None;
4365 }
4366 let ch = lang.children(id).to_vec();
4367 if lang.kind(ch[0]) != NodeKind::VarRef {
4368 return None;
4369 }
4370 let name = lang.var_name(ch[0])?.to_string();
4371 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4372 let init = *lang.const_fields.get(&name)?;
4373 let arr = match lang.kind(init) {
4374 NodeKind::ArrayLit => init,
4375 NodeKind::NewArray => {
4376 let ic = lang.children(init).to_vec();
4377 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4378 ic[0]
4379 } else {
4380 return None;
4381 }
4382 }
4383 _ => return None,
4384 };
4385 let elems = lang.children(arr).to_vec();
4386 if idx < 0 || idx >= elems.len() as i64 {
4387 return None;
4388 }
4389 let elem = lang.copy_subtree(elems[idx as usize]);
4390 Some(Edit::Replace {
4391 target: id,
4392 with: elem,
4393 })
4394 }
4395}
4396
4397pub struct DeadPureCall;
4477
4478impl Rule<JavaAst> for DeadPureCall {
4479 fn name(&self) -> &'static str {
4480 "dead_pure_call"
4481 }
4482 fn kinds(&self) -> &'static [NodeKind] {
4483 &[NodeKind::ExprStmt]
4484 }
4485 fn structural(&self) -> bool {
4486 true
4487 }
4488 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4489 let lang = ctx.lang;
4490 if lang.kind(id) != NodeKind::ExprStmt {
4491 return None;
4492 }
4493 let Some(&call) = lang.children(id).first() else { return None };
4494 if lang.kind(call) != NodeKind::Call {
4495 return None;
4496 }
4497 let cch = lang.children(call).to_vec();
4498 let Some(&callee) = cch.first() else { return None };
4499 let (recv, name) = match lang.data(callee) {
4502 NodeData::Member { name } => {
4503 let Some(&recv_expr) = lang.children(callee).first() else { return None };
4504 let recv_name = match lang.data(recv_expr) {
4507 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4508 NodeData::Member { name } => lang.sn(*name).to_string(),
4509 _ => return None,
4510 };
4511 if !recv_name
4512 .chars()
4513 .next()
4514 .is_some_and(|c| c.is_uppercase())
4515 {
4516 return None;
4517 }
4518 (recv_name, lang.sn(*name))
4519 }
4520 _ => return None,
4521 };
4522 let args = &cch[1..];
4523 let pure = match (recv.as_str(), name) {
4524 ("Long" | "Integer" | "Float" | "Double" | "Short" | "Byte" | "Character", "compare") => {
4525 args.iter().all(|&a| match lang.data(a) {
4529 NodeData::Literal(
4530 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_),
4531 ) => true,
4532 NodeData::Literal(Lit::NumRaw { .. }) => true,
4534 NodeData::VarRef { .. } => {
4535 let t = lang.var_type(a);
4536 matches!(
4537 t,
4538 Some(JType::Int | JType::Long | JType::Float | JType::Double | JType::Char
4539 | JType::Short | JType::Byte)
4540 )
4541 }
4542 _ => false,
4543 })
4544 }
4545 ("Objects", "toString") => args.len() == 1,
4546 _ => false,
4547 };
4548 if !pure {
4549 return None;
4550 }
4551 Some(Edit::Delete { node: id })
4552 }
4553}
4554
4555pub struct CtorDelegationHoist;
4556
4557fn hoist_resolve_arg(
4562 lang: &mut JavaAst,
4563 assign: &std::collections::HashMap<String, JavaId>,
4564 a: JavaId,
4565 visiting: &mut std::collections::HashSet<String>,
4566) -> Option<JavaId> {
4567 let name = match lang.data(a) {
4568 NodeData::VarRef { name } => lang.sn(*name).to_string(),
4569 _ => return Some(a), };
4571 let Some(&expr) = assign.get(&name) else {
4573 return Some(a);
4574 };
4575 if !visiting.insert(name.clone()) {
4576 return None; }
4578 let r = hoist_subst(lang, assign, expr, visiting);
4579 visiting.remove(&name);
4580 r
4581}
4582
4583fn hoist_subst(
4585 lang: &mut JavaAst,
4586 assign: &std::collections::HashMap<String, JavaId>,
4587 e: JavaId,
4588 visiting: &mut std::collections::HashSet<String>,
4589) -> Option<JavaId> {
4590 match lang.data(e) {
4591 NodeData::VarRef { name } => {
4592 let n = lang.sn(*name).to_string();
4593 if let Some(&src) = assign.get(&n) {
4594 if !visiting.insert(n.clone()) {
4595 return None;
4596 }
4597 let r = hoist_subst(lang, assign, src, visiting);
4598 visiting.remove(&n);
4599 r
4600 } else {
4601 Some(e)
4602 }
4603 }
4604 _ => {
4605 let ch = lang.children(e).to_vec();
4606 let mut new_ch = Vec::with_capacity(ch.len());
4607 for c in ch {
4608 new_ch.push(hoist_subst(lang, assign, c, visiting)?);
4609 }
4610 Some(lang.clone_node(e, new_ch))
4611 }
4612 }
4613}
4614
4615impl Rule<JavaAst> for CtorDelegationHoist {
4616 fn name(&self) -> &'static str {
4617 "ctor_delegation_hoist"
4618 }
4619 fn kinds(&self) -> &'static [NodeKind] {
4620 &[NodeKind::Block]
4621 }
4622 fn structural(&self) -> bool {
4623 true
4624 }
4625 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4626 if ctx.parent(id).is_some() {
4629 return None;
4630 }
4631 let lang = ctx.lang;
4632 if lang.kind(id) != NodeKind::Block {
4633 return None;
4634 }
4635 let stmts = lang.children(id).to_vec();
4636 let mut ctor_idx: Option<usize> = None;
4638 for (i, &st) in stmts.iter().enumerate() {
4639 if *lang.data(st) != NodeData::ExprStmt {
4640 continue;
4641 }
4642 let Some(&inner) = lang.children(st).first() else { continue };
4643 if lang.kind(inner) != NodeKind::Call {
4644 continue;
4645 }
4646 let Some(&callee) = lang.children(inner).first() else { continue };
4647 if matches!(lang.data(callee), NodeData::This | NodeData::Super) {
4648 ctor_idx = Some(i);
4649 break;
4650 }
4651 }
4652 let ci = ctor_idx?;
4653 if ci == 0 {
4654 return None; }
4656 let mut last_assign: std::collections::HashMap<String, JavaId> =
4659 std::collections::HashMap::new();
4660 for &st in &stmts[..ci] {
4661 let (name, value) = match lang.data(st) {
4662 NodeData::VarDecl { name, .. } => {
4663 let init = lang.children(st).first().copied();
4664 match init {
4665 Some(e) => (name.clone(), e),
4666 None => continue, }
4668 }
4669 NodeData::ExprStmt => {
4670 let Some(&a) = lang.children(st).first() else {
4671 return None;
4672 };
4673 if lang.kind(a) != NodeKind::Assign
4676 || <JavaAst as Lang>::assign_op(&*lang, a).is_some()
4677 {
4678 return None;
4679 }
4680 let ch = lang.children(a).to_vec();
4681 let (t, v) = (*ch.first()?, *ch.get(1)?);
4682 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
4683 return None; }
4685 let vn = lang.var_name(t)?;
4686 (vn.to_string(), v)
4687 }
4688 _ => return None, };
4690 last_assign.insert(name, value);
4695 }
4696 let call_stmt = stmts[ci];
4698 let call = *lang.children(call_stmt).first()?;
4699 let cch = lang.children(call).to_vec();
4700 let callee = *cch.first()?;
4701 let mut new_args: Vec<JavaId> = Vec::with_capacity(cch.len() - 1);
4702 for &a in &cch[1..] {
4703 new_args.push(hoist_resolve_arg(lang, &last_assign, a, &mut Default::default())?);
4704 }
4705 let callee_copy = lang.copy_subtree(callee);
4707 let new_call = lang.call(callee_copy, new_args);
4708 let new_stmt = lang.expr_stmt(new_call);
4709 let mut insert: Vec<JavaId> = Vec::with_capacity(ci + 1);
4710 insert.push(new_stmt);
4711 insert.extend(stmts[..ci].iter().copied());
4712 Some(Edit::Splice {
4713 node: id,
4714 index: 0,
4715 remove: ci + 1,
4716 insert,
4717 })
4718 }
4719}
4720
4721pub struct TrailingElseFlatten;
4739
4740impl Rule<JavaAst> for TrailingElseFlatten {
4741 fn name(&self) -> &'static str {
4742 "trailing_else_flatten"
4743 }
4744 fn kinds(&self) -> &'static [NodeKind] {
4745 &[NodeKind::If]
4746 }
4747 fn structural(&self) -> bool {
4748 true
4749 }
4750 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4751 let (parent, idx) = (ctx.parent(id), ctx.index(id));
4752 let lang = ctx.lang;
4753 if lang.kind(id) != NodeKind::If {
4754 return None;
4755 }
4756 let ch = lang.children(id).to_vec();
4757 if ch.len() != 3 {
4758 return None;
4759 }
4760 let (_cond, then, els) = (ch[0], ch[1], ch[2]);
4761 if lang.kind(then) != NodeKind::Block {
4763 return None;
4764 }
4765 let then_stmts = lang.children(then).to_vec();
4766 let then_terminal = then_stmts
4767 .last()
4768 .is_some_and(|&s| matches!(lang.kind(s), NodeKind::Return | NodeKind::Throw));
4769 if !then_terminal {
4770 return None;
4771 }
4772 if lang.kind(els) == NodeKind::Block {
4777 let else_stmts = lang.children(els).to_vec();
4778 if else_stmts.len() == 1 && lang.kind(else_stmts[0]) == NodeKind::Return {
4779 if lang.children(else_stmts[0]).is_empty() {
4781 return Some(Edit::Splice {
4783 node: id,
4784 index: 0,
4785 remove: 3,
4786 insert: vec![ch[0], then],
4787 });
4788 }
4789 }
4790 }
4791 let parent = parent?;
4793 if lang.kind(parent) != NodeKind::Block {
4794 return None;
4795 }
4796 let idx = idx?;
4797 let mut insert: Vec<JavaId> = vec![ch[0], then];
4798 if lang.kind(els) == NodeKind::Block {
4800 insert.extend(lang.children(els).to_vec());
4801 } else {
4802 insert.push(els);
4803 }
4804 Some(Edit::Splice {
4805 node: parent,
4806 index: idx,
4807 remove: 1,
4808 insert,
4809 })
4810 }
4811}
4812
4813pub struct TrailingIfContinue;
4814
4815impl Rule<JavaAst> for TrailingIfContinue {
4816 fn name(&self) -> &'static str {
4817 "trailing_if_continue"
4818 }
4819 fn kinds(&self) -> &'static [NodeKind] {
4820 &[NodeKind::If]
4821 }
4822 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4823 let parent = ctx.parent(id);
4824 let grandparent = parent.and_then(|p| ctx.parent(p));
4825 let lang = ctx.lang;
4826 if lang.kind(id) != NodeKind::If {
4827 return None;
4828 }
4829 let ch = lang.children(id).to_vec();
4830 if ch.len() != 2 {
4832 return None;
4833 }
4834 let then = ch[1];
4835 if lang.kind(then) != NodeKind::Block {
4836 return None;
4837 }
4838 let tbody = lang.children(then).to_vec();
4839 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4840 return None;
4841 }
4842 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4844 return None;
4845 }
4846 let p = parent?;
4849 if lang.kind(p) != NodeKind::Block {
4850 return None;
4851 }
4852 let pch = lang.children(p).to_vec();
4853 if pch.last() != Some(&id) {
4854 return None;
4855 }
4856 let gp = grandparent?;
4858 match lang.kind(gp) {
4859 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4860 let gch = lang.children(gp).to_vec();
4861 if gch.last() == Some(&p) {
4862 Some(Edit::Delete { node: id })
4863 } else {
4864 None
4865 }
4866 }
4867 _ => None,
4868 }
4869 }
4870}
4871
4872pub struct TrailingAssignReturn;
4873
4874impl Rule<JavaAst> for TrailingAssignReturn {
4875 fn name(&self) -> &'static str {
4876 "trailing_assign_return"
4877 }
4878 fn kinds(&self) -> &'static [NodeKind] {
4879 &[NodeKind::Assign]
4880 }
4881 fn structural(&self) -> bool {
4884 true
4885 }
4886 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4887 let parent = ctx.parent(id);
4888 let grandparent = parent.and_then(|p| ctx.parent(p));
4889 let lang = ctx.lang;
4890 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4892 return None;
4893 }
4894 let ch = lang.children(id).to_vec();
4895 if ch.len() != 2 {
4896 return None;
4897 }
4898 let (target, value) = (ch[0], ch[1]);
4899 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4901 return None;
4902 }
4903 let name = lang.var_name(target)?.to_string();
4904 let parent = parent?;
4906 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4907 parent
4908 } else if lang.kind(parent) == NodeKind::Block {
4909 id
4910 } else {
4911 return None;
4912 };
4913 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4914 grandparent?
4915 } else {
4916 parent
4917 };
4918 if lang.kind(block) != NodeKind::Block {
4919 return None;
4920 }
4921 let stmts = lang.children(block).to_vec();
4922 let idx = stmts.iter().position(|&x| x == stmt)?;
4923 let next = *stmts.get(idx + 1)?;
4925 let (flow_val, is_return) = match lang.kind(next) {
4926 NodeKind::Return => (lang.children(next).first().copied(), true),
4927 NodeKind::Throw => (lang.children(next).first().copied(), false),
4928 _ => return None,
4929 };
4930 let flow_val = flow_val?;
4931 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4932 return None;
4933 }
4934 for &s in &stmts[idx + 2..] {
4937 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4938 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4939 }) {
4940 return None;
4941 }
4942 }
4943 let new_flow = if is_return {
4945 lang.ret(Some(value))
4946 } else {
4947 lang.throw(value)
4948 };
4949 Some(Edit::Multi(vec![
4950 Edit::Replace { target: next, with: new_flow },
4951 Edit::Delete { node: stmt },
4952 ]))
4953 }
4954}
4955
4956pub struct ConstFieldPropagate;
4957
4958impl Rule<JavaAst> for ConstFieldPropagate {
4959 fn name(&self) -> &'static str {
4960 "const_field_propagate"
4961 }
4962 fn kinds(&self) -> &'static [NodeKind] {
4963 &[NodeKind::VarRef]
4964 }
4965 fn structural(&self) -> bool {
4968 true
4969 }
4970 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4971 let parent = ctx.parent(id);
4972 let _grandparent = parent.and_then(|p| ctx.parent(p));
4973 let lang = ctx.lang;
4974 if lang.kind(id) != NodeKind::VarRef {
4975 return None;
4976 }
4977 let name = lang.var_name(id)?.to_string();
4978 if !lang.const_scalars.contains(&name) {
4979 return None;
4980 }
4981 if let Some(p) = parent {
4982 let is_first = lang.children(p).first() == Some(&id);
4983 match lang.kind(p) {
4984 NodeKind::Assign if is_first => return None,
4986 NodeKind::Unary if is_first => {
4987 if let NodeData::Unary { op } = lang.data(p) {
4988 if op.is_incdec() {
4989 return None;
4990 }
4991 }
4992 }
4993 NodeKind::Call if is_first => return None,
4995 _ => {}
4996 }
4997 }
4998 let init = *lang.const_fields.get(&name)?;
4999 let lit = lang.copy_subtree(init);
5000 Some(Edit::Replace { target: id, with: lit })
5001 }
5002}
5003
5004pub struct NoopPrivateCall;
5005
5006impl Rule<JavaAst> for NoopPrivateCall {
5007 fn name(&self) -> &'static str {
5008 "noop_private_call"
5009 }
5010 fn kinds(&self) -> &'static [NodeKind] {
5011 &[NodeKind::ExprStmt]
5012 }
5013 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5014 let lang = ctx.lang;
5015 if lang.kind(id) != NodeKind::ExprStmt {
5016 return None;
5017 }
5018 let ch = lang.children(id).to_vec();
5019 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
5020 return None;
5021 }
5022 let call = ch[0];
5023 let cch = lang.children(call).to_vec();
5024 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
5025 return None; }
5027 let name = lang.var_name(cch[0])?.to_string();
5028 let arity = cch.len() - 1;
5029 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
5030 if cands.is_empty() {
5031 return None;
5032 }
5033 let arg_types: Vec<Option<JType>> =
5037 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
5038 let mut applicable_noop = false;
5039 let mut applicable_count = 0usize;
5040 for c in cands {
5041 let ok = c
5042 .params
5043 .iter()
5044 .zip(arg_types.iter())
5045 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
5046 if ok {
5047 applicable_count += 1;
5048 applicable_noop = c.noop;
5049 }
5050 }
5051 if applicable_count != 1 || !applicable_noop {
5052 return None;
5053 }
5054 for &arg in &cch[1..] {
5056 if lang.effect(arg) > Effect::MayRead {
5057 return None;
5058 }
5059 }
5060 Some(Edit::Delete { node: id })
5061 }
5062}
5063
5064fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
5068 match lang.kind(arg) {
5069 NodeKind::Literal => match lang.literal(arg)? {
5070 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
5071 LitRef::Char(_) => Some(JType::Char),
5072 LitRef::Bool(_) => Some(JType::Bool),
5073 LitRef::Int(_) => Some(JType::Int),
5074 LitRef::Long(_) => Some(JType::Long),
5075 LitRef::Float(_) => Some(JType::Float),
5076 LitRef::Double(_) => Some(JType::Double),
5077 LitRef::Null => None,
5078 },
5079 NodeKind::VarRef => {
5080 let nm = lang.var_name(arg)?;
5081 lang.var_type(arg)
5082 .cloned()
5083 .or_else(|| lang.field_types.get(nm).cloned())
5084 }
5085 _ => None,
5086 }
5087}
5088
5089fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
5094 let Some(a) = at else { return true };
5095 match pt {
5096 JType::Ref(p) => {
5097 let pbase = p.split('<').next().unwrap_or(p).trim();
5098 pbase == "Object"
5099 || pbase == "java.lang.Object"
5100 || match a {
5101 JType::Ref(arend) => {
5102 let abase = arend.split('<').next().unwrap_or(arend).trim();
5103 abase == pbase
5104 }
5105 _ => false,
5106 }
5107 }
5108 JType::Array(_) => false, prim => match a {
5110 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
5111 },
5112 }
5113}
5114
5115pub struct ConstMethodInline;
5125
5126impl Rule<JavaAst> for ConstMethodInline {
5127 fn name(&self) -> &'static str {
5128 "const_method_inline"
5129 }
5130 fn kinds(&self) -> &'static [NodeKind] {
5131 &[NodeKind::Call]
5132 }
5133 fn structural(&self) -> bool {
5134 true
5135 }
5136 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5137 let at_stmt_pos = ctx
5139 .parent(id)
5140 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
5141 .unwrap_or(false);
5142 let lang = ctx.lang;
5143 if lang.kind(id) != NodeKind::Call {
5144 return None;
5145 }
5146 let ch = lang.children(id).to_vec();
5147 let (method_name, recv_class) = match lang.data(ch[0]) {
5151 NodeData::VarRef { name } => (*name, None),
5152 NodeData::Member { name } => {
5153 let recv = lang.children(ch[0])[0];
5154 match lang.var_name(recv) {
5155 Some(r) if r == "this" => (*name, None),
5156 Some(r) if r.chars().next().is_some_and(|c| c.is_uppercase()) => {
5157 (*name, Some(r.to_string()))
5158 }
5159 _ => return None,
5160 }
5161 }
5162 _ => return None,
5163 };
5164 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
5165 let (params, body_expr, owner) = entry;
5166 if let Some(recv) = recv_class {
5173 if recv != owner {
5174 return None;
5175 }
5176 }
5177 {
5179 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
5180 for nm in &names {
5181 if subtree_calls_self(lang, body_expr, nm) {
5182 return None;
5183 }
5184 }
5185 }
5186 let args = ch[1..].to_vec();
5187 if args.len() != params.len() || params.is_empty() {
5188 return None;
5189 }
5190 for &a in &args {
5191 lang.literal(a)?;
5192 }
5193 let map: std::collections::HashMap<String, JavaId> = params
5194 .iter()
5195 .zip(args.iter())
5196 .map(|(p, &a)| (p.clone(), a))
5197 .collect();
5198 let with = copy_subst(lang, body_expr, &map);
5199 if at_stmt_pos {
5205 match lang.kind(with) {
5206 NodeKind::Call | NodeKind::New => {}
5207 _ => return None,
5208 }
5209 }
5210 Some(Edit::Replace {
5211 target: id,
5212 with,
5213 })
5214 }
5215}
5216
5217fn copy_subst(
5219 lang: &mut JavaAst,
5220 node: JavaId,
5221 map: &std::collections::HashMap<String, JavaId>,
5222) -> JavaId {
5223 if let NodeData::VarRef { name } = lang.data(node) {
5224 if let Some(&repl) = map.get(lang.sn(*name)) {
5225 return lang.copy_subtree(repl);
5226 }
5227 }
5228 let children: Vec<JavaId> = lang.children(node).to_vec();
5229 let new_children: Vec<JavaId> = children
5230 .iter()
5231 .map(|&c| copy_subst(lang, c, map))
5232 .collect();
5233 lang.clone_node(node, new_children)
5234}
5235
5236
5237pub struct TwrRecover;
5270
5271fn twr_is_throwable(ty_raw: &str) -> bool {
5272 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
5273}
5274
5275fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
5277 if lang.kind(n) == NodeKind::ExprStmt {
5278 if let Some(&e) = lang.children(n).first() {
5279 return e;
5280 }
5281 }
5282 n
5283}
5284
5285fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
5287 if lang.kind(n) == NodeKind::Block {
5288 lang.children(n).to_vec()
5289 } else {
5290 vec![n]
5291 }
5292}
5293
5294fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
5296 if lang.kind(e) != NodeKind::Call {
5297 return false;
5298 }
5299 let ch = lang.children(e);
5300 if ch.len() != 1 {
5301 return false;
5302 }
5303 match lang.data(ch[0]) {
5304 NodeData::Member { name } if lang.sn(*name) == "close" => {
5305 let Some(&recv) = lang.children(ch[0]).first() else {
5306 return false;
5307 };
5308 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
5309 }
5310 _ => false,
5311 }
5312}
5313
5314fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
5315 if lang.kind(n) != NodeKind::ExprStmt {
5316 return false;
5317 }
5318 lang.children(n)
5319 .first()
5320 .is_some_and(|&e| twr_is_close_call(lang, e, r))
5321}
5322
5323fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
5325 if lang.kind(n) != NodeKind::ExprStmt {
5326 return false;
5327 }
5328 let Some(&e) = lang.children(n).first() else {
5329 return false;
5330 };
5331 if lang.kind(e) != NodeKind::Call {
5332 return false;
5333 }
5334 let ch = lang.children(e);
5335 if ch.len() != 2 {
5336 return false;
5337 }
5338 match lang.data(ch[0]) {
5339 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
5340 let Some(&recv) = lang.children(ch[0]).first() else {
5341 return false;
5342 };
5343 lang.kind(recv) == NodeKind::VarRef
5344 && lang.var_name(recv) == Some(primary)
5345 && lang.kind(ch[1]) == NodeKind::VarRef
5346 && lang.var_name(ch[1]) == Some(sup)
5347 }
5348 _ => false,
5349 }
5350}
5351
5352fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
5353 let mut stack = vec![root];
5354 while let Some(n) = stack.pop() {
5355 if n == target {
5356 return true;
5357 }
5358 stack.extend(lang.children(n).iter().copied());
5359 }
5360 false
5361}
5362
5363fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
5365 let mut stack = vec![root];
5366 while let Some(n) = stack.pop() {
5367 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
5368 let ok = allowed
5369 .iter()
5370 .any(|&a| a == n || node_contains(lang, a, n));
5371 if !ok {
5372 return true;
5373 }
5374 }
5375 if lang.kind(n) == NodeKind::VarDecl {
5376 if let Some(nm) = lang.var_name(n) {
5377 if nm == name && !allowed.contains(&n) {
5378 return true;
5380 }
5381 }
5382 }
5383 stack.extend(lang.children(n).iter().copied());
5384 }
5385 false
5386}
5387
5388fn twr_close_with_suppressed(
5392 lang: &JavaAst,
5393 try_node: JavaId,
5394 primary: &str,
5395) -> Option<(String, String)> {
5396 let ch = lang.children(try_node).to_vec();
5397 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
5398 return None;
5399 }
5400 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
5401 return None;
5402 };
5403 if !twr_is_throwable(ty_raw) {
5404 return None;
5405 }
5406 let sup = sup.clone();
5407 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5408 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
5409 return None;
5410 }
5411 let close_stmts = twr_stmts(lang, ch[0]);
5412 if close_stmts.len() != 1 {
5413 return None;
5414 }
5415 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
5416 return None;
5417 }
5418 let e = *lang.children(close_stmts[0]).first()?;
5419 let cc = lang.children(e);
5420 if cc.len() != 1 {
5421 return None;
5422 }
5423 let NodeData::Member { name } = lang.data(cc[0]) else {
5424 return None;
5425 };
5426 if lang.sn(*name) != "close" {
5427 return None;
5428 }
5429 let recv = *lang.children(cc[0]).first()?;
5430 if lang.kind(recv) != NodeKind::VarRef {
5431 return None;
5432 }
5433 Some((lang.var_name(recv)?.to_string(), sup))
5434}
5435
5436impl Rule<JavaAst> for TwrRecover {
5437 fn name(&self) -> &'static str {
5438 "twr_recover"
5439 }
5440 fn kinds(&self) -> &'static [NodeKind] {
5441 &[NodeKind::Try]
5442 }
5443 fn structural(&self) -> bool {
5444 true
5445 }
5446 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5447 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5449 let lang = ctx.lang;
5450 if lang.kind(id) != NodeKind::Try {
5451 return None;
5452 }
5453 let ch = lang.children(id).to_vec();
5454 let try_idx = ch
5456 .iter()
5457 .position(|&c| lang.kind(c) == NodeKind::Block)?;
5458 if try_idx != 0 {
5459 return None;
5460 }
5461 let try_block = ch[0];
5462 if lang.kind(parent) != NodeKind::Block {
5463 return None;
5464 }
5465 let pch = lang.children(parent).to_vec();
5466
5467 if ch.len() == 2 {
5469 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
5470 if twr_is_throwable(ty_raw) {
5471 let t = t.clone();
5472 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5473 if catch_stmts.len() == 2 {
5474 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5476 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5477 && lang
5478 .children(throw_stmt)
5479 .first()
5480 .is_some_and(|&x| {
5481 lang.kind(x) == NodeKind::VarRef
5482 && lang.var_name(x) == Some(t.as_str())
5483 });
5484 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
5485 if let Some((r, sup)) =
5486 twr_close_with_suppressed(lang, inner_try, t.as_str())
5487 {
5488 if idx >= 1 && idx + 1 < pch.len() {
5489 let decl_r = pch[idx - 1];
5490 let trailing = pch[idx + 1];
5491 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
5492 if name == &r {
5493 let (name, ty) = (name.clone(), ty.clone());
5494 let init =
5495 lang.children(decl_r).first().copied();
5496 if let Some(init) = init {
5497 let provably_new = lang.kind(init)
5499 == NodeKind::New;
5500 let close_ok =
5501 twr_is_close_stmt(lang, trailing, &r);
5502 if provably_new && close_ok {
5503 let root = walk_root;
5505 let r_ok = !refs_escape(
5506 lang,
5507 root,
5508 &r,
5509 &[
5510 decl_r,
5511 init,
5512 *lang
5513 .children(inner_try)
5514 .first()
5515 .unwrap_or(&inner_try),
5516 trailing,
5517 try_block,
5518 ],
5519 );
5520 let t_ok = !refs_escape(
5521 lang, root, &t,
5522 &[throw_stmt, ch[1]],
5523 );
5524 let sup_ok = !refs_escape(
5525 lang, root, &sup,
5526 &[ch[1]],
5527 );
5528 if r_ok && t_ok && sup_ok {
5529 let init_copy =
5530 lang.copy_subtree(init);
5531 let res = lang.var_decl(
5532 &name, ty,
5533 Some(init_copy),
5534 );
5535 let new_try = lang.try_(
5536 vec![res],
5537 try_block,
5538 Vec::new(),
5539 None,
5540 );
5541 return Some(Edit::Splice {
5542 node: parent,
5543 index: idx - 1,
5544 remove: 3,
5545 insert: vec![new_try],
5546 });
5547 }
5548 }
5549 }
5550 }
5551 }
5552 }
5553 }
5554 }
5555 }
5556 }
5557 }
5558 }
5559
5560 if ch.len() == 3 {
5562 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5563 return None;
5564 };
5565 if !twr_is_throwable(ty_raw) {
5566 return None;
5567 }
5568 let t = t.clone();
5569 if lang.kind(ch[2]) != NodeKind::Block {
5571 return None;
5572 }
5573 let fin_stmts = twr_stmts(lang, ch[2]);
5574 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5575 return None;
5576 }
5577 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5579 if catch_stmts.len() != 2 {
5580 return None;
5581 }
5582 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5583 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5584 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5585 return None;
5586 };
5587 let as_ch = lang.children(assign_expr);
5588 if as_ch.len() != 2 {
5589 return None;
5590 }
5591 let primary = match lang.data(as_ch[0]) {
5592 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5593 _ => return None,
5594 };
5595 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5596 && lang.var_name(as_ch[1]) == Some(t.as_str())
5597 && lang.kind(as_ch[0]) == NodeKind::VarRef
5598 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5599 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5600 && lang.children(throw_stmt)
5601 .first()
5602 .is_some_and(|&x| {
5603 lang.kind(x) == NodeKind::VarRef
5604 && lang.var_name(x) == Some(t.as_str())
5605 });
5606 if !assign_ok || !throw_ok {
5607 return None;
5608 }
5609 let if1 = fin_stmts[0];
5611 let i1 = lang.children(if1).to_vec();
5612 if i1.len() < 2 {
5613 return None;
5614 }
5615 let cond1 = i1[0];
5616 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5617 return None;
5618 };
5619 let c1 = lang.children(cond1);
5620 let r = match lang.data(c1[0]) {
5621 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5622 lang.sn(*name).to_string()
5623 }
5624 _ => {
5625 return None;
5626 }
5627 };
5628 let then1 = i1[1];
5629 let inner_stmts = twr_stmts(lang, then1);
5630 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5631 return None;
5632 }
5633 let if2 = inner_stmts[0];
5634 let i2 = lang.children(if2).to_vec();
5635 if i2.len() != 3 {
5636 return None;
5637 }
5638 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5639 return None;
5640 };
5641 let c2 = lang.children(i2[0]);
5642 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5643 && lang.var_name(c2[0]) == Some(primary.as_str())
5644 && lang.literal(c2[1]) == Some(LitRef::Null);
5645 if !primary_ok {
5646 return None;
5647 }
5648 let (then2, els2) = (i2[1], i2[2]);
5649 let t2_stmts = twr_stmts(lang, then2);
5651 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5652 return None;
5653 }
5654 let Some((r2, sup)) =
5655 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5656 else {
5657 return None;
5658 };
5659 if r2 != r {
5660 return None;
5661 }
5662 let e2_stmts = twr_stmts(lang, els2);
5664 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5665 return None;
5666 }
5667 if idx < 2 {
5669 return None;
5670 }
5671 let decl_r = pch[idx - 2];
5672 let decl_primary = pch[idx - 1];
5673 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5674 (
5675 NodeData::VarDecl { name: rn, .. },
5676 NodeData::VarDecl { name: pn, .. },
5677 ) => (rn.clone(), pn.clone()),
5678 _ => return None,
5679 };
5680 if r_decl != r || primary_decl != primary {
5681 return None;
5682 }
5683 let pinit_null = lang
5685 .children(decl_primary)
5686 .first()
5687 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5688 if !pinit_null {
5689 return None;
5690 }
5691 let r_init = lang.children(decl_r).first().copied()?;
5692 let ty = match lang.data(decl_r) {
5693 NodeData::VarDecl { ty, .. } => ty.clone(),
5694 _ => return None,
5695 };
5696 let root = walk_root;
5698 let r_allowed = [decl_r, r_init, ch[2], try_block];
5700 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5701 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5702 if refs_escape(lang, root, &r, &r_allowed)
5703 || refs_escape(lang, root, &primary, &p_allowed)
5704 || refs_escape(lang, root, &t, &t_allowed)
5705 || refs_escape(lang, root, &sup, &[ch[2]])
5706 {
5707 return None;
5708 }
5709 let init_copy = lang.copy_subtree(r_init);
5710 let res = lang.var_decl(&r, ty, Some(init_copy));
5711 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5712 return Some(Edit::Splice {
5713 node: parent,
5714 index: idx - 2,
5715 remove: 3,
5716 insert: vec![new_try],
5717 });
5718 }
5719 None
5720 }
5721}
5722
5723pub struct StringSwitchRecover;
5748
5749fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5750 out.push_str(&format!("{:?} ", lang.data(n)));
5751 for &c in lang.children(n) {
5752 subtree_sig(lang, c, out);
5753 }
5754}
5755
5756fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5758 let ch = lang.children(sw).to_vec();
5759 let mut out = Vec::new();
5760 for &c in &ch[1..] {
5761 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5762 return None;
5763 };
5764 let is_default = *is_default;
5765 let cc = lang.children(c);
5766 let label = if is_default {
5767 None
5768 } else {
5769 if *labels != 1 {
5770 return None; }
5772 let lab = cc[0];
5773 let lit = lang.literal(lab)?;
5774 let v = match lit {
5775 LitRef::Int(v) | LitRef::Long(v) => v,
5776 _ => return None, };
5778 Some(v)
5779 };
5780 out.push((label, cc[*labels as usize..].to_vec()));
5781 }
5782 Some(out)
5783}
5784
5785fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5786 let Some(&last) = stmts.last() else {
5787 return false; };
5789 let last = if lang.kind(last) == NodeKind::Block {
5790 match lang.children(last).last() {
5791 Some(&l) => l,
5792 None => return false,
5793 }
5794 } else {
5795 last
5796 };
5797 matches!(
5798 lang.kind(last),
5799 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5800 )
5801}
5802
5803impl Rule<JavaAst> for StringSwitchRecover {
5804 fn name(&self) -> &'static str {
5805 "string_switch_recover"
5806 }
5807 fn kinds(&self) -> &'static [NodeKind] {
5808 &[NodeKind::Switch]
5809 }
5810 fn structural(&self) -> bool {
5811 true
5812 }
5813 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5814 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5815 let lang = ctx.lang;
5816 if lang.kind(id) != NodeKind::Switch {
5817 return None;
5818 }
5819 let ch = lang.children(id).to_vec();
5820 if ch.len() < 2 {
5821 return None;
5822 }
5823 let x = match lang.data(ch[0]) {
5825 NodeData::Call => {
5826 let cc = lang.children(ch[0]);
5827 if cc.len() != 1 {
5828 return None;
5829 }
5830 match lang.data(cc[0]) {
5831 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5832 let recv = *lang.children(cc[0]).first()?;
5833 if lang.kind(recv) != NodeKind::VarRef {
5834 return None;
5835 }
5836 lang.var_name(recv)?.to_string()
5837 }
5838 _ => return None,
5839 }
5840 }
5841 _ => {
5842 return None;
5843 }
5844 };
5845 struct CaseInfo {
5847 lit: String,
5848 k: i64,
5849 inner_switch: JavaId,
5850 }
5851 let mut infos: Vec<CaseInfo> = Vec::new();
5852 let mut v_name: Option<String> = None;
5853 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5855 for &c in &ch[1..] {
5856 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5857 return None;
5858 };
5859 let is_default = *is_default;
5860 if is_default {
5861 return None; }
5863 if *labels != 1 {
5864 return None;
5865 }
5866 let cc = lang.children(c);
5867 let h = match lang.literal(cc[0])? {
5868 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5869 _ => return None,
5870 };
5871 let body = cc[1..].to_vec();
5872 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5874 lang.children(body[0]).to_vec()
5875 } else {
5876 body
5877 };
5878 let mut rest = body;
5879 let mut _e_decl: Option<(String, JavaId)> = None;
5881 if let Some(&first) = rest.first() {
5882 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5883 if let Some(&init) = lang.children(first).first() {
5884 if is_x_equals(lang, init, &x) {
5885 _e_decl = Some((name.clone(), first));
5886 rest = rest[1..].to_vec();
5887 }
5888 }
5889 }
5890 }
5891 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5892 return None;
5893 }
5894 let ifn = rest[0];
5895 let ic = lang.children(ifn).to_vec();
5896 if ic.len() != 3 {
5897 return None;
5898 }
5899 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5901 return None;
5902 };
5903 let guard = *lang.children(ic[0]).first()?;
5904 let lit = match lang.data(guard) {
5905 NodeData::VarRef { name } => {
5906 let Some((en, decl)) = &_e_decl else {
5908 return None;
5909 };
5910 if lang.sn(*name) != en {
5911 return None;
5912 }
5913 let init = *lang.children(*decl).first()?;
5914 equals_lit(lang, init, &x)?
5915 }
5916 NodeData::Call => equals_lit(lang, guard, &x)?,
5917 _ => return None,
5918 };
5919 guard_nodes.push(guard);
5920 if let Some((_, decl)) = &_e_decl {
5921 guard_nodes.push(*decl);
5922 }
5923 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5925 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5926 NodeData::Break { label: None });
5927 if !then_ok {
5928 return None;
5929 }
5930 let els = twr_stmts(lang, ic[2]);
5932 let (k, inner_switch, assign_node) = match els.len() {
5933 2 => {
5934 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5935 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5936 return None;
5937 };
5938 let a_ch = lang.children(assign_expr);
5939 let vn = match lang.data(a_ch[0]) {
5940 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5941 _ => return None,
5942 };
5943 let k = match lang.literal(a_ch[1])? {
5944 LitRef::Int(v) | LitRef::Long(v) => v,
5945 _ => return None,
5946 };
5947 if lang.kind(els[1]) != NodeKind::Switch {
5948 return None;
5949 }
5950 let sel = *lang.children(els[1]).first()?;
5952 if lang.kind(sel) != NodeKind::VarRef
5953 || lang.var_name(sel) != Some(vn.as_str())
5954 {
5955 return None;
5956 }
5957 match &v_name {
5958 Some(v) if *v != vn => return None,
5959 _ => v_name = Some(vn),
5960 }
5961 (k, els[1], Some(els[0]))
5962 }
5963 1 => {
5964 if lang.kind(els[0]) != NodeKind::Switch {
5965 return None;
5966 }
5967 let sel = *lang.children(els[0]).first()?;
5969 let k = match lang.literal(sel)? {
5970 LitRef::Int(v) | LitRef::Long(v) => v,
5971 _ => return None,
5972 };
5973 (k, els[0], None)
5974 }
5975 _ => return None,
5976 };
5977 if let Some(a) = assign_node {
5978 v_nodes.push(a);
5979 }
5980 v_nodes.push(*lang.children(inner_switch).first()?);
5981 let hlit: i32 = lit
5983 .chars()
5984 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
5985 if hlit != h {
5986 return None;
5987 }
5988 let _ = assign_node;
5989 infos.push(CaseInfo {
5990 lit,
5991 k,
5992 inner_switch,
5993 });
5994 }
5995 if infos.is_empty() {
5996 return None;
5997 }
5998 if lang.kind(parent) != NodeKind::Block {
6000 return None;
6001 }
6002 let pch = lang.children(parent).to_vec();
6003 let mut k = idx + 1;
6004 if k < pch.len() {
6006 let texpr = unwrap_expr_stmt(lang, pch[k]);
6007 if let NodeData::Assign { op: None } = lang.data(texpr) {
6008 let a_ch = lang.children(texpr);
6009 if a_ch.len() == 2
6010 && lang.kind(a_ch[0]) == NodeKind::VarRef
6011 && lang.var_name(a_ch[0]) == v_name.as_deref()
6012 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
6013 {
6014 v_nodes.push(pch[k]);
6015 k += 1;
6016 }
6017 }
6018 }
6019 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
6020 return None;
6021 }
6022 let trailing = pch[k];
6023 let tsel = *lang.children(trailing).first()?;
6025 match lang.data(tsel) {
6026 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
6027 v_nodes.push(tsel);
6028 }
6029 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
6030 _ => {
6031 lang.literal(tsel)?;
6032 }
6033 }
6034 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
6037 for &c in &lang.children(sw)[1..] {
6038 subtree_sig(lang, c, out);
6039 }
6040 }
6041 let mut sig0 = String::new();
6042 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
6043 for info in &infos[1..] {
6044 let mut sig = String::new();
6045 switch_cases_sig(lang, info.inner_switch, &mut sig);
6046 if sig != sig0 {
6047 return None;
6048 }
6049 }
6050 {
6051 let mut sig = String::new();
6052 switch_cases_sig(lang, trailing, &mut sig);
6053 if sig != sig0 {
6054 return None;
6055 }
6056 }
6057 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
6059 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
6060 std::collections::HashMap::new();
6061 let mut default_body: Option<Vec<JavaId>> = None;
6062 for (lab, stmts) in &bodies {
6063 match lab {
6064 Some(v) => {
6065 body_map.insert(*v, stmts.clone());
6066 }
6067 None => default_body = Some(stmts.clone()),
6068 }
6069 }
6070 let Some(default_body) = default_body else {
6071 return None;
6072 };
6073 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
6075 if !ssw_body_terminal(lang, stmts) {
6076 return None;
6077 }
6078 }
6079 let root = ctx_root;
6081 if let Some(vn) = &v_name {
6082 let mut allowed: Vec<JavaId> = v_nodes.clone();
6083 allowed.push(trailing);
6084 if idx >= 1 {
6085 let cand = pch[idx - 1];
6086 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
6087 if name == vn {
6088 allowed.push(cand);
6089 if idx != 1 {
6091 allowed.pop();
6092 }
6093 }
6094 }
6095 }
6096 if refs_escape(lang, root, vn, &allowed) {
6097 return None;
6098 }
6099 }
6100 let mut new_cases: Vec<JavaId> = Vec::new();
6102 for info in &infos {
6103 let Some(body) = body_map.get(&info.k) else {
6104 return None;
6105 };
6106 let lab = lang.lit(Lit::Str(info.lit.clone()));
6107 let case = lang.case_(vec![lab], false, false, body.clone());
6108 new_cases.push(case);
6109 }
6110 let def_case = lang.case_(Vec::new(), true, false, default_body);
6111 new_cases.push(def_case);
6112 let sel = lang.var(&x);
6113 let new_switch = lang.switch_(sel, new_cases);
6114 let remove = k - idx + 1;
6116 Some(Edit::Splice {
6117 node: parent,
6118 index: idx,
6119 remove,
6120 insert: vec![new_switch],
6121 })
6122 }
6123}
6124
6125fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
6127 if lang.kind(e) != NodeKind::Call {
6128 return None;
6129 }
6130 let cc = lang.children(e);
6131 if cc.len() != 2 {
6132 return None;
6133 }
6134 match lang.data(cc[0]) {
6135 NodeData::Member { name } if lang.sn(*name) == "equals" => {
6136 let recv = *lang.children(cc[0]).first()?;
6137 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
6138 return None;
6139 }
6140 match lang.literal(cc[1])? {
6141 LitRef::Str(s) => Some(s.to_string()),
6142 _ => None,
6143 }
6144 }
6145 _ => None,
6146 }
6147}
6148
6149fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
6150 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
6151}
6152
6153pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6160 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
6161 rules.push(Box::new(CastSimplify));
6162 rules.push(Box::new(SelfCompare));
6163 rules.push(Box::new(StringBuilderFold));
6164 rules.push(Box::new(BoxUnboxChain));
6165 rules.push(Box::new(IteratorToForEach));
6166 rules.push(Box::new(NewStringFold));
6167 rules.push(Box::new(NewStringCharArrayFold));
6168 rules.push(Box::new(EmptyFinallyStrip));
6169 rules.push(Box::new(TryUnwrapNoCatch));
6170 rules.push(Box::new(TryUnwrapRethrow));
6171 rules.push(Box::new(StaticExec));
6172 rules.push(Box::new(LoopHeadBreak));
6173 rules.push(Box::new(WhileIteratorToForEach));
6174 rules.push(Box::new(ConcatValueOfDrop));
6175 rules.push(Box::new(StringBuilderStatements));
6176 rules.push(Box::new(TrailingContinueJava));
6177 rules.push(Box::new(XorNoise));
6178 rules.push(Box::new(StrLenFold));
6179 rules.push(Box::new(LiteralEval));
6180 rules.push(Box::new(Base64NewStringFold));
6181 rules.push(Box::new(UrlDecodeFold));
6182 rules.push(Box::new(CffRecover));
6183 rules.push(Box::new(StaticArrayIndexFold));
6184 rules.push(Box::new(TrailingAssignReturn));
6185 rules.push(Box::new(DeadPureCall));
6186 rules.push(Box::new(CtorDelegationHoist));
6187 rules.push(Box::new(TrailingElseFlatten));
6188 rules.push(Box::new(TrailingIfContinue));
6189 rules.push(Box::new(ConstFieldPropagate));
6190 rules.push(Box::new(NoopPrivateCall));
6191 rules.push(Box::new(ConstMethodInline));
6192 rules.push(Box::new(TwrRecover));
6193 rules.push(Box::new(StringSwitchRecover));
6194 rules
6195}
6196
6197pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
6199 let mut rules = default_java_rules();
6200 let mut engine_all = cure_engine::rules::all_rules();
6201 rules.append(&mut engine_all);
6202 rules
6203}
6204
6205pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
6207 let rules = default_java_rules();
6208 cure_engine::simplify(ast, root, &rules, cfg)
6209}
6210
6211pub fn simplify_unit_with_facts(
6216 ast: &mut JavaAst,
6217 unit: &mut CompilationUnit,
6218 cfg: &Config,
6219 facts: &cure_java_ast::JavaFacts,
6220) -> Report {
6221 ast.set_facts(facts.clone());
6222 let r = simplify_unit(ast, unit, cfg);
6223 ast.clear_facts();
6224 r
6225}
6226
6227pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
6228 let mut total = Report::default();
6229 for _cycle in 0..6 {
6234 let mut round = Report::default();
6235 collect_unit_consts(ast, unit);
6236 if cfg.remove_dead_methods {
6239 collect_noop_private_methods(ast, unit);
6240 } else {
6241 ast.noop_private_methods.clear();
6242 }
6243 for ty in &mut unit.types {
6244 simplify_type(ast, ty, cfg, &mut round);
6245 }
6246 if cfg.remove_dead_methods {
6247 round.edits += remove_dead_private_fields(ast, unit);
6248 round.edits += remove_dead_private_methods(ast, unit);
6249 round.edits += remove_unused_imports(ast, unit);
6250 }
6251 total.edits += round.edits;
6252 total.iterations += round.iterations;
6253 total.discarded += round.discarded;
6254 for (k, v) in round.by_rule {
6255 *total.by_rule.entry(k).or_insert(0) += v;
6256 }
6257 if round.edits == 0 {
6262 break;
6263 }
6264 }
6265 total
6266}
6267
6268fn collect_anon_and_string_names(
6275 ast: &JavaAst,
6276 unit: &CompilationUnit,
6277) -> std::collections::HashSet<String> {
6278 let mut out: std::collections::HashSet<String> = Default::default();
6279 let mut bodies: Vec<JavaId> = Vec::new();
6280 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6281 for root in bodies {
6282 let mut stack = vec![root];
6283 while let Some(n) = stack.pop() {
6284 match ast.data(n) {
6285 NodeData::New { anon_raw: Some(raw), .. } => {
6286 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6287 if !id.is_empty() {
6288 out.insert(id.to_string());
6289 }
6290 }
6291 }
6292 NodeData::Literal(Lit::Str(text)) => {
6293 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6294 if !id.is_empty() {
6295 out.insert(id.to_string());
6296 }
6297 }
6298 }
6299 _ => {}
6300 }
6301 for &c in ast.children(n) {
6302 stack.push(c);
6303 }
6304 }
6305 }
6306 out
6307}
6308
6309fn collect_all_blind_refs(
6338 ast: &JavaAst,
6339 unit: &CompilationUnit,
6340) -> std::collections::HashSet<String> {
6341 let mut out: std::collections::HashSet<String> = Default::default();
6342 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
6343 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6344 if !id.is_empty() {
6345 out.insert(id.to_string());
6346 }
6347 }
6348 };
6349 let mut bodies: Vec<JavaId> = Vec::new();
6351 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
6352 for root in bodies {
6353 let mut stack = vec![root];
6354 while let Some(n) = stack.pop() {
6355 match ast.data(n) {
6356 NodeData::Raw { text } => split_into(text, &mut out),
6357 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
6358 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
6359 NodeData::ForEach { ty, .. } => {
6361 collect_ty_idents(ty, &mut out);
6362 }
6363 NodeData::NewArray { ty, .. } => {
6365 collect_ty_idents(ty, &mut out);
6366 }
6367 NodeData::Lambda { params_raw } => {
6371 split_into(params_raw, &mut out);
6372 }
6373 _ => {}
6374 }
6375 for &c in ast.children(n) {
6376 stack.push(c);
6377 }
6378 }
6379 }
6380 for r in &unit.raws {
6384 split_into(r, &mut out);
6385 }
6386 for_each_type(unit, &mut |ty: &TypeDecl| {
6389 collect_mods_idents_all(&ty.mods, &mut out);
6390 for e in &ty.enum_constants {
6391 split_into(e, &mut out);
6392 }
6393 split_into(&ty.ty_params, &mut out);
6394 split_into(&ty.header, &mut out);
6395 for m in &ty.members {
6396 match m {
6397 Member::Method { mods, name, ty_params, params, .. }
6398 | Member::Constructor { mods, name, ty_params, params, .. } => {
6399 collect_mods_idents_all(mods, &mut out);
6400 split_into(ty_params, &mut out);
6401 for p in params {
6402 collect_mods_idents_all(&p.mods, &mut out);
6403 collect_ty_idents(&p.ty, &mut out);
6404 }
6405 if has_bare_methodsource(mods) {
6410 out.insert(name.clone());
6411 }
6412 }
6413 Member::Field { mods, declarators, .. } => {
6414 collect_mods_idents_all(mods, &mut out);
6415 let _ = declarators;
6417 }
6418 Member::Type(t) => {
6419 let _ = t; }
6421 Member::Raw(text) => {
6428 split_into(text, &mut out);
6429 }
6430 _ => {}
6431 }
6432 }
6433 });
6434 out
6435}
6436
6437fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
6441 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6442 if !id.is_empty() {
6443 out.insert(id.to_string());
6444 }
6445 }
6446}
6447
6448fn has_bare_methodsource(mods: &str) -> bool {
6455 let hay = mods.as_bytes();
6456 const NEEDLE: &[u8] = b"MethodSource";
6457 let mut i = 0usize;
6458 while let Some(p) = mods[i..].find("MethodSource") {
6459 let at = i + p;
6460 let end = at + NEEDLE.len();
6461 let prev_ok = at == 0
6464 || !(hay[at - 1].is_ascii_alphanumeric()
6465 || hay[at - 1] == b'_'
6466 || hay[at - 1] == b'$');
6467 let next_ident =
6468 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
6469 if prev_ok && !next_ident {
6470 let mut j = end;
6471 while j < hay.len() && hay[j].is_ascii_whitespace() {
6472 j += 1;
6473 }
6474 if j >= hay.len() || hay[j] != b'(' {
6475 return true;
6476 }
6477 }
6478 i = end;
6479 }
6480 false
6481}
6482
6483const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
6487 "writeObject",
6488 "readObject",
6489 "readObjectNoData",
6490 "writeReplace",
6491 "readResolve",
6492 "validateObject",
6493];
6494
6495fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6496 use std::collections::HashSet;
6497 let mut removed = 0usize;
6498 let anon_names = collect_anon_and_string_names(ast, unit);
6500 let blind = collect_all_blind_refs(ast, unit);
6505 loop {
6506 let mut referenced: HashSet<(String, usize)> = HashSet::new();
6513 let mut veto: HashSet<String> = HashSet::new();
6514 let mut roots: Vec<JavaId> = Vec::new();
6515 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6516 for body in roots {
6517 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
6518 }
6519 for_each_type(unit, &mut |ty| {
6520 for m in &ty.members {
6521 if let Member::Method { name, params, .. } = m {
6522 if params.iter().any(|p| p.varargs) {
6523 veto.insert(name.clone());
6524 }
6525 }
6526 }
6527 });
6528 let mut changed = false;
6530 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6531 let before = ty.members.len();
6532 ty.members.retain(|m| {
6533 if let Member::Method { mods, name, params, .. } = m {
6534 let is_private = mods.split_whitespace().any(|w| w == "private");
6535 let annotated = mods.contains('@');
6536 let keep = !is_private
6537 || annotated
6538 || params.iter().any(|p| p.varargs)
6540 || veto.contains(name)
6541 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
6545 || anon_names.contains(name)
6546 || blind.contains(name)
6547 || referenced.contains(&(name.clone(), params.len()));
6548 keep
6549 } else {
6550 true
6551 }
6552 });
6553 if ty.members.len() != before {
6554 removed += before - ty.members.len();
6555 changed = true;
6556 }
6557 });
6558 if !changed {
6559 return removed;
6560 }
6561 }
6562}
6563
6564fn strip_ta_prefix(name: &str) -> &str {
6569 if !name.starts_with('<') {
6570 return name;
6571 }
6572 let b = name.as_bytes();
6573 let mut depth = 0i32;
6574 for (i, &c) in b.iter().enumerate() {
6575 match c {
6576 b'<' => depth += 1,
6577 b'>' => {
6578 depth -= 1;
6579 if depth == 0 {
6580 return &name[i + 1..];
6581 }
6582 }
6583 _ => {}
6584 }
6585 }
6586 name }
6588
6589fn collect_call_arity_refs(
6591 ast: &JavaAst,
6592 root: JavaId,
6593 out: &mut std::collections::HashSet<(String, usize)>,
6594 veto: &mut std::collections::HashSet<String>,
6595) {
6596 let mut stack = vec![root];
6597 while let Some(id) = stack.pop() {
6598 match ast.data(id) {
6599 NodeData::Call => {
6600 let ch = ast.children(id).to_vec();
6601 if let Some(&callee) = ch.first() {
6602 let name = match ast.data(callee) {
6603 NodeData::Member { name } | NodeData::VarRef { name } => {
6604 ast.sn(*name).to_string()
6605 }
6606 _ => String::new(),
6607 };
6608 if !name.is_empty() {
6609 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6610 }
6611 }
6612 }
6613 NodeData::MethodRef { name } => {
6614 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6615 veto.insert(strip_ta_prefix(short).to_string());
6616 }
6617 }
6618 _ => {}
6619 }
6620 for &c in ast.children(id) {
6621 stack.push(c);
6622 }
6623 }
6624}
6625
6626fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6628 for ty in &unit.types {
6629 for_each_type_inner(ty, f);
6630 }
6631}
6632fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6633 f(ty);
6634 for m in &ty.members {
6635 if let Member::Type(t) = m {
6636 for_each_type_inner(t, f);
6637 }
6638 }
6639}
6640
6641fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6643 for ty in &mut unit.types {
6644 for_each_type_mut_inner(ty, f);
6645 }
6646}
6647fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6648 f(ty);
6649 for m in &mut ty.members {
6650 if let Member::Type(t) = m {
6651 for_each_type_mut_inner(t, f);
6652 }
6653 }
6654}
6655
6656fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6658 for m in &ty.members {
6659 match m {
6660 Member::Method { body: Some(b), .. }
6661 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6662 Member::Initializer { body, .. } => out.push(*body),
6663 Member::Field { declarators, .. } => {
6664 for d in declarators {
6665 if let Some(init) = d.init {
6666 out.push(init);
6667 }
6668 }
6669 }
6670 Member::Type(t) => collect_member_roots(t, out),
6671 _ => {}
6672 }
6673 }
6674}
6675
6676fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6691 use std::collections::{HashMap, HashSet};
6692 ast.noop_private_methods.clear();
6693 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6694 let mut veto: HashSet<String> = HashSet::new();
6695 for_each_type(unit, &mut |ty: &TypeDecl| {
6696 for m in &ty.members {
6697 if let Member::Method { mods, name, params, body, throws, .. } = m {
6698 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6699 veto.insert(name.clone());
6700 continue;
6701 }
6702 let is_private = mods.split_whitespace().any(|w| w == "private");
6703 let empty = match body {
6704 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6705 None => false,
6706 };
6707 let no_throws = throws.is_empty();
6711 let cand = NoopCandidate {
6712 params: params.iter().map(|p| p.ty.clone()).collect(),
6713 noop: is_private && empty && no_throws,
6714 };
6715 by_name
6716 .entry(name.clone())
6717 .or_default()
6718 .entry(params.len())
6719 .or_default()
6720 .push(cand);
6721 }
6722 }
6723 });
6724 let blind = collect_all_blind_refs(ast, unit);
6727 let mut roots: Vec<JavaId> = Vec::new();
6730 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6731 for root in roots {
6732 let mut stack = vec![root];
6733 while let Some(n) = stack.pop() {
6734 if let NodeData::MethodRef { name } = ast.data(n) {
6735 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6736 veto.insert(strip_ta_prefix(short).to_string());
6737 }
6738 }
6739 for &c in ast.children(n) {
6740 stack.push(c);
6741 }
6742 }
6743 }
6744 for (name, arities) in by_name {
6745 if veto.contains(&name) || blind.contains(&name) {
6746 continue;
6747 }
6748 ast.noop_private_methods.insert(name, arities);
6749 }
6750}
6751
6752fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6757 use std::collections::HashSet;
6758 let mut idents: HashSet<String> = HashSet::new();
6759 let mut roots: Vec<JavaId> = Vec::new();
6761 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6762 for root in roots {
6763 let mut stack = vec![root];
6764 while let Some(n) = stack.pop() {
6765 match ast.data(n) {
6766 NodeData::VarDecl { name, ty, .. } => {
6767 idents.insert(name.clone());
6768 collect_ty_idents(ty, &mut idents);
6769 }
6770 NodeData::VarRef { name } => {
6771 let n = ast.sn(*name);
6775 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6776 if !id.is_empty() {
6777 idents.insert(id.to_string());
6778 }
6779 }
6780 }
6781 NodeData::Member { name } => {
6782 idents.insert(ast.sn(*name).to_string());
6783 }
6784 NodeData::MethodRef { name } => {
6785 let n = ast.sn(*name);
6786 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6787 if !id.is_empty() {
6788 idents.insert(id.to_string());
6789 }
6790 }
6791 }
6792 NodeData::Label { name } => {
6793 idents.insert(name.clone());
6794 }
6795 NodeData::New { ty, anon_raw, .. } => {
6796 collect_ty_idents(ty, &mut idents);
6797 if let Some(raw) = anon_raw {
6800 for id in
6801 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6802 {
6803 if !id.is_empty() {
6804 idents.insert(id.to_string());
6805 }
6806 }
6807 }
6808 }
6809 NodeData::Cast { ty } => {
6810 collect_ty_idents(ty, &mut idents);
6811 }
6812 NodeData::InstanceOf { ty, .. } => {
6813 collect_ty_idents(ty, &mut idents);
6814 }
6815 NodeData::Catch { ty_raw, .. } => {
6816 for id in
6818 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6819 {
6820 if !id.is_empty() {
6821 idents.insert(id.to_string());
6822 }
6823 }
6824 }
6825 NodeData::Raw { text } => {
6826 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6827 if !id.is_empty() {
6828 idents.insert(id.to_string());
6829 }
6830 }
6831 }
6832 _ => {}
6833 }
6834 for &c in ast.children(n) {
6835 stack.push(c);
6836 }
6837 }
6838 }
6839 for name in collect_all_blind_refs(ast, unit) {
6843 idents.insert(name);
6844 }
6845 for r in &unit.raws {
6848 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6849 if !id.is_empty() {
6850 idents.insert(id.to_string());
6851 }
6852 }
6853 }
6854 for_each_type(unit, &mut |ty| {
6857 collect_mods_idents(&ty.mods, &mut idents);
6858 idents.insert(ty.name.clone());
6859 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6860 if !id.is_empty() {
6861 idents.insert(id.to_string());
6862 }
6863 }
6864 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6868 if !id.is_empty() {
6869 idents.insert(id.to_string());
6870 }
6871 }
6872 for e in ty.extends.iter().chain(ty.implements.iter()) {
6873 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6874 if !id.is_empty() {
6875 idents.insert(id.to_string());
6876 }
6877 }
6878 }
6879 for m in &ty.members {
6880 match m {
6881 Member::Field { mods, ty, declarators } => {
6882 collect_mods_idents(mods, &mut idents);
6883 collect_ty_idents(ty, &mut idents);
6884 for d in declarators {
6885 idents.insert(d.name.clone());
6886 }
6887 }
6888 Member::Method { mods, ret, name, params, throws, .. } => {
6889 collect_mods_idents(mods, &mut idents);
6890 collect_ty_idents(ret, &mut idents);
6891 idents.insert(name.clone());
6892 for p in params {
6893 collect_mods_idents(&p.mods, &mut idents);
6894 collect_ty_idents(&p.ty, &mut idents);
6895 idents.insert(p.name.clone());
6896 }
6897 for t in throws {
6898 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6899 if !id.is_empty() {
6900 idents.insert(id.to_string());
6901 }
6902 }
6903 }
6904 }
6905 Member::Constructor { mods, name, params, throws, .. } => {
6906 collect_mods_idents(mods, &mut idents);
6907 idents.insert(name.clone());
6908 for p in params {
6909 collect_mods_idents(&p.mods, &mut idents);
6910 collect_ty_idents(&p.ty, &mut idents);
6911 idents.insert(p.name.clone());
6912 }
6913 for t in throws {
6914 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6915 if !id.is_empty() {
6916 idents.insert(id.to_string());
6917 }
6918 }
6919 }
6920 }
6921 Member::Type(t) => {
6922 let _ = t;
6924 }
6925 _ => {}
6926 }
6927 }
6928 });
6929 let before = unit.imports.len();
6931 unit.imports.retain(|imp| {
6932 if imp.contains('*') {
6933 return true; }
6935 let path = imp.trim_start_matches("static ").trim();
6937 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6938 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6939 return true; }
6941 idents.contains(simple)
6942 });
6943 before - unit.imports.len()
6944}
6945
6946fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6947 match t {
6948 JType::Ref(r) => {
6949 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6950 if !id.is_empty() {
6951 out.insert(id.to_string());
6952 }
6953 }
6954 }
6955 JType::Array(inner) => collect_ty_idents(inner, out),
6956 _ => {}
6957 }
6958}
6959
6960fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6961 if mods.contains('@') {
6963 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6964 if !id.is_empty() {
6965 out.insert(id.to_string());
6966 }
6967 }
6968 }
6969}
6970
6971fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6977 use std::collections::HashSet;
6978 let mut removed = 0usize;
6979 let anon_names = collect_anon_and_string_names(ast, unit);
6981 let blind = collect_all_blind_refs(ast, unit);
6984 let mut referenced: HashSet<String> = HashSet::new();
6986 let mut bodies: Vec<JavaId> = Vec::new();
6987 for_each_type(unit, &mut |ty: &TypeDecl| {
6988 for m in &ty.members {
6989 match m {
6990 Member::Method { body: Some(b), .. }
6991 | Member::Constructor { body: Some(b), .. }
6992 | Member::Initializer { body: b, .. } => bodies.push(*b),
6993 Member::Field { declarators, .. } => {
6994 for d in declarators {
6995 if let Some(init) = d.init {
6996 bodies.push(init);
6997 }
6998 }
6999 }
7000 _ => {}
7001 }
7002 }
7003 });
7004 for root in bodies {
7005 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
7007 while let Some((n, is_callee)) = stack.pop() {
7008 match ast.kind(n) {
7009 NodeKind::VarRef if !is_callee => {
7010 if let Some(nm) = ast.var_name(n) {
7011 referenced.insert(nm.to_string());
7012 }
7013 }
7014 NodeKind::Member => {
7015 if let NodeData::Member { name: sym } = ast.data(n) {
7016 referenced.insert(ast.sn(*sym).to_string());
7017 }
7018 }
7019 _ => {}
7020 }
7021 if ast.kind(n) == NodeKind::Call {
7022 let ch = ast.children(n).to_vec();
7023 if let Some(&first) = ch.first() {
7024 stack.push((first, true));
7025 }
7026 for &c in &ch[1..] {
7027 stack.push((c, false));
7028 }
7029 continue;
7030 }
7031 for &c in ast.children(n) {
7032 stack.push((c, false));
7033 }
7034 }
7035 }
7036 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
7042 for m in ty.members.iter_mut() {
7043 if let Member::Field { mods, declarators, .. } = m {
7044 let is_private = mods.split_whitespace().any(|w| w == "private");
7045 let annotated = mods.contains('@');
7046 if !is_private || annotated {
7047 continue;
7048 }
7049 let dead: Vec<bool> = declarators
7050 .iter()
7051 .map(|d| {
7052 let reflective =
7053 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
7054 let init_ok =
7055 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
7056 !reflective
7057 && init_ok
7058 && !referenced.contains(&d.name)
7059 && !anon_names.contains(&d.name)
7060 && !blind.contains(&d.name)
7061 })
7062 .collect();
7063 let before = declarators.len();
7064 let mut idx = 0usize;
7065 declarators.retain(|_| {
7066 let keep = !dead[idx];
7067 idx += 1;
7068 keep
7069 });
7070 removed += before - declarators.len();
7071 }
7072 }
7073 let before = ty.members.len();
7074 ty.members
7075 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
7076 removed += before - ty.members.len();
7077 });
7078 removed
7079}
7080
7081fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
7082 for m in &mut ty.members {
7083 simplify_member(ast, m, cfg, total);
7084 }
7085}
7086
7087fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
7088 let params: Vec<(String, JType)> = match m {
7090 Member::Method { params, .. } | Member::Constructor { params, .. } => params
7091 .iter()
7092 .map(|p| (p.name.clone(), p.ty.clone()))
7093 .collect(),
7094 _ => Vec::new(),
7095 };
7096 ast.set_param_scope(¶ms);
7097 match m {
7098 Member::Method { body: Some(b), .. }
7099 | Member::Constructor { body: Some(b), .. } => {
7100 let r = simplify(ast, *b, cfg);
7101 merge_report(total, r);
7102 }
7103 Member::Initializer { body, .. } => {
7104 let r = simplify(ast, *body, cfg);
7105 merge_report(total, r);
7106 }
7107 Member::Field { declarators, .. } => {
7108 for d in declarators.iter_mut() {
7109 if let Some(init) = d.init {
7110 let r = simplify(ast, init, cfg);
7111 merge_report(total, r);
7112 let rules = default_java_rules();
7116 let mut cur: JavaId = init;
7117 for _ in 0..8 {
7118 match cure_engine::fold_root(ast, cur, &rules, cfg) {
7119 Some((new_root, name)) => {
7120 d.init = Some(new_root);
7121 cur = new_root;
7122 *total.by_rule.entry(name).or_insert(0) += 1;
7123 total.edits += 1;
7124 let r = simplify(ast, new_root, cfg);
7126 merge_report(total, r);
7127 }
7128 None => break,
7129 }
7130 }
7131 }
7132 }
7133 }
7134 Member::Type(t) => simplify_type(ast, t, cfg, total),
7135 Member::Raw(_) => {}
7136 _ => {}
7137 }
7138}
7139
7140fn merge_report(total: &mut Report, r: Report) {
7141 total.edits += r.edits;
7142 total.iterations += r.iterations;
7143 for (k, v) in r.by_rule {
7144 *total.by_rule.entry(k).or_insert(0) += v;
7145 }
7146}
7147
7148pub fn default_config() -> Config {
7150 Config::default()
7151}