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};
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 for_each_elem_ok(ty: &JType) -> bool {
505 match ty {
506 JType::Ref(n) => !n.trim_start().starts_with('?'),
507 _ => true,
508 }
509}
510
511fn collect_var_refs(lang: &JavaAst, root: JavaId, name: &str, out: &mut Vec<JavaId>) {
513 let mut stack = vec![root];
514 while let Some(n) = stack.pop() {
515 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
516 out.push(n);
517 }
518 for &c in lang.children(n) {
519 stack.push(c);
520 }
521 }
522}
523
524fn parent_of_recv(lang: &JavaAst, root: JavaId, target: JavaId) -> JavaId {
526 let mut stack = vec![root];
527 while let Some(n) = stack.pop() {
528 for &c in lang.children(n) {
529 if c == target {
530 return n;
531 }
532 stack.push(c);
533 }
534 }
535 root
536}
537
538fn subtree_has_var(lang: &JavaAst, id: JavaId, name: &str) -> bool {
539 let mut stack = vec![id];
540 while let Some(n) = stack.pop() {
541 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
542 return true;
543 }
544 for &c in lang.children(n) {
545 stack.push(c);
546 }
547 }
548 false
549}
550
551fn subtree_has_binding(lang: &JavaAst, id: JavaId, name: &str) -> bool {
553 let mut stack = vec![id];
554 while let Some(n) = stack.pop() {
555 match lang.data(n) {
556 NodeData::Catch { name: cn, .. } if cn == name => return true,
557 NodeData::ForEach { name: vn, .. } if vn == name => return true,
558 NodeData::Lambda { params_raw } => {
561 for part in
562 params_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
563 {
564 if part == name {
565 return true;
566 }
567 }
568 }
569 NodeData::InstanceOf { bind: Some(b), .. } if b == name => return true,
571 NodeData::VarDecl { name: dn, .. } if dn == name => return true,
574 _ => {}
575 }
576 for &c in lang.children(n) {
577 stack.push(c);
578 }
579 }
580 false
581}
582
583pub struct NewStringFold;
589
590impl Rule<JavaAst> for NewStringFold {
591 fn name(&self) -> &'static str {
592 "new_string_fold"
593 }
594 fn kinds(&self) -> &'static [NodeKind] {
595 &[NodeKind::New]
596 }
597 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
598 let root = ctx.root();
599 let lang = ctx.lang;
600 let NodeData::New { ty, .. } = lang.data(id) else {
601 return None;
602 };
603 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
604 return None;
605 }
606 if lang.unit_shadows_lang {
610 return None;
611 }
612 let _ = root;
615 if lang.unit_string_identity {
616 return None;
617 }
618 let args = lang.children(id).to_vec();
619 match args.len() {
620 0 => {
621 let empty = lang.build_str("");
622 Some(Edit::Replace {
623 target: id,
624 with: empty,
625 })
626 }
627 1 => {
628 if matches!(lang.literal(args[0]), Some(LitRef::Str(_))) {
629 Some(Edit::Replace {
630 target: id,
631 with: args[0],
632 })
633 } else {
634 None
635 }
636 }
637 _ => None,
638 }
639 }
640}
641
642
643pub struct NewStringCharArrayFold;
649
650impl Rule<JavaAst> for NewStringCharArrayFold {
651 fn name(&self) -> &'static str {
652 "new_string_char_array_fold"
653 }
654 fn kinds(&self) -> &'static [NodeKind] {
655 &[NodeKind::New]
656 }
657 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
658 let root = ctx.root();
659 let lang = ctx.lang;
660 let NodeData::New { ty, .. } = lang.data(id) else {
661 return None;
662 };
663 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
664 return None;
665 }
666 let _ = root;
667 if lang.unit_shadows_lang || lang.unit_string_identity {
668 return None;
669 }
670 let args = lang.children(id).to_vec();
672 if args.len() != 1 {
673 return None;
674 }
675 let NodeData::NewArray { sized, .. } = lang.data(args[0]) else {
676 return None;
677 };
678 if *sized != 0 {
679 return None;
680 }
681 let arr_children = lang.children(args[0]).to_vec();
682 let lit = *arr_children.first()?;
683 if lang.kind(lit) != NodeKind::ArrayLit {
684 return None;
685 }
686 let mut text = String::new();
687 for &c in lang.children(lit) {
688 match lang.literal(c) {
689 Some(LitRef::Char(ch)) => text.push(ch),
690 _ => return None, }
692 }
693 let with = lang.build_str(&text);
694 Some(Edit::Replace {
695 target: id,
696 with,
697 })
698 }
699}
700
701pub struct EmptyFinallyStrip;
709
710impl Rule<JavaAst> for EmptyFinallyStrip {
711 fn name(&self) -> &'static str {
712 "empty_finally_strip"
713 }
714 fn kinds(&self) -> &'static [NodeKind] {
715 &[NodeKind::Try]
716 }
717 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
718 let lang = ctx.lang;
719 if lang.kind(id) != NodeKind::Try {
720 return None;
721 }
722 let ch = lang.children(id).to_vec();
723 let is_empty_block = |n: JavaId| -> bool {
730 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
731 };
732 let try_idx = ch.iter().position(|&c| lang.kind(c) == NodeKind::Block)?;
733 if ch.len() < try_idx + 2 {
734 return None; }
736 let last = *ch.last()?;
738 if lang.kind(last) == NodeKind::Catch {
739 return None;
740 }
741 if !is_empty_block(last) {
742 return None;
743 }
744 Some(Edit::Splice {
747 node: id,
748 index: ch.len() - 1,
749 remove: 1,
750 insert: Vec::new(),
751 })
752 }
753}
754
755fn print_equiv(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
801 if lang.kind(a) != lang.kind(b) {
802 let (ia, ib) = (lang.kind(a), lang.kind(b));
804 let unwrap = |k: NodeKind, x: JavaId| -> Option<JavaId> {
805 if k == NodeKind::ExprStmt {
806 lang.children(x).first().copied()
807 } else {
808 Some(x)
809 }
810 };
811 return match (unwrap(ia, a), unwrap(ib, b)) {
812 (Some(x), Some(y)) => print_equiv_inner(lang, x, y),
813 _ => false,
814 };
815 }
816 print_equiv_inner(lang, a, b)
817}
818
819fn print_equiv_inner(lang: &JavaAst, a: JavaId, b: JavaId) -> bool {
820 if lang.kind(a) != lang.kind(b) {
821 return false;
822 }
823 match (lang.literal(a), lang.literal(b)) {
825 (Some(x), Some(y)) => {
826 if x != y {
827 return false;
828 }
829 }
830 (None, None) => {}
831 _ => return false,
832 }
833 let (ca, cb) = (lang.children(a), lang.children(b));
834 if ca.len() != cb.len() {
835 return false;
836 }
837 ca.iter().zip(cb.iter()).all(|(&x, &y)| print_equiv_inner(lang, x, y))
838}
839
840fn rest_reassigns_field(lang: &JavaAst, rest: &[JavaId], name: &str) -> bool {
844 for &root in rest {
845 let mut stack = vec![root];
846 while let Some(n) = stack.pop() {
847 match lang.kind(n) {
848 NodeKind::Assign => {
849 let t = lang.children(n)[0];
850 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
851 return true;
852 }
853 }
854 NodeKind::Unary => {
855 if let NodeData::Unary { op } = lang.data(n) {
856 if op.is_incdec() {
857 let t = lang.children(n)[0];
858 if lang.kind(t) == NodeKind::VarRef
859 && lang.var_name(t) == Some(name)
860 {
861 return true;
862 }
863 }
864 }
865 }
866 _ => {}
867 }
868 for &c in lang.children(n) {
869 stack.push(c);
870 }
871 }
872 }
873 false
874}
875
876pub struct StaticExec;
877
878impl Rule<JavaAst> for StaticExec {
879 fn name(&self) -> &'static str {
880 "static_exec"
881 }
882 fn kinds(&self) -> &'static [NodeKind] {
883 &[NodeKind::Block]
884 }
885 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
886 if ctx.parent(id).is_some() {
889 return None;
890 }
891 let lang = ctx.lang;
892 if lang.kind(id) != NodeKind::Block {
893 return None;
894 }
895 let stmts = lang.children(id).to_vec();
896 if stmts.len() < 3 {
897 return None; }
899 let mut ex = vexec::Exec::new(lang);
905 let run = ex.run_prefix(&stmts).ok()?;
906 if std::env::var("CURE_DBG_P1").is_ok() {
907 eprintln!(
908 "[P1-se] run ok: cuts={} total_completed={} budget={} log_len={} root_cut=({},{}) events={:?}",
909 run.cuts.len(),
910 run.cuts.iter().map(|c| c.completed).sum::<usize>(),
911 ex.budget_exhausted(),
912 ex.effect_log().len(),
913 run.cuts.last().map(|c| c.log_start).unwrap_or(0),
914 run.cuts.last().map(|c| c.log_end).unwrap_or(0),
915 ex.effect_log().iter().map(|e| match e {
916 vexec::EffectEvent::FieldWrite(_) => "FW",
917 vexec::EffectEvent::ClassLoad { .. } => "CL",
918 vexec::EffectEvent::OpaqueFieldWrite { .. } => "OFW",
919 }).collect::<Vec<_>>()
920 );
921 }
922 if ex.budget_exhausted() {
924 return None;
925 }
926 let root_idx = run.root_cut_index();
927 let total_completed: usize = run.cuts.iter().map(|c| c.completed).sum();
929 if total_completed < 3 {
930 return None;
931 }
932 let true_rest = run.true_rest();
936 if !true_rest.is_empty() {
937 for ev in ex.effect_log().iter() {
938 if let vexec::EffectEvent::FieldWrite(k) = ev {
939 if let Some(name) = lang.name_of_key(*k) {
940 if lang.final_fields.contains(&name)
945 && rest_reassigns_field(lang, &true_rest, &name)
946 {
947 return None;
948 }
949 }
950 }
951 }
952 }
953 let log: Vec<vexec::EffectEvent> = ex.effect_log().to_vec();
955 let field_finals: Vec<(u32, vexec::VVal)> =
956 ex.field_writes().iter().map(|(k, v)| (*k, v.clone())).collect();
957 let decl_order: Vec<u32> = ex.decl_order().to_vec();
958 let ret_payload: Option<Option<vexec::VVal>> = match &run.early_exit {
961 Some(vexec::Flow::Return(v)) => Some(v.clone()),
962 _ => None,
963 };
964 let mut escaped_by_cut: Vec<Vec<(u32, vexec::VVal, u8, JType)>> = Vec::new();
967 for (j, cut) in run.cuts.iter().enumerate() {
968 let range_end = if j == 0 {
969 decl_order.len()
970 } else {
971 run.cuts[j - 1].decl_start
972 };
973 let rest_j = run.rest_for_cut(j);
974 let mut esc: Vec<(u32, vexec::VVal, u8, JType)> = Vec::new();
975 for &k in &decl_order[cut.decl_start.min(range_end)..range_end] {
976 if ex.var_value(k).is_none() {
977 continue; }
979 let name = lang.name_of_key(k)?;
980 let referenced = rest_j.iter().any(|&st| {
981 cure_engine::analysis::subtree_contains(lang, st, |n| {
982 lang.kind(n) == NodeKind::VarRef
983 && lang.var_name(n) == Some(name.as_str())
984 })
985 });
986 if referenced {
987 esc.push((
988 k,
989 ex.var_value(k).cloned().unwrap_or(vexec::VVal::Undef),
990 ex.var_domain(k),
991 ex.var_decl_ty(k).unwrap_or(JType::Var),
994 ));
995 }
996 }
997 escaped_by_cut.push(esc);
998 }
999 drop(ex);
1000 let mut edits: Vec<Edit<JavaAst>> = Vec::new();
1002 for (j, cut) in run.cuts.iter().enumerate() {
1003 let block_id = if j == root_idx {
1004 id
1005 } else {
1006 cut.block_hint?
1007 };
1008 let mut insert: Vec<JavaId> = Vec::new();
1009 for pos in cut.log_start..cut.log_end {
1011 match &log[pos] {
1012 vexec::EffectEvent::FieldWrite(k) => {
1013 let is_last = !log[pos + 1..]
1015 .iter()
1016 .any(|ev| matches!(ev, vexec::EffectEvent::FieldWrite(k2) if k2 == k));
1017 if !is_last {
1018 continue;
1019 }
1020 let name = lang.name_of_key(*k)?;
1021 if lang.is_param_name(&name) {
1023 return None;
1024 }
1025 let v = field_finals
1026 .iter()
1027 .find(|(fk, _)| fk == k)
1028 .map(|(_, v)| v.clone())?;
1029 let v = match (&v, lang.field_types.get(&name)) {
1034 (vexec::VVal::L(x), Some(JType::Int)) => vexec::VVal::I(*x as i32),
1035 (vexec::VVal::L(x), Some(JType::Byte)) => vexec::VVal::I(*x as i8 as i32),
1036 (vexec::VVal::L(x), Some(JType::Short)) => vexec::VVal::I(*x as i16 as i32),
1037 (vexec::VVal::L(x), Some(JType::Char)) => vexec::VVal::I(*x as u16 as i32),
1038 _ => v,
1039 };
1040 let (val, mut extra) = materialize(lang, &v)?;
1041 let tgt = lang.var(&name);
1042 insert.push(lang.assign(tgt, val));
1043 insert.append(&mut extra);
1044 }
1045 vexec::EffectEvent::OpaqueFieldWrite { k, stmt, call, args } => {
1046 if std::env::var("CURE_DBG_P1").is_ok() {
1047 eprintln!("[P1-mat] OpaqueFieldWrite entered k={}", k);
1048 }
1049 let name = lang.name_of_key(*k)?;
1054 if lang.is_param_name(&name) {
1056 return None;
1057 }
1058 let cch = lang.children(*call).to_vec();
1059 let mut new_args: Vec<JavaId> = Vec::new();
1060 for (i, &arg) in cch.iter().enumerate().skip(1) {
1061 let val = args.get(i - 1).cloned().flatten();
1062 let replaced = match val {
1063 Some(vexec::VVal::S(t)) => Some(lang.lit(Lit::Str(t))),
1064 Some(vexec::VVal::I(x)) => Some(lang.lit(Lit::Int(x as i64))),
1065 Some(vexec::VVal::L(x)) => Some(lang.lit(Lit::Long(x))),
1066 Some(vexec::VVal::B(b)) => Some(lang.lit(Lit::Bool(b))),
1067 _ => None,
1068 };
1069 new_args.push(replaced.unwrap_or_else(|| lang.copy_subtree(arg)));
1070 }
1071 let callee = *lang.children(*call).first()?;
1072 let callee_copy = lang.copy_subtree(callee);
1073 let mut ch = vec![callee_copy];
1074 ch.extend(new_args);
1075 let new_call = lang.clone_node(*call, ch);
1076 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1077 lang.clone_node(*stmt, vec![new_call])
1078 } else {
1079 new_call
1080 };
1081 let tgt = lang.var(&name);
1083 let assign = lang.assign(tgt, new_stmt);
1084 insert.push(assign);
1085 }
1086 vexec::EffectEvent::ClassLoad { stmt, call, name } => {
1087 let lit = lang.lit(Lit::Str(name.clone()));
1095 let callee = *lang.children(*call).first()?;
1096 let callee_copy = lang.copy_subtree(callee);
1097 let new_call = lang.clone_node(*call, vec![callee_copy, lit]);
1098 let new_stmt = if lang.kind(*stmt) == NodeKind::ExprStmt {
1099 lang.clone_node(*stmt, vec![new_call])
1100 } else {
1101 new_call
1102 };
1103 let try_block = lang.block(vec![new_stmt]);
1104 let catch_block = lang.block(vec![]);
1105 let catch_arm = lang.catch_("Exception", "cure$ex", catch_block);
1106 let wrapped = lang.try_(vec![], try_block, vec![catch_arm], None);
1107 insert.push(wrapped);
1108 }
1109 }
1110 }
1111 let mut alias_first: std::collections::HashMap<usize, String> =
1116 std::collections::HashMap::new();
1117 for (k, v, dom, dty) in &escaped_by_cut[j] {
1118 let name = lang.name_of_key(*k)?;
1119 let (val, mut extra) = match v {
1120 vexec::VVal::CA(a) => {
1121 let addr = std::rc::Rc::as_ptr(a) as usize;
1122 match alias_first.get(&addr) {
1123 Some(first) => (lang.var(first), vec![]),
1124 None => {
1125 alias_first.insert(addr, name.clone());
1126 materialize(lang, v)?
1127 }
1128 }
1129 }
1130 vexec::VVal::SA(a) => {
1131 let addr = std::rc::Rc::as_ptr(a) as usize;
1132 match alias_first.get(&addr) {
1133 Some(first) => (lang.var(first), vec![]),
1134 None => {
1135 alias_first.insert(addr, name.clone());
1136 materialize(lang, v)?
1137 }
1138 }
1139 }
1140 _ => materialize(lang, v)?,
1141 };
1142 let (ty, val) = match (*dom, val) {
1147 (2, lit) if lang.kind(lit) == NodeKind::Literal => {
1148 match lang.literal(lit) {
1151 Some(LitRef::Int(v)) => {
1152 match u32::try_from(v).ok().and_then(char::from_u32) {
1153 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1154 None => (JType::Char, lit),
1155 }
1156 }
1157 Some(LitRef::Long(v)) => {
1158 match u32::try_from(v as i32 as i64).ok().and_then(char::from_u32) {
1159 Some(c) => (JType::Char, lang.lit(Lit::Char(c))),
1160 None => (JType::Char, lit),
1161 }
1162 }
1163 _ => (JType::Char, lit),
1164 }
1165 }
1166 (3, lit) => (JType::Byte, lit),
1167 (4, lit) => (JType::Short, lit),
1168 (5, lit) if lang.kind(lit) == NodeKind::Literal => {
1169 if let Some(LitRef::Int(v)) = lang.literal(lit) {
1170 (JType::Long, lang.build_int(v, true))
1171 } else {
1172 (JType::Long, lit)
1173 }
1174 }
1175 (_, lit) => {
1180 if matches!(dty, JType::Ref(_)) {
1181 (dty.clone(), lit)
1182 } else {
1183 (JType::Var, lit)
1184 }
1185 }
1186 };
1187 let stmt = lang.var_decl(&name, ty, Some(val));
1188 insert.push(stmt);
1189 insert.append(&mut extra);
1190 }
1191 if j == root_idx {
1193 if let Some(v) = ret_payload.clone().flatten() {
1194 let (val, mut extra) = materialize(lang, &v)?;
1195 let stmt = lang.ret(Some(val));
1196 insert.push(stmt);
1197 insert.append(&mut extra);
1198 }
1199 }
1200 if insert.is_empty() && cut.completed == 0 {
1202 continue;
1203 }
1204 {
1207 let removed = lang.children(block_id).to_vec();
1208 if insert.len() == cut.completed
1209 && cut.completed <= removed.len()
1210 && insert
1211 .iter()
1212 .zip(removed[..cut.completed].iter())
1213 .all(|(&n, &o)| print_equiv(lang, n, o))
1214 {
1215 continue;
1216 }
1217 }
1218 edits.push(Edit::Splice {
1219 node: block_id,
1220 index: 0,
1221 remove: cut.completed,
1222 insert,
1223 });
1224 }
1225 if std::env::var("CURE_DBG_P1").is_ok() {
1226 eprintln!("[P1-se] edits={} (empty→reject)", edits.len());
1227 }
1228 if edits.is_empty() {
1229 return None;
1230 }
1231 if edits.len() == 1 {
1233 Some(edits.pop().unwrap())
1234 } else {
1235 Some(Edit::Multi(edits))
1236 }
1237 }
1238}
1239
1240fn materialize(lang: &mut JavaAst, v: &vexec::VVal) -> Option<(JavaId, Vec<JavaId>)> {
1243 use vexec::VVal;
1244 match v {
1245 VVal::I(x) => Some((lang.lit(Lit::Int(*x as i64)), vec![])),
1246 VVal::L(x) => Some((lang.lit(Lit::Long(*x)), vec![])),
1247 VVal::B(b) => Some((lang.lit(Lit::Bool(*b)), vec![])),
1248 VVal::S(s) => Some((lang.lit(Lit::Str(s.clone())), vec![])),
1249 VVal::CA(a) => {
1250 let chars: Vec<char> = a.borrow().clone();
1252 let lits: Vec<JavaId> = chars.iter().map(|&c| lang.lit(Lit::Char(c))).collect();
1253 let lit_arr = lang.array_lit(lits);
1254 let node = lang.new_array(JType::Char, 1, 0, vec![], Some(lit_arr));
1255 Some((node, vec![]))
1256 }
1257 VVal::SA(elems) => {
1258 let arr = elems.borrow().clone();
1261 if arr.iter().all(|e| matches!(e, VVal::S(_))) {
1262 let lits: Vec<JavaId> = arr
1263 .iter()
1264 .map(|e| match e {
1265 VVal::S(sv) => lang.lit(Lit::Str(sv.clone())),
1266 _ => unreachable!(),
1267 })
1268 .collect();
1269 let lit_arr = lang.array_lit(lits);
1270 let node =
1271 lang.new_array(JType::Ref("String".to_string()), 1, 0, vec![], Some(lit_arr));
1272 Some((node, vec![]))
1273 } else {
1274 None
1279 }
1280 }
1281 VVal::Undef => None,
1283 }
1284}
1285
1286pub struct TryUnwrapRethrow;
1287
1288impl Rule<JavaAst> for TryUnwrapRethrow {
1289 fn name(&self) -> &'static str {
1290 "try_unwrap_rethrow"
1291 }
1292 fn kinds(&self) -> &'static [NodeKind] {
1293 &[NodeKind::Try]
1294 }
1295 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1296 let lang = ctx.lang;
1297 if lang.kind(id) != NodeKind::Try {
1298 return None;
1299 }
1300 let ch = lang.children(id).to_vec();
1301 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1303 let catches: Vec<JavaId> =
1304 ch.iter().copied().filter(|&c| lang.kind(c) == NodeKind::Catch).collect();
1305 if catches.is_empty() {
1306 return None;
1307 }
1308 let rethrow_only = |c: JavaId| -> bool {
1309 let block = match lang.children(c).first() {
1311 Some(&b) => b,
1312 None => return false,
1313 };
1314 let stmts = lang.children(block);
1315 if stmts.len() != 1 {
1316 return false;
1317 }
1318 let stmt = stmts[0];
1319 if lang.kind(stmt) != NodeKind::Throw {
1320 return false;
1321 }
1322 let Some(&thrown) = lang.children(stmt).first() else { return false };
1323 if lang.kind(thrown) != NodeKind::VarRef {
1325 return false;
1326 }
1327 match &lang.node(c).data {
1328 NodeData::Catch { name, .. } => lang.var_name(thrown) == Some(name.as_str()),
1329 _ => false,
1330 }
1331 };
1332 let all_rethrow = catches.iter().all(|&c| rethrow_only(c));
1333 if all_rethrow {
1334 let extras: Vec<JavaId> = ch
1336 .iter()
1337 .copied()
1338 .filter(|&c| c != try_block && lang.kind(c) != NodeKind::Catch)
1339 .collect();
1340 if extras.is_empty() {
1341 return Some(Edit::Replace {
1342 target: id,
1343 with: try_block,
1344 });
1345 }
1346 return None;
1347 }
1348 let catch_slots: Vec<usize> = ch
1354 .iter()
1355 .enumerate()
1356 .filter(|(_, &c)| lang.kind(c) == NodeKind::Catch)
1357 .map(|(i, _)| i)
1358 .collect();
1359 for &slot in &catch_slots {
1360 let c = ch[slot];
1361 if !rethrow_only(c) {
1362 continue;
1363 }
1364 let later_all_rethrow = catch_slots
1365 .iter()
1366 .filter(|&&s2| s2 > slot)
1367 .all(|&s2| rethrow_only(ch[s2]));
1368 if !later_all_rethrow {
1369 continue;
1370 }
1371 return Some(Edit::Splice {
1372 node: id,
1373 index: slot,
1374 remove: 1,
1375 insert: vec![],
1376 });
1377 }
1378 None
1379 }
1380}
1381
1382pub struct TryUnwrapNoCatch;
1383
1384impl Rule<JavaAst> for TryUnwrapNoCatch {
1385 fn name(&self) -> &'static str {
1386 "try_unwrap_no_catch"
1387 }
1388 fn kinds(&self) -> &'static [NodeKind] {
1389 &[NodeKind::Try]
1390 }
1391 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1392 let lang = ctx.lang;
1393 if lang.kind(id) != NodeKind::Try {
1394 return None;
1395 }
1396 let ch = lang.children(id).to_vec();
1397 if ch.is_empty() {
1398 return None;
1399 }
1400 let has_catch = ch.iter().any(|&c| lang.kind(c) == NodeKind::Catch);
1403 if has_catch {
1404 return None;
1405 }
1406 let try_block = *ch.iter().find(|&&c| lang.kind(c) == NodeKind::Block)?;
1410 let rest: Vec<JavaId> = ch
1411 .iter()
1412 .copied()
1413 .filter(|&c| c != try_block)
1414 .collect();
1415 for &r in &rest {
1418 let is_empty_block = lang.kind(r) == NodeKind::Block && lang.children(r).is_empty();
1419 if !is_empty_block {
1420 return None; }
1422 }
1423 Some(Edit::Replace {
1424 target: id,
1425 with: try_block,
1426 })
1427 }
1428}
1429
1430pub struct LoopHeadBreak;
1438
1439impl Rule<JavaAst> for LoopHeadBreak {
1440 fn name(&self) -> &'static str {
1441 "loop_head_break"
1442 }
1443 fn kinds(&self) -> &'static [NodeKind] {
1444 &[NodeKind::While, NodeKind::DoWhile]
1445 }
1446 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1447 let lang = ctx.lang;
1448 let body = match lang.kind(id) {
1449 NodeKind::While => {
1450 let ch = lang.children(id).to_vec();
1451 if !matches!(lang.literal(ch[0]), Some(LitRef::Bool(true))) {
1452 return None;
1453 }
1454 ch[1]
1455 }
1456 NodeKind::DoWhile => {
1457 let ch = lang.children(id).to_vec();
1458 if !matches!(lang.literal(ch[1]), Some(LitRef::Bool(true))) {
1459 return None;
1460 }
1461 ch[0]
1462 }
1463 _ => return None,
1464 };
1465 if lang.kind(body) != NodeKind::Block {
1466 return None;
1467 }
1468 let bch = lang.children(body).to_vec();
1469 let is_do = lang.kind(id) == NodeKind::DoWhile;
1470 if is_do && bch.len() != 1 {
1472 return None;
1473 }
1474 let first = *bch.first()?;
1475 if lang.kind(first) != NodeKind::If {
1476 return None;
1477 }
1478 let ich = lang.children(first).to_vec();
1479 let c = ich[0];
1480
1481 let bare_break = |n: JavaId| matches!(lang.data(n), NodeData::Break { label: None });
1482 let is_break = |n: JavaId| match lang.kind(n) {
1483 NodeKind::Break => bare_break(n),
1484 NodeKind::Block if lang.children(n).len() == 1 => bare_break(lang.children(n)[0]),
1485 _ => false,
1486 };
1487 let bare_continue = |n: JavaId| matches!(lang.data(n), NodeData::Continue { label: None });
1488 let ends_with_continue = |n: JavaId| -> bool {
1489 match lang.kind(n) {
1490 NodeKind::Continue => bare_continue(n),
1491 NodeKind::Block => lang
1492 .children(n)
1493 .last()
1494 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l)),
1495 _ => false,
1496 }
1497 };
1498 let strip_tail_continue = |n: JavaId| -> Vec<JavaId> {
1499 match lang.kind(n) {
1500 NodeKind::Continue => vec![],
1501 NodeKind::Block => {
1502 let ch = lang.children(n).to_vec();
1503 if ch
1504 .last()
1505 .is_some_and(|&l| lang.kind(l) == NodeKind::Continue && bare_continue(l))
1506 {
1507 ch[..ch.len() - 1].to_vec()
1508 } else {
1509 ch
1510 }
1511 }
1512 _ => vec![n],
1513 }
1514 };
1515
1516 match ich.len() {
1517 2 => {
1518 if is_do || !is_break(ich[1]) {
1520 return None;
1521 }
1522 let mut rest: Vec<JavaId> = bch[1..].to_vec();
1523 if let Some(&last) = rest.last() {
1524 if lang.kind(last) == NodeKind::Continue && bare_continue(last) {
1525 rest.pop();
1526 }
1527 }
1528 let nc = lang.build_unary(UnOp::Not, c);
1529 let inner = lang.build_block(rest);
1530 let w = lang.while_(nc, inner);
1531 Some(Edit::Replace {
1532 target: id,
1533 with: w,
1534 })
1535 }
1536 3 => {
1537 let (then, els) = (ich[1], ich[2]);
1538 if ends_with_continue(then) && is_break(els) {
1540 let rest = strip_tail_continue(then);
1541 let inner = lang.build_block(rest);
1542 let w = lang.while_(c, inner);
1543 return Some(Edit::Replace {
1544 target: id,
1545 with: w,
1546 });
1547 }
1548 if is_break(then) && ends_with_continue(els) {
1550 let rest = strip_tail_continue(els);
1551 let inner = lang.build_block(rest);
1552 let nc = lang.build_unary(UnOp::Not, c);
1553 let w = lang.while_(nc, inner);
1554 return Some(Edit::Replace {
1555 target: id,
1556 with: w,
1557 });
1558 }
1559 if !is_do && is_break(then) {
1561 let mut all = strip_tail_continue(els);
1562 all.extend(bch[1..].iter().copied());
1563 let inner = lang.build_block(all);
1564 let nc = lang.build_unary(UnOp::Not, c);
1565 let w = lang.while_(nc, inner);
1566 return Some(Edit::Replace {
1567 target: id,
1568 with: w,
1569 });
1570 }
1571 None
1572 }
1573 _ => None,
1574 }
1575 }
1576}
1577
1578pub struct WhileIteratorToForEach;
1579
1580impl Rule<JavaAst> for WhileIteratorToForEach {
1581 fn name(&self) -> &'static str {
1582 "while_iterator_to_for_each"
1583 }
1584 fn kinds(&self) -> &'static [NodeKind] {
1585 &[NodeKind::While]
1586 }
1587 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1588 let parent = ctx.parent(id)?;
1590 let idx = ctx.index(id)?;
1591 let lang = ctx.lang;
1592 if lang.kind(id) != NodeKind::While {
1593 return None;
1594 }
1595 let wch = lang.children(id).to_vec();
1596 let body = wch[1];
1597 if lang.kind(parent) != NodeKind::Block {
1599 return None;
1600 }
1601 if idx == 0 {
1602 return None;
1603 }
1604 let stmts = lang.children(parent).to_vec();
1605 let cond = wch[0];
1608 let it_hint = match lang.kind(cond) {
1609 NodeKind::Call => {
1610 let cc0 = lang.children(cond).to_vec();
1611 match lang.data(*cc0.first()?) {
1612 NodeData::Member { name } if lang.sn(*name) == "hasNext" => {
1613 lang.var_name(lang.children(cc0[0])[0]).map(|x| x.to_string())
1614 }
1615 _ => None,
1616 }
1617 }
1618 _ => None,
1619 };
1620 let mut decl = None;
1621 if let Some(hint) = it_hint {
1622 let mut up = idx;
1623 for _ in 0..4 {
1624 if up == 0 {
1625 break;
1626 }
1627 up -= 1;
1628 let st = stmts[up];
1629 if lang.kind(st) == NodeKind::VarDecl && lang.var_name(st) == Some(hint.as_str()) {
1630 decl = Some(st);
1631 break;
1632 }
1633 if subtree_has_var(lang, st, &hint) {
1634 break;
1635 }
1636 }
1637 }
1638 let decl = decl?;
1639 let it_name = lang.var_name(decl)?.to_string();
1640 let init_call = lang.children(decl).first().copied()?;
1641 if lang.kind(init_call) != NodeKind::Call {
1642 return None;
1643 }
1644 let ic = lang.children(init_call).to_vec();
1645 let NodeData::Member { name: m0 } = lang.data(*ic.first()?) else {
1646 return None;
1647 };
1648 if lang.sn(*m0) != "iterator" || ic.len() != 1 {
1649 return None;
1650 }
1651 let iterable = lang.children(ic[0])[0];
1652 if subtree_has_var(&*lang, iterable, &it_name) {
1653 return None;
1654 }
1655 if lang.kind(cond) != NodeKind::Call {
1657 return None;
1658 }
1659 let cc = lang.children(cond).to_vec();
1660 let NodeData::Member { name: m1 } = lang.data(*cc.first()?) else {
1661 return None;
1662 };
1663 if lang.sn(*m1) != "hasNext" || cc.len() != 1 {
1664 return None;
1665 }
1666 if lang.var_name(lang.children(cc[0])[0]) != Some(it_name.as_str()) {
1667 return None;
1668 }
1669 if lang.kind(body) != NodeKind::Block {
1671 return None;
1672 }
1673 let bch = lang.children(body).to_vec();
1674 let first = *bch.first()?;
1675 if lang.kind(first) != NodeKind::VarDecl {
1676 let it_refs_in_body: Vec<JavaId> = {
1679 let mut v = Vec::new();
1680 collect_var_refs(&*lang, body, &it_name, &mut v);
1681 v
1682 };
1683 if it_refs_in_body.len() != 1 {
1684 return None;
1685 }
1686 let next_recv = it_refs_in_body[0];
1687 let member = parent_of_recv(lang, body, next_recv);
1689 if lang.kind(member) != NodeKind::Member {
1690 return None;
1691 }
1692 let NodeData::Member { name: mn } = lang.data(member) else {
1693 return None;
1694 };
1695 if lang.sn(*mn) != "next" {
1696 return None;
1697 }
1698 let next_call = parent_of_recv(lang, body, member);
1699 if lang.kind(next_call) != NodeKind::Call {
1700 return None;
1701 }
1702 if lang.children(next_call).len() != 1 {
1703 return None;
1704 }
1705 let mut wrapped = next_call;
1707 loop {
1708 let par = parent_of_recv(lang, body, wrapped);
1709 match lang.kind(par) {
1710 NodeKind::Cast | NodeKind::Paren => wrapped = par,
1711 _ => break,
1712 }
1713 }
1714 {
1724 let mut cur = wrapped;
1725 while cur != body {
1726 let par = parent_of_recv(lang, body, cur);
1727 if par == body || par == cur {
1728 break; }
1730 let ch = lang.children(par).to_vec();
1731 let idx = ch.iter().position(|&c| c == cur);
1732 match lang.kind(par) {
1733 NodeKind::Block
1734 | NodeKind::ExprStmt
1735 | NodeKind::Label
1736 | NodeKind::Return
1737 | NodeKind::Throw => {}
1738 NodeKind::Ternary => {
1739 if idx.unwrap_or(0) > 0 {
1741 return None;
1742 }
1743 }
1744 NodeKind::Binary => {
1745 if let Some(op) = lang.bin_op(par) {
1746 if matches!(op, BinOp::And | BinOp::Or) && idx == Some(1) {
1747 return None; }
1749 }
1750 }
1751 NodeKind::If
1752 | NodeKind::While
1753 | NodeKind::DoWhile
1754 | NodeKind::For
1755 | NodeKind::Switch
1756 | NodeKind::Try
1757 | NodeKind::Catch
1758 | NodeKind::Synchronized
1759 | NodeKind::ForEach => return None,
1760 NodeKind::Lambda | NodeKind::Assert => return None,
1765 _ => {} }
1767 cur = par;
1768 }
1769 }
1770 let it_decl_elem = match lang.data(decl) {
1778 NodeData::VarDecl { ty, .. } => generic_elem_ty(ty),
1779 _ => None,
1780 };
1781 let elem = match it_decl_elem {
1782 Some(t) => t,
1783 None => match lang.var_type(iterable) {
1784 Some(t) => generic_elem_ty(t).unwrap_or(JType::Ref("Object".into())),
1785 None => JType::Ref("Object".into()),
1786 },
1787 };
1788 let elem = if for_each_elem_ok(&elem) {
1789 elem
1790 } else {
1791 JType::Ref("Object".into())
1792 };
1793 let is_object = matches!(&elem, JType::Ref(n) if n == "Object");
1794 let (replace_target, e_ty) = match lang.data(wrapped) {
1795 NodeData::Cast { .. } if is_object => (next_call, elem),
1796 NodeData::Cast { ty } => (wrapped, if is_object { ty.clone() } else { elem }),
1797 _ => (wrapped, elem),
1798 };
1799 let taken = |nm: &str| {
1803 subtree_has_var(&*lang, body, nm)
1804 || subtree_has_binding(&*lang, body, nm)
1805 };
1806 let e_name = if taken("e") {
1807 if taken("e2") { "e3" } else { "e2" }
1808 } else {
1809 "e"
1810 };
1811 let e_ref = lang.var(e_name);
1812 let foreach = lang.for_each(e_name, e_ty, iterable, body);
1813 let wrapped_parent = parent_of_recv(lang, body, wrapped);
1817 if lang.kind(wrapped_parent) == NodeKind::ExprStmt
1818 && *lang.children(wrapped_parent).first().unwrap_or(&wrapped) == wrapped
1819 {
1820 let stmt_block = parent_of_recv(lang, body, wrapped_parent);
1821 if let Some(si) = lang
1822 .children(stmt_block)
1823 .iter()
1824 .position(|&c| c == wrapped_parent)
1825 {
1826 return Some(Edit::Multi(vec![
1827 Edit::Splice {
1828 node: stmt_block,
1829 index: si,
1830 remove: 1,
1831 insert: vec![],
1832 },
1833 Edit::Splice {
1834 node: parent,
1835 index: idx,
1836 remove: 1,
1837 insert: vec![foreach],
1838 },
1839 Edit::Delete { node: decl },
1840 ]));
1841 }
1842 }
1843 return Some(Edit::Multi(vec![
1844 Edit::Replace {
1845 target: replace_target,
1846 with: e_ref,
1847 },
1848 Edit::Splice {
1849 node: parent,
1850 index: idx,
1851 remove: 1,
1852 insert: vec![foreach],
1853 },
1854 Edit::Delete { node: decl },
1855 ]));
1856 }
1857 let NodeData::VarDecl { name: e_name, ty, .. } = lang.data(first) else {
1858 return None;
1859 };
1860 let (e_name, ty) = (e_name.clone(), ty.clone());
1861 let next_call = lang.children(first).first().copied()?;
1862 let next_call = unwrap_paren_cast(&*lang, next_call);
1863 if lang.kind(next_call) != NodeKind::Call {
1864 return None;
1865 }
1866 let nc = lang.children(next_call).to_vec();
1867 let NodeData::Member { name: m2 } = lang.data(*nc.first()?) else {
1868 return None;
1869 };
1870 if lang.sn(*m2) != "next" || nc.len() != 1 {
1871 return None;
1872 }
1873 if lang.var_name(lang.children(nc[0])[0]) != Some(it_name.as_str()) {
1874 return None;
1875 }
1876 let elem_is_object = match lang.var_type(iterable) {
1878 Some(it_t) => generic_elem_ty(it_t).is_none(),
1879 None => true,
1880 };
1881 if elem_is_object && ty != JType::Ref("Object".into()) {
1882 return None;
1883 }
1884 let ty = if for_each_elem_ok(&ty) { ty } else { JType::Ref("Object".into()) };
1886 let rest = bch[1..].to_vec();
1887 for &r in &rest {
1888 if subtree_has_var(&*lang, r, &it_name) {
1889 return None;
1890 }
1891 }
1892 let new_body = lang.build_block(rest);
1893 let foreach = lang.for_each(&e_name, ty, iterable, new_body);
1894 Some(Edit::Multi(vec![
1897 Edit::Splice {
1898 node: parent,
1899 index: idx,
1900 remove: 1,
1901 insert: vec![foreach],
1902 },
1903 Edit::Delete { node: decl },
1904 ]))
1905 }
1906}
1907
1908pub struct ConcatValueOfDrop;
1917
1918impl Rule<JavaAst> for ConcatValueOfDrop {
1919 fn name(&self) -> &'static str {
1920 "concat_value_of_drop"
1921 }
1922 fn kinds(&self) -> &'static [NodeKind] {
1923 &[NodeKind::Binary]
1924 }
1925 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
1926 let root = ctx.root();
1927 let lang = ctx.lang;
1928 if lang.kind(id) != NodeKind::Binary {
1929 return None;
1930 }
1931 if lang.bin_op(id) != Some(BinOp::Add) {
1932 return None;
1933 }
1934 let ch = lang.children(id).to_vec();
1935 let (l, r) = (ch[0], ch[1]);
1936 let becomes_constant = |x: JavaId, other: JavaId| {
1939 lang.literal(x).is_some() && matches!(lang.literal(other), Some(LitRef::Str(_)))
1940 };
1941 let is_value_of = |n: JavaId| -> Option<JavaId> {
1942 if lang.kind(n) != NodeKind::Call {
1943 return None;
1944 }
1945 let c = lang.children(n).to_vec();
1946 if c.len() != 2 {
1947 return None;
1948 }
1949 let callee = c[0];
1950 if let NodeData::Member { name } = lang.data(callee) {
1951 if lang.sn(*name) == "valueOf" && lang.var_name(lang.children(callee)[0]) == Some("String") {
1952 return Some(c[1]);
1953 }
1954 }
1955 if let NodeData::VarRef { name } = lang.data(callee) {
1956 if lang.sn(*name) == "valueOf" {
1957 return Some(c[1]);
1958 }
1959 }
1960 None
1961 };
1962 let stringy = |n: JavaId| -> bool {
1963 matches!(lang.literal(n), Some(LitRef::Str(_)))
1964 || lang
1965 .var_type(n)
1966 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"))
1967 };
1968 if let Some(x) = is_value_of(l) {
1970 if stringy(r) {
1971 if becomes_constant(x, r) && lang.has_string_identity_compare(root) {
1972 return None;
1973 }
1974 let with = lang.build_bin(BinOp::Add, x, r);
1975 return Some(Edit::Replace {
1976 target: id,
1977 with,
1978 });
1979 }
1980 }
1981 if let Some(x) = is_value_of(r) {
1983 if stringy(l) {
1984 if becomes_constant(x, l) && lang.has_string_identity_compare(root) {
1985 return None;
1986 }
1987 let with = lang.build_bin(BinOp::Add, l, x);
1988 return Some(Edit::Replace {
1989 target: id,
1990 with,
1991 });
1992 }
1993 }
1994 None
1995 }
1996}
1997
1998
1999pub struct StringBuilderStatements;
2012
2013fn count_var_uses_in(lang: &JavaAst, root: JavaId, name: &str) -> usize {
2014 let mut n = 0;
2017 let mut stack = vec![root];
2018 while let Some(x) = stack.pop() {
2019 if lang.kind(x) == NodeKind::Assign && lang.assign_op(x).is_none() {
2020 let ch = lang.children(x).to_vec();
2021 if let Some(&t) = ch.first() {
2022 let is_named_target =
2023 lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name);
2024 if !is_named_target {
2025 stack.push(t);
2026 }
2027 for &c in &ch[1..] {
2028 stack.push(c);
2029 }
2030 }
2031 continue;
2032 }
2033 if lang.kind(x) == NodeKind::VarRef && lang.var_name(x) == Some(name) {
2034 n += 1;
2035 }
2036 for &c in lang.children(x) {
2037 stack.push(c);
2038 }
2039 }
2040 n
2041}
2042
2043impl Rule<JavaAst> for StringBuilderStatements {
2044 fn name(&self) -> &'static str {
2045 "string_builder_statements"
2046 }
2047 fn kinds(&self) -> &'static [NodeKind] {
2048 &[NodeKind::VarDecl]
2049 }
2050 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2051 let parent = ctx.parent(id)?;
2053 let idx = ctx.index(id)?;
2054 let root = ctx.root();
2055 let lang = ctx.lang;
2056
2057 if lang.kind(id) != NodeKind::VarDecl {
2058 return None;
2059 }
2060 if lang.kind(parent) != NodeKind::Block {
2061 return None;
2062 }
2063 let v0 = lang.var_name(id)?.to_string();
2064 let init = *lang.children(id).first()?;
2066 if lang.kind(init) != NodeKind::Call {
2067 return None;
2068 }
2069 let ic = lang.children(init).to_vec();
2070 if ic.len() != 2 {
2071 return None;
2072 }
2073 let NodeData::Member { name: m0 } = lang.data(ic[0]) else {
2074 return None;
2075 };
2076 if lang.sn(*m0) != "append" {
2077 return None;
2078 }
2079 let sb_new = lang.children(ic[0])[0];
2080 let (ctor_arg, a0) = match lang.data(sb_new) {
2081 NodeData::New { ty, .. } => {
2082 let is_sb = !lang.unit_shadows_lang
2083 && matches!(ty, JType::Ref(n) if n == "StringBuilder" || n == "java.lang.StringBuilder");
2084 if !is_sb {
2085 return None;
2086 }
2087 let args = lang.children(sb_new).to_vec();
2088 match args.len() {
2089 0 => (None, ic[1]),
2090 1 => {
2091 let a = args[0];
2092 let stringy = matches!(lang.literal(a), Some(LitRef::Str(_)))
2093 || lang
2094 .var_type(a)
2095 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2096 if stringy {
2097 (Some(a), ic[1])
2098 } else if matches!(
2099 lang.literal(a),
2100 Some(LitRef::Int(_) | LitRef::Long(_))
2101 ) {
2102 (None, ic[1])
2104 } else {
2105 return None; }
2107 }
2108 _ => return None,
2109 }
2110 }
2111 _ => return None,
2112 };
2113
2114 let stmts = lang.children(parent).to_vec();
2115 let mut parts: Vec<JavaId> = vec![a0];
2117 let mut expected_reads: std::collections::HashMap<String, usize> =
2118 std::collections::HashMap::new();
2119 let mut chain_len = 1usize; let mut cur = v0.clone();
2121 while idx + chain_len < stmts.len() {
2122 let s = stmts[idx + chain_len];
2123 let (recv, arg, new_var, consumed) = match lang.data(s) {
2124 NodeData::Assign { op: None } => {
2126 let ch = lang.children(s).to_vec();
2127 if ch.len() != 2 {
2128 break;
2129 }
2130 let (t, v) = (ch[0], ch[1]);
2131 if lang.kind(t) != NodeKind::VarRef
2132 || lang.var_name(t) != Some(cur.as_str())
2133 || lang.kind(v) != NodeKind::Call
2134 {
2135 break;
2136 }
2137 let vc = lang.children(v).to_vec();
2138 if vc.len() != 2 {
2139 break;
2140 }
2141 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2142 break;
2143 };
2144 if lang.sn(*mn) != "append" {
2145 break;
2146 }
2147 let recv = lang.children(vc[0])[0];
2148 (recv, vc[1], false, s)
2149 }
2150 NodeData::ExprStmt => {
2151 let ch = lang.children(s).to_vec();
2152 if ch.len() != 1 {
2153 break;
2154 }
2155 let inner = ch[0];
2156 if !matches!(lang.data(inner), NodeData::Assign { op: None }) {
2157 break;
2158 }
2159 let ach = lang.children(inner).to_vec();
2160 if ach.len() != 2 {
2161 break;
2162 }
2163 let (t, v) = (ach[0], ach[1]);
2164 if lang.kind(t) != NodeKind::VarRef
2165 || lang.var_name(t) != Some(cur.as_str())
2166 || lang.kind(v) != NodeKind::Call
2167 {
2168 break;
2169 }
2170 let vc = lang.children(v).to_vec();
2171 if vc.len() != 2 {
2172 break;
2173 }
2174 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2175 break;
2176 };
2177 if lang.sn(*mn) != "append" {
2178 break;
2179 }
2180 let recv = lang.children(vc[0])[0];
2181 (recv, vc[1], false, s)
2182 }
2183 NodeData::VarDecl { .. } => {
2185 let init = match lang.children(s).first() {
2186 Some(&x) => x,
2187 None => break,
2188 };
2189 if lang.kind(init) != NodeKind::Call {
2190 break;
2191 }
2192 let vc = lang.children(init).to_vec();
2193 if vc.len() != 2 {
2194 break;
2195 }
2196 let NodeData::Member { name: mn } = lang.data(vc[0]) else {
2197 break;
2198 };
2199 if lang.sn(*mn) != "append" {
2200 break;
2201 }
2202 let recv = lang.children(vc[0])[0];
2203 if lang.var_name(recv) != Some(cur.as_str()) {
2204 break;
2205 }
2206 (recv, vc[1], true, s)
2207 }
2208 _ => break,
2209 };
2210 if lang.var_name(recv) != Some(cur.as_str()) {
2212 break;
2213 }
2214 parts.push(arg);
2215 *expected_reads.entry(cur.clone()).or_insert(0) += 1;
2216 if new_var {
2217 cur = lang.var_name(consumed)?.to_string();
2218 }
2219 chain_len += 1;
2220 let _ = consumed;
2221 }
2222
2223 let final_stmt = *stmts.get(idx + chain_len)?;
2225 let NodeData::VarDecl { .. } = lang.data(final_stmt) else {
2226 return None;
2227 };
2228 let s_init = *lang.children(final_stmt).first()?;
2229 if lang.kind(s_init) != NodeKind::Call {
2230 return None;
2231 }
2232 let sc = lang.children(s_init).to_vec();
2233 if sc.len() != 1 {
2234 return None;
2235 }
2236 let NodeData::Member { name: mn } = lang.data(sc[0]) else {
2237 return None;
2238 };
2239 if lang.sn(*mn) != "toString" {
2240 return None;
2241 }
2242 if lang.var_name(lang.children(sc[0])[0]) != Some(cur.as_str()) {
2243 return None;
2244 }
2245
2246 *expected_reads.entry(cur.clone()).or_insert(0) += 1; for (v, exp) in &expected_reads {
2250 if count_var_uses_in(&*lang, parent, v) != *exp {
2251 return None;
2252 }
2253 }
2254
2255 let mut all_parts: Vec<JavaId> = Vec::new();
2257 if let Some(ca) = ctor_arg {
2258 all_parts.push(ca);
2259 }
2260 all_parts.extend(parts);
2261 if all_parts.iter().all(|&p| lang.literal(p).is_some())
2262 && lang.has_string_identity_compare(root)
2263 {
2264 return None;
2265 }
2266 if all_parts.is_empty() {
2267 let empty = lang.build_str("");
2268 return Some(Edit::Multi(vec![
2269 Edit::Replace {
2270 target: s_init,
2271 with: empty,
2272 },
2273 Edit::Splice {
2274 node: parent,
2275 index: idx,
2276 remove: chain_len,
2277 insert: Vec::new(),
2278 },
2279 ]));
2280 }
2281 let first = all_parts[0];
2282 let stringy_first = matches!(lang.literal(first), Some(LitRef::Str(_)))
2283 || lang
2284 .var_type(first)
2285 .is_some_and(|t| matches!(t, JType::Ref(n) if n == "String"));
2286 let mut acc = if stringy_first {
2287 first
2288 } else {
2289 let empty = lang.build_str("");
2290 lang.build_bin(BinOp::Add, empty, first)
2291 };
2292 for &p in &all_parts[1..] {
2293 acc = lang.build_bin(BinOp::Add, acc, p);
2294 }
2295 Some(Edit::Multi(vec![
2296 Edit::Replace {
2297 target: s_init,
2298 with: acc,
2299 },
2300 Edit::Splice {
2301 node: parent,
2302 index: idx,
2303 remove: chain_len,
2304 insert: Vec::new(),
2305 },
2306 ]))
2307 }
2308}
2309
2310
2311pub struct TrailingContinueJava;
2318
2319impl Rule<JavaAst> for TrailingContinueJava {
2320 fn name(&self) -> &'static str {
2321 "trailing_continue"
2322 }
2323 fn kinds(&self) -> &'static [NodeKind] {
2324 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2325 }
2326 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2327 let parent = ctx.parent(id)?;
2328 let lang = ctx.lang;
2329 let body = match lang.kind(id) {
2330 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2331 *lang.children(id).last()?
2332 }
2333 _ => return None,
2334 };
2335 if lang.kind(body) != NodeKind::Block {
2336 return None;
2337 }
2338 let ch = lang.children(body).to_vec();
2339 let last = *ch.last()?;
2340 if lang.kind(last) != NodeKind::Continue {
2341 return None;
2342 }
2343 let label = match lang.data(last) {
2344 NodeData::Continue { label } => label.clone(),
2345 _ => return None,
2346 };
2347 match label {
2348 None => Some(Edit::Delete { node: last }),
2349 Some(l) => {
2350 if lang.kind(parent) == NodeKind::Label {
2352 if let NodeData::Label { name } = lang.data(parent) {
2353 if *name == l {
2354 return Some(Edit::Delete { node: last });
2355 }
2356 }
2357 }
2358 None
2359 }
2360 }
2361 }
2362}
2363
2364
2365pub struct XorNoise;
2373
2374impl Rule<JavaAst> for XorNoise {
2375 fn name(&self) -> &'static str {
2376 "xor_noise"
2377 }
2378 fn kinds(&self) -> &'static [NodeKind] {
2379 &[NodeKind::Binary]
2380 }
2381 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2382 let lang = ctx.lang;
2383 if lang.kind(id) != NodeKind::Binary {
2384 return None;
2385 }
2386 let op = lang.bin_op(id)?;
2387 if op != BinOp::BitXor {
2388 return None;
2389 }
2390 let ch = lang.children(id).to_vec();
2391 let (l, r) = (ch[0], ch[1]);
2392 let int_lit = |n: JavaId| lang.literal(n).and_then(|x| x.as_int());
2394 if int_lit(r) == Some(0) {
2395 return Some(Edit::Replace {
2396 target: id,
2397 with: l,
2398 });
2399 }
2400 if int_lit(l) == Some(0) {
2401 return Some(Edit::Replace {
2402 target: id,
2403 with: r,
2404 });
2405 }
2406 if lang.kind(l) != NodeKind::Binary || lang.bin_op(l) != Some(BinOp::BitXor) {
2408 return None;
2409 }
2410 let ich = lang.children(l).to_vec();
2411 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2412 let k2 = int_lit(r)?;
2413 let x = ich[0];
2414 let k = k1 ^ k2;
2415 let with = if k == 0 {
2416 x
2417 } else {
2418 let lit = lang.build_int(k, false);
2419 lang.build_bin(BinOp::BitXor, x, lit)
2420 };
2421 Some(Edit::Replace {
2422 target: id,
2423 with,
2424 })
2425 }
2426}
2427
2428pub struct StrLenFold;
2433
2434impl Rule<JavaAst> for StrLenFold {
2435 fn name(&self) -> &'static str {
2436 "str_len_fold"
2437 }
2438 fn kinds(&self) -> &'static [NodeKind] {
2439 &[NodeKind::Call]
2440 }
2441 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2442 let _at_stmt_pos = ctx
2444 .parent(id)
2445 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
2446 .unwrap_or(false);
2447 let lang = ctx.lang;
2448 if lang.kind(id) != NodeKind::Call {
2449 return None;
2450 }
2451 let ch = lang.children(id).to_vec();
2452 if ch.len() != 1 {
2453 return None;
2454 }
2455 let NodeData::Member { name } = lang.data(ch[0]) else {
2456 return None;
2457 };
2458 if lang.sn(*name) != "length" {
2459 return None;
2460 }
2461 let recv = lang.children(ch[0])[0];
2462 if let Some(LitRef::Str(s)) = lang.literal(recv) {
2463 let with = lang.build_int(s.chars().count() as i64, false);
2464 return Some(Edit::Replace {
2465 target: id,
2466 with,
2467 });
2468 }
2469 None
2470 }
2471}
2472
2473
2474pub struct LiteralEval;
2487
2488impl Rule<JavaAst> for LiteralEval {
2489 fn name(&self) -> &'static str {
2490 "literal_eval"
2491 }
2492 fn kinds(&self) -> &'static [NodeKind] {
2493 &[NodeKind::Call, NodeKind::Index, NodeKind::Cast]
2494 }
2495 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2496 let root = ctx.root();
2497 let lang = ctx.lang;
2498 let edit = match lang.kind(id) {
2499 NodeKind::Call => self.eval_call(&mut *lang, id),
2500 NodeKind::Index => self.eval_index(&mut *lang, id),
2501 NodeKind::Cast => eval_cast_literal(&mut *lang, id),
2502 _ => None,
2503 };
2504 if let Some(Edit::Replace { with, .. }) = &edit {
2508 if matches!(lang.literal(*with), Some(LitRef::Str(_)))
2509 && lang.has_string_identity_compare(root)
2510 {
2511 if std::env::var("CURE_DEBUG_GATE").is_ok() {
2512 eprintln!("[gate] blocked literal_eval at {id:?}");
2513 }
2514 return None;
2515 }
2516 }
2517 edit
2518 }
2519}
2520
2521impl LiteralEval {
2522 fn eval_call(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2523 let ch = lang.children(id).to_vec();
2524 let callee = ch[0];
2525 let NodeData::Member { name: method } = lang.data(callee) else {
2526 return None;
2527 };
2528 let method = *method;
2529 let recv = lang.children(callee)[0];
2530 let args: Vec<Lit> = ch[1..]
2532 .iter()
2533 .map(|&a| litref_to_lit(lang.literal(a)))
2534 .collect::<Option<Vec<_>>>()?;
2535
2536 let result: Option<Lit> = if let Some(LitRef::Str(s)) = lang.literal(recv) {
2537 eval_str_method(s, lang.sn(method), &args)
2538 } else if let Some(cls) = lang.var_name(recv) {
2539 eval_static_method(cls, lang.sn(method), &args)
2540 } else {
2541 None
2542 };
2543 let lit = result?;
2544 let with = lit_to_node(lang, &lit);
2545 Some(Edit::Replace {
2546 target: id,
2547 with,
2548 })
2549 }
2550
2551 fn eval_index(&self, lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2552 let ch = lang.children(id).to_vec();
2554 let arr = match lang.kind(ch[0]) {
2556 NodeKind::ArrayLit => ch[0],
2557 NodeKind::NewArray => {
2558 let nc = lang.children(ch[0]);
2559 let NodeData::NewArray { sized, .. } = lang.data(ch[0]) else {
2560 return None;
2561 };
2562 if *sized != 0 || nc.len() != 1 || lang.kind(nc[0]) != NodeKind::ArrayLit {
2563 return None;
2564 }
2565 nc[0]
2566 }
2567 _ => return None,
2568 };
2569 let idx = lang.literal(ch[1])?.as_int()?;
2570 let elems = lang.children(arr).to_vec();
2571 if idx < 0 || idx >= elems.len() as i64 {
2572 return None;
2573 }
2574 Some(Edit::Replace {
2575 target: id,
2576 with: elems[idx as usize],
2577 })
2578 }
2579}
2580
2581fn litref_to_lit(l: Option<LitRef<'_>>) -> Option<Lit> {
2582 Some(match l? {
2583 LitRef::Str(s) => Lit::Str(s.to_string()),
2584 LitRef::Int(v) => Lit::Int(v),
2585 LitRef::Long(v) => Lit::Long(v),
2586 LitRef::Bool(b) => Lit::Bool(b),
2587 LitRef::Char(c) => Lit::Char(c),
2588 LitRef::Float(v) => Lit::Float(v),
2589 LitRef::Double(v) => Lit::Double(v),
2590 LitRef::Null => return None,
2591 })
2592}
2593
2594fn lit_to_node(lang: &mut JavaAst, lit: &Lit) -> JavaId {
2595 match lit {
2596 Lit::Str(s) => lang.build_str(s),
2597 Lit::Int(v) => lang.build_int(*v, false),
2598 Lit::Long(v) => lang.build_int(*v, true),
2599 Lit::Bool(b) => lang.build_bool(*b),
2600 Lit::Char(c) => lang.build_char(*c),
2601 Lit::Float(v) => lang.lit(Lit::Float(*v)),
2602 Lit::Double(v) => lang.lit(Lit::Double(*v)),
2603 _ => lang.lit(Lit::Null),
2604 }
2605}
2606
2607fn eval_cast_literal(lang: &mut JavaAst, id: JavaId) -> Option<Edit<JavaAst>> {
2609 let NodeData::Cast { ty } = lang.data(id) else {
2610 return None;
2611 };
2612 let ty = ty.clone();
2613 let inner = lang.children(id)[0];
2614 let lit = litref_to_lit(lang.literal(inner))?;
2615 let with = match (&ty, &lit) {
2616 (JType::Char, Lit::Int(v)) if (0_i64..=0xFFFF).contains(v) => {
2617 lang.build_char(char::from_u32(*v as u32)?)
2618 }
2619 (JType::Char, Lit::Char(_)) => inner,
2620 (JType::Int, Lit::Char(c)) => lang.build_int(*c as i64, false),
2621 (JType::Long, Lit::Char(c)) => lang.build_int(*c as i64, true),
2622 (JType::Long, Lit::Int(v)) => lang.build_int(*v, true),
2623 (JType::Int, Lit::Long(v)) if *v >= i32::MIN as i64 && *v <= i32::MAX as i64 => {
2624 lang.build_int(*v, false)
2625 }
2626 (JType::Int, Lit::Int(_)) => inner,
2627 _ => return None,
2628 };
2629 Some(Edit::Replace {
2630 target: id,
2631 with,
2632 })
2633}
2634
2635fn eval_str_method(s: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2636 let chars: Vec<char> = s.chars().collect();
2637 let n = chars.len() as i64;
2638 Some(match (method, args) {
2639 ("isEmpty", []) => Lit::Bool(s.is_empty()),
2640 ("intern", []) => Lit::Str(s.to_string()),
2642 ("trim", []) => Lit::Str(s.trim_matches(|c: char| c <= ' ').to_string()),
2643 ("concat", [Lit::Str(b)]) => Lit::Str(format!("{s}{b}")),
2644 ("startsWith", [Lit::Str(p)]) => Lit::Bool(s.starts_with(p.as_str())),
2645 ("endsWith", [Lit::Str(p)]) => Lit::Bool(s.ends_with(p.as_str())),
2646 ("contains", [Lit::Str(p)]) => Lit::Bool(s.contains(p.as_str())),
2647 ("equals", [Lit::Str(p)]) => Lit::Bool(s == p),
2648 ("equalsIgnoreCase", [Lit::Str(p)]) => {
2649 Lit::Bool(s.to_lowercase() == p.to_lowercase())
2650 }
2651 ("indexOf", [Lit::Char(c)]) => Lit::Int(
2652 s.find(*c).map(|i| i as i64).unwrap_or(-1),
2653 ),
2654 ("indexOf", [Lit::Str(p)]) => Lit::Int(
2655 s.find(p.as_str()).map(|i| i as i64).unwrap_or(-1),
2656 ),
2657 ("length", []) => Lit::Int(n),
2658 ("charAt", [Lit::Int(i)]) if *i >= 0 && *i < n => {
2659 Lit::Char(chars[*i as usize])
2660 }
2661 ("substring", [Lit::Int(b)]) if *b >= 0 && *b <= n => {
2662 Lit::Str(s.chars().skip(*b as usize).collect())
2663 }
2664 ("substring", [Lit::Int(b), Lit::Int(e)]) if *b >= 0 && *b <= *e && *e <= n => {
2665 Lit::Str(chars[*b as usize..*e as usize].iter().collect())
2666 }
2667 ("replace", [Lit::Char(a), Lit::Char(b)]) => {
2668 Lit::Str(s.chars().map(|c| if c == *a { *b } else { c }).collect())
2669 }
2670 ("replace", [Lit::Str(a), Lit::Str(b)]) if !a.is_empty() => {
2671 Lit::Str(s.replace(a.as_str(), b))
2672 }
2673 ("hashCode", []) => Lit::Int(
2675 s.chars()
2676 .fold(0i32, |h, c| h.wrapping_mul(31).wrapping_add(c as i32))
2677 as i64,
2678 ),
2679 _ => return None,
2680 })
2681}
2682
2683fn eval_static_method(cls: &str, method: &str, args: &[Lit]) -> Option<Lit> {
2684 let cls = cls.rsplit('.').next().unwrap_or(cls);
2685 Some(match (cls, method, args) {
2686 ("Integer", "parseInt", [Lit::Str(s)]) => {
2687 Lit::Int(s.trim().parse::<i64>().ok()?)
2688 }
2689 ("Integer", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2690 ("Integer", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2691 ("Long", "parseLong", [Lit::Str(s)]) => Lit::Long(s.trim().parse::<i64>().ok()?),
2692 ("Long", "toString", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2693 ("Long", "toString", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2694 ("Boolean", "parseBoolean", [Lit::Str(s)]) => {
2695 Lit::Bool(s.eq_ignore_ascii_case("true"))
2696 }
2697 ("Boolean", "toString", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2698 ("String", "valueOf", [Lit::Str(s)]) => Lit::Str(s.clone()),
2699 ("String", "valueOf", [Lit::Int(v)]) => Lit::Str(v.to_string()),
2700 ("String", "valueOf", [Lit::Long(v)]) => Lit::Str(v.to_string()),
2701 ("String", "valueOf", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2702 ("String", "valueOf", [Lit::Bool(b)]) => Lit::Str(b.to_string()),
2703 ("String", "format", fmt_args @ [Lit::Str(_), ..]) => {
2704 let fmt = match &fmt_args[0] {
2705 Lit::Str(s) => s.clone(),
2706 _ => return None,
2707 };
2708 let out = eval_string_format(&fmt, &fmt_args[1..])?;
2709 Lit::Str(out)
2710 }
2711 ("Math", "abs", [Lit::Int(v)]) => Lit::Int(v.wrapping_abs()),
2712 ("Math", "abs", [Lit::Long(v)]) => Lit::Long(v.wrapping_abs()),
2713 ("Math", "max", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.max(b)),
2714 ("Math", "min", [Lit::Int(a), Lit::Int(b)]) => Lit::Int(*a.min(b)),
2715 ("Math", "max", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.max(b)),
2716 ("Math", "min", [Lit::Long(a), Lit::Long(b)]) => Lit::Long(*a.min(b)),
2717 ("Character", "isDigit", [Lit::Char(c)]) => Lit::Bool(c.is_ascii_digit()),
2718 ("Character", "isLetter", [Lit::Char(c)]) => Lit::Bool(c.is_alphabetic()),
2719 ("Character", "isWhitespace", [Lit::Char(c)]) => Lit::Bool(c.is_whitespace()),
2720 ("Character", "isUpperCase", [Lit::Char(c)]) => Lit::Bool(c.is_uppercase()),
2721 ("Character", "isLowerCase", [Lit::Char(c)]) => Lit::Bool(c.is_lowercase()),
2722 ("Character", "toUpperCase", [Lit::Char(c)]) => Lit::Char(c.to_uppercase().next()?),
2723 ("Character", "toLowerCase", [Lit::Char(c)]) => Lit::Char(c.to_lowercase().next()?),
2724 ("Character", "toString", [Lit::Char(c)]) => Lit::Str(c.to_string()),
2725 _ => return None,
2726 })
2727}
2728
2729fn eval_string_format(fmt: &str, args: &[Lit]) -> Option<String> {
2731 let mut out = String::new();
2732 let mut it = fmt.chars().peekable();
2733 let mut arg_i = 0usize;
2734 while let Some(c) = it.next() {
2735 if c != '%' {
2736 out.push(c);
2737 continue;
2738 }
2739 match it.next()? {
2740 '%' => out.push('%'),
2741 's' => {
2742 let a = args.get(arg_i)?;
2743 arg_i += 1;
2744 match a {
2745 Lit::Str(s) => out.push_str(s),
2746 Lit::Char(ch) => out.push(*ch),
2747 Lit::Bool(b) => out.push_str(&b.to_string()),
2748 _ => out.push_str(&lit_debug_str(a)),
2749 }
2750 }
2751 'd' => {
2752 let a = args.get(arg_i)?;
2753 arg_i += 1;
2754 match a {
2755 Lit::Int(v) => out.push_str(&v.to_string()),
2756 Lit::Long(v) => out.push_str(&v.to_string()),
2757 Lit::Char(ch) => out.push_str(&(*ch as i64).to_string()),
2758 _ => return None,
2759 }
2760 }
2761 _ => return None,
2763 }
2764 }
2765 if arg_i != args.len() {
2766 return None; }
2768 Some(out)
2769}
2770
2771fn lit_debug_str(l: &Lit) -> String {
2772 match l {
2773 Lit::Int(v) => v.to_string(),
2774 Lit::Long(v) => v.to_string(),
2775 _ => "?".into(),
2776 }
2777}
2778
2779fn member_name(ast: &JavaAst, m: JavaId) -> Option<String> {
2802 match ast.data(m) {
2803 NodeData::Member { name } => Some(ast.sn(*name).to_string()),
2804 _ => None,
2805 }
2806}
2807
2808pub struct UrlDecodeFold;
2809
2810impl Rule<JavaAst> for UrlDecodeFold {
2811 fn name(&self) -> &'static str {
2812 "url_decode_fold"
2813 }
2814 fn kinds(&self) -> &'static [NodeKind] {
2815 &[NodeKind::Call]
2816 }
2817 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2818 let lang = ctx.lang;
2819 let ch = lang.children(id).to_vec();
2822 if ch.len() != 3 {
2823 return None; }
2825 let callee = ch[0];
2826 if lang.kind(callee) != NodeKind::Member {
2827 return None;
2828 }
2829 if member_name(lang, callee).as_deref() != Some("decode") {
2830 return None;
2831 }
2832 let recv = *lang.children(callee).first()?;
2834 let recv_name = match lang.kind(recv) {
2835 NodeKind::Member => member_name(lang, recv)?,
2836 NodeKind::VarRef => lang.var_name(recv)?.to_string(),
2837 _ => return None,
2838 };
2839 if !recv_name.ends_with("URLDecoder") {
2840 return None;
2841 }
2842 let s_lit = match lang.literal(ch[1])? {
2843 LitRef::Str(s) => s.to_string(),
2844 _ => return None,
2845 };
2846 let cs = match lang.literal(ch[2])? {
2848 LitRef::Str(s) => s.to_string(),
2849 _ => return None,
2850 };
2851 if cs != "UTF-8" {
2852 return None;
2853 }
2854 if s_lit.len() > 8192 {
2856 return None;
2857 }
2858 let decoded = url_decode(&s_lit)?;
2859 if decoded.chars().any(|c| {
2861 c.is_control() || (c as u32) >= 0xD800 && (c as u32) <= 0xDFFF
2862 }) {
2863 return None;
2864 }
2865 let lit = lang.lit(Lit::Str(decoded));
2866 Some(Edit::Replace { target: id, with: lit })
2867 }
2868}
2869
2870fn url_decode(s: &str) -> Option<String> {
2873 let b = s.as_bytes();
2874 let mut out: Vec<u8> = Vec::with_capacity(b.len());
2875 let mut i = 0usize;
2876 while i < b.len() {
2877 match b[i] {
2878 b'+' => {
2879 out.push(b' ');
2880 i += 1;
2881 }
2882 b'%' => {
2883 if i + 3 > b.len() {
2884 return None;
2885 }
2886 let hi = hex_val(b[i + 1])?;
2887 let lo = hex_val(b[i + 2])?;
2888 out.push(hi * 16 + lo);
2889 i += 3;
2890 }
2891 c => {
2892 out.push(c);
2893 i += 1;
2894 }
2895 }
2896 }
2897 String::from_utf8(out).ok()
2898}
2899
2900fn hex_val(c: u8) -> Option<u8> {
2901 match c {
2902 b'0'..=b'9' => Some(c - b'0'),
2903 b'a'..=b'f' => Some(c - b'a' + 10),
2904 b'A'..=b'F' => Some(c - b'A' + 10),
2905 _ => None,
2906 }
2907}
2908
2909pub struct Base64NewStringFold;
2910
2911impl Rule<JavaAst> for Base64NewStringFold {
2912 fn name(&self) -> &'static str {
2913 "base64_new_string_fold"
2914 }
2915 fn kinds(&self) -> &'static [NodeKind] {
2916 &[NodeKind::New]
2917 }
2918 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
2919 let root = ctx.root();
2920 let lang = ctx.lang;
2921 let NodeData::New { ty, .. } = lang.data(id) else {
2922 return None;
2923 };
2924 if !matches!(ty, JType::Ref(n) if n == "String" || n == "java.lang.String") {
2925 return None;
2926 }
2927 if lang.has_string_identity_compare(root) {
2929 return None;
2930 }
2931 let args = lang.children(id).to_vec();
2932 if args.len() != 1 {
2933 return None;
2934 }
2935 if lang.kind(args[0]) != NodeKind::Call {
2937 return None;
2938 }
2939 let dc = lang.children(args[0]).to_vec();
2940 if dc.len() != 2 {
2941 return None;
2942 }
2943 let NodeData::Member { name: dn } = lang.data(dc[0]) else {
2944 return None;
2945 };
2946 if lang.sn(*dn) != "decode" {
2947 return None;
2948 }
2949 let recv = lang.children(dc[0])[0];
2951 if lang.kind(recv) != NodeKind::Call {
2952 return None;
2953 }
2954 let gc = lang.children(recv).to_vec();
2955 let NodeData::Member { name: gn } = lang.data(*gc.first()?) else {
2956 return None;
2957 };
2958 let url_safe = match lang.sn(*gn) {
2959 "getDecoder" => false,
2960 "getUrlDecoder" => true,
2961 _ => return None,
2962 };
2963 let owner = lang.children(gc[0])[0];
2965 let owner_ok = match lang.data(owner) {
2966 NodeData::Member { name } => lang.sn(*name) == "Base64",
2967 _ => matches!(lang.var_name(owner), Some(n) if n == "Base64" || n.ends_with(".Base64")),
2968 };
2969 if !owner_ok {
2970 return None;
2971 }
2972 let LitRef::Str(b64) = lang.literal(dc[1])? else {
2973 return None;
2974 };
2975 let bytes = base64_decode(b64, url_safe)?;
2976 let text = String::from_utf8(bytes).ok()?;
2977 let with = lang.build_str(&text);
2978 Some(Edit::Replace {
2979 target: id,
2980 with,
2981 })
2982 }
2983}
2984
2985fn base64_decode(s: &str, url_safe: bool) -> Option<Vec<u8>> {
2987 let val = |c: char| -> Option<u32> {
2988 match c {
2989 'A'..='Z' => Some(c as u32 - 'A' as u32),
2990 'a'..='z' => Some(c as u32 - 'a' as u32 + 26),
2991 '0'..='9' => Some(c as u32 - '0' as u32 + 52),
2992 '+' if !url_safe => Some(62),
2993 '/' if !url_safe => Some(63),
2994 '-' if url_safe => Some(62),
2995 '_' if url_safe => Some(63),
2996 _ => None,
2997 }
2998 };
2999 let clean: String = s.chars().filter(|&c| c != '=' && !c.is_whitespace()).collect();
3000 if clean.len() % 4 == 1 {
3001 return None;
3002 }
3003 let mut out = Vec::new();
3004 let mut buf = 0u32;
3005 let mut bits = 0u32;
3006 for c in clean.chars() {
3007 buf = (buf << 6) | val(c)?;
3008 bits += 6;
3009 if bits >= 8 {
3010 bits -= 8;
3011 out.push((buf >> bits) as u8);
3012 buf &= (1 << bits) - 1;
3013 }
3014 }
3015 Some(out)
3016}
3017
3018
3019pub struct CffRecover;
3041
3042struct CffCase {
3043 body: Vec<JavaId>,
3044 tail: CffTail,
3045}
3046
3047enum CffTail {
3048 Goto(i64),
3049 Cond { cond: JavaId, a: i64, b: i64 },
3050 Term(JavaId),
3051 Exit,
3052}
3053
3054#[derive(Clone, Copy, PartialEq, Debug)]
3055enum Flow {
3056 Done,
3057 Stop,
3058 Cycle(usize),
3059 Cond(i64),
3060 External(i64),
3061}
3062
3063struct CffCtx<'a> {
3064 lang: &'a mut JavaAst,
3065 cases: std::collections::HashMap<i64, CffCase>,
3066 default_term: Option<Vec<JavaId>>,
3068 used_default: bool,
3069 reached: std::collections::HashSet<i64>,
3071 emitted: std::collections::HashSet<i64>,
3073}
3074
3075impl Rule<JavaAst> for CffRecover {
3076 fn name(&self) -> &'static str {
3077 "cff_recover"
3078 }
3079 fn kinds(&self) -> &'static [NodeKind] {
3080 &[NodeKind::While]
3081 }
3082 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
3083 let parent = ctx.parent(id)?;
3084 let idx = ctx.index(id)?;
3085 let lang = ctx.lang;
3086 if lang.kind(id) != NodeKind::While {
3087 return None;
3088 }
3089 let wch = lang.children(id).to_vec();
3090 if !matches!(lang.literal(wch[0]), Some(LitRef::Bool(true))) {
3091 return None;
3092 }
3093 if lang.kind(wch[1]) != NodeKind::Block {
3094 return None;
3095 }
3096 let body_ch = lang.children(wch[1]).to_vec();
3097 let (switch, exit_if) = match body_ch.len() {
3098 1 => (body_ch[0], None),
3099 2 => (body_ch[0], Some(body_ch[1])),
3100 _ => return None,
3101 };
3102 if lang.kind(switch) != NodeKind::Switch {
3103 return None;
3104 }
3105 let sch = lang.children(switch).to_vec();
3106 if lang.kind(sch[0]) != NodeKind::VarRef || !lang.is_local_var(sch[0]) {
3107 return None;
3108 }
3109 let v = lang.var_name(sch[0])?.to_string();
3110
3111 let sentinel = match exit_if {
3113 None => None,
3114 Some(ei) => {
3115 if lang.kind(ei) != NodeKind::If {
3116 return None;
3117 }
3118 let ich = lang.children(ei).to_vec();
3119 if ich.len() != 2 || lang.bin_op(ich[0]) != Some(BinOp::Eq) {
3120 return None;
3121 }
3122 let cch = lang.children(ich[0]).to_vec();
3123 let sent = if lang.kind(cch[0]) == NodeKind::VarRef
3124 && lang.var_name(cch[0]) == Some(v.as_str())
3125 {
3126 lang.literal(cch[1]).and_then(|x| x.as_int())
3127 } else if lang.kind(cch[1]) == NodeKind::VarRef
3128 && lang.var_name(cch[1]) == Some(v.as_str())
3129 {
3130 lang.literal(cch[0]).and_then(|x| x.as_int())
3131 } else {
3132 return None;
3133 }?;
3134 let then_ok = match lang.kind(ich[1]) {
3135 NodeKind::Break => matches!(lang.data(ich[1]), NodeData::Break { label: None }),
3136 NodeKind::Block if lang.children(ich[1]).len() == 1 => {
3137 matches!(lang.data(lang.children(ich[1])[0]), NodeData::Break { label: None })
3138 }
3139 _ => false,
3140 };
3141 if !then_ok {
3142 return None;
3143 }
3144 Some(sent)
3145 }
3146 };
3147
3148 let mut cases: std::collections::HashMap<i64, CffCase> = Default::default();
3150 let mut default_term: Option<Vec<JavaId>> = None;
3151 for &c in &sch[1..] {
3152 let NodeData::Case { labels, is_default, arrow } = lang.data(c) else {
3153 return None;
3154 };
3155 let (labels, is_default, arrow) = (*labels, *is_default, *arrow);
3156 let ch = lang.children(c).to_vec();
3157 let mut stmts: Vec<JavaId> = ch[labels as usize..].to_vec();
3158 if !arrow
3160 && stmts
3161 .last()
3162 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3163 {
3164 stmts.pop();
3165 }
3166 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Block {
3167 let mut inner = lang.children(stmts[0]).to_vec();
3168 if !arrow
3169 && inner
3170 .last()
3171 .is_some_and(|&n| matches!(lang.data(n), NodeData::Break { label: None }))
3172 {
3173 inner.pop();
3174 }
3175 stmts = inner;
3176 }
3177 if is_default {
3178 if stmts.len() == 1 && lang.kind(stmts[0]) == NodeKind::Return {
3179 default_term = Some(stmts);
3180 continue;
3181 }
3182 return None;
3183 }
3184 if labels != 1 {
3186 return None;
3187 }
3188 let state = match lang.literal(ch[0]) {
3189 Some(LitRef::Int(v)) => v,
3190 _ => return None,
3191 };
3192 let tail = parse_cff_tail(lang, &v, &mut stmts)?;
3193 for &st in &stmts {
3195 if subtree_has_var(lang, st, &v) {
3196 return None;
3197 }
3198 }
3199 cases.insert(state, CffCase { body: stmts, tail });
3200 }
3201 if cases.is_empty() {
3202 return None;
3203 }
3204
3205 if idx == 0 {
3208 return None;
3209 }
3210 let block_ch = lang.children(parent).to_vec();
3211 let mut init = None;
3212 let mut init_idx = idx;
3213 {
3214 let mut up = idx;
3215 for _ in 0..8 {
3216 if up == 0 {
3217 break;
3218 }
3219 up -= 1;
3220 let st = block_ch[up];
3221 match scan_cff_event(lang, st, &v) {
3222 Some(CffEvent::Read) => return None,
3223 Some(CffEvent::Write) | Some(CffEvent::Decl) => {
3224 init = parse_cff_init(lang, &v, st);
3225 init_idx = up;
3226 break;
3227 }
3228 None => continue,
3229 }
3230 }
3231 }
3232 let init = init?;
3233
3234 for &st in &block_ch[idx + 1..] {
3236 if subtree_has_var(lang, st, &v) {
3237 return None;
3238 }
3239 }
3240
3241 let mut to_exit: Vec<i64> = Vec::new();
3243 for (k, ci) in cases.iter() {
3244 if let CffTail::Goto(g) = ci.tail {
3245 if !cases.contains_key(&g) {
3246 if !(sentinel == Some(g) || default_term.is_some()) {
3247 return None;
3248 }
3249 to_exit.push(*k);
3250 }
3251 }
3252 }
3253 for k in to_exit {
3254 if let Some(ci) = cases.get_mut(&k) {
3255 ci.tail = CffTail::Exit;
3256 }
3257 }
3258
3259 let mut c = CffCtx {
3260 lang,
3261 cases,
3262 default_term,
3263 used_default: false,
3264 reached: Default::default(),
3265 emitted: Default::default(),
3266 };
3267 let stmts = cff_run(&mut c, init)?;
3268
3269 if stmts.is_empty() {
3272 return None;
3273 }
3274
3275 Some(Edit::Multi(vec![
3278 Edit::Splice {
3279 node: parent,
3280 index: idx,
3281 remove: 1,
3282 insert: stmts,
3283 },
3284 Edit::Delete {
3285 node: block_ch[init_idx],
3286 },
3287 ]))
3288 }
3289}
3290
3291fn parse_cff_tail(lang: &JavaAst, v: &str, stmts: &mut Vec<JavaId>) -> Option<CffTail> {
3292 let last = *stmts.last()?;
3293 if lang.kind(last) == NodeKind::Return {
3294 stmts.pop();
3295 return Some(CffTail::Term(last));
3296 }
3297 let assign_of = |n: JavaId| -> Option<JavaId> {
3298 let inner = match lang.kind(n) {
3299 NodeKind::Assign => n,
3300 NodeKind::ExprStmt => {
3301 let ch = lang.children(n);
3302 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3303 ch[0]
3304 } else {
3305 return None;
3306 }
3307 }
3308 _ => return None,
3309 };
3310 if lang.assign_op(inner).is_some() {
3311 return None;
3312 }
3313 let ch = lang.children(inner).to_vec();
3314 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3315 return None;
3316 }
3317 Some(inner)
3318 };
3319 if let Some(assign) = assign_of(last) {
3320 let value = lang.children(assign)[1];
3321 if let Some(k) = lang.literal(value).and_then(|x| x.as_int()) {
3322 stmts.pop();
3323 return Some(CffTail::Goto(k));
3324 }
3325 if lang.kind(value) == NodeKind::Ternary {
3326 let tch = lang.children(value).to_vec();
3327 let a = lang.literal(tch[1]).and_then(|x| x.as_int())?;
3328 let b = lang.literal(tch[2]).and_then(|x| x.as_int())?;
3329 stmts.pop();
3330 return Some(CffTail::Cond { cond: tch[0], a, b });
3331 }
3332 return None;
3333 }
3334 if lang.kind(last) == NodeKind::If {
3335 let ich = lang.children(last).to_vec();
3336 if ich.len() != 3 {
3337 return None;
3338 }
3339 let arm = |n: JavaId| -> Option<i64> {
3340 let target = match lang.kind(n) {
3341 NodeKind::Assign | NodeKind::ExprStmt => assign_of(n)?,
3342 NodeKind::Block if lang.children(n).len() == 1 => assign_of(lang.children(n)[0])?,
3343 _ => return None,
3344 };
3345 lang.literal(lang.children(target)[1]).and_then(|x| x.as_int())
3346 };
3347 let a = arm(ich[1])?;
3348 let b = arm(ich[2])?;
3349 stmts.pop();
3350 return Some(CffTail::Cond { cond: ich[0], a, b });
3351 }
3352 None
3353}
3354
3355
3356enum CffEvent {
3358 Read,
3359 Write,
3360 Decl,
3362}
3363
3364fn scan_cff_event(lang: &JavaAst, node: JavaId, name: &str) -> Option<CffEvent> {
3365 match lang.kind(node) {
3366 NodeKind::VarRef => {
3367 if lang.var_name(node) == Some(name) {
3368 Some(CffEvent::Read)
3369 } else {
3370 None
3371 }
3372 }
3373 NodeKind::VarDecl => {
3374 if lang.var_name(node) == Some(name) {
3375 Some(CffEvent::Decl)
3376 } else {
3377 for &c in lang.children(node) {
3378 if let Some(e) = scan_cff_event(lang, c, name) {
3379 return Some(e);
3380 }
3381 }
3382 None
3383 }
3384 }
3385 NodeKind::Assign => {
3386 let ch = lang.children(node);
3387 if let Some(&val) = ch.get(1) {
3388 if let Some(e) = scan_cff_event(lang, val, name) {
3389 if matches!(e, CffEvent::Read) {
3390 return Some(e);
3391 }
3392 }
3393 }
3394 if lang.assign_op(node).is_none() {
3395 if let Some(&t) = ch.first() {
3396 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3397 return Some(CffEvent::Write);
3398 }
3399 }
3400 }
3401 None
3402 }
3403 NodeKind::If | NodeKind::While => scan_cff_event(lang, *lang.children(node).first()?, name),
3404 NodeKind::Ternary => scan_cff_event(lang, *lang.children(node).first()?, name),
3405 NodeKind::Binary => {
3406 let op = lang.bin_op(node)?;
3407 let ch = lang.children(node);
3408 if op.is_short_circuit() {
3409 scan_cff_event(lang, *ch.first()?, name)
3410 } else {
3411 for &c in ch {
3412 if let Some(e) = scan_cff_event(lang, c, name) {
3413 return Some(e);
3414 }
3415 }
3416 None
3417 }
3418 }
3419 _ => {
3420 for &c in lang.children(node) {
3421 if let Some(e) = scan_cff_event(lang, c, name) {
3422 return Some(e);
3423 }
3424 }
3425 None
3426 }
3427 }
3428}
3429
3430fn parse_cff_init(lang: &JavaAst, v: &str, stmt: JavaId) -> Option<i64> {
3431 match lang.kind(stmt) {
3432 NodeKind::VarDecl => {
3433 let ch = lang.children(stmt).to_vec();
3434 if ch.len() != 1 || lang.var_name(stmt) != Some(v) {
3435 return None;
3436 }
3437 lang.literal(ch[0]).and_then(|x| x.as_int())
3438 }
3439 NodeKind::Assign => {
3440 if lang.assign_op(stmt).is_some() {
3441 return None;
3442 }
3443 let ch = lang.children(stmt).to_vec();
3444 if lang.kind(ch[0]) != NodeKind::VarRef || lang.var_name(ch[0]) != Some(v) {
3445 return None;
3446 }
3447 lang.literal(ch[1]).and_then(|x| x.as_int())
3448 }
3449 NodeKind::ExprStmt => {
3450 let ch = lang.children(stmt).to_vec();
3451 if ch.len() == 1 {
3452 parse_cff_init(lang, v, ch[0])
3453 } else {
3454 None
3455 }
3456 }
3457 _ => None,
3458 }
3459}
3460
3461fn flatten(pairs: &[(i64, Vec<JavaId>)], out: &mut Vec<JavaId>, c: &mut CffCtx) {
3462 for (s, stmts) in pairs {
3463 c.emitted.insert(*s);
3464 out.extend(stmts.iter().copied());
3465 }
3466}
3467
3468fn flatten_prefix(pairs: &[(i64, Vec<JavaId>)], m: i64, out: &mut Vec<JavaId>) {
3469 for (s, stmts) in pairs {
3470 if *s == m {
3471 break;
3472 }
3473 out.extend(stmts.iter().copied());
3474 }
3475}
3476
3477fn suffix_pairs(pairs: &[(i64, Vec<JavaId>)], m: i64) -> Vec<(i64, Vec<JavaId>)> {
3478 let pos = pairs.iter().position(|(s, _)| *s == m).unwrap();
3479 pairs[pos..].to_vec()
3480}
3481
3482fn collect(c: &mut CffCtx, start: i64, stop: Option<i64>) -> Option<(Vec<(i64, Vec<JavaId>)>, Flow)> {
3484 let mut path: Vec<(i64, Vec<JavaId>)> = Vec::new();
3485 let mut cur = start;
3486 let mut guard = 0usize;
3487 loop {
3488 guard += 1;
3489 if guard > 10_000 {
3490 return None;
3491 }
3492 if Some(cur) == stop {
3493 return Some((path, Flow::Stop));
3494 }
3495 if let Some(i) = path.iter().position(|(s, _)| *s == cur) {
3496 return Some((path, Flow::Cycle(i)));
3497 }
3498 if c.emitted.contains(&cur) {
3499 return Some((path, Flow::External(cur)));
3500 }
3501 c.reached.insert(cur);
3502 let ci = c.cases.get(&cur)?;
3503 let mut body = ci.body.clone();
3504 match ci.tail {
3505 CffTail::Goto(k) => {
3506 path.push((cur, body));
3507 cur = k;
3508 }
3509 CffTail::Term(r) => {
3510 body.push(r);
3511 path.push((cur, body));
3512 return Some((path, Flow::Done));
3513 }
3514 CffTail::Exit => {
3515 if c.default_term.is_some() {
3516 if let Some(dt) = c.default_term.clone() {
3517 body.extend(dt);
3518 c.used_default = true;
3519 }
3520 }
3521 path.push((cur, body));
3522 return Some((path, Flow::Done));
3523 }
3524 CffTail::Cond { .. } => return Some((path, Flow::Cond(cur))),
3525 }
3526 }
3527}
3528
3529fn cff_run(c: &mut CffCtx, init: i64) -> Option<Vec<JavaId>> {
3531 let (path, flow) = collect(c, init, None)?;
3532 cff_structure(c, path, flow)
3533}
3534
3535fn cff_run_from(c: &mut CffCtx, start: i64) -> Option<Vec<JavaId>> {
3537 let (path, flow) = collect(c, start, None)?;
3538 cff_structure(c, path, flow)
3539}
3540
3541fn cff_structure(c: &mut CffCtx, mut path: Vec<(i64, Vec<JavaId>)>, mut flow: Flow) -> Option<Vec<JavaId>> {
3545 let mut stmts: Vec<JavaId> = Vec::new();
3546 loop {
3547 match flow {
3548 Flow::Done => {
3549 flatten(&path, &mut stmts, c);
3550 return Some(stmts);
3551 }
3552 Flow::Cycle(i) => {
3553 flatten(&path[..i], &mut stmts, c);
3554 let mut suffix = Vec::new();
3555 flatten(&path[i..], &mut suffix, c);
3556 let inner = c.lang.build_block(suffix);
3557 let cond = c.lang.build_bool(true);
3558 let w = c.lang.while_(cond, inner);
3559 stmts.push(w);
3560 return Some(stmts);
3561 }
3562 Flow::Stop | Flow::External(_) => return None,
3563 Flow::Cond(state) => {
3564 flatten(&path, &mut stmts, c);
3565 path.clear();
3566 let ci = c.cases.get(&state)?;
3567 stmts.extend(ci.body.iter().copied());
3568 c.emitted.insert(state);
3569 let CffTail::Cond { cond, a, b } = ci.tail else {
3570 return None;
3571 };
3572 let (pa, fa) = collect(c, a, Some(state))?;
3574 if fa == Flow::Stop {
3575 let mut lb = Vec::new();
3576 flatten(&pa, &mut lb, c);
3577 let inner = c.lang.build_block(lb);
3578 let w = c.lang.while_(cond, inner);
3579 stmts.push(w);
3580 let (p2, f2) = collect(c, b, None)?;
3581 path = p2;
3582 flow = f2;
3583 continue;
3584 }
3585 let (pb, fb) = collect(c, b, Some(state))?;
3586 if fb == Flow::Stop {
3587 let mut lb = Vec::new();
3588 flatten(&pb, &mut lb, c);
3589 let inner = c.lang.build_block(lb);
3590 let nc = c.lang.build_unary(UnOp::Not, cond);
3591 let w = c.lang.while_(nc, inner);
3592 stmts.push(w);
3593 let (p2, f2) = collect(c, a, None)?;
3594 path = p2;
3595 flow = f2;
3596 continue;
3597 }
3598 let common = pa
3600 .iter()
3601 .find(|(s, _)| pb.iter().any(|(t, _)| *s == *t))
3602 .map(|(s, _)| *s);
3603 let same_end = match (fa, fb) {
3604 (Flow::Done, Flow::Done) => true,
3605 (Flow::Cond(x), Flow::Cond(y)) => x == y,
3606 _ => false,
3607 };
3608 match common {
3609 Some(m) if same_end => {
3610 let mut pre_a = Vec::new();
3611 let mut pre_b = Vec::new();
3612 flatten_prefix(&pa, m, &mut pre_a);
3613 flatten_prefix(&pb, m, &mut pre_b);
3614 let ba = c.lang.build_block(pre_a);
3615 let bb = c.lang.build_block(pre_b);
3616 let ifs = c.lang.if_(cond, ba, Some(bb));
3617 stmts.push(ifs);
3618 path = suffix_pairs(&pa, m);
3619 flow = fa;
3620 continue;
3621 }
3622 Some(_) => return None,
3623 None => {
3624 let mut fa_stmts = Vec::new();
3626 let mut fb_stmts = Vec::new();
3627 flatten(&pa, &mut fa_stmts, c);
3628 flatten(&pb, &mut fb_stmts, c);
3629 match (fa, fb) {
3630 (Flow::Done, Flow::Done) => {
3631 let ba = c.lang.build_block(fa_stmts);
3632 let bb = c.lang.build_block(fb_stmts);
3633 let ifs = c.lang.if_(cond, ba, Some(bb));
3634 stmts.push(ifs);
3635 return Some(stmts);
3636 }
3637 (Flow::Cond(x), Flow::Cond(y)) if x == y => {
3639 let ba = c.lang.build_block(fa_stmts);
3640 let bb = c.lang.build_block(fb_stmts);
3641 let ifs = c.lang.if_(cond, ba, Some(bb));
3642 stmts.push(ifs);
3643 let (p2, f2) = collect(c, x, None)?;
3644 path = p2;
3645 flow = f2;
3646 continue;
3647 }
3648 (Flow::Done, Flow::Cond(x)) => {
3650 let cont = cff_run_from(c, x)?;
3651 fb_stmts.extend(cont);
3652 let ba = c.lang.build_block(fa_stmts);
3653 let bb = c.lang.build_block(fb_stmts);
3654 let ifs = c.lang.if_(cond, ba, Some(bb));
3655 stmts.push(ifs);
3656 return Some(stmts);
3657 }
3658 (Flow::Cond(x), Flow::Done) => {
3659 let cont = cff_run_from(c, x)?;
3660 fa_stmts.extend(cont);
3661 let ba = c.lang.build_block(fa_stmts);
3662 let bb = c.lang.build_block(fb_stmts);
3663 let ifs = c.lang.if_(cond, ba, Some(bb));
3664 stmts.push(ifs);
3665 return Some(stmts);
3666 }
3667 _ => return None,
3668 }
3669 }
3670 }
3671 }
3672 }
3673 }
3674}
3675
3676
3677
3678pub fn collect_unit_consts(ast: &mut JavaAst, unit: &CompilationUnit) {
3688 ast.const_fields.clear();
3689 ast.const_scalars.clear();
3690 ast.inline_methods.clear();
3691 ast.field_types.clear();
3692 ast.final_fields.clear();
3693 ast.raw_generic_names = {
3696 let mut m: std::collections::HashSet<String> = [
3697 "List", "ArrayList", "LinkedList", "Collection", "Set", "HashSet",
3698 "TreeSet", "LinkedHashSet", "Map", "HashMap", "TreeMap", "LinkedHashMap",
3699 "Hashtable", "SortedMap", "SortedSet", "NavigableMap", "NavigableSet",
3700 "Iterable", "Iterator", "ListIterator", "Comparator", "Optional",
3701 "Stream", "Class", "Comparable", "Entry", "AbstractList", "AbstractMap",
3702 "WeakHashMap", "IdentityHashMap", "ConcurrentHashMap", "ConcurrentMap",
3703 "Queue", "Deque", "ArrayDeque", "Vector", "Stack", "Enumeration",
3704 "Callable", "Future", "Supplier", "Function", "BiFunction", "Consumer",
3705 "Predicate", "UnaryOperator", "BinaryOperator", "UnmodifiableList",
3706 "AbstractSet", "AbstractCollection", "AbstractQueue", "Spliterator",
3708 "EnumMap", "EnumSet", "PriorityQueue", "DelayQueue", "LinkedBlockingQueue",
3709 "LinkedTransferQueue", "ConcurrentLinkedQueue", "ConcurrentSkipListMap",
3710 "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet",
3711 "ArrayBlockingQueue", "SynchronousQueue", "ThreadLocal", "Reference",
3712 "SoftReference", "WeakReference", "AtomicReference", "AtomicStampedReference",
3713 ]
3714 .iter()
3715 .map(|x| x.to_string())
3716 .collect();
3717 fn collect_generics(ty: &TypeDecl, m: &mut std::collections::HashSet<String>) {
3718 if !ty.ty_params.trim().is_empty() {
3719 m.insert(ty.name.clone());
3720 }
3721 for mem in &ty.members {
3722 if let Member::Type(t) = mem {
3723 collect_generics(t, m);
3724 }
3725 }
3726 }
3727 for ty in &unit.types {
3728 collect_generics(ty, &mut m);
3729 }
3730 m
3731 };
3732 ast.unit_shadows_lang = {
3733 fn decl_shadows(ty: &TypeDecl) -> bool {
3734 if ty.name == "String" || ty.name == "StringBuilder" {
3735 return true;
3736 }
3737 ty.members.iter().any(|m| match m {
3738 Member::Type(t) => decl_shadows(t),
3739 _ => false,
3740 })
3741 }
3742 unit.types.iter().any(decl_shadows)
3743 };
3744
3745 let mut shadowed: std::collections::HashSet<String> = Default::default();
3750 let mut write_counts: std::collections::HashMap<String, u32> = Default::default();
3751 let mut field_decls: std::collections::HashMap<String, u32> = Default::default();
3752 let mut bodies: Vec<JavaId> = Vec::new();
3753 for ty in &unit.types {
3754 collect_bodies_recursive(ty, &mut bodies, &mut shadowed, &mut field_decls);
3755 }
3756 for ty in &unit.types {
3759 collect_field_types(ty, &mut ast.field_types, &mut ast.final_fields);
3760 }
3761 for &root in &bodies {
3765 let mut stack = vec![root];
3766 while let Some(n) = stack.pop() {
3767 if ast.kind(n) == NodeKind::VarDecl {
3768 if let Some(nm) = ast.var_name(n) {
3769 shadowed.insert(nm.to_string());
3770 }
3771 }
3772 match ast.data(n) {
3773 NodeData::Assign { .. } => {
3774 let t = ast.children(n)[0];
3775 if ast.kind(t) == NodeKind::Member {
3776 if let NodeData::Member { name: sym } = ast.data(t) {
3777 *write_counts.entry(ast.sn(*sym).to_string()).or_insert(0) += 1;
3778 }
3779 }
3780 let mut ts = vec![t];
3781 while let Some(x) = ts.pop() {
3782 if ast.kind(x) == NodeKind::VarRef {
3783 if let Some(nm) = ast.var_name(x) {
3784 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3785 }
3786 }
3787 for &c in ast.children(x) {
3788 ts.push(c);
3789 }
3790 }
3791 }
3792 NodeData::Unary { op } if op.is_incdec() => {
3793 let t = ast.children(n)[0];
3794 if ast.kind(t) == NodeKind::VarRef {
3795 if let Some(nm) = ast.var_name(t) {
3796 *write_counts.entry(nm.to_string()).or_insert(0) += 1;
3797 }
3798 }
3799 }
3800 _ => {}
3801 }
3802 for &c in ast.children(n) {
3803 stack.push(c);
3804 }
3805 }
3806 }
3807
3808 for ty in &unit.types {
3810 let mut own_lits: std::collections::HashMap<String, JavaId> = Default::default();
3812 collect_own_clinit_lits(ast, ty, &mut own_lits);
3813 for m in &ty.members {
3814 if let Member::Field { mods, ty: fty, declarators } = m {
3815 let words: Vec<&str> = mods.split_whitespace().collect();
3816 let is_static = words.contains(&"static");
3817 let is_final = words.contains(&"final");
3818 let is_private = words.contains(&"private");
3819 for d in declarators {
3820 if field_decls.get(&d.name).copied().unwrap_or(0) != 1 {
3824 continue;
3825 }
3826 let init = match d.init {
3827 Some(init) => {
3828 if !is_final
3830 || !is_const_init(ast, init)
3831 || shadowed.contains(&d.name)
3832 || write_counts.get(&d.name).copied().unwrap_or(0) != 0
3833 {
3834 continue;
3835 }
3836 init
3837 }
3838 None => {
3839 if !is_static
3846 || (!is_final && !is_private)
3847 || shadowed.contains(&d.name)
3848 || write_counts.get(&d.name).copied().unwrap_or(0) != 1
3849 {
3850 continue;
3851 }
3852 match own_lits.get(&d.name) {
3853 Some(&init) if is_const_init(ast, init) => init,
3854 _ => continue,
3855 }
3856 }
3857 };
3858 if ast.const_fields.contains_key(&d.name) {
3860 ast.const_fields.remove(&d.name);
3861 ast.const_scalars.remove(&d.name);
3862 } else {
3863 let scalar_ok = scalar_lit_of(ast, init)
3864 .is_some_and(|l| lit_type_matches_decl(fty, l));
3865 ast.const_fields.insert(d.name.clone(), init);
3866 if scalar_ok {
3867 ast.const_scalars.insert(d.name.clone());
3868 }
3869 }
3870 }
3871 }
3872 }
3873 }
3874
3875 {
3882 let identity_assert = [
3883 "assertSame",
3884 "assertNotSame",
3885 "isSameInstanceAs",
3886 "isNotSameInstanceAs",
3887 "referenceEquals",
3888 "same",
3889 ];
3890 let mut hit = false;
3891 for root in &bodies {
3892 let mut stack = vec![*root];
3893 while let Some(n) = stack.pop() {
3894 if let NodeData::Call = ast.data(n) {
3895 if let Some(&callee) = ast.children(n).first() {
3896 let nm = match ast.data(callee) {
3897 NodeData::Member { name } => ast.sn(*name),
3898 NodeData::VarRef { name } => ast.sn(*name),
3899 _ => "",
3900 };
3901 if identity_assert.contains(&nm) {
3902 hit = true;
3903 break;
3904 }
3905 }
3906 }
3907 for &c in ast.children(n) {
3908 stack.push(c);
3909 }
3910 }
3911 if hit {
3912 break;
3913 }
3914 }
3915 ast.unit_string_identity =
3916 ast.any_string_identity(&bodies) || hit;
3917 }
3918
3919 let mut method_name_count: std::collections::HashMap<String, usize> =
3929 std::collections::HashMap::new();
3930 for_each_type(unit, &mut |ty: &TypeDecl| {
3931 for m in &ty.members {
3932 if let Member::Method { name, .. } = m {
3933 *method_name_count.entry(name.clone()).or_insert(0) += 1;
3934 }
3935 }
3936 });
3937 for ty in &unit.types {
3938 for m in &ty.members {
3939 if let Member::Method { mods, name, params, body: Some(b), .. } = m {
3940 let words: Vec<&str> = mods.split_whitespace().collect();
3941 let non_overridable = words.contains(&"private")
3942 || words.contains(&"static")
3943 || words.contains(&"final");
3944 if !non_overridable {
3945 continue;
3946 }
3947 let ch = ast.children(*b).to_vec();
3948 if ch.len() == 1 && ast.kind(ch[0]) == NodeKind::Return {
3949 if let Some(&expr) = ast.children(ch[0]).first() {
3950 if params.len() <= 3
3951 && !subtree_has_anon(ast, expr)
3956 && !params.iter().any(|p| p.varargs)
3960 && !subtree_calls_self(ast, expr, name)
3961 && !subtree_has_type_sensitive(ast, expr)
3967 && method_name_count.get(name).copied() == Some(1)
3968 {
3969 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
3970 ast.inline_methods.insert(name.clone(), (ps, expr));
3971 }
3972 }
3973 }
3974 }
3975 }
3976 }
3977}
3978
3979fn is_const_init(ast: &JavaAst, init: JavaId) -> bool {
3981 match ast.kind(init) {
3982 NodeKind::Literal => true,
3983 NodeKind::ArrayLit => ast.children(init).iter().all(|&e| ast.kind(e) == NodeKind::Literal),
3984 NodeKind::NewArray => {
3985 let ch = ast.children(init).to_vec();
3986 ch.len() == 1
3987 && ast.kind(ch[0]) == NodeKind::ArrayLit
3988 && ast.children(ch[0]).iter().all(|&e| ast.kind(e) == NodeKind::Literal)
3989 }
3990 _ => false,
3991 }
3992}
3993
3994fn collect_bodies_recursive(
3998 ty: &TypeDecl,
3999 bodies: &mut Vec<JavaId>,
4000 shadowed: &mut std::collections::HashSet<String>,
4001 field_decls: &mut std::collections::HashMap<String, u32>,
4002) {
4003 for m in &ty.members {
4004 match m {
4005 Member::Method { body: Some(b), params, .. }
4006 | Member::Constructor { body: Some(b), params, .. } => {
4007 bodies.push(*b);
4008 for p in params {
4009 shadowed.insert(p.name.clone());
4010 }
4011 }
4012 Member::Initializer { body, .. } => bodies.push(*body),
4013 Member::Field { declarators, .. } => {
4014 for d in declarators {
4015 *field_decls.entry(d.name.clone()).or_insert(0) += 1;
4016 if let Some(init) = d.init {
4017 bodies.push(init);
4018 }
4019 }
4020 }
4021 Member::Type(t) => {
4022 collect_bodies_recursive(t, bodies, shadowed, field_decls);
4023 }
4024 _ => {}
4025 }
4026 }
4027}
4028
4029fn collect_field_types(
4031 ty: &TypeDecl,
4032 out: &mut std::collections::HashMap<String, JType>,
4033 finals: &mut std::collections::HashSet<String>,
4034) {
4035 for m in &ty.members {
4036 match m {
4037 Member::Field { mods, ty: fty, declarators } => {
4038 let is_final = mods.split_whitespace().any(|w| w == "final");
4039 for d in declarators {
4040 if out.contains_key(&d.name) {
4041 out.remove(&d.name);
4042 finals.remove(&d.name);
4043 } else {
4044 out.insert(d.name.clone(), fty.clone());
4045 if is_final {
4046 finals.insert(d.name.clone());
4047 }
4048 }
4049 }
4050 }
4051 Member::Type(t) => collect_field_types(t, out, finals),
4052 _ => {}
4053 }
4054 }
4055}
4056
4057fn collect_own_clinit_lits(
4062 ast: &JavaAst,
4063 ty: &TypeDecl,
4064 out: &mut std::collections::HashMap<String, JavaId>,
4065) {
4066 for m in &ty.members {
4067 if let Member::Initializer { is_static: true, body } = m {
4068 if ast.kind(*body) != NodeKind::Block {
4069 continue;
4070 }
4071 for &st in ast.children(*body) {
4072 let asg = match ast.kind(st) {
4073 NodeKind::ExprStmt => {
4074 let c = ast.children(st)[0];
4075 if ast.kind(c) == NodeKind::Assign {
4076 c
4077 } else {
4078 continue;
4079 }
4080 }
4081 NodeKind::Assign => st,
4082 _ => continue,
4083 };
4084 let ch = ast.children(asg).to_vec();
4085 if ast.kind(ch[0]) != NodeKind::VarRef {
4086 continue;
4087 }
4088 let Some(nm) = ast.var_name(ch[0]) else { continue };
4089 if is_const_init(ast, ch[1]) {
4090 out.insert(nm.to_string(), ch[1]);
4091 }
4092 }
4093 }
4094 }
4095}
4096
4097fn scalar_lit_of(ast: &JavaAst, init: JavaId) -> Option<cure_engine::kind::LitRef<'_>> {
4099 if ast.kind(init) == NodeKind::Literal {
4100 ast.literal(init)
4101 } else {
4102 None
4103 }
4104}
4105
4106fn lit_type_matches_decl(fty: &JType, lit: cure_engine::kind::LitRef) -> bool {
4111 use cure_engine::kind::LitRef;
4112 match (fty, lit) {
4113 (JType::Ref(r), LitRef::Str(_)) => {
4114 let base = r.split('<').next().unwrap_or(r).trim();
4115 base == "String" || base == "java.lang.String"
4116 }
4117 (JType::Char, LitRef::Char(_)) => true,
4118 (JType::Bool, LitRef::Bool(_)) => true,
4119 (JType::Int, LitRef::Int(_)) => true,
4120 (JType::Long, LitRef::Long(_)) => true,
4121 (JType::Float, LitRef::Float(_)) => true,
4122 (JType::Double, LitRef::Double(_)) => true,
4123 _ => false,
4124 }
4125}
4126
4127fn subtree_has_type_sensitive(ast: &JavaAst, root: JavaId) -> bool {
4131 let mut stack = vec![root];
4132 while let Some(n) = stack.pop() {
4133 match ast.kind(n) {
4134 NodeKind::Switch | NodeKind::InstanceOf | NodeKind::Raw => return true,
4135 _ => {}
4136 }
4137 for &c in ast.children(n) {
4138 stack.push(c);
4139 }
4140 }
4141 false
4142}
4143
4144fn subtree_has_anon(ast: &JavaAst, root: JavaId) -> bool {
4145 let mut stack = vec![root];
4146 while let Some(n) = stack.pop() {
4147 if let NodeData::New { anon_raw: Some(_), .. } = ast.data(n) {
4148 return true;
4149 }
4150 for &c in ast.children(n) {
4151 stack.push(c);
4152 }
4153 }
4154 false
4155}
4156
4157fn subtree_calls_self(ast: &JavaAst, root: JavaId, name: &str) -> bool {
4158 let mut stack = vec![root];
4159 while let Some(n) = stack.pop() {
4160 if ast.kind(n) == NodeKind::Call {
4161 let callee = ast.children(n)[0];
4162 if ast.var_name(callee) == Some(name) {
4163 return true;
4164 }
4165 if let NodeData::Member { name: m } = ast.data(callee) {
4170 if ast.sn(*m) == name {
4171 return true;
4172 }
4173 }
4174 }
4175 for &c in ast.children(n) {
4176 stack.push(c);
4177 }
4178 }
4179 false
4180}
4181
4182pub struct StaticArrayIndexFold;
4187
4188impl Rule<JavaAst> for StaticArrayIndexFold {
4189 fn name(&self) -> &'static str {
4190 "static_array_index_fold"
4191 }
4192 fn kinds(&self) -> &'static [NodeKind] {
4193 &[NodeKind::Index]
4194 }
4195 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4196 let lang = ctx.lang;
4197 if lang.kind(id) != NodeKind::Index {
4198 return None;
4199 }
4200 let ch = lang.children(id).to_vec();
4201 if lang.kind(ch[0]) != NodeKind::VarRef {
4202 return None;
4203 }
4204 let name = lang.var_name(ch[0])?.to_string();
4205 let idx = lang.literal(ch[1]).and_then(|x| x.as_int())?;
4206 let init = *lang.const_fields.get(&name)?;
4207 let arr = match lang.kind(init) {
4208 NodeKind::ArrayLit => init,
4209 NodeKind::NewArray => {
4210 let ic = lang.children(init).to_vec();
4211 if ic.len() == 1 && lang.kind(ic[0]) == NodeKind::ArrayLit {
4212 ic[0]
4213 } else {
4214 return None;
4215 }
4216 }
4217 _ => return None,
4218 };
4219 let elems = lang.children(arr).to_vec();
4220 if idx < 0 || idx >= elems.len() as i64 {
4221 return None;
4222 }
4223 let elem = lang.copy_subtree(elems[idx as usize]);
4224 Some(Edit::Replace {
4225 target: id,
4226 with: elem,
4227 })
4228 }
4229}
4230
4231pub struct TrailingIfContinue;
4283
4284impl Rule<JavaAst> for TrailingIfContinue {
4285 fn name(&self) -> &'static str {
4286 "trailing_if_continue"
4287 }
4288 fn kinds(&self) -> &'static [NodeKind] {
4289 &[NodeKind::If]
4290 }
4291 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4292 let parent = ctx.parent(id);
4293 let grandparent = parent.and_then(|p| ctx.parent(p));
4294 let lang = ctx.lang;
4295 if lang.kind(id) != NodeKind::If {
4296 return None;
4297 }
4298 let ch = lang.children(id).to_vec();
4299 if ch.len() != 2 {
4301 return None;
4302 }
4303 let then = ch[1];
4304 if lang.kind(then) != NodeKind::Block {
4305 return None;
4306 }
4307 let tbody = lang.children(then).to_vec();
4308 if tbody.len() != 1 || lang.kind(tbody[0]) != NodeKind::Continue {
4309 return None;
4310 }
4311 if !matches!(lang.data(tbody[0]), NodeData::Continue { label: None }) {
4313 return None;
4314 }
4315 let p = parent?;
4318 if lang.kind(p) != NodeKind::Block {
4319 return None;
4320 }
4321 let pch = lang.children(p).to_vec();
4322 if pch.last() != Some(&id) {
4323 return None;
4324 }
4325 let gp = grandparent?;
4327 match lang.kind(gp) {
4328 NodeKind::While | NodeKind::For | NodeKind::ForEach | NodeKind::DoWhile => {
4329 let gch = lang.children(gp).to_vec();
4330 if gch.last() == Some(&p) {
4331 Some(Edit::Delete { node: id })
4332 } else {
4333 None
4334 }
4335 }
4336 _ => None,
4337 }
4338 }
4339}
4340
4341pub struct TrailingAssignReturn;
4342
4343impl Rule<JavaAst> for TrailingAssignReturn {
4344 fn name(&self) -> &'static str {
4345 "trailing_assign_return"
4346 }
4347 fn kinds(&self) -> &'static [NodeKind] {
4348 &[NodeKind::Assign]
4349 }
4350 fn structural(&self) -> bool {
4353 true
4354 }
4355 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4356 let parent = ctx.parent(id);
4357 let grandparent = parent.and_then(|p| ctx.parent(p));
4358 let lang = ctx.lang;
4359 if !matches!(lang.data(id), NodeData::Assign { op: None }) {
4361 return None;
4362 }
4363 let ch = lang.children(id).to_vec();
4364 if ch.len() != 2 {
4365 return None;
4366 }
4367 let (target, value) = (ch[0], ch[1]);
4368 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
4370 return None;
4371 }
4372 let name = lang.var_name(target)?.to_string();
4373 let parent = parent?;
4375 let stmt = if lang.kind(parent) == NodeKind::ExprStmt {
4376 parent
4377 } else if lang.kind(parent) == NodeKind::Block {
4378 id
4379 } else {
4380 return None;
4381 };
4382 let block = if lang.kind(parent) == NodeKind::ExprStmt {
4383 grandparent?
4384 } else {
4385 parent
4386 };
4387 if lang.kind(block) != NodeKind::Block {
4388 return None;
4389 }
4390 let stmts = lang.children(block).to_vec();
4391 let idx = stmts.iter().position(|&x| x == stmt)?;
4392 let next = *stmts.get(idx + 1)?;
4394 let (flow_val, is_return) = match lang.kind(next) {
4395 NodeKind::Return => (lang.children(next).first().copied(), true),
4396 NodeKind::Throw => (lang.children(next).first().copied(), false),
4397 _ => return None,
4398 };
4399 let flow_val = flow_val?;
4400 if lang.kind(flow_val) != NodeKind::VarRef || lang.var_name(flow_val) != Some(name.as_str()) {
4401 return None;
4402 }
4403 for &s in &stmts[idx + 2..] {
4406 if cure_engine::analysis::subtree_contains(lang, s, |n| {
4407 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
4408 }) {
4409 return None;
4410 }
4411 }
4412 let new_flow = if is_return {
4414 lang.ret(Some(value))
4415 } else {
4416 lang.throw(value)
4417 };
4418 Some(Edit::Multi(vec![
4419 Edit::Replace { target: next, with: new_flow },
4420 Edit::Delete { node: stmt },
4421 ]))
4422 }
4423}
4424
4425pub struct ConstFieldPropagate;
4426
4427impl Rule<JavaAst> for ConstFieldPropagate {
4428 fn name(&self) -> &'static str {
4429 "const_field_propagate"
4430 }
4431 fn kinds(&self) -> &'static [NodeKind] {
4432 &[NodeKind::VarRef]
4433 }
4434 fn structural(&self) -> bool {
4437 true
4438 }
4439 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4440 let parent = ctx.parent(id);
4441 let _grandparent = parent.and_then(|p| ctx.parent(p));
4442 let lang = ctx.lang;
4443 if lang.kind(id) != NodeKind::VarRef {
4444 return None;
4445 }
4446 let name = lang.var_name(id)?.to_string();
4447 if !lang.const_scalars.contains(&name) {
4448 return None;
4449 }
4450 if let Some(p) = parent {
4451 let is_first = lang.children(p).first() == Some(&id);
4452 match lang.kind(p) {
4453 NodeKind::Assign if is_first => return None,
4455 NodeKind::Unary if is_first => {
4456 if let NodeData::Unary { op } = lang.data(p) {
4457 if op.is_incdec() {
4458 return None;
4459 }
4460 }
4461 }
4462 NodeKind::Call if is_first => return None,
4464 _ => {}
4465 }
4466 }
4467 let init = *lang.const_fields.get(&name)?;
4468 let lit = lang.copy_subtree(init);
4469 Some(Edit::Replace { target: id, with: lit })
4470 }
4471}
4472
4473pub struct NoopPrivateCall;
4474
4475impl Rule<JavaAst> for NoopPrivateCall {
4476 fn name(&self) -> &'static str {
4477 "noop_private_call"
4478 }
4479 fn kinds(&self) -> &'static [NodeKind] {
4480 &[NodeKind::ExprStmt]
4481 }
4482 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4483 let lang = ctx.lang;
4484 if lang.kind(id) != NodeKind::ExprStmt {
4485 return None;
4486 }
4487 let ch = lang.children(id).to_vec();
4488 if ch.len() != 1 || lang.kind(ch[0]) != NodeKind::Call {
4489 return None;
4490 }
4491 let call = ch[0];
4492 let cch = lang.children(call).to_vec();
4493 if cch.is_empty() || lang.kind(cch[0]) != NodeKind::VarRef {
4494 return None; }
4496 let name = lang.var_name(cch[0])?.to_string();
4497 let arity = cch.len() - 1;
4498 let cands = lang.noop_private_methods.get(&name)?.get(&arity)?;
4499 if cands.is_empty() {
4500 return None;
4501 }
4502 let arg_types: Vec<Option<JType>> =
4506 cch[1..].iter().map(|&a| static_arg_type(lang, a)).collect();
4507 let mut applicable_noop = false;
4508 let mut applicable_count = 0usize;
4509 for c in cands {
4510 let ok = c
4511 .params
4512 .iter()
4513 .zip(arg_types.iter())
4514 .all(|(pt, at)| arg_applicable(at.as_ref(), pt));
4515 if ok {
4516 applicable_count += 1;
4517 applicable_noop = c.noop;
4518 }
4519 }
4520 if applicable_count != 1 || !applicable_noop {
4521 return None;
4522 }
4523 for &arg in &cch[1..] {
4525 if lang.effect(arg) > Effect::MayRead {
4526 return None;
4527 }
4528 }
4529 Some(Edit::Delete { node: id })
4530 }
4531}
4532
4533fn static_arg_type(lang: &JavaAst, arg: JavaId) -> Option<JType> {
4537 match lang.kind(arg) {
4538 NodeKind::Literal => match lang.literal(arg)? {
4539 LitRef::Str(_) => Some(JType::Ref("String".to_string())),
4540 LitRef::Char(_) => Some(JType::Char),
4541 LitRef::Bool(_) => Some(JType::Bool),
4542 LitRef::Int(_) => Some(JType::Int),
4543 LitRef::Long(_) => Some(JType::Long),
4544 LitRef::Float(_) => Some(JType::Float),
4545 LitRef::Double(_) => Some(JType::Double),
4546 LitRef::Null => None,
4547 },
4548 NodeKind::VarRef => {
4549 let nm = lang.var_name(arg)?;
4550 lang.var_type(arg)
4551 .cloned()
4552 .or_else(|| lang.field_types.get(nm).cloned())
4553 }
4554 _ => None,
4555 }
4556}
4557
4558fn arg_applicable(at: Option<&JType>, pt: &JType) -> bool {
4563 let Some(a) = at else { return true };
4564 match pt {
4565 JType::Ref(p) => {
4566 let pbase = p.split('<').next().unwrap_or(p).trim();
4567 pbase == "Object"
4568 || pbase == "java.lang.Object"
4569 || match a {
4570 JType::Ref(arend) => {
4571 let abase = arend.split('<').next().unwrap_or(arend).trim();
4572 abase == pbase
4573 }
4574 _ => false,
4575 }
4576 }
4577 JType::Array(_) => false, prim => match a {
4579 p2 => std::mem::discriminant(p2) == std::mem::discriminant(prim),
4580 },
4581 }
4582}
4583
4584pub struct ConstMethodInline;
4594
4595impl Rule<JavaAst> for ConstMethodInline {
4596 fn name(&self) -> &'static str {
4597 "const_method_inline"
4598 }
4599 fn kinds(&self) -> &'static [NodeKind] {
4600 &[NodeKind::Call]
4601 }
4602 fn structural(&self) -> bool {
4603 true
4604 }
4605 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4606 let at_stmt_pos = ctx
4608 .parent(id)
4609 .map(|p| ctx.lang.kind(p) == NodeKind::ExprStmt)
4610 .unwrap_or(false);
4611 let lang = ctx.lang;
4612 if lang.kind(id) != NodeKind::Call {
4613 return None;
4614 }
4615 let ch = lang.children(id).to_vec();
4616 let method_name = match lang.data(ch[0]) {
4617 NodeData::VarRef { name } => *name,
4618 NodeData::Member { name } => {
4619 let recv = lang.children(ch[0])[0];
4620 match lang.var_name(recv) {
4621 Some(r) if r == "this" || r.chars().next().is_some_and(|c| c.is_uppercase()) => {
4622 *name
4623 }
4624 _ => return None,
4625 }
4626 }
4627 _ => return None,
4628 };
4629 let entry = lang.inline_methods.get(lang.sn(method_name)).cloned()?;
4630 let (params, body_expr) = entry;
4631 {
4633 let names: Vec<String> = lang.inline_methods.keys().cloned().collect();
4634 for nm in &names {
4635 if subtree_calls_self(lang, body_expr, nm) {
4636 return None;
4637 }
4638 }
4639 }
4640 let args = ch[1..].to_vec();
4641 if args.len() != params.len() || params.is_empty() {
4642 return None;
4643 }
4644 for &a in &args {
4645 lang.literal(a)?;
4646 }
4647 let map: std::collections::HashMap<String, JavaId> = params
4648 .iter()
4649 .zip(args.iter())
4650 .map(|(p, &a)| (p.clone(), a))
4651 .collect();
4652 let with = copy_subst(lang, body_expr, &map);
4653 if at_stmt_pos {
4659 match lang.kind(with) {
4660 NodeKind::Call | NodeKind::New => {}
4661 _ => return None,
4662 }
4663 }
4664 Some(Edit::Replace {
4665 target: id,
4666 with,
4667 })
4668 }
4669}
4670
4671fn copy_subst(
4673 lang: &mut JavaAst,
4674 node: JavaId,
4675 map: &std::collections::HashMap<String, JavaId>,
4676) -> JavaId {
4677 if let NodeData::VarRef { name } = lang.data(node) {
4678 if let Some(&repl) = map.get(lang.sn(*name)) {
4679 return lang.copy_subtree(repl);
4680 }
4681 }
4682 let children: Vec<JavaId> = lang.children(node).to_vec();
4683 let new_children: Vec<JavaId> = children
4684 .iter()
4685 .map(|&c| copy_subst(lang, c, map))
4686 .collect();
4687 lang.clone_node(node, new_children)
4688}
4689
4690
4691pub struct TwrRecover;
4724
4725fn twr_is_throwable(ty_raw: &str) -> bool {
4726 ty_raw == "Throwable" || ty_raw == "java.lang.Throwable"
4727}
4728
4729fn unwrap_expr_stmt(lang: &JavaAst, n: JavaId) -> JavaId {
4731 if lang.kind(n) == NodeKind::ExprStmt {
4732 if let Some(&e) = lang.children(n).first() {
4733 return e;
4734 }
4735 }
4736 n
4737}
4738
4739fn twr_stmts(lang: &JavaAst, n: JavaId) -> Vec<JavaId> {
4741 if lang.kind(n) == NodeKind::Block {
4742 lang.children(n).to_vec()
4743 } else {
4744 vec![n]
4745 }
4746}
4747
4748fn twr_is_close_call(lang: &JavaAst, e: JavaId, r: &str) -> bool {
4750 if lang.kind(e) != NodeKind::Call {
4751 return false;
4752 }
4753 let ch = lang.children(e);
4754 if ch.len() != 1 {
4755 return false;
4756 }
4757 match lang.data(ch[0]) {
4758 NodeData::Member { name } if lang.sn(*name) == "close" => {
4759 let Some(&recv) = lang.children(ch[0]).first() else {
4760 return false;
4761 };
4762 lang.kind(recv) == NodeKind::VarRef && lang.var_name(recv) == Some(r)
4763 }
4764 _ => false,
4765 }
4766}
4767
4768fn twr_is_close_stmt(lang: &JavaAst, n: JavaId, r: &str) -> bool {
4769 if lang.kind(n) != NodeKind::ExprStmt {
4770 return false;
4771 }
4772 lang.children(n)
4773 .first()
4774 .is_some_and(|&e| twr_is_close_call(lang, e, r))
4775}
4776
4777fn twr_is_add_suppressed(lang: &JavaAst, n: JavaId, primary: &str, sup: &str) -> bool {
4779 if lang.kind(n) != NodeKind::ExprStmt {
4780 return false;
4781 }
4782 let Some(&e) = lang.children(n).first() else {
4783 return false;
4784 };
4785 if lang.kind(e) != NodeKind::Call {
4786 return false;
4787 }
4788 let ch = lang.children(e);
4789 if ch.len() != 2 {
4790 return false;
4791 }
4792 match lang.data(ch[0]) {
4793 NodeData::Member { name } if lang.sn(*name) == "addSuppressed" => {
4794 let Some(&recv) = lang.children(ch[0]).first() else {
4795 return false;
4796 };
4797 lang.kind(recv) == NodeKind::VarRef
4798 && lang.var_name(recv) == Some(primary)
4799 && lang.kind(ch[1]) == NodeKind::VarRef
4800 && lang.var_name(ch[1]) == Some(sup)
4801 }
4802 _ => false,
4803 }
4804}
4805
4806fn node_contains(lang: &JavaAst, root: JavaId, target: JavaId) -> bool {
4807 let mut stack = vec![root];
4808 while let Some(n) = stack.pop() {
4809 if n == target {
4810 return true;
4811 }
4812 stack.extend(lang.children(n).iter().copied());
4813 }
4814 false
4815}
4816
4817fn refs_escape(lang: &JavaAst, root: JavaId, name: &str, allowed: &[JavaId]) -> bool {
4819 let mut stack = vec![root];
4820 while let Some(n) = stack.pop() {
4821 if lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name) {
4822 let ok = allowed
4823 .iter()
4824 .any(|&a| a == n || node_contains(lang, a, n));
4825 if !ok {
4826 return true;
4827 }
4828 }
4829 if lang.kind(n) == NodeKind::VarDecl {
4830 if let Some(nm) = lang.var_name(n) {
4831 if nm == name && !allowed.contains(&n) {
4832 return true;
4834 }
4835 }
4836 }
4837 stack.extend(lang.children(n).iter().copied());
4838 }
4839 false
4840}
4841
4842fn twr_close_with_suppressed(
4846 lang: &JavaAst,
4847 try_node: JavaId,
4848 primary: &str,
4849) -> Option<(String, String)> {
4850 let ch = lang.children(try_node).to_vec();
4851 if ch.len() != 2 || lang.kind(ch[0]) != NodeKind::Block {
4852 return None;
4853 }
4854 let NodeData::Catch { ty_raw, name: sup } = lang.data(ch[1]) else {
4855 return None;
4856 };
4857 if !twr_is_throwable(ty_raw) {
4858 return None;
4859 }
4860 let sup = sup.clone();
4861 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
4862 if catch_stmts.len() != 1 || !twr_is_add_suppressed(lang, catch_stmts[0], primary, &sup) {
4863 return None;
4864 }
4865 let close_stmts = twr_stmts(lang, ch[0]);
4866 if close_stmts.len() != 1 {
4867 return None;
4868 }
4869 if lang.kind(close_stmts[0]) != NodeKind::ExprStmt {
4870 return None;
4871 }
4872 let e = *lang.children(close_stmts[0]).first()?;
4873 let cc = lang.children(e);
4874 if cc.len() != 1 {
4875 return None;
4876 }
4877 let NodeData::Member { name } = lang.data(cc[0]) else {
4878 return None;
4879 };
4880 if lang.sn(*name) != "close" {
4881 return None;
4882 }
4883 let recv = *lang.children(cc[0]).first()?;
4884 if lang.kind(recv) != NodeKind::VarRef {
4885 return None;
4886 }
4887 Some((lang.var_name(recv)?.to_string(), sup))
4888}
4889
4890impl Rule<JavaAst> for TwrRecover {
4891 fn name(&self) -> &'static str {
4892 "twr_recover"
4893 }
4894 fn kinds(&self) -> &'static [NodeKind] {
4895 &[NodeKind::Try]
4896 }
4897 fn structural(&self) -> bool {
4898 true
4899 }
4900 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
4901 let (parent, idx, walk_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
4903 let lang = ctx.lang;
4904 if lang.kind(id) != NodeKind::Try {
4905 return None;
4906 }
4907 let ch = lang.children(id).to_vec();
4908 let try_idx = ch
4910 .iter()
4911 .position(|&c| lang.kind(c) == NodeKind::Block)?;
4912 if try_idx != 0 {
4913 return None;
4914 }
4915 let try_block = ch[0];
4916 if lang.kind(parent) != NodeKind::Block {
4917 return None;
4918 }
4919 let pch = lang.children(parent).to_vec();
4920
4921 if ch.len() == 2 {
4923 if let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) {
4924 if twr_is_throwable(ty_raw) {
4925 let t = t.clone();
4926 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
4927 if catch_stmts.len() == 2 {
4928 let (inner_try, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
4930 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
4931 && lang
4932 .children(throw_stmt)
4933 .first()
4934 .is_some_and(|&x| {
4935 lang.kind(x) == NodeKind::VarRef
4936 && lang.var_name(x) == Some(t.as_str())
4937 });
4938 if throw_ok && lang.kind(inner_try) == NodeKind::Try {
4939 if let Some((r, sup)) =
4940 twr_close_with_suppressed(lang, inner_try, t.as_str())
4941 {
4942 if idx >= 1 && idx + 1 < pch.len() {
4943 let decl_r = pch[idx - 1];
4944 let trailing = pch[idx + 1];
4945 if let NodeData::VarDecl { name, ty, .. } = lang.data(decl_r) {
4946 if name == &r {
4947 let (name, ty) = (name.clone(), ty.clone());
4948 let init =
4949 lang.children(decl_r).first().copied();
4950 if let Some(init) = init {
4951 let provably_new = lang.kind(init)
4953 == NodeKind::New;
4954 let close_ok =
4955 twr_is_close_stmt(lang, trailing, &r);
4956 if provably_new && close_ok {
4957 let root = walk_root;
4959 let r_ok = !refs_escape(
4960 lang,
4961 root,
4962 &r,
4963 &[
4964 decl_r,
4965 init,
4966 *lang
4967 .children(inner_try)
4968 .first()
4969 .unwrap_or(&inner_try),
4970 trailing,
4971 try_block,
4972 ],
4973 );
4974 let t_ok = !refs_escape(
4975 lang, root, &t,
4976 &[throw_stmt, ch[1]],
4977 );
4978 let sup_ok = !refs_escape(
4979 lang, root, &sup,
4980 &[ch[1]],
4981 );
4982 if r_ok && t_ok && sup_ok {
4983 let init_copy =
4984 lang.copy_subtree(init);
4985 let res = lang.var_decl(
4986 &name, ty,
4987 Some(init_copy),
4988 );
4989 let new_try = lang.try_(
4990 vec![res],
4991 try_block,
4992 Vec::new(),
4993 None,
4994 );
4995 return Some(Edit::Splice {
4996 node: parent,
4997 index: idx - 1,
4998 remove: 3,
4999 insert: vec![new_try],
5000 });
5001 }
5002 }
5003 }
5004 }
5005 }
5006 }
5007 }
5008 }
5009 }
5010 }
5011 }
5012 }
5013
5014 if ch.len() == 3 {
5016 let NodeData::Catch { ty_raw, name: t } = lang.data(ch[1]) else {
5017 return None;
5018 };
5019 if !twr_is_throwable(ty_raw) {
5020 return None;
5021 }
5022 let t = t.clone();
5023 if lang.kind(ch[2]) != NodeKind::Block {
5025 return None;
5026 }
5027 let fin_stmts = twr_stmts(lang, ch[2]);
5028 if fin_stmts.len() != 1 || lang.kind(fin_stmts[0]) != NodeKind::If {
5029 return None;
5030 }
5031 let catch_stmts = twr_stmts(lang, *lang.children(ch[1]).first()?);
5033 if catch_stmts.len() != 2 {
5034 return None;
5035 }
5036 let (assign_stmt, throw_stmt) = (catch_stmts[0], catch_stmts[1]);
5037 let assign_expr = unwrap_expr_stmt(lang, assign_stmt);
5038 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5039 return None;
5040 };
5041 let as_ch = lang.children(assign_expr);
5042 if as_ch.len() != 2 {
5043 return None;
5044 }
5045 let primary = match lang.data(as_ch[0]) {
5046 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5047 _ => return None,
5048 };
5049 let assign_ok = lang.kind(as_ch[1]) == NodeKind::VarRef
5050 && lang.var_name(as_ch[1]) == Some(t.as_str())
5051 && lang.kind(as_ch[0]) == NodeKind::VarRef
5052 && lang.var_name(as_ch[0]) == Some(primary.as_str());
5053 let throw_ok = lang.kind(throw_stmt) == NodeKind::Throw
5054 && lang.children(throw_stmt)
5055 .first()
5056 .is_some_and(|&x| {
5057 lang.kind(x) == NodeKind::VarRef
5058 && lang.var_name(x) == Some(t.as_str())
5059 });
5060 if !assign_ok || !throw_ok {
5061 return None;
5062 }
5063 let if1 = fin_stmts[0];
5065 let i1 = lang.children(if1).to_vec();
5066 if i1.len() < 2 {
5067 return None;
5068 }
5069 let cond1 = i1[0];
5070 let NodeData::Binary { op: BinOp::Ne } = lang.data(cond1) else {
5071 return None;
5072 };
5073 let c1 = lang.children(cond1);
5074 let r = match lang.data(c1[0]) {
5075 NodeData::VarRef { name } if lang.literal(c1[1]) == Some(LitRef::Null) => {
5076 lang.sn(*name).to_string()
5077 }
5078 _ => {
5079 return None;
5080 }
5081 };
5082 let then1 = i1[1];
5083 let inner_stmts = twr_stmts(lang, then1);
5084 if inner_stmts.len() != 1 || lang.kind(inner_stmts[0]) != NodeKind::If {
5085 return None;
5086 }
5087 let if2 = inner_stmts[0];
5088 let i2 = lang.children(if2).to_vec();
5089 if i2.len() != 3 {
5090 return None;
5091 }
5092 let NodeData::Binary { op: BinOp::Ne } = lang.data(i2[0]) else {
5093 return None;
5094 };
5095 let c2 = lang.children(i2[0]);
5096 let primary_ok = lang.kind(c2[0]) == NodeKind::VarRef
5097 && lang.var_name(c2[0]) == Some(primary.as_str())
5098 && lang.literal(c2[1]) == Some(LitRef::Null);
5099 if !primary_ok {
5100 return None;
5101 }
5102 let (then2, els2) = (i2[1], i2[2]);
5103 let t2_stmts = twr_stmts(lang, then2);
5105 if t2_stmts.len() != 1 || lang.kind(t2_stmts[0]) != NodeKind::Try {
5106 return None;
5107 }
5108 let Some((r2, sup)) =
5109 twr_close_with_suppressed(lang, t2_stmts[0], primary.as_str())
5110 else {
5111 return None;
5112 };
5113 if r2 != r {
5114 return None;
5115 }
5116 let e2_stmts = twr_stmts(lang, els2);
5118 if e2_stmts.len() != 1 || !twr_is_close_stmt(lang, e2_stmts[0], &r) {
5119 return None;
5120 }
5121 if idx < 2 {
5123 return None;
5124 }
5125 let decl_r = pch[idx - 2];
5126 let decl_primary = pch[idx - 1];
5127 let (r_decl, primary_decl) = match (lang.data(decl_r), lang.data(decl_primary)) {
5128 (
5129 NodeData::VarDecl { name: rn, .. },
5130 NodeData::VarDecl { name: pn, .. },
5131 ) => (rn.clone(), pn.clone()),
5132 _ => return None,
5133 };
5134 if r_decl != r || primary_decl != primary {
5135 return None;
5136 }
5137 let pinit_null = lang
5139 .children(decl_primary)
5140 .first()
5141 .is_some_and(|&n| lang.literal(n) == Some(LitRef::Null));
5142 if !pinit_null {
5143 return None;
5144 }
5145 let r_init = lang.children(decl_r).first().copied()?;
5146 let ty = match lang.data(decl_r) {
5147 NodeData::VarDecl { ty, .. } => ty.clone(),
5148 _ => return None,
5149 };
5150 let root = walk_root;
5152 let r_allowed = [decl_r, r_init, ch[2], try_block];
5154 let t_allowed = [throw_stmt, assign_stmt, ch[1], ch[2]];
5155 let p_allowed = [decl_primary, assign_stmt, ch[2]];
5156 if refs_escape(lang, root, &r, &r_allowed)
5157 || refs_escape(lang, root, &primary, &p_allowed)
5158 || refs_escape(lang, root, &t, &t_allowed)
5159 || refs_escape(lang, root, &sup, &[ch[2]])
5160 {
5161 return None;
5162 }
5163 let init_copy = lang.copy_subtree(r_init);
5164 let res = lang.var_decl(&r, ty, Some(init_copy));
5165 let new_try = lang.try_(vec![res], try_block, Vec::new(), None);
5166 return Some(Edit::Splice {
5167 node: parent,
5168 index: idx - 2,
5169 remove: 3,
5170 insert: vec![new_try],
5171 });
5172 }
5173 None
5174 }
5175}
5176
5177pub struct StringSwitchRecover;
5202
5203fn subtree_sig(lang: &JavaAst, n: JavaId, out: &mut String) {
5204 out.push_str(&format!("{:?} ", lang.data(n)));
5205 for &c in lang.children(n) {
5206 subtree_sig(lang, c, out);
5207 }
5208}
5209
5210fn ssw_bodies(lang: &JavaAst, sw: JavaId) -> Option<Vec<(Option<i64>, Vec<JavaId>)>> {
5212 let ch = lang.children(sw).to_vec();
5213 let mut out = Vec::new();
5214 for &c in &ch[1..] {
5215 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5216 return None;
5217 };
5218 let is_default = *is_default;
5219 let cc = lang.children(c);
5220 let label = if is_default {
5221 None
5222 } else {
5223 if *labels != 1 {
5224 return None; }
5226 let lab = cc[0];
5227 let lit = lang.literal(lab)?;
5228 let v = match lit {
5229 LitRef::Int(v) | LitRef::Long(v) => v,
5230 _ => return None, };
5232 Some(v)
5233 };
5234 out.push((label, cc[*labels as usize..].to_vec()));
5235 }
5236 Some(out)
5237}
5238
5239fn ssw_body_terminal(lang: &JavaAst, stmts: &[JavaId]) -> bool {
5240 let Some(&last) = stmts.last() else {
5241 return false; };
5243 let last = if lang.kind(last) == NodeKind::Block {
5244 match lang.children(last).last() {
5245 Some(&l) => l,
5246 None => return false,
5247 }
5248 } else {
5249 last
5250 };
5251 matches!(
5252 lang.kind(last),
5253 NodeKind::Return | NodeKind::Break | NodeKind::Throw | NodeKind::Continue
5254 )
5255}
5256
5257impl Rule<JavaAst> for StringSwitchRecover {
5258 fn name(&self) -> &'static str {
5259 "string_switch_recover"
5260 }
5261 fn kinds(&self) -> &'static [NodeKind] {
5262 &[NodeKind::Switch]
5263 }
5264 fn structural(&self) -> bool {
5265 true
5266 }
5267 fn check(&self, ctx: RewriteCtx<'_, JavaAst>, id: JavaId) -> Option<Edit<JavaAst>> {
5268 let (parent, idx, ctx_root) = (ctx.parent(id)?, ctx.index(id)?, ctx.root());
5269 let lang = ctx.lang;
5270 if lang.kind(id) != NodeKind::Switch {
5271 return None;
5272 }
5273 let ch = lang.children(id).to_vec();
5274 if ch.len() < 2 {
5275 return None;
5276 }
5277 let x = match lang.data(ch[0]) {
5279 NodeData::Call => {
5280 let cc = lang.children(ch[0]);
5281 if cc.len() != 1 {
5282 return None;
5283 }
5284 match lang.data(cc[0]) {
5285 NodeData::Member { name } if lang.sn(*name) == "hashCode" => {
5286 let recv = *lang.children(cc[0]).first()?;
5287 if lang.kind(recv) != NodeKind::VarRef {
5288 return None;
5289 }
5290 lang.var_name(recv)?.to_string()
5291 }
5292 _ => return None,
5293 }
5294 }
5295 _ => {
5296 return None;
5297 }
5298 };
5299 struct CaseInfo {
5301 lit: String,
5302 k: i64,
5303 inner_switch: JavaId,
5304 }
5305 let mut infos: Vec<CaseInfo> = Vec::new();
5306 let mut v_name: Option<String> = None;
5307 let mut v_nodes: Vec<JavaId> = Vec::new(); let mut guard_nodes: Vec<JavaId> = Vec::new();
5309 for &c in &ch[1..] {
5310 let NodeData::Case { labels, is_default, .. } = lang.data(c) else {
5311 return None;
5312 };
5313 let is_default = *is_default;
5314 if is_default {
5315 return None; }
5317 if *labels != 1 {
5318 return None;
5319 }
5320 let cc = lang.children(c);
5321 let h = match lang.literal(cc[0])? {
5322 LitRef::Int(v) | LitRef::Long(v) => v as i32,
5323 _ => return None,
5324 };
5325 let body = cc[1..].to_vec();
5326 let body = if body.len() == 1 && lang.kind(body[0]) == NodeKind::Block {
5328 lang.children(body[0]).to_vec()
5329 } else {
5330 body
5331 };
5332 let mut rest = body;
5333 let mut _e_decl: Option<(String, JavaId)> = None;
5335 if let Some(&first) = rest.first() {
5336 if let NodeData::VarDecl { name, .. } = lang.data(first) {
5337 if let Some(&init) = lang.children(first).first() {
5338 if is_x_equals(lang, init, &x) {
5339 _e_decl = Some((name.clone(), first));
5340 rest = rest[1..].to_vec();
5341 }
5342 }
5343 }
5344 }
5345 if rest.len() != 1 || lang.kind(rest[0]) != NodeKind::If {
5346 return None;
5347 }
5348 let ifn = rest[0];
5349 let ic = lang.children(ifn).to_vec();
5350 if ic.len() != 3 {
5351 return None;
5352 }
5353 let NodeData::Unary { op: UnOp::Not } = lang.data(ic[0]) else {
5355 return None;
5356 };
5357 let guard = *lang.children(ic[0]).first()?;
5358 let lit = match lang.data(guard) {
5359 NodeData::VarRef { name } => {
5360 let Some((en, decl)) = &_e_decl else {
5362 return None;
5363 };
5364 if lang.sn(*name) != en {
5365 return None;
5366 }
5367 let init = *lang.children(*decl).first()?;
5368 equals_lit(lang, init, &x)?
5369 }
5370 NodeData::Call => equals_lit(lang, guard, &x)?,
5371 _ => return None,
5372 };
5373 guard_nodes.push(guard);
5374 if let Some((_, decl)) = &_e_decl {
5375 guard_nodes.push(*decl);
5376 }
5377 let then_ok = twr_stmts(lang, ic[1]).len() == 1
5379 && matches!(lang.data(twr_stmts(lang, ic[1])[0]),
5380 NodeData::Break { label: None });
5381 if !then_ok {
5382 return None;
5383 }
5384 let els = twr_stmts(lang, ic[2]);
5386 let (k, inner_switch, assign_node) = match els.len() {
5387 2 => {
5388 let assign_expr = unwrap_expr_stmt(lang, els[0]);
5389 let NodeData::Assign { op: None } = lang.data(assign_expr) else {
5390 return None;
5391 };
5392 let a_ch = lang.children(assign_expr);
5393 let vn = match lang.data(a_ch[0]) {
5394 NodeData::VarRef { name } => lang.sn(*name).to_string(),
5395 _ => return None,
5396 };
5397 let k = match lang.literal(a_ch[1])? {
5398 LitRef::Int(v) | LitRef::Long(v) => v,
5399 _ => return None,
5400 };
5401 if lang.kind(els[1]) != NodeKind::Switch {
5402 return None;
5403 }
5404 let sel = *lang.children(els[1]).first()?;
5406 if lang.kind(sel) != NodeKind::VarRef
5407 || lang.var_name(sel) != Some(vn.as_str())
5408 {
5409 return None;
5410 }
5411 match &v_name {
5412 Some(v) if *v != vn => return None,
5413 _ => v_name = Some(vn),
5414 }
5415 (k, els[1], Some(els[0]))
5416 }
5417 1 => {
5418 if lang.kind(els[0]) != NodeKind::Switch {
5419 return None;
5420 }
5421 let sel = *lang.children(els[0]).first()?;
5423 let k = match lang.literal(sel)? {
5424 LitRef::Int(v) | LitRef::Long(v) => v,
5425 _ => return None,
5426 };
5427 (k, els[0], None)
5428 }
5429 _ => return None,
5430 };
5431 if let Some(a) = assign_node {
5432 v_nodes.push(a);
5433 }
5434 v_nodes.push(*lang.children(inner_switch).first()?);
5435 let hlit: i32 = lit
5437 .chars()
5438 .fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32));
5439 if hlit != h {
5440 return None;
5441 }
5442 let _ = assign_node;
5443 infos.push(CaseInfo {
5444 lit,
5445 k,
5446 inner_switch,
5447 });
5448 }
5449 if infos.is_empty() {
5450 return None;
5451 }
5452 if lang.kind(parent) != NodeKind::Block {
5454 return None;
5455 }
5456 let pch = lang.children(parent).to_vec();
5457 let mut k = idx + 1;
5458 if k < pch.len() {
5460 let texpr = unwrap_expr_stmt(lang, pch[k]);
5461 if let NodeData::Assign { op: None } = lang.data(texpr) {
5462 let a_ch = lang.children(texpr);
5463 if a_ch.len() == 2
5464 && lang.kind(a_ch[0]) == NodeKind::VarRef
5465 && lang.var_name(a_ch[0]) == v_name.as_deref()
5466 && lang.literal(a_ch[1]) == Some(LitRef::Int(-1))
5467 {
5468 v_nodes.push(pch[k]);
5469 k += 1;
5470 }
5471 }
5472 }
5473 if k >= pch.len() || lang.kind(pch[k]) != NodeKind::Switch {
5474 return None;
5475 }
5476 let trailing = pch[k];
5477 let tsel = *lang.children(trailing).first()?;
5479 match lang.data(tsel) {
5480 NodeData::VarRef { name } if Some(lang.sn(*name)) == v_name.as_deref() => {
5481 v_nodes.push(tsel);
5482 }
5483 NodeData::Literal(Lit::Int(_)) | NodeData::Literal(Lit::Long(_)) => {}
5484 _ => {
5485 lang.literal(tsel)?;
5486 }
5487 }
5488 fn switch_cases_sig(lang: &JavaAst, sw: JavaId, out: &mut String) {
5491 for &c in &lang.children(sw)[1..] {
5492 subtree_sig(lang, c, out);
5493 }
5494 }
5495 let mut sig0 = String::new();
5496 switch_cases_sig(lang, infos[0].inner_switch, &mut sig0);
5497 for info in &infos[1..] {
5498 let mut sig = String::new();
5499 switch_cases_sig(lang, info.inner_switch, &mut sig);
5500 if sig != sig0 {
5501 return None;
5502 }
5503 }
5504 {
5505 let mut sig = String::new();
5506 switch_cases_sig(lang, trailing, &mut sig);
5507 if sig != sig0 {
5508 return None;
5509 }
5510 }
5511 let bodies = ssw_bodies(lang, infos[0].inner_switch)?;
5513 let mut body_map: std::collections::HashMap<i64, Vec<JavaId>> =
5514 std::collections::HashMap::new();
5515 let mut default_body: Option<Vec<JavaId>> = None;
5516 for (lab, stmts) in &bodies {
5517 match lab {
5518 Some(v) => {
5519 body_map.insert(*v, stmts.clone());
5520 }
5521 None => default_body = Some(stmts.clone()),
5522 }
5523 }
5524 let Some(default_body) = default_body else {
5525 return None;
5526 };
5527 for stmts in body_map.values().chain(std::iter::once(&default_body)) {
5529 if !ssw_body_terminal(lang, stmts) {
5530 return None;
5531 }
5532 }
5533 let root = ctx_root;
5535 if let Some(vn) = &v_name {
5536 let mut allowed: Vec<JavaId> = v_nodes.clone();
5537 allowed.push(trailing);
5538 if idx >= 1 {
5539 let cand = pch[idx - 1];
5540 if let NodeData::VarDecl { name, .. } = lang.data(cand) {
5541 if name == vn {
5542 allowed.push(cand);
5543 if idx != 1 {
5545 allowed.pop();
5546 }
5547 }
5548 }
5549 }
5550 if refs_escape(lang, root, vn, &allowed) {
5551 return None;
5552 }
5553 }
5554 let mut new_cases: Vec<JavaId> = Vec::new();
5556 for info in &infos {
5557 let Some(body) = body_map.get(&info.k) else {
5558 return None;
5559 };
5560 let lab = lang.lit(Lit::Str(info.lit.clone()));
5561 let case = lang.case_(vec![lab], false, false, body.clone());
5562 new_cases.push(case);
5563 }
5564 let def_case = lang.case_(Vec::new(), true, false, default_body);
5565 new_cases.push(def_case);
5566 let sel = lang.var(&x);
5567 let new_switch = lang.switch_(sel, new_cases);
5568 let remove = k - idx + 1;
5570 Some(Edit::Splice {
5571 node: parent,
5572 index: idx,
5573 remove,
5574 insert: vec![new_switch],
5575 })
5576 }
5577}
5578
5579fn equals_lit(lang: &JavaAst, e: JavaId, x: &str) -> Option<String> {
5581 if lang.kind(e) != NodeKind::Call {
5582 return None;
5583 }
5584 let cc = lang.children(e);
5585 if cc.len() != 2 {
5586 return None;
5587 }
5588 match lang.data(cc[0]) {
5589 NodeData::Member { name } if lang.sn(*name) == "equals" => {
5590 let recv = *lang.children(cc[0]).first()?;
5591 if lang.kind(recv) != NodeKind::VarRef || lang.var_name(recv) != Some(x) {
5592 return None;
5593 }
5594 match lang.literal(cc[1])? {
5595 LitRef::Str(s) => Some(s.to_string()),
5596 _ => None,
5597 }
5598 }
5599 _ => None,
5600 }
5601}
5602
5603fn is_x_equals(lang: &JavaAst, e: JavaId, x: &str) -> bool {
5604 lang.kind(e) == NodeKind::Call && equals_lit(lang, e, x).is_some()
5605}
5606
5607pub fn default_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
5614 let mut rules: Vec<Box<dyn Rule<JavaAst>>> = cure_engine::rules::default_rules();
5615 rules.push(Box::new(CastSimplify));
5616 rules.push(Box::new(SelfCompare));
5617 rules.push(Box::new(StringBuilderFold));
5618 rules.push(Box::new(BoxUnboxChain));
5619 rules.push(Box::new(IteratorToForEach));
5620 rules.push(Box::new(NewStringFold));
5621 rules.push(Box::new(NewStringCharArrayFold));
5622 rules.push(Box::new(EmptyFinallyStrip));
5623 rules.push(Box::new(TryUnwrapNoCatch));
5624 rules.push(Box::new(TryUnwrapRethrow));
5625 rules.push(Box::new(StaticExec));
5626 rules.push(Box::new(LoopHeadBreak));
5627 rules.push(Box::new(WhileIteratorToForEach));
5628 rules.push(Box::new(ConcatValueOfDrop));
5629 rules.push(Box::new(StringBuilderStatements));
5630 rules.push(Box::new(TrailingContinueJava));
5631 rules.push(Box::new(XorNoise));
5632 rules.push(Box::new(StrLenFold));
5633 rules.push(Box::new(LiteralEval));
5634 rules.push(Box::new(Base64NewStringFold));
5635 rules.push(Box::new(UrlDecodeFold));
5636 rules.push(Box::new(CffRecover));
5637 rules.push(Box::new(StaticArrayIndexFold));
5638 rules.push(Box::new(TrailingAssignReturn));
5639 rules.push(Box::new(TrailingIfContinue));
5640 rules.push(Box::new(ConstFieldPropagate));
5641 rules.push(Box::new(NoopPrivateCall));
5642 rules.push(Box::new(ConstMethodInline));
5643 rules.push(Box::new(TwrRecover));
5644 rules.push(Box::new(StringSwitchRecover));
5645 rules
5646}
5647
5648pub fn all_java_rules() -> Vec<Box<dyn Rule<JavaAst>>> {
5650 let mut rules = default_java_rules();
5651 let mut engine_all = cure_engine::rules::all_rules();
5652 rules.append(&mut engine_all);
5653 rules
5654}
5655
5656pub fn simplify(ast: &mut JavaAst, root: JavaId, cfg: &Config) -> Report {
5658 let rules = default_java_rules();
5659 cure_engine::simplify(ast, root, &rules, cfg)
5660}
5661
5662pub fn simplify_unit(ast: &mut JavaAst, unit: &mut CompilationUnit, cfg: &Config) -> Report {
5664 let mut total = Report::default();
5665 for _cycle in 0..6 {
5670 let mut round = Report::default();
5671 collect_unit_consts(ast, unit);
5672 if cfg.remove_dead_methods {
5675 collect_noop_private_methods(ast, unit);
5676 } else {
5677 ast.noop_private_methods.clear();
5678 }
5679 for ty in &mut unit.types {
5680 simplify_type(ast, ty, cfg, &mut round);
5681 }
5682 if cfg.remove_dead_methods {
5683 round.edits += remove_dead_private_fields(ast, unit);
5684 round.edits += remove_dead_private_methods(ast, unit);
5685 round.edits += remove_unused_imports(ast, unit);
5686 }
5687 total.edits += round.edits;
5688 total.iterations += round.iterations;
5689 total.discarded += round.discarded;
5690 for (k, v) in round.by_rule {
5691 *total.by_rule.entry(k).or_insert(0) += v;
5692 }
5693 if round.edits == 0 {
5698 break;
5699 }
5700 }
5701 total
5702}
5703
5704fn collect_anon_and_string_names(
5711 ast: &JavaAst,
5712 unit: &CompilationUnit,
5713) -> std::collections::HashSet<String> {
5714 let mut out: std::collections::HashSet<String> = Default::default();
5715 let mut bodies: Vec<JavaId> = Vec::new();
5716 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
5717 for root in bodies {
5718 let mut stack = vec![root];
5719 while let Some(n) = stack.pop() {
5720 match ast.data(n) {
5721 NodeData::New { anon_raw: Some(raw), .. } => {
5722 for id in raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
5723 if !id.is_empty() {
5724 out.insert(id.to_string());
5725 }
5726 }
5727 }
5728 NodeData::Literal(Lit::Str(text)) => {
5729 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
5730 if !id.is_empty() {
5731 out.insert(id.to_string());
5732 }
5733 }
5734 }
5735 _ => {}
5736 }
5737 for &c in ast.children(n) {
5738 stack.push(c);
5739 }
5740 }
5741 }
5742 out
5743}
5744
5745fn collect_all_blind_refs(
5774 ast: &JavaAst,
5775 unit: &CompilationUnit,
5776) -> std::collections::HashSet<String> {
5777 let mut out: std::collections::HashSet<String> = Default::default();
5778 let split_into = |text: &str, out: &mut std::collections::HashSet<String>| {
5779 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
5780 if !id.is_empty() {
5781 out.insert(id.to_string());
5782 }
5783 }
5784 };
5785 let mut bodies: Vec<JavaId> = Vec::new();
5787 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut bodies));
5788 for root in bodies {
5789 let mut stack = vec![root];
5790 while let Some(n) = stack.pop() {
5791 match ast.data(n) {
5792 NodeData::Raw { text } => split_into(text, &mut out),
5793 NodeData::New { anon_raw: Some(raw), .. } => split_into(raw, &mut out),
5794 NodeData::Literal(Lit::Str(text)) => split_into(text, &mut out),
5795 NodeData::ForEach { ty, .. } => {
5797 collect_ty_idents(ty, &mut out);
5798 }
5799 NodeData::NewArray { ty, .. } => {
5801 collect_ty_idents(ty, &mut out);
5802 }
5803 NodeData::Lambda { params_raw } => {
5807 split_into(params_raw, &mut out);
5808 }
5809 _ => {}
5810 }
5811 for &c in ast.children(n) {
5812 stack.push(c);
5813 }
5814 }
5815 }
5816 for r in &unit.raws {
5820 split_into(r, &mut out);
5821 }
5822 for_each_type(unit, &mut |ty: &TypeDecl| {
5825 collect_mods_idents_all(&ty.mods, &mut out);
5826 for e in &ty.enum_constants {
5827 split_into(e, &mut out);
5828 }
5829 split_into(&ty.ty_params, &mut out);
5830 split_into(&ty.header, &mut out);
5831 for m in &ty.members {
5832 match m {
5833 Member::Method { mods, name, ty_params, params, .. }
5834 | Member::Constructor { mods, name, ty_params, params, .. } => {
5835 collect_mods_idents_all(mods, &mut out);
5836 split_into(ty_params, &mut out);
5837 for p in params {
5838 collect_mods_idents_all(&p.mods, &mut out);
5839 collect_ty_idents(&p.ty, &mut out);
5840 }
5841 if has_bare_methodsource(mods) {
5846 out.insert(name.clone());
5847 }
5848 }
5849 Member::Field { mods, declarators, .. } => {
5850 collect_mods_idents_all(mods, &mut out);
5851 let _ = declarators;
5853 }
5854 Member::Type(t) => {
5855 let _ = t; }
5857 Member::Raw(text) => {
5864 split_into(text, &mut out);
5865 }
5866 _ => {}
5867 }
5868 }
5869 });
5870 out
5871}
5872
5873fn collect_mods_idents_all(mods: &str, out: &mut std::collections::HashSet<String>) {
5877 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
5878 if !id.is_empty() {
5879 out.insert(id.to_string());
5880 }
5881 }
5882}
5883
5884fn has_bare_methodsource(mods: &str) -> bool {
5891 let hay = mods.as_bytes();
5892 const NEEDLE: &[u8] = b"MethodSource";
5893 let mut i = 0usize;
5894 while let Some(p) = mods[i..].find("MethodSource") {
5895 let at = i + p;
5896 let end = at + NEEDLE.len();
5897 let prev_ok = at == 0
5900 || !(hay[at - 1].is_ascii_alphanumeric()
5901 || hay[at - 1] == b'_'
5902 || hay[at - 1] == b'$');
5903 let next_ident =
5904 end < hay.len() && (hay[end].is_ascii_alphanumeric() || hay[end] == b'_' || hay[end] == b'$');
5905 if prev_ok && !next_ident {
5906 let mut j = end;
5907 while j < hay.len() && hay[j].is_ascii_whitespace() {
5908 j += 1;
5909 }
5910 if j >= hay.len() || hay[j] != b'(' {
5911 return true;
5912 }
5913 }
5914 i = end;
5915 }
5916 false
5917}
5918
5919const REFLECTIVE_MAGIC_METHODS: &[&str] = &[
5923 "writeObject",
5924 "readObject",
5925 "readObjectNoData",
5926 "writeReplace",
5927 "readResolve",
5928 "validateObject",
5929];
5930
5931fn remove_dead_private_methods(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
5932 use std::collections::HashSet;
5933 let mut removed = 0usize;
5934 let anon_names = collect_anon_and_string_names(ast, unit);
5936 let blind = collect_all_blind_refs(ast, unit);
5941 loop {
5942 let mut referenced: HashSet<(String, usize)> = HashSet::new();
5949 let mut veto: HashSet<String> = HashSet::new();
5950 let mut roots: Vec<JavaId> = Vec::new();
5951 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
5952 for body in roots {
5953 collect_call_arity_refs(ast, body, &mut referenced, &mut veto);
5954 }
5955 for_each_type(unit, &mut |ty| {
5956 for m in &ty.members {
5957 if let Member::Method { name, params, .. } = m {
5958 if params.iter().any(|p| p.varargs) {
5959 veto.insert(name.clone());
5960 }
5961 }
5962 }
5963 });
5964 let mut changed = false;
5966 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
5967 let before = ty.members.len();
5968 ty.members.retain(|m| {
5969 if let Member::Method { mods, name, params, .. } = m {
5970 let is_private = mods.split_whitespace().any(|w| w == "private");
5971 let annotated = mods.contains('@');
5972 let keep = !is_private
5973 || annotated
5974 || params.iter().any(|p| p.varargs)
5976 || veto.contains(name)
5977 || REFLECTIVE_MAGIC_METHODS.contains(&name.as_str())
5981 || anon_names.contains(name)
5982 || blind.contains(name)
5983 || referenced.contains(&(name.clone(), params.len()));
5984 keep
5985 } else {
5986 true
5987 }
5988 });
5989 if ty.members.len() != before {
5990 removed += before - ty.members.len();
5991 changed = true;
5992 }
5993 });
5994 if !changed {
5995 return removed;
5996 }
5997 }
5998}
5999
6000fn strip_ta_prefix(name: &str) -> &str {
6005 if !name.starts_with('<') {
6006 return name;
6007 }
6008 let b = name.as_bytes();
6009 let mut depth = 0i32;
6010 for (i, &c) in b.iter().enumerate() {
6011 match c {
6012 b'<' => depth += 1,
6013 b'>' => {
6014 depth -= 1;
6015 if depth == 0 {
6016 return &name[i + 1..];
6017 }
6018 }
6019 _ => {}
6020 }
6021 }
6022 name }
6024
6025fn collect_call_arity_refs(
6027 ast: &JavaAst,
6028 root: JavaId,
6029 out: &mut std::collections::HashSet<(String, usize)>,
6030 veto: &mut std::collections::HashSet<String>,
6031) {
6032 let mut stack = vec![root];
6033 while let Some(id) = stack.pop() {
6034 match ast.data(id) {
6035 NodeData::Call => {
6036 let ch = ast.children(id).to_vec();
6037 if let Some(&callee) = ch.first() {
6038 let name = match ast.data(callee) {
6039 NodeData::Member { name } | NodeData::VarRef { name } => {
6040 ast.sn(*name).to_string()
6041 }
6042 _ => String::new(),
6043 };
6044 if !name.is_empty() {
6045 out.insert((strip_ta_prefix(&name).to_string(), ch.len() - 1));
6046 }
6047 }
6048 }
6049 NodeData::MethodRef { name } => {
6050 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6051 veto.insert(strip_ta_prefix(short).to_string());
6052 }
6053 }
6054 _ => {}
6055 }
6056 for &c in ast.children(id) {
6057 stack.push(c);
6058 }
6059 }
6060}
6061
6062fn for_each_type(unit: &CompilationUnit, f: &mut dyn FnMut(&TypeDecl)) {
6064 for ty in &unit.types {
6065 for_each_type_inner(ty, f);
6066 }
6067}
6068fn for_each_type_inner(ty: &TypeDecl, f: &mut dyn FnMut(&TypeDecl)) {
6069 f(ty);
6070 for m in &ty.members {
6071 if let Member::Type(t) = m {
6072 for_each_type_inner(t, f);
6073 }
6074 }
6075}
6076
6077fn for_each_type_mut(unit: &mut CompilationUnit, f: &mut dyn FnMut(&mut TypeDecl)) {
6079 for ty in &mut unit.types {
6080 for_each_type_mut_inner(ty, f);
6081 }
6082}
6083fn for_each_type_mut_inner(ty: &mut TypeDecl, f: &mut dyn FnMut(&mut TypeDecl)) {
6084 f(ty);
6085 for m in &mut ty.members {
6086 if let Member::Type(t) = m {
6087 for_each_type_mut_inner(t, f);
6088 }
6089 }
6090}
6091
6092fn collect_member_roots(ty: &TypeDecl, out: &mut Vec<JavaId>) {
6094 for m in &ty.members {
6095 match m {
6096 Member::Method { body: Some(b), .. }
6097 | Member::Constructor { body: Some(b), .. } => out.push(*b),
6098 Member::Initializer { body, .. } => out.push(*body),
6099 Member::Field { declarators, .. } => {
6100 for d in declarators {
6101 if let Some(init) = d.init {
6102 out.push(init);
6103 }
6104 }
6105 }
6106 Member::Type(t) => collect_member_roots(t, out),
6107 _ => {}
6108 }
6109 }
6110}
6111
6112fn collect_noop_private_methods(ast: &mut JavaAst, unit: &CompilationUnit) {
6127 use std::collections::{HashMap, HashSet};
6128 ast.noop_private_methods.clear();
6129 let mut by_name: HashMap<String, HashMap<usize, Vec<NoopCandidate>>> = HashMap::new();
6130 let mut veto: HashSet<String> = HashSet::new();
6131 for_each_type(unit, &mut |ty: &TypeDecl| {
6132 for m in &ty.members {
6133 if let Member::Method { mods, name, params, body, throws, .. } = m {
6134 if mods.contains('@') || params.iter().any(|p| p.varargs) {
6135 veto.insert(name.clone());
6136 continue;
6137 }
6138 let is_private = mods.split_whitespace().any(|w| w == "private");
6139 let empty = match body {
6140 Some(b) => ast.kind(*b) == NodeKind::Block && ast.children(*b).is_empty(),
6141 None => false,
6142 };
6143 let no_throws = throws.is_empty();
6147 let cand = NoopCandidate {
6148 params: params.iter().map(|p| p.ty.clone()).collect(),
6149 noop: is_private && empty && no_throws,
6150 };
6151 by_name
6152 .entry(name.clone())
6153 .or_default()
6154 .entry(params.len())
6155 .or_default()
6156 .push(cand);
6157 }
6158 }
6159 });
6160 let blind = collect_all_blind_refs(ast, unit);
6163 let mut roots: Vec<JavaId> = Vec::new();
6166 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6167 for root in roots {
6168 let mut stack = vec![root];
6169 while let Some(n) = stack.pop() {
6170 if let NodeData::MethodRef { name } = ast.data(n) {
6171 if let Some(short) = ast.sn(*name).rsplit("::").next() {
6172 veto.insert(strip_ta_prefix(short).to_string());
6173 }
6174 }
6175 for &c in ast.children(n) {
6176 stack.push(c);
6177 }
6178 }
6179 }
6180 for (name, arities) in by_name {
6181 if veto.contains(&name) || blind.contains(&name) {
6182 continue;
6183 }
6184 ast.noop_private_methods.insert(name, arities);
6185 }
6186}
6187
6188fn remove_unused_imports(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6193 use std::collections::HashSet;
6194 let mut idents: HashSet<String> = HashSet::new();
6195 let mut roots: Vec<JavaId> = Vec::new();
6197 for_each_type(unit, &mut |ty| collect_member_roots(ty, &mut roots));
6198 for root in roots {
6199 let mut stack = vec![root];
6200 while let Some(n) = stack.pop() {
6201 match ast.data(n) {
6202 NodeData::VarDecl { name, ty, .. } => {
6203 idents.insert(name.clone());
6204 collect_ty_idents(ty, &mut idents);
6205 }
6206 NodeData::VarRef { name } => {
6207 let n = ast.sn(*name);
6211 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6212 if !id.is_empty() {
6213 idents.insert(id.to_string());
6214 }
6215 }
6216 }
6217 NodeData::Member { name } => {
6218 idents.insert(ast.sn(*name).to_string());
6219 }
6220 NodeData::MethodRef { name } => {
6221 let n = ast.sn(*name);
6222 for id in n.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6223 if !id.is_empty() {
6224 idents.insert(id.to_string());
6225 }
6226 }
6227 }
6228 NodeData::Label { name } => {
6229 idents.insert(name.clone());
6230 }
6231 NodeData::New { ty, anon_raw, .. } => {
6232 collect_ty_idents(ty, &mut idents);
6233 if let Some(raw) = anon_raw {
6236 for id in
6237 raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6238 {
6239 if !id.is_empty() {
6240 idents.insert(id.to_string());
6241 }
6242 }
6243 }
6244 }
6245 NodeData::Cast { ty } => {
6246 collect_ty_idents(ty, &mut idents);
6247 }
6248 NodeData::InstanceOf { ty, .. } => {
6249 collect_ty_idents(ty, &mut idents);
6250 }
6251 NodeData::Catch { ty_raw, .. } => {
6252 for id in
6254 ty_raw.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
6255 {
6256 if !id.is_empty() {
6257 idents.insert(id.to_string());
6258 }
6259 }
6260 }
6261 NodeData::Raw { text } => {
6262 for id in text.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6263 if !id.is_empty() {
6264 idents.insert(id.to_string());
6265 }
6266 }
6267 }
6268 _ => {}
6269 }
6270 for &c in ast.children(n) {
6271 stack.push(c);
6272 }
6273 }
6274 }
6275 for name in collect_all_blind_refs(ast, unit) {
6279 idents.insert(name);
6280 }
6281 for r in &unit.raws {
6284 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6285 if !id.is_empty() {
6286 idents.insert(id.to_string());
6287 }
6288 }
6289 }
6290 for_each_type(unit, &mut |ty| {
6293 collect_mods_idents(&ty.mods, &mut idents);
6294 idents.insert(ty.name.clone());
6295 for id in ty.ty_params.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6296 if !id.is_empty() {
6297 idents.insert(id.to_string());
6298 }
6299 }
6300 for id in ty.header.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6304 if !id.is_empty() {
6305 idents.insert(id.to_string());
6306 }
6307 }
6308 for e in ty.extends.iter().chain(ty.implements.iter()) {
6309 for id in e.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6310 if !id.is_empty() {
6311 idents.insert(id.to_string());
6312 }
6313 }
6314 }
6315 for m in &ty.members {
6316 match m {
6317 Member::Field { mods, ty, declarators } => {
6318 collect_mods_idents(mods, &mut idents);
6319 collect_ty_idents(ty, &mut idents);
6320 for d in declarators {
6321 idents.insert(d.name.clone());
6322 }
6323 }
6324 Member::Method { mods, ret, name, params, throws, .. } => {
6325 collect_mods_idents(mods, &mut idents);
6326 collect_ty_idents(ret, &mut idents);
6327 idents.insert(name.clone());
6328 for p in params {
6329 collect_mods_idents(&p.mods, &mut idents);
6330 collect_ty_idents(&p.ty, &mut idents);
6331 idents.insert(p.name.clone());
6332 }
6333 for t in throws {
6334 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6335 if !id.is_empty() {
6336 idents.insert(id.to_string());
6337 }
6338 }
6339 }
6340 }
6341 Member::Constructor { mods, name, params, throws, .. } => {
6342 collect_mods_idents(mods, &mut idents);
6343 idents.insert(name.clone());
6344 for p in params {
6345 collect_mods_idents(&p.mods, &mut idents);
6346 collect_ty_idents(&p.ty, &mut idents);
6347 idents.insert(p.name.clone());
6348 }
6349 for t in throws {
6350 for id in t.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6351 if !id.is_empty() {
6352 idents.insert(id.to_string());
6353 }
6354 }
6355 }
6356 }
6357 Member::Type(t) => {
6358 let _ = t;
6360 }
6361 _ => {}
6362 }
6363 }
6364 });
6365 let before = unit.imports.len();
6367 unit.imports.retain(|imp| {
6368 if imp.contains('*') {
6369 return true; }
6371 let path = imp.trim_start_matches("static ").trim();
6373 let simple = path.rsplit('.').next().unwrap_or(path).trim();
6374 if simple.is_empty() || !simple.chars().next().is_some_and(|c| c.is_uppercase()) {
6375 return true; }
6377 idents.contains(simple)
6378 });
6379 before - unit.imports.len()
6380}
6381
6382fn collect_ty_idents(t: &JType, out: &mut std::collections::HashSet<String>) {
6383 match t {
6384 JType::Ref(r) => {
6385 for id in r.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6386 if !id.is_empty() {
6387 out.insert(id.to_string());
6388 }
6389 }
6390 }
6391 JType::Array(inner) => collect_ty_idents(inner, out),
6392 _ => {}
6393 }
6394}
6395
6396fn collect_mods_idents(mods: &str, out: &mut std::collections::HashSet<String>) {
6397 if mods.contains('@') {
6399 for id in mods.split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$') {
6400 if !id.is_empty() {
6401 out.insert(id.to_string());
6402 }
6403 }
6404 }
6405}
6406
6407fn remove_dead_private_fields(ast: &JavaAst, unit: &mut CompilationUnit) -> usize {
6413 use std::collections::HashSet;
6414 let mut removed = 0usize;
6415 let anon_names = collect_anon_and_string_names(ast, unit);
6417 let blind = collect_all_blind_refs(ast, unit);
6420 let mut referenced: HashSet<String> = HashSet::new();
6422 let mut bodies: Vec<JavaId> = Vec::new();
6423 for_each_type(unit, &mut |ty: &TypeDecl| {
6424 for m in &ty.members {
6425 match m {
6426 Member::Method { body: Some(b), .. }
6427 | Member::Constructor { body: Some(b), .. }
6428 | Member::Initializer { body: b, .. } => bodies.push(*b),
6429 Member::Field { declarators, .. } => {
6430 for d in declarators {
6431 if let Some(init) = d.init {
6432 bodies.push(init);
6433 }
6434 }
6435 }
6436 _ => {}
6437 }
6438 }
6439 });
6440 for root in bodies {
6441 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
6443 while let Some((n, is_callee)) = stack.pop() {
6444 match ast.kind(n) {
6445 NodeKind::VarRef if !is_callee => {
6446 if let Some(nm) = ast.var_name(n) {
6447 referenced.insert(nm.to_string());
6448 }
6449 }
6450 NodeKind::Member => {
6451 if let NodeData::Member { name: sym } = ast.data(n) {
6452 referenced.insert(ast.sn(*sym).to_string());
6453 }
6454 }
6455 _ => {}
6456 }
6457 if ast.kind(n) == NodeKind::Call {
6458 let ch = ast.children(n).to_vec();
6459 if let Some(&first) = ch.first() {
6460 stack.push((first, true));
6461 }
6462 for &c in &ch[1..] {
6463 stack.push((c, false));
6464 }
6465 continue;
6466 }
6467 for &c in ast.children(n) {
6468 stack.push((c, false));
6469 }
6470 }
6471 }
6472 for_each_type_mut(unit, &mut |ty: &mut TypeDecl| {
6478 for m in ty.members.iter_mut() {
6479 if let Member::Field { mods, declarators, .. } = m {
6480 let is_private = mods.split_whitespace().any(|w| w == "private");
6481 let annotated = mods.contains('@');
6482 if !is_private || annotated {
6483 continue;
6484 }
6485 let dead: Vec<bool> = declarators
6486 .iter()
6487 .map(|d| {
6488 let reflective =
6489 d.name == "serialVersionUID" || d.name == "serialPersistentFields";
6490 let init_ok =
6491 d.init.map(|i| is_const_init(ast, i)).unwrap_or(true);
6492 !reflective
6493 && init_ok
6494 && !referenced.contains(&d.name)
6495 && !anon_names.contains(&d.name)
6496 && !blind.contains(&d.name)
6497 })
6498 .collect();
6499 let before = declarators.len();
6500 let mut idx = 0usize;
6501 declarators.retain(|_| {
6502 let keep = !dead[idx];
6503 idx += 1;
6504 keep
6505 });
6506 removed += before - declarators.len();
6507 }
6508 }
6509 let before = ty.members.len();
6510 ty.members
6511 .retain(|m| !matches!(m, Member::Field { declarators, .. } if declarators.is_empty()));
6512 removed += before - ty.members.len();
6513 });
6514 removed
6515}
6516
6517fn simplify_type(ast: &mut JavaAst, ty: &mut TypeDecl, cfg: &Config, total: &mut Report) {
6518 for m in &mut ty.members {
6519 simplify_member(ast, m, cfg, total);
6520 }
6521}
6522
6523fn simplify_member(ast: &mut JavaAst, m: &mut Member, cfg: &Config, total: &mut Report) {
6524 let params: Vec<(String, JType)> = match m {
6526 Member::Method { params, .. } | Member::Constructor { params, .. } => params
6527 .iter()
6528 .map(|p| (p.name.clone(), p.ty.clone()))
6529 .collect(),
6530 _ => Vec::new(),
6531 };
6532 ast.set_param_scope(¶ms);
6533 match m {
6534 Member::Method { body: Some(b), .. }
6535 | Member::Constructor { body: Some(b), .. } => {
6536 let r = simplify(ast, *b, cfg);
6537 merge_report(total, r);
6538 }
6539 Member::Initializer { body, .. } => {
6540 let r = simplify(ast, *body, cfg);
6541 merge_report(total, r);
6542 }
6543 Member::Field { declarators, .. } => {
6544 for d in declarators.iter_mut() {
6545 if let Some(init) = d.init {
6546 let r = simplify(ast, init, cfg);
6547 merge_report(total, r);
6548 let rules = default_java_rules();
6552 let mut cur: JavaId = init;
6553 for _ in 0..8 {
6554 match cure_engine::fold_root(ast, cur, &rules, cfg) {
6555 Some((new_root, name)) => {
6556 d.init = Some(new_root);
6557 cur = new_root;
6558 *total.by_rule.entry(name).or_insert(0) += 1;
6559 total.edits += 1;
6560 let r = simplify(ast, new_root, cfg);
6562 merge_report(total, r);
6563 }
6564 None => break,
6565 }
6566 }
6567 }
6568 }
6569 }
6570 Member::Type(t) => simplify_type(ast, t, cfg, total),
6571 Member::Raw(_) => {}
6572 _ => {}
6573 }
6574}
6575
6576fn merge_report(total: &mut Report, r: Report) {
6577 total.edits += r.edits;
6578 total.iterations += r.iterations;
6579 for (k, v) in r.by_rule {
6580 *total.by_rule.entry(k).or_insert(0) += v;
6581 }
6582}
6583
6584pub fn default_config() -> Config {
6586 Config::default()
6587}