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