1use std::collections::{HashMap, HashSet};
14
15use cure_engine::analysis::{subtree_metrics, TreeMetrics};
16use cure_engine::lang::{ReassocOutcome, StmtKill};
17use cure_engine::Lang;
18pub use cure_tree::ChildList;
19use cure_tree::{EventStore, NameTable};
20
21pub use cure_engine::kind::{BinOp, LitRef, NodeKind, UnOp};
23pub use cure_engine::Effect;
24
25#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
31pub struct JavaId(pub u32);
32impl Default for JavaId {
33 fn default() -> Self {
34 JavaId(u32::MAX)
35 }
36}
37
38#[derive(Clone, PartialEq, Eq, Debug)]
40pub enum JType {
41 Bool,
42 Byte,
43 Short,
44 Int,
45 Long,
46 Char,
47 Float,
48 Double,
49 Void,
50 Ref(String),
52 Array(Box<JType>),
53 Var,
55}
56
57impl JType {
58 pub fn is_ref(&self) -> bool {
59 matches!(self, JType::Ref(_) | JType::Array(_))
60 }
61 pub fn is_integral(&self) -> bool {
62 matches!(
63 self,
64 JType::Byte | JType::Short | JType::Int | JType::Long | JType::Char
65 )
66 }
67 pub fn is_bool(&self) -> bool {
68 matches!(self, JType::Bool)
69 }
70}
71
72#[derive(Clone, Copy, PartialEq, Debug)]
74pub enum NumVal {
75 Int(i64),
76 Long(i64),
77 Float(f64),
78 Double(f64),
79}
80
81#[derive(Clone, PartialEq, Debug)]
83pub enum Lit {
84 Bool(bool),
85 Int(i64),
86 Long(i64),
87 Float(f64),
88 Double(f64),
89 Char(char),
90 Str(String),
91 TextBlock(String),
93 NumRaw { text: String, val: NumVal },
95 Null,
96}
97
98#[derive(Clone, PartialEq, Debug)]
100pub enum NodeData {
101 Block,
103 Group,
107 Empty,
108 Yield,
110
111 ExprStmt,
112 VarDecl { name: String, ty: JType, final_mod: bool },
113 Assign { op: Option<BinOp> },
114 If,
115 While,
116 DoWhile,
117 For { inits: u8, has_cond: bool, steps: u8 },
118 ForEach { name: String, ty: JType },
119 Return,
120 Break { label: Option<String> },
121 Continue { label: Option<String> },
122 Throw,
123 Try,
125 Catch { ty_raw: String, name: String },
127 Synchronized,
129 Switch,
131 Case { labels: u16, is_default: bool, arrow: bool },
133 Label { name: String },
135 Assert,
137 Raw { text: String },
139
140 Binary { op: BinOp },
142 Unary { op: UnOp },
143 Call,
145 Member { name: Sym },
147 Index,
149 Cast { ty: JType },
150 Paren,
151 Ternary,
152 New { ty: JType, anon_raw: Option<String> },
154 NewArray { ty: JType, dims: u16, sized: u16 },
157 ArrayLit,
158 VarRef { name: Sym },
159 Literal(Lit),
160 This,
161 Super,
163 InstanceOf { ty: JType, bind: Option<String> },
165 Lambda { params_raw: String },
167 MethodRef { name: Sym },
169}
170
171#[derive(Clone, PartialEq, Debug)]
172pub struct Node {
173 pub data: NodeData,
174 pub children: ChildList<JavaId>,
175}
176
177#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
182pub struct Sym(pub u32);
183
184fn lit_value_eq(x: &Lit, y: &Lit) -> bool {
186 fn r(l: &Lit) -> Option<cure_engine::kind::LitRef<'_>> {
187 Some(match l {
188 Lit::Bool(b) => LitRef::Bool(*b),
189 Lit::Int(v) => LitRef::Int(*v),
190 Lit::Long(v) => LitRef::Long(*v),
191 Lit::Float(v) => LitRef::Float(*v),
192 Lit::Double(v) => LitRef::Double(*v),
193 Lit::Char(c) => LitRef::Char(*c),
194 Lit::Str(s) => LitRef::Str(s.as_str()),
195 Lit::NumRaw { val, .. } => match val {
196 NumVal::Int(v) => LitRef::Int(*v),
197 NumVal::Long(v) => LitRef::Long(*v),
198 NumVal::Float(v) => LitRef::Float(*v),
199 NumVal::Double(v) => LitRef::Double(*v),
200 },
201 Lit::Null => LitRef::Null,
202 Lit::TextBlock(_) => return None,
203 })
204 }
205 match (r(x), r(y)) {
208 (Some(rx), Some(ry)) => {
209 if let (LitRef::Double(a), LitRef::Double(b)) = (rx, ry) {
210 return a == b && a.is_sign_negative() == b.is_sign_negative();
211 }
212 if let (LitRef::Float(a), LitRef::Float(b)) = (rx, ry) {
213 return a == b && a.is_sign_negative() == b.is_sign_negative();
214 }
215 rx == ry
216 }
217 _ => false,
218 }
219}
220
221#[derive(Clone, Debug)]
223pub struct NoopCandidate {
224 pub params: Vec<JType>,
226 pub noop: bool,
228}
229
230
231#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
243pub struct VarFacts {
244 pub single_def: bool,
246 pub single_use: bool,
248 pub bool_valued: bool,
250}
251
252#[derive(Debug, Clone, Default)]
255pub struct JavaFacts {
256 pub vars: HashMap<String, VarFacts>,
257 pub call_effects: HashMap<String, std::collections::HashSet<cure_engine::Effect>>,
258}
259
260impl JavaFacts {
261 pub fn var(&self, name: &str) -> VarFacts {
262 self.vars.get(name).copied().unwrap_or_default()
263 }
264}
265
266#[derive(Clone, Copy, Debug)]
271struct StmtFact {
272 key: u32,
273 refs: u32,
274 lambda_cap: bool,
275 shadow: bool,
277}
278
279#[derive(Clone, Copy, Debug)]
282struct StmtKillFact {
283 key: u32,
284 kind: u8,
285 assign: JavaId,
286}
287
288
289#[derive(Clone, Copy, Debug)]
290struct StmtOpq {
291 opaque: bool,
292}
293
294#[derive(Default, Debug)]
295pub struct JavaAst {
296 pub(crate) nodes: Vec<Node>,
297 pub facts: Option<JavaFacts>,
299 effect_cache: Vec<Option<Effect>>,
302 var_types: Vec<Option<JType>>,
305 param_scope: Vec<(String, JType)>,
307 pub const_fields: HashMap<String, JavaId>,
310 pub const_scalars: HashSet<String>,
314 pub noop_private_methods: HashMap<String, HashMap<usize, Vec<NoopCandidate>>>,
320 pub field_types: HashMap<String, JType>,
323 pub unit_shadows_lang: bool,
328 pub raw_generic_names: std::collections::HashSet<String>,
332 pub final_fields: HashSet<String>,
336 pub current_return_ty: Option<JType>,
339 pub inline_methods: HashMap<String, (Vec<String>, JavaId, String)>,
345 string_identity: std::cell::Cell<Option<(JavaId, bool)>>,
348 pub unit_string_identity: bool,
353 names: NameTable,
357 events: EventStore<JavaId, u32>,
361 stmt_facts: Vec<Option<Box<[StmtFact]>>>,
363 stmt_kills: Vec<Option<Box<[StmtKillFact]>>>,
365 stmt_opq: Vec<Option<StmtOpq>>,
367 prepared_root: Option<JavaId>,
372}
373
374#[derive(Clone, PartialEq, Debug)]
380pub struct Param {
381 pub mods: String,
383 pub ty: JType,
384 pub varargs: bool,
386 pub name: String,
387}
388
389#[derive(Clone, PartialEq, Debug)]
391pub struct Declarator {
392 pub name: String,
393 pub extra_dims: u16,
395 pub init: Option<JavaId>,
397}
398
399#[derive(Clone, PartialEq, Debug)]
401pub enum Member {
402 Field { mods: String, ty: JType, declarators: Vec<Declarator> },
403 Method {
404 mods: String,
405 ty_params: String,
407 ret: JType,
408 name: String,
409 params: Vec<Param>,
410 throws: Vec<String>,
411 body: Option<JavaId>,
413 },
414 Constructor {
415 mods: String,
416 ty_params: String,
417 name: String,
418 params: Vec<Param>,
419 throws: Vec<String>,
420 body: Option<JavaId>,
421 compact: bool,
423 },
424 Initializer { is_static: bool, body: JavaId },
426 Type(Box<TypeDecl>),
428 Raw(String),
430}
431
432#[derive(Clone, Copy, PartialEq, Eq, Debug)]
433pub enum TypeKind {
434 Class,
435 Interface,
436 Enum,
437 Record,
438 Annotation,
439}
440
441#[derive(Clone, PartialEq, Debug)]
443pub struct TypeDecl {
444 pub kind: TypeKind,
445 pub mods: String,
447 pub name: String,
448 pub ty_params: String,
450 pub header: String,
452 pub extends: Vec<String>,
453 pub implements: Vec<String>,
454 pub permits: Vec<String>,
456 pub enum_constants: Vec<String>,
458 pub members: Vec<Member>,
459}
460
461#[derive(Clone, PartialEq, Debug, Default)]
463pub struct CompilationUnit {
464 pub package: Option<String>,
466 pub package_annotations: String,
469 pub header_comments: String,
472 pub imports: Vec<String>,
474 pub types: Vec<TypeDecl>,
475 pub raws: Vec<String>,
477}
478
479#[derive(Clone, PartialEq, Eq, Debug)]
481pub struct ParseError {
482 pub line: usize,
483 pub col: usize,
484 pub message: String,
485}
486
487impl JavaAst {
492 pub fn new() -> Self {
493 Self::default()
494 }
495
496 pub fn clone_node(&mut self, id: JavaId, children: Vec<JavaId>) -> JavaId {
498 let data = self.nodes[id.0 as usize].data.clone();
499 self.push(data, children)
500 }
501
502 fn push(&mut self, data: NodeData, children: Vec<JavaId>) -> JavaId {
503 let id = JavaId(self.nodes.len() as u32);
504 self.nodes.push(Node { data, children: ChildList::from_vec(children) });
505 id
506 }
507
508 pub fn node(&self, id: JavaId) -> &Node {
509 &self.nodes[id.0 as usize]
510 }
511 pub fn data(&self, id: JavaId) -> &NodeData {
512 &self.node(id).data
513 }
514
515 pub fn kind(&self, id: JavaId) -> NodeKind {
517 match &self.nodes[id.0 as usize].data {
518 NodeData::Block | NodeData::Group => NodeKind::Block,
519 NodeData::Empty => NodeKind::Empty,
520 NodeData::Yield => NodeKind::Yield,
521 NodeData::ExprStmt => NodeKind::ExprStmt,
522 NodeData::VarDecl { .. } => NodeKind::VarDecl,
523 NodeData::Assign { .. } => NodeKind::Assign,
524 NodeData::If => NodeKind::If,
525 NodeData::While => NodeKind::While,
526 NodeData::DoWhile => NodeKind::DoWhile,
527 NodeData::For { .. } => NodeKind::For,
528 NodeData::ForEach { .. } => NodeKind::ForEach,
529 NodeData::Return => NodeKind::Return,
530 NodeData::Break { .. } => NodeKind::Break,
531 NodeData::Continue { .. } => NodeKind::Continue,
532 NodeData::Throw => NodeKind::Throw,
533 NodeData::Try => NodeKind::Try,
534 NodeData::Catch { .. } => NodeKind::Catch,
535 NodeData::Synchronized => NodeKind::Synchronized,
536 NodeData::Switch => NodeKind::Switch,
537 NodeData::Case { .. } => NodeKind::Case,
538 NodeData::Label { .. } => NodeKind::Label,
539 NodeData::Assert => NodeKind::Assert,
540 NodeData::Raw { .. } => NodeKind::Raw,
541 NodeData::Binary { .. } => NodeKind::Binary,
542 NodeData::Unary { .. } => NodeKind::Unary,
543 NodeData::Call => NodeKind::Call,
544 NodeData::Member { .. } => NodeKind::Member,
545 NodeData::Index => NodeKind::Index,
546 NodeData::Cast { .. } => NodeKind::Cast,
547 NodeData::Paren => NodeKind::Paren,
548 NodeData::Ternary => NodeKind::Ternary,
549 NodeData::New { .. } => NodeKind::New,
550 NodeData::NewArray { .. } => NodeKind::NewArray,
551 NodeData::ArrayLit => NodeKind::ArrayLit,
552 NodeData::VarRef { .. } => NodeKind::VarRef,
553 NodeData::Literal(_) => NodeKind::Literal,
554 NodeData::This => NodeKind::This,
555 NodeData::Super => NodeKind::This,
556 NodeData::InstanceOf { .. } => NodeKind::InstanceOf,
557 NodeData::Lambda { .. } => NodeKind::Lambda,
558 NodeData::MethodRef { .. } => NodeKind::MethodRef,
559 }
560 }
561 pub fn node_count(&self) -> usize {
563 self.nodes.len()
564 }
565
566 pub fn children(&self, id: JavaId) -> &[JavaId] {
567 self.nodes[id.0 as usize].children.as_slice()
568 }
569
570 pub fn block(&mut self, stmts: Vec<JavaId>) -> JavaId {
573 self.push(NodeData::Block, stmts)
574 }
575 pub fn empty(&mut self) -> JavaId {
576 self.push(NodeData::Empty, vec![])
577 }
578 pub fn expr_stmt(&mut self, e: JavaId) -> JavaId {
579 self.push(NodeData::ExprStmt, vec![e])
580 }
581 pub fn var_decl(&mut self, name: &str, ty: JType, init: Option<JavaId>) -> JavaId {
582 self.var_decl_final(name, ty, init, false)
583 }
584 pub fn var_decl_final(
588 &mut self,
589 name: &str,
590 ty: JType,
591 init: Option<JavaId>,
592 final_mod: bool,
593 ) -> JavaId {
594 self.push(
595 NodeData::VarDecl {
596 name: name.into(),
597 ty,
598 final_mod,
599 },
600 init.into_iter().collect(),
601 )
602 }
603 pub fn assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
604 self.push(NodeData::Assign { op: None }, vec![target, value])
605 }
606 pub fn assign_op(&mut self, op: BinOp, target: JavaId, value: JavaId) -> JavaId {
607 self.push(NodeData::Assign { op: Some(op) }, vec![target, value])
608 }
609 pub fn if_(&mut self, cond: JavaId, then: JavaId, els: Option<JavaId>) -> JavaId {
610 let mut ch = vec![cond, then];
611 ch.extend(els);
612 self.push(NodeData::If, ch)
613 }
614 pub fn while_(&mut self, cond: JavaId, body: JavaId) -> JavaId {
615 self.push(NodeData::While, vec![cond, body])
616 }
617 pub fn do_while(&mut self, body: JavaId, cond: JavaId) -> JavaId {
618 self.push(NodeData::DoWhile, vec![body, cond])
619 }
620 pub fn for_(
621 &mut self,
622 inits: Vec<JavaId>,
623 cond: Option<JavaId>,
624 steps: Vec<JavaId>,
625 body: JavaId,
626 ) -> JavaId {
627 let ni = inits.len();
628 let ns = steps.len();
629 let mut ch = inits;
630 let has_cond = cond.is_some();
631 ch.extend(cond);
632 ch.extend(steps);
633 ch.push(body);
634 self.push(
635 NodeData::For {
636 inits: ni as u8,
637 has_cond,
638 steps: ns as u8,
639 },
640 ch,
641 )
642 }
643 pub fn for_each(&mut self, name: &str, ty: JType, iterable: JavaId, body: JavaId) -> JavaId {
644 self.push(
645 NodeData::ForEach {
646 name: name.into(),
647 ty,
648 },
649 vec![iterable, body],
650 )
651 }
652 pub fn yield_(&mut self, expr: JavaId) -> JavaId {
653 self.push(NodeData::Yield, vec![expr])
654 }
655 pub fn ret(&mut self, value: Option<JavaId>) -> JavaId {
656 self.push(NodeData::Return, value.into_iter().collect())
657 }
658 pub fn break_(&mut self, label: Option<&str>) -> JavaId {
659 self.push(
660 NodeData::Break {
661 label: label.map(Into::into),
662 },
663 vec![],
664 )
665 }
666 pub fn continue_(&mut self, label: Option<&str>) -> JavaId {
667 self.push(
668 NodeData::Continue {
669 label: label.map(Into::into),
670 },
671 vec![],
672 )
673 }
674 pub fn throw(&mut self, e: JavaId) -> JavaId {
675 self.push(NodeData::Throw, vec![e])
676 }
677 pub fn try_(
678 &mut self,
679 resources: Vec<JavaId>,
680 try_block: JavaId,
681 catches: Vec<JavaId>,
682 finally: Option<JavaId>,
683 ) -> JavaId {
684 let mut ch = resources;
685 ch.push(try_block);
686 ch.extend(catches);
687 ch.extend(finally);
688 self.push(NodeData::Try, ch)
689 }
690 pub fn catch_(&mut self, ty_raw: &str, name: &str, block: JavaId) -> JavaId {
691 self.push(
692 NodeData::Catch {
693 ty_raw: ty_raw.into(),
694 name: name.into(),
695 },
696 vec![block],
697 )
698 }
699 pub fn synchronized(&mut self, lock: JavaId, block: JavaId) -> JavaId {
700 self.push(NodeData::Synchronized, vec![lock, block])
701 }
702 pub fn switch_(&mut self, subject: JavaId, cases: Vec<JavaId>) -> JavaId {
703 let mut ch = vec![subject];
704 ch.extend(cases);
705 self.push(NodeData::Switch, ch)
706 }
707 pub fn case_(
708 &mut self,
709 labels: Vec<JavaId>,
710 is_default: bool,
711 arrow: bool,
712 stmts: Vec<JavaId>,
713 ) -> JavaId {
714 let n = labels.len() as u16;
715 let mut ch = labels;
716 ch.extend(stmts);
717 self.push(
718 NodeData::Case {
719 labels: n,
720 is_default,
721 arrow,
722 },
723 ch,
724 )
725 }
726 pub fn label(&mut self, name: &str, stmt: JavaId) -> JavaId {
727 self.push(NodeData::Label { name: name.into() }, vec![stmt])
728 }
729 pub fn assert_(&mut self, cond: JavaId, msg: Option<JavaId>) -> JavaId {
730 let mut ch = vec![cond];
731 ch.extend(msg);
732 self.push(NodeData::Assert, ch)
733 }
734 pub fn raw(&mut self, text: &str) -> JavaId {
735 self.push(NodeData::Raw { text: text.into() }, vec![])
736 }
737
738 pub fn bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
741 self.push(NodeData::Binary { op }, vec![l, r])
742 }
743 pub fn un(&mut self, op: UnOp, e: JavaId) -> JavaId {
744 self.push(NodeData::Unary { op }, vec![e])
745 }
746 pub fn call(&mut self, callee: JavaId, args: Vec<JavaId>) -> JavaId {
747 let mut ch = vec![callee];
748 ch.extend(args);
749 self.push(NodeData::Call, ch)
750 }
751 pub fn member(&mut self, obj: JavaId, name: &str) -> JavaId {
752 let sym = self.intern_sym(name);
753 self.push(NodeData::Member { name: sym }, vec![obj])
754 }
755 pub fn index(&mut self, arr: JavaId, idx: JavaId) -> JavaId {
756 self.push(NodeData::Index, vec![arr, idx])
757 }
758 pub fn cast(&mut self, ty: JType, e: JavaId) -> JavaId {
759 self.push(NodeData::Cast { ty }, vec![e])
760 }
761 pub fn paren(&mut self, e: JavaId) -> JavaId {
762 self.push(NodeData::Paren, vec![e])
763 }
764 pub fn ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
765 self.push(NodeData::Ternary, vec![c, a, b])
766 }
767 pub fn new_(&mut self, ty: JType, args: Vec<JavaId>) -> JavaId {
768 self.push(NodeData::New { ty, anon_raw: None }, args)
769 }
770 pub fn new_anon(&mut self, ty: JType, args: Vec<JavaId>, body_raw: String) -> JavaId {
771 self.push(
772 NodeData::New {
773 ty,
774 anon_raw: Some(body_raw),
775 },
776 args,
777 )
778 }
779 pub fn new_array(
780 &mut self,
781 ty: JType,
782 dims: u16,
783 sized: u16,
784 sizes: Vec<JavaId>,
785 init: Option<JavaId>,
786 ) -> JavaId {
787 let mut ch = sizes;
788 ch.extend(init);
789 self.push(NodeData::NewArray { ty, dims, sized }, ch)
790 }
791 pub fn group(&mut self, stmts: Vec<JavaId>) -> JavaId {
793 self.push(NodeData::Group, stmts)
794 }
795 pub fn array_lit(&mut self, elems: Vec<JavaId>) -> JavaId {
796 self.push(NodeData::ArrayLit, elems)
797 }
798 pub fn var(&mut self, name: &str) -> JavaId {
799 let sym = self.intern_sym(name);
800 self.push(NodeData::VarRef { name: sym }, vec![])
801 }
802 pub fn lit(&mut self, l: Lit) -> JavaId {
803 self.push(NodeData::Literal(l), vec![])
804 }
805 fn build_int_impl(&mut self, v: i64, long: bool) -> JavaId {
806 if long {
807 self.lit(Lit::Long(v))
808 } else {
809 self.lit(Lit::Int(v))
810 }
811 }
812 pub fn this(&mut self) -> JavaId {
813 self.push(NodeData::This, vec![])
814 }
815 pub fn super_(&mut self) -> JavaId {
816 self.push(NodeData::Super, vec![])
817 }
818 pub fn instance_of(&mut self, e: JavaId, ty: JType, bind: Option<&str>) -> JavaId {
819 self.push(
820 NodeData::InstanceOf {
821 ty,
822 bind: bind.map(Into::into),
823 },
824 vec![e],
825 )
826 }
827 pub fn lambda(&mut self, params_raw: &str, body: JavaId) -> JavaId {
828 self.push(
829 NodeData::Lambda {
830 params_raw: params_raw.into(),
831 },
832 vec![body],
833 )
834 }
835 pub fn method_ref(&mut self, receiver: JavaId, name: &str) -> JavaId {
836 let sym = self.intern_sym(name);
837 self.push(NodeData::MethodRef { name: sym }, vec![receiver])
838 }
839
840 pub fn method_call(&mut self, obj: JavaId, name: &str, args: Vec<JavaId>) -> JavaId {
844 let m = self.member(obj, name);
845 self.call(m, args)
846 }
847 pub fn plain_call(&mut self, name: &str, args: Vec<JavaId>) -> JavaId {
849 let f = self.var(name);
850 self.call(f, args)
851 }
852
853 pub fn var_type(&self, id: JavaId) -> Option<&JType> {
855 self.var_types.get(id.0 as usize).and_then(|t| t.as_ref())
856 }
857
858 pub fn any_string_identity(&self, roots: &[JavaId]) -> bool {
864 roots.iter().any(|&r| {
865 let hit = Self::scan_string_identity(self, r);
866 if hit {
867 self.string_identity.set(Some((r, true)));
868 }
869 hit
870 })
871 }
872
873 pub fn has_string_identity_compare(&self, root: JavaId) -> bool {
874 if let Some((r, v)) = self.string_identity.get() {
875 if r == root {
876 return v;
877 }
878 }
879 let v = Self::scan_string_identity(self, root);
880 self.string_identity.set(Some((root, v)));
881 v
882 }
883
884 fn scan_string_identity(&self, root: JavaId) -> bool {
885 let mut stack = vec![root];
886 while let Some(id) = stack.pop() {
887 if let NodeData::Binary { op: BinOp::Eq | BinOp::Ne } = self.data(id) {
888 let ch = self.children(id);
889 if ch.len() == 2
890 && self.expr_maybe_string(ch[0])
891 && self.expr_maybe_string(ch[1])
892 {
893 return true;
894 }
895 }
896 for &c in self.children(id) {
897 stack.push(c);
898 }
899 }
900 false
901 }
902
903 fn expr_maybe_string(&self, id: JavaId) -> bool {
905 match self.data(id) {
906 NodeData::Literal(Lit::Str(_)) => true,
907 NodeData::Literal(_) | NodeData::This | NodeData::Super
909 | NodeData::NewArray { .. } | NodeData::ArrayLit | NodeData::InstanceOf { .. } => false,
910 NodeData::VarRef { .. } => self.var_type(id).map_or(true, Self::type_maybe_string),
911 NodeData::Cast { ty } => Self::type_maybe_string(ty),
912 NodeData::Paren => self
913 .children(id)
914 .first()
915 .map_or(true, |&c| self.expr_maybe_string(c)),
916 NodeData::Binary { op } if *op == BinOp::Add => {
918 self.children(id).iter().any(|&c| self.expr_maybe_string(c))
919 }
920 NodeData::Binary { .. } => false,
921 NodeData::Ternary => self
922 .children(id)
923 .get(1..)
924 .map_or(true, |cs| cs.iter().any(|&c| self.expr_maybe_string(c))),
925 _ => true,
927 }
928 }
929
930 fn type_maybe_string(t: &JType) -> bool {
932 match t {
933 JType::Bool | JType::Byte | JType::Short | JType::Int | JType::Long
934 | JType::Char | JType::Float | JType::Double | JType::Void
935 | JType::Array(_) => false, JType::Var => true, JType::Ref(n) => {
938 let base = n.split('<').next().unwrap_or(n).trim();
939 let last = base.rsplit('.').next().unwrap_or(base);
940 matches!(
941 last,
942 "String" | "Object" | "CharSequence" | "Comparable" | "Serializable"
943 ) || n.contains('<') || (last.len() <= 2 && last.chars().next().is_some_and(|c| c.is_uppercase()))
946 }
947 }
948 }
949
950
951 pub fn set_param_scope(&mut self, params: &[(String, JType)]) {
953 self.param_scope = params.to_vec();
954 }
955
956 pub fn is_param_name(&self, name: &str) -> bool {
959 self.param_scope.iter().any(|(n, _)| n == name)
960 }
961
962 pub fn set_facts(&mut self, facts: JavaFacts) {
967 self.facts = Some(facts);
968 }
969 pub fn clear_facts(&mut self) {
971 self.facts = None;
972 }
973
974 pub fn name_of_key(&self, k: u32) -> Option<String> {
975 Some(self.names.name(k).to_string())
976 }
977 pub fn clear_param_scope(&mut self) {
978 self.param_scope.clear();
979 }
980}
981
982fn type_base_name(n: &str) -> &str {
990 let no_args = match n.find('<') {
991 Some(i) => &n[..i],
992 None => n,
993 };
994 no_args.rsplit('.').next().unwrap_or(no_args)
995}
996
997fn fold_i64(op: BinOp, a: i64, b: i64) -> Option<i64> {
1004 use BinOp::*;
1005 Some(match op {
1006 Add => a.wrapping_add(b),
1007 Sub => a.wrapping_sub(b),
1008 Mul => a.wrapping_mul(b),
1009 Div => {
1010 if b == 0 {
1011 return None;
1012 }
1013 a.wrapping_div(b)
1014 }
1015 Rem => {
1016 if b == 0 {
1017 return None;
1018 }
1019 a.wrapping_rem(b)
1020 }
1021 Shl => a.wrapping_shl((b as u64 & 63) as u32),
1022 Shr => a.wrapping_shr((b as u64 & 63) as u32),
1023 UShr => ((a as u64).wrapping_shr((b as u64 & 63) as u32)) as i64,
1024 BitAnd => a & b,
1025 BitXor => a ^ b,
1026 BitOr => a | b,
1027 _ => return None,
1028 })
1029}
1030
1031fn fold_i32(op: BinOp, a: i64, b: i64) -> Option<i64> {
1033 use BinOp::*;
1034 let (x, y) = (a as i32, b as i32);
1035 Some(match op {
1036 Add => x.wrapping_add(y) as i64,
1037 Sub => x.wrapping_sub(y) as i64,
1038 Mul => x.wrapping_mul(y) as i64,
1039 Div => {
1040 if y == 0 {
1041 return None;
1042 }
1043 x.wrapping_div(y) as i64
1044 }
1045 Rem => {
1046 if y == 0 {
1047 return None;
1048 }
1049 x.wrapping_rem(y) as i64
1050 }
1051 Shl => x.wrapping_shl((y as u32) & 31) as i64,
1052 Shr => x.wrapping_shr((y as u32) & 31) as i64,
1053 UShr => (x as u32).wrapping_shr((y as u32) & 31) as i64,
1054 BitAnd => (x & y) as i64,
1055 BitXor => (x ^ y) as i64,
1056 BitOr => (x | y) as i64,
1057 _ => return None,
1058 })
1059}
1060
1061fn cmp_i64(op: BinOp, x: i64, y: i64) -> Option<bool> {
1063 use BinOp::*;
1064 Some(match op {
1065 Lt => x < y,
1066 Le => x <= y,
1067 Gt => x > y,
1068 Ge => x >= y,
1069 Eq => x == y,
1070 Ne => x != y,
1071 _ => return None,
1072 })
1073}
1074
1075fn narrow_typed_expr(ast: &JavaAst, id: JavaId) -> bool {
1077 match ast.data(id) {
1078 NodeData::VarRef { .. } => matches!(
1079 ast.var_type(id),
1080 Some(JType::Short | JType::Byte | JType::Char)
1081 ),
1082 NodeData::Cast { ty } => matches!(ty, JType::Short | JType::Byte | JType::Char),
1083 NodeData::Literal(Lit::Char(_)) => true,
1084 _ => false,
1085 }
1086}
1087
1088impl Lang for JavaAst {
1089 type Id = JavaId;
1090 type NameKey = u32;
1091
1092 fn kind(&self, id: JavaId) -> NodeKind {
1093 JavaAst::kind(self, id)
1094 }
1095
1096 fn children(&self, id: JavaId) -> &[JavaId] {
1097 JavaAst::children(self, id)
1098 }
1099
1100 fn set_child(&mut self, parent: JavaId, index: usize, new: JavaId) {
1101 self.nodes[parent.0 as usize].children.set(index, new);
1102 }
1103 fn remove_child(&mut self, parent: JavaId, index: usize) {
1104 self.nodes[parent.0 as usize].children.remove(index);
1105 }
1106 fn splice(&mut self, node: JavaId, index: usize, remove: usize, insert: Vec<JavaId>) {
1107 self.nodes[node.0 as usize].children.splice(index, remove, insert);
1108 }
1109
1110 fn build_return(&mut self, value: Option<JavaId>) -> JavaId {
1111 self.ret(value)
1112 }
1113 fn build_block(&mut self, stmts: Vec<JavaId>) -> JavaId {
1114 self.block(stmts)
1115 }
1116 fn build_unary(&mut self, op: UnOp, operand: JavaId) -> JavaId {
1117 self.un(op, operand)
1118 }
1119 fn build_bool(&mut self, v: bool) -> JavaId {
1120 self.lit(Lit::Bool(v))
1121 }
1122 fn build_int(&mut self, v: i64, long: bool) -> JavaId {
1123 self.build_int_impl(v, long)
1124 }
1125 fn build_str(&mut self, s: &str) -> JavaId {
1126 self.lit(Lit::Str(s.to_string()))
1127 }
1128 fn build_char(&mut self, c: char) -> JavaId {
1129 self.lit(Lit::Char(c))
1130 }
1131 fn build_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
1132 self.bin(op, l, r)
1133 }
1134 fn build_ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
1135 self.ternary(c, a, b)
1136 }
1137 fn build_assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
1138 self.assign(target, value)
1139 }
1140
1141 fn fold_lit_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<JavaId> {
1144 let ll = self.literal(l)?;
1145 let rr = self.literal(r)?;
1146 if op == BinOp::Add {
1150 let str_of = |x: LitRef<'_>| -> Option<String> {
1151 match x {
1152 LitRef::Str(s) => Some(s.to_string()),
1153 LitRef::Char(c) => Some(c.to_string()),
1154 LitRef::Int(v) => Some(v.to_string()),
1155 LitRef::Long(v) => Some(v.to_string()),
1156 LitRef::Bool(b) => Some(b.to_string()),
1157 _ => None,
1158 }
1159 };
1160 if matches!(ll, LitRef::Str(_)) || matches!(rr, LitRef::Str(_)) {
1161 if let (Some(a), Some(b)) = (str_of(ll), str_of(rr)) {
1162 return Some(self.build_str(&format!("{a}{b}")));
1163 }
1164 }
1165 }
1166 let int_of = |x: LitRef<'_>| -> Option<i64> {
1169 match x {
1170 LitRef::Int(v) | LitRef::Long(v) => Some(v),
1172 LitRef::Char(c) => Some(c as u32 as i64),
1173 _ => None,
1174 }
1175 };
1176 let (a, b) = (int_of(ll)?, int_of(rr)?);
1177 let wide = matches!(ll, LitRef::Long(_)) || matches!(rr, LitRef::Long(_));
1178 let v = if wide { fold_i64(op, a, b) } else { fold_i32(op, a, b) }?;
1179 Some(self.build_int(v, wide))
1180 }
1181
1182 fn fold_lit_cmp(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<bool> {
1183 let ll = self.literal(l)?;
1184 let rr = self.literal(r)?;
1185 let int_of = |x: LitRef<'_>| -> Option<i64> {
1187 match x {
1188 LitRef::Int(v) | LitRef::Long(v) => Some(v),
1189 LitRef::Char(c) => Some(c as u32 as i64),
1190 _ => None,
1191 }
1192 };
1193 if let (Some(x), Some(y)) = (int_of(ll), int_of(rr)) {
1194 return cmp_i64(op, x, y);
1195 }
1196 if let (LitRef::Bool(x), LitRef::Bool(y)) = (ll, rr) {
1199 if matches!(op, BinOp::Eq | BinOp::Ne) {
1200 return Some(if op == BinOp::Eq { x == y } else { x != y });
1201 }
1202 }
1203 None
1205 }
1206
1207 fn fold_lit_neg(&mut self, v: i64, wide: bool) -> Option<i64> {
1208 let n = -v;
1210 if !wide && n > i32::MAX as i64 {
1211 return None;
1212 }
1213 Some(n)
1214 }
1215
1216 fn reassoc_delta(
1217 &mut self,
1218 op1: BinOp,
1219 k1: i64,
1220 op2: BinOp,
1221 k2: i64,
1222 wide: bool,
1223 ) -> Option<ReassocOutcome<JavaId>> {
1224 use BinOp::*;
1225 let raw: i64 = match (op1, op2) {
1226 (BitXor, BitXor) => k1 ^ k2,
1227 (a, b) if matches!(a, Add | Sub) && matches!(b, Add | Sub) => {
1228 let sign = |o: BinOp| if o == Sub { -1i64 } else { 1i64 };
1229 sign(a)
1232 .wrapping_mul(k1)
1233 .wrapping_add(sign(b).wrapping_mul(k2))
1234 }
1235 _ => return None,
1236 };
1237 let d = if wide { raw } else { raw as i32 as i64 };
1240 if d == 0 {
1241 return Some(ReassocOutcome::Neutral);
1242 }
1243 if op1 == BitXor && op2 == BitXor {
1244 return Some(ReassocOutcome::Lit(self.build_int(d, wide), BitXor));
1245 }
1246 if d > 0 {
1247 Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
1248 } else {
1249 let nd = if wide { d.checked_neg().unwrap_or(d) } else { (d as i32).wrapping_neg() as i64 };
1253 if nd > 0 {
1254 Some(ReassocOutcome::Lit(self.build_int(nd, wide), Sub))
1255 } else {
1256 Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
1257 }
1258 }
1259 }
1260
1261 fn copy_subtree(&mut self, id: JavaId) -> JavaId {
1262 let old_children: Vec<JavaId> = self.nodes[id.0 as usize].children.as_slice().to_vec();
1263 let mut children = Vec::with_capacity(old_children.len());
1264 for c in old_children {
1265 children.push(self.copy_subtree(c));
1266 }
1267 let data = self.nodes[id.0 as usize].data.clone();
1268 self.push(data, children)
1269 }
1270
1271 fn effect(&self, id: JavaId) -> Effect {
1272 if let Some(e) = self.effect_cache.get(id.0 as usize).and_then(|x| *x) {
1273 return e;
1274 }
1275 let mut e = self.own_effect(id);
1277 for &c in self.nodes[id.0 as usize].children.as_slice() {
1278 e = e.worst(self.effect(c));
1279 }
1280 e
1281 }
1282
1283 fn invalidate_effect(&mut self, id: JavaId) {
1284 if let Some(slot) = self.effect_cache.get_mut(id.0 as usize) {
1285 *slot = None;
1286 }
1287 self.events.invalidate(id.0 as usize);
1290 if let Some(slot) = self.stmt_facts.get_mut(id.0 as usize) {
1291 *slot = None;
1292 }
1293 if let Some(slot) = self.stmt_kills.get_mut(id.0 as usize) {
1294 *slot = None;
1295 }
1296 if let Some(slot) = self.stmt_opq.get_mut(id.0 as usize) {
1297 *slot = None;
1298 }
1299 }
1300
1301 fn debug_verify_events(&self, stmt: JavaId) {
1302 if !verify_events_on() {
1303 return;
1304 }
1305 if let Some(indexed) = self.region_events(stmt) {
1306 let Some(fresh) = cure_tree::collect_region_events(self, stmt) else {
1307 return;
1308 };
1309 let key = |e: &cure_engine::kind::RegionEvent<JavaId, u32>| {
1311 (e.key, matches!(e.kind, cure_engine::kind::EventKind::Use), e.node.0)
1312 };
1313 let a: Vec<_> = indexed.iter().map(key).collect();
1314 let b: Vec<_> = fresh.iter().map(key).collect();
1315 if a != b {
1316 eprintln!(
1317 "[EVENTS-STALE] stmt #{} kind={:?}: indexed {} events, fresh {} events",
1318 stmt.0, self.kind(stmt), a.len(), b.len()
1319 );
1320 }
1321 }
1322 }
1323
1324 fn region_events(
1325 &self,
1326 id: JavaId,
1327 ) -> Option<&[cure_engine::kind::RegionEvent<JavaId, u32>]> {
1328 self.events.slot(id.0 as usize)
1330 }
1331
1332 fn var_key(&self, id: JavaId) -> Option<u32> {
1333 self.names.var_key(id.0 as usize)
1334 }
1335
1336 fn stmt_var_ref_count(&self, stmt: JavaId, key: u32) -> Option<u32> {
1337 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1338 Some(
1339 slot.binary_search_by_key(&key, |f| f.key)
1340 .ok()
1341 .map_or(0, |i| slot[i].refs),
1342 )
1343 }
1344
1345 fn stmt_lambda_captures(&self, stmt: JavaId, key: u32) -> Option<bool> {
1346 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1347 Some(
1348 slot.binary_search_by_key(&key, |f| f.key)
1349 .ok()
1350 .is_some_and(|i| slot[i].lambda_cap),
1351 )
1352 }
1353
1354 fn stmt_kill_event(&self, stmt: JavaId, key: u32) -> Option<StmtKill<JavaId>> {
1355 let slot = self.stmt_kills.get(stmt.0 as usize)?.as_deref()?;
1356 Some(match slot.binary_search_by_key(&key, |f| f.key) {
1357 Ok(i) => match slot[i].kind {
1358 1 => StmtKill::Read,
1359 2 => StmtKill::Shadow,
1360 _ => StmtKill::DomWrite(slot[i].assign),
1361 },
1362 Err(_) => StmtKill::None,
1363 })
1364 }
1365
1366 fn stmt_mentions(&self, stmt: JavaId, key: u32) -> Option<bool> {
1367 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1368 Some(
1369 slot.binary_search_by_key(&key, |f| f.key)
1370 .ok()
1371 .is_some_and(|i| slot[i].refs > 0 || slot[i].shadow),
1372 )
1373 }
1374
1375 fn stmt_opaque(&self, stmt: JavaId) -> Option<bool> {
1376 self.stmt_opq.get(stmt.0 as usize)?.map(|f| f.opaque)
1377 }
1378
1379 fn fact_single_def(&self, key: u32) -> bool {
1382 match (&self.facts, self.names.name(key)) {
1383 (Some(f), n) => f.var(n).single_def,
1384 _ => false,
1385 }
1386 }
1387 fn fact_single_use(&self, key: u32) -> bool {
1388 match (&self.facts, self.names.name(key)) {
1389 (Some(f), n) => f.var(n).single_use,
1390 _ => false,
1391 }
1392 }
1393 fn fact_bool_valued(&self, key: u32) -> bool {
1394 match (&self.facts, self.names.name(key)) {
1395 (Some(f), n) => f.var(n).bool_valued,
1396 _ => false,
1397 }
1398 }
1399 fn fact_call_effect(&self, name: &str) -> Option<cure_engine::Effect> {
1400 self.facts
1401 .as_ref()
1402 .and_then(|f| {
1403 f.call_effects.get(name).map(|set| {
1404 set.iter().copied().fold(Effect::Pure, Effect::worst)
1405 })
1406 })
1407 }
1408
1409 fn node_index(&self, id: JavaId) -> usize {
1410 id.0 as usize
1411 }
1412 fn id_of_index(&self, idx: usize) -> JavaId {
1413 JavaId(idx as u32)
1414 }
1415
1416 fn is_opaque(&self, id: JavaId) -> bool {
1417 match self.data(id) {
1418 NodeData::Raw { .. } => true,
1420 NodeData::New { anon_raw: Some(_), .. } => true,
1423 _ => false,
1424 }
1425 }
1426
1427 fn own_effect(&self, id: JavaId) -> Effect {
1428 match self.data(id) {
1429 NodeData::Literal(_)
1430 | NodeData::This
1431 | NodeData::Super
1432 | NodeData::Paren
1433 | NodeData::Block
1434 | NodeData::Group
1435 | NodeData::Empty
1436 | NodeData::VarDecl { .. }
1437 | NodeData::Break { .. }
1438 | NodeData::Continue { .. }
1439 | NodeData::Try
1440 | NodeData::Catch { .. }
1441 | NodeData::Switch
1442 | NodeData::Case { .. }
1443 | NodeData::Label { .. }
1444 | NodeData::ExprStmt
1445 | NodeData::Ternary
1446 | NodeData::ArrayLit
1447 | NodeData::Lambda { .. }
1448 | NodeData::MethodRef { .. }
1449 | NodeData::InstanceOf { .. } => Effect::Pure,
1450 NodeData::VarRef { .. } => Effect::MayRead,
1451 NodeData::Member { .. } => Effect::MayThrow,
1452 NodeData::Index => Effect::MayThrow,
1453 NodeData::New { .. } | NodeData::NewArray { .. } => Effect::MayThrow,
1454 NodeData::Call => Effect::Unknown,
1455 NodeData::Assign { .. } => Effect::MayWrite,
1456 NodeData::Unary { op } => {
1457 if op.is_incdec() {
1458 Effect::MayWrite
1459 } else {
1460 Effect::Pure
1461 }
1462 }
1463 NodeData::Binary { op } => {
1464 if op.may_div_zero() {
1465 Effect::MayThrow
1466 } else {
1467 Effect::Pure
1468 }
1469 }
1470 NodeData::Cast { ty } => {
1471 if ty.is_ref() {
1472 Effect::MayThrow
1474 } else {
1475 Effect::Pure
1476 }
1477 }
1478 NodeData::For { .. } | NodeData::ForEach { .. } | NodeData::If => Effect::MayRead,
1479 NodeData::While | NodeData::DoWhile => Effect::MayRead,
1480 NodeData::Return | NodeData::Yield => Effect::MayThrow,
1481 NodeData::Throw => Effect::MayThrow,
1482 NodeData::Assert => Effect::MayThrow,
1483 NodeData::Synchronized => Effect::MayWrite,
1484 NodeData::Raw { .. } => Effect::Unknown,
1485 }
1486 }
1487
1488 fn is_bool(&self, id: JavaId) -> bool {
1489 match self.data(id) {
1490 NodeData::Literal(l) => matches!(l, Lit::Bool(_)),
1491 NodeData::Unary { op } => *op == UnOp::Not,
1492 NodeData::Binary { op } => {
1493 op.is_comparison() || matches!(op, BinOp::And | BinOp::Or)
1494 }
1495 NodeData::Ternary => {
1496 let ch = self.children(id);
1497 self.is_bool(ch[1]) && self.is_bool(ch[2])
1498 }
1499 NodeData::Paren => self.is_bool(self.children(id)[0]),
1500 NodeData::InstanceOf { .. } => true,
1501 NodeData::VarRef { .. } => self
1502 .var_types
1503 .get(id.0 as usize)
1504 .and_then(|t| t.as_ref())
1505 .is_some_and(|t| t.is_bool()),
1506 _ => false,
1507 }
1508 }
1509
1510 fn is_exact_int(&self, id: JavaId) -> bool {
1511 match self.data(id) {
1512 NodeData::Literal(l) => matches!(
1513 l,
1514 Lit::Int(_) | Lit::Long(_) | Lit::Char(_) | Lit::NumRaw { val: NumVal::Int(_) | NumVal::Long(_), .. }
1515 ),
1516 NodeData::VarRef { .. } => {
1517 self.var_types.get(id.0 as usize).and_then(|t| t.as_ref()).is_some_and(|t| t.is_integral())
1518 }
1519 NodeData::Binary { op } => {
1520 let ch = self.children(id);
1521 (op.is_arith() || op.is_comparison())
1522 && self.is_exact_int(ch[0])
1523 && self.is_exact_int(ch[1])
1524 }
1525 NodeData::Cast { ty } => ty.is_integral(),
1526 NodeData::Paren => self.is_exact_int(self.children(id)[0]),
1527 NodeData::Ternary => {
1528 let ch = self.children(id);
1529 self.is_exact_int(ch[1]) && self.is_exact_int(ch[2])
1530 }
1531 _ => false,
1532 }
1533 }
1534
1535 fn is_local_var(&self, id: JavaId) -> bool {
1536 matches!(self.data(id), NodeData::VarRef { .. }) && self.var_type(id).is_some()
1542 }
1543
1544 fn is_inferred_decl(&self, decl: JavaId) -> bool {
1545 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Var, .. })
1546 }
1547
1548 fn is_char_decl(&self, decl: JavaId) -> bool {
1549 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Char, .. })
1550 }
1551
1552 fn has_pattern_bindings(&self, root: JavaId) -> bool {
1553 let mut stack = vec![root];
1554 while let Some(n) = stack.pop() {
1555 if let NodeData::InstanceOf { bind: Some(_), .. } = self.data(n) {
1556 return true;
1557 }
1558 for &c in self.children(n) {
1559 stack.push(c);
1560 }
1561 }
1562 false
1563 }
1564
1565 fn is_long_decl(&self, decl: JavaId) -> bool {
1566 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Long, .. })
1567 }
1568 fn is_wide_decl(&self, decl: JavaId) -> bool {
1569 !matches!(
1572 self.data(decl),
1573 NodeData::VarDecl { ty: JType::Byte | JType::Short | JType::Char, .. }
1574 )
1575 }
1576
1577 fn ternary_merge_ok(&self, target: Option<JavaId>, branches: &[JavaId]) -> bool {
1578 if branches.iter().any(|&b| {
1582 matches!(
1583 self.data(b),
1584 NodeData::New {
1585 ty: JType::Ref(n),
1586 ..
1587 } if n.ends_with("<>")
1588 )
1589 }) {
1590 return false;
1591 }
1592 let dest = match target {
1593 Some(t) => self.var_type(t).cloned(),
1594 None => self.current_return_ty.clone(),
1595 };
1596 let narrow = matches!(dest, Some(JType::Short | JType::Byte | JType::Char));
1597 if !narrow {
1598 return true;
1599 }
1600 branches.iter().all(|&b| narrow_typed_expr(self, b))
1603 }
1604
1605 fn ternary_arg_unsound(&self, decl: JavaId, _value: JavaId) -> bool {
1606 matches!(
1609 self.data(decl),
1610 NodeData::VarDecl {
1611 ty: JType::Ref(n),
1612 ..
1613 } if n.contains('<')
1614 )
1615 }
1616
1617 fn propagation_cast_unsound(&self, decl: JavaId, value: JavaId) -> bool {
1618 if self.receiver_boxing_unsound(decl, value) {
1619 return true;
1620 }
1621 let declared = match self.data(decl) {
1626 NodeData::VarDecl { ty, .. } => ty,
1627 _ => return false,
1628 };
1629 let generic_or_array = match declared {
1630 JType::Ref(n) => n.contains('<'),
1631 JType::Array(_) => true,
1632 _ => false,
1633 };
1634 generic_or_array && !matches!(self.data(value), NodeData::Literal(_))
1635 }
1636
1637 fn receiver_propagation_unsound(
1638 &self,
1639 decl: JavaId,
1640 value: JavaId,
1641 use_parent: Option<JavaId>,
1642 use_at_head: bool,
1643 ) -> bool {
1644 let is_receiver = matches!(use_parent, Some(p) if {
1647 matches!(self.data(p), NodeData::Member { .. }) && use_at_head
1648 });
1649 if is_receiver {
1650 if self.receiver_boxing_unsound(decl, value) {
1652 return true;
1653 }
1654 if self.receiver_new_type_unsound(decl, value) {
1665 return true;
1666 }
1667 if matches!(
1673 self.data(decl),
1674 NodeData::VarDecl {
1675 ty: JType::Ref(n),
1676 ..
1677 } if n.contains('<')
1678 ) && matches!(self.data(value), NodeData::Call)
1679 {
1680 return true;
1681 }
1682 let raw_ref = match self.data(decl) {
1686 NodeData::VarDecl { ty: JType::Ref(n), .. } => {
1687 !n.contains('<')
1688 && n.rsplit('.').next().is_some_and(|base| {
1689 self.raw_generic_names.contains(base)
1690 })
1691 }
1692 _ => false,
1693 };
1694 if !raw_ref {
1695 return false;
1696 }
1697 match self.data(value) {
1699 NodeData::Call => true,
1700 NodeData::New { ty, .. } => {
1701 matches!(ty, JType::Ref(n) if n.contains('<'))
1702 }
1703 _ => false,
1704 }
1705 } else {
1706 if matches!(use_parent, Some(p) if {
1713 matches!(self.data(p), NodeData::Index) && use_at_head
1714 }) {
1715 return self.index_base_newarray_unsound(decl, value);
1716 }
1717 false
1718 }
1719 }
1720
1721 fn bin_op(&self, id: JavaId) -> Option<BinOp> {
1722 match self.data(id) {
1723 NodeData::Binary { op } => Some(*op),
1724 _ => None,
1725 }
1726 }
1727 fn un_op(&self, id: JavaId) -> Option<UnOp> {
1728 match self.data(id) {
1729 NodeData::Unary { op } => Some(*op),
1730 _ => None,
1731 }
1732 }
1733 fn literal(&self, id: JavaId) -> Option<LitRef<'_>> {
1734 match self.data(id) {
1735 NodeData::Literal(l) => Some(match l {
1736 Lit::Bool(b) => LitRef::Bool(*b),
1737 Lit::Int(v) => LitRef::Int(*v),
1738 Lit::Long(v) => LitRef::Long(*v),
1739 Lit::Float(v) => LitRef::Float(*v),
1740 Lit::Double(v) => LitRef::Double(*v),
1741 Lit::Char(c) => LitRef::Char(*c),
1742 Lit::Str(s) => LitRef::Str(s),
1743 Lit::TextBlock(_) => return None,
1749 Lit::NumRaw { val, .. } => match val {
1750 NumVal::Int(v) => LitRef::Int(*v),
1751 NumVal::Long(v) => LitRef::Long(*v),
1752 NumVal::Float(v) => LitRef::Float(*v),
1753 NumVal::Double(v) => LitRef::Double(*v),
1754 },
1755 Lit::Null => LitRef::Null,
1756 }),
1757 _ => None,
1758 }
1759 }
1760 fn var_name(&self, id: JavaId) -> Option<&str> {
1761 match self.data(id) {
1762 NodeData::VarRef { name } => Some(self.sn(*name)),
1763 NodeData::VarDecl { name, .. } => Some(name),
1764 NodeData::ForEach { name, .. } | NodeData::Catch { name, .. } => Some(name),
1765 _ => None,
1766 }
1767 }
1768 fn member_name(&self, id: JavaId) -> Option<&str> {
1769 match self.data(id) {
1770 NodeData::Member { name } => Some(self.sn(*name)),
1771 NodeData::MethodRef { name } => Some(self.sn(*name)),
1772 _ => None,
1773 }
1774 }
1775 fn double_not_sound(&self, _operand: JavaId) -> bool {
1776 true
1780 }
1781
1782 fn var_cast_position_ok(&self, value: JavaId, typed: JavaId) -> bool {
1783 if !matches!(self.data(value), NodeData::VarRef { .. }) {
1785 return true;
1786 }
1787 let Some(d) = self.var_type(value).cloned() else {
1791 return true;
1792 };
1793 let t = match self.data(typed) {
1794 NodeData::Cast { ty } => ty.clone(),
1795 NodeData::InstanceOf { ty, .. } => ty.clone(),
1796 _ => return true,
1797 };
1798 if d == t {
1799 return true;
1800 }
1801 let obj = |ty: &JType| {
1805 matches!(ty, JType::Ref(r) if r == "java.lang.Object" || r == "Object")
1806 };
1807 obj(&d) || obj(&t)
1808 }
1809 fn is_field_name(&self, name: &str) -> bool {
1810 self.field_types.contains_key(name)
1811 }
1812 fn payload_equal(&self, a: JavaId, b: JavaId) -> bool {
1813 if let (NodeData::Literal(x), NodeData::Literal(y)) = (self.data(a), self.data(b)) {
1816 return lit_value_eq(x, y);
1817 }
1818 self.data(a) == self.data(b)
1819 }
1820 fn assign_op(&self, id: JavaId) -> Option<BinOp> {
1821 match self.data(id) {
1822 NodeData::Assign { op } => *op,
1823 _ => None,
1824 }
1825 }
1826
1827 fn prepare(&mut self, root: JavaId) {
1831
1832 if cnt_prepare_on() {
1833 PREPARE_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1834 PREPARE_NODES.fetch_add(self.nodes.len(), std::sync::atomic::Ordering::Relaxed);
1835 }
1836 let n_nodes = self.nodes.len();
1840 let key_slots = self.names.key_slots();
1841 if key_slots < n_nodes {
1842 let names: Vec<Option<String>> = (key_slots..n_nodes)
1843 .map(|i| self.var_name(JavaId(i as u32)).map(|n| n.to_string()))
1844 .collect();
1845 self.names.extend_owned(key_slots, &names);
1846 }
1847 self.string_identity.set(None);
1849 self.events.resize(n_nodes);
1860 self.stmt_facts.resize_with(n_nodes, || None);
1861 self.stmt_kills.resize_with(n_nodes, || None);
1862 self.stmt_opq.resize_with(n_nodes, || None);
1863 if self.events.is_clean() {
1864 if self.prepared_root == Some(root) {
1865 return;
1866 }
1867 self.rebuild_var_types(root);
1870 self.prepared_root = Some(root);
1871 return;
1872 }
1873 self.prepared_root = Some(root);
1874 self.events.mark_clean();
1875 for sr in cure_tree::stmt_roots(self, root) {
1876 let idx = sr.0 as usize;
1877 if self.events.slot(idx).is_none() {
1878 if let Some(events) = cure_tree::collect_region_events(self, sr) {
1879 self.events.put(idx, events);
1880 }
1881 }
1882 if self.stmt_facts.get(idx).map_or(true, |f| f.is_none()) {
1883 let facts = self.collect_stmt_facts(sr);
1884 self.stmt_facts[idx] = Some(facts);
1885 }
1886 if self.stmt_kills.get(idx).map_or(true, |f| f.is_none()) {
1887 let kills = self.collect_stmt_kill_facts(sr);
1888 self.stmt_kills[idx] = Some(kills);
1889 }
1890 if self.stmt_opq.get(idx).map_or(true, |f| f.is_none()) {
1891 if let Some(opq) = self.stmt_opaque_fact(sr) {
1892 self.stmt_opq[idx] = Some(opq);
1893 }
1894 }
1895 }
1896 let effects = cure_tree::rebuild_effects(self, n_nodes);
1899 self.effect_cache = effects;
1900 self.rebuild_var_types(root);
1901 }
1902}
1903
1904impl JavaAst {
1905 fn collect_stmt_facts(&self, root: JavaId) -> Box<[StmtFact]> {
1909 let mut counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1910 let mut caps: std::collections::HashSet<u32> = std::collections::HashSet::new();
1911 let mut shadows: std::collections::HashSet<u32> = std::collections::HashSet::new();
1912 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
1913 while let Some((id, in_lambda)) = stack.pop() {
1914 let node = &self.nodes[id.0 as usize];
1915 let is_lambda = matches!(node.data, NodeData::Lambda { .. });
1916 let is_named_decl = matches!(
1917 node.data,
1918 NodeData::VarDecl { .. } | NodeData::ForEach { .. } | NodeData::Catch { .. }
1919 );
1920 if matches!(node.data, NodeData::VarRef { .. }) {
1921 if let Some(key) = self.names.var_key(id.0 as usize) {
1922 *counts.entry(key).or_insert(0) += 1;
1923 if in_lambda {
1924 caps.insert(key);
1925 }
1926 }
1927 } else if is_named_decl {
1928 if let Some(key) = self.names.var_key(id.0 as usize) {
1929 shadows.insert(key);
1930 }
1931 }
1932 let child_in_lambda = in_lambda || is_lambda;
1933 for &c in node.children.as_slice() {
1934 stack.push((c, child_in_lambda));
1935 }
1936 }
1937 let mut keys: std::collections::HashSet<u32> = counts.keys().copied().collect();
1938 keys.extend(shadows.iter().copied());
1939 let mut facts: Vec<StmtFact> = keys
1940 .into_iter()
1941 .map(|key| StmtFact {
1942 key,
1943 refs: counts.get(&key).copied().unwrap_or(0),
1944 lambda_cap: caps.contains(&key),
1945 shadow: shadows.contains(&key),
1946 })
1947 .collect();
1948 facts.sort_unstable_by_key(|f| f.key);
1949 facts.into_boxed_slice()
1950 }
1951
1952 fn stmt_opaque_fact(&self, root: JavaId) -> Option<StmtOpq> {
1954 let mut stack = vec![root];
1955 while let Some(id) = stack.pop() {
1956 if self.is_opaque(id) {
1957 return Some(StmtOpq { opaque: true });
1958 }
1959 for &c in self.nodes[id.0 as usize].children.as_slice() {
1960 stack.push(c);
1961 }
1962 }
1963 Some(StmtOpq { opaque: false })
1964 }
1965
1966 fn collect_stmt_kill_facts(&self, root: JavaId) -> Box<[StmtKillFact]> {
1969 let mut facts: Vec<StmtKillFact> = cure_engine::rules::collect_stmt_kill_events(self, root)
1970 .into_iter()
1971 .map(|(key, event)| match event {
1972 StmtKill::None => unreachable!("collector only records real events"),
1973 StmtKill::Read => StmtKillFact {
1974 key,
1975 kind: 1,
1976 assign: JavaId(0),
1977 },
1978 StmtKill::Shadow => StmtKillFact {
1979 key,
1980 kind: 2,
1981 assign: JavaId(0),
1982 },
1983 StmtKill::DomWrite(assign) => StmtKillFact {
1984 key,
1985 kind: 3,
1986 assign,
1987 },
1988 })
1989 .collect();
1990 facts.sort_unstable_by_key(|f| f.key);
1991 facts.into_boxed_slice()
1992 }
1993
1994 pub(crate) fn rebuild_var_types(&mut self, root: JavaId) {
1996 self.var_types.clear();
1998 self.var_types.resize(self.nodes.len(), None);
1999 #[derive(Clone)]
2000 enum Frame {
2001 Enter(JavaId),
2002 Exit,
2003 PopScope,
2004 Bind(String, JType),
2005 }
2006 let mut base: HashMap<String, JType> = HashMap::new();
2007 for (n, t) in &self.param_scope {
2008 base.insert(n.clone(), t.clone());
2009 }
2010 let mut scopes: Vec<HashMap<String, JType>> = vec![base];
2011 let mut stack = vec![Frame::Enter(root)];
2012 while let Some(f) = stack.pop() {
2013 match f {
2014 Frame::Enter(id) => {
2015 let node = &self.nodes[id.0 as usize];
2016 enum Action {
2017 None,
2018 Scope,
2019 Bind(String, JType),
2020 }
2021 let action = match &node.data {
2022 NodeData::Block | NodeData::For { .. } | NodeData::Try => Action::Scope,
2023 NodeData::VarDecl { name, ty, .. } => {
2024 scopes.last_mut().unwrap().insert(name.clone(), ty.clone());
2025 Action::None
2026 }
2027 NodeData::VarRef { name } => {
2028 if let Some(t) = scopes
2029 .iter()
2030 .rev()
2031 .find_map(|s| s.get(self.sn(*name)))
2032 .cloned()
2033 {
2034 self.var_types[id.0 as usize] = Some(t);
2035 }
2036 Action::None
2037 }
2038 NodeData::Catch { name, .. } => {
2039 Action::Bind(name.clone(), JType::Var)
2040 }
2041 NodeData::ForEach { name, ty } => {
2042 Action::Bind(name.clone(), ty.clone())
2043 }
2044 _ => Action::None,
2045 };
2046 stack.push(Frame::Exit);
2049 if matches!(action, Action::Scope | Action::Bind(_, _)) {
2050 scopes.push(HashMap::new());
2051 stack.push(Frame::PopScope);
2052 }
2053 for &c in node.children.as_slice().iter().rev() {
2054 stack.push(Frame::Enter(c));
2055 }
2056 if let Action::Bind(n, t) = action {
2057 stack.push(Frame::Bind(n, t));
2058 }
2059 }
2060 Frame::Bind(name, ty) => {
2061 scopes.last_mut().unwrap().insert(name, ty);
2062 }
2063 Frame::PopScope => {
2064 scopes.pop();
2065 }
2066 Frame::Exit => {}
2067 }
2068 }
2069 }
2070}
2071
2072impl JavaAst {
2084 fn receiver_new_type_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2096 let NodeData::New { ty, .. } = self.data(value) else {
2097 return false;
2098 };
2099 let decl_ty = match self.data(decl) {
2100 NodeData::VarDecl { ty, .. } => ty,
2101 _ => return false,
2102 };
2103 if matches!(decl_ty, JType::Var) {
2106 return false;
2107 }
2108 let name = match ty {
2109 JType::Ref(n) => n.as_str(),
2110 _ => return true,
2112 };
2113 if name.ends_with("<>") {
2115 return true;
2116 }
2117 match decl_ty {
2118 JType::Ref(d) => type_base_name(d) != type_base_name(name),
2119 _ => true,
2121 }
2122 }
2123
2124 fn index_base_newarray_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2130 let NodeData::NewArray { ty, dims, .. } = self.data(value) else {
2131 return false;
2132 };
2133 let decl_ty = match self.data(decl) {
2134 NodeData::VarDecl { ty, .. } => ty,
2135 _ => return false,
2136 };
2137 if matches!(decl_ty, JType::Var) {
2138 return false;
2139 }
2140 let mut elem: &JType = decl_ty;
2142 let mut nest = 0u16;
2143 while let JType::Array(inner) = elem {
2144 elem = inner.as_ref();
2145 nest += 1;
2146 }
2147 nest != *dims || *elem != *ty
2148 }
2149
2150 fn receiver_boxing_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2151 let declared_ref = match self.data(decl) {
2152 NodeData::VarDecl { ty, .. } => ty.is_ref(),
2153 _ => false,
2154 };
2155 if !declared_ref {
2156 return false;
2157 }
2158 match self.data(value) {
2159 NodeData::Literal(
2160 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_) | Lit::Bool(_)
2161 | Lit::Char(_),
2162 ) => true,
2163 NodeData::VarRef { .. } => matches!(
2165 self.var_type(value),
2166 Some(
2167 JType::Int
2168 | JType::Long
2169 | JType::Byte
2170 | JType::Short
2171 | JType::Char
2172 | JType::Float
2173 | JType::Double
2174 | JType::Bool
2175 )
2176 ),
2177 NodeData::Unary { .. } | NodeData::Binary { .. } => true,
2179 NodeData::Ternary => true,
2183 NodeData::Cast { .. } => true,
2185 NodeData::InstanceOf { .. } => true,
2187 NodeData::Call | NodeData::Member { .. } | NodeData::Index => {
2193 match self.data(decl) {
2194 NodeData::VarDecl { ty: JType::Ref(n), .. } => {
2195 let boxed = [
2196 "Character", "Integer", "Long", "Boolean", "Double",
2197 "Float", "Byte", "Short", "Number", "AtomicInteger",
2198 "AtomicLong",
2199 ];
2200 n.rsplit('.').next().is_some_and(|b| boxed.contains(&b))
2201 }
2202 _ => false,
2203 }
2204 }
2205 _ => false,
2206 }
2207 }
2208
2209 pub fn intern_sym(&mut self, name: &str) -> Sym {
2212 Sym(self.names.intern(name))
2213 }
2214
2215 pub fn sn(&self, sym: Sym) -> &str {
2217 self.names.name(sym.0)
2218 }
2219
2220
2221
2222
2223}
2224
2225
2226pub fn unit_metrics(ast: &JavaAst, unit: &CompilationUnit) -> TreeMetrics {
2234 let mut total = TreeMetrics::default();
2235 for ty in &unit.types {
2236 metrics_of_type(ast, ty, &mut total);
2237 }
2238 total
2239}
2240
2241fn metrics_of_type(ast: &JavaAst, ty: &TypeDecl, total: &mut TreeMetrics) {
2242 for m in &ty.members {
2243 match m {
2244 Member::Method { body: Some(b), .. } | Member::Constructor { body: Some(b), .. } => {
2245 total.add(subtree_metrics(ast, *b));
2246 }
2247 Member::Initializer { body, .. } => {
2248 total.add(subtree_metrics(ast, *body));
2249 }
2250 Member::Type(t) => metrics_of_type(ast, t, total),
2251 _ => {}
2252 }
2253 }
2254}
2255
2256static ENV_CACHED: std::sync::OnceLock<(bool, bool)> = std::sync::OnceLock::new();
2257fn env_flags() -> (bool, bool) {
2258 *ENV_CACHED.get_or_init(|| {
2259 (
2260 std::env::var("CURE_CNT_PREPARE").is_ok(),
2261 std::env::var("CURE_VERIFY_EVENTS").is_ok(),
2262 )
2263 })
2264}
2265fn cnt_prepare_on() -> bool { env_flags().0 }
2266fn verify_events_on() -> bool { env_flags().1 }
2267
2268pub static PREPARE_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2269pub static PREPARE_NODES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2270
2271pub static COLLECT_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2272
2273#[cfg(test)]
2278mod tests {
2279 use super::*;
2280
2281 #[test]
2282 fn builder_smoke() {
2283 let mut a = JavaAst::new();
2284 let one = a.lit(Lit::Int(1));
2285 let x = a.var_decl("x", JType::Int, Some(one));
2286 let xv = a.var("x");
2287 let r = a.ret(Some(xv));
2288 let body = a.block(vec![x, r]);
2289 assert_eq!(a.kind(body), NodeKind::Block);
2290 assert_eq!(a.children(body).len(), 2);
2291 }
2292
2293 #[test]
2294 fn effect_aggregation() {
2295 let mut a = JavaAst::new();
2296 let f = a.plain_call("foo", vec![]);
2297 let r = a.ret(Some(f));
2298 let body = a.block(vec![r]);
2299 a.prepare(body);
2300 assert_eq!(a.effect(f), Effect::Unknown);
2301 assert_eq!(a.effect(r), Effect::Unknown);
2302 assert_eq!(a.effect(body), Effect::Unknown);
2303
2304 let mut b = JavaAst::new();
2305 let two = b.lit(Lit::Int(2));
2306 let three = b.lit(Lit::Int(3));
2307 let add = b.bin(BinOp::Add, two, three);
2308 let r2 = b.ret(Some(add));
2309 let body2 = b.block(vec![r2]);
2310 b.prepare(body2);
2311 assert_eq!(b.effect(add), Effect::Pure);
2312 assert_eq!(b.effect(r2), Effect::MayThrow);
2314 assert_eq!(b.effect(body2), Effect::MayThrow);
2315 }
2316
2317 #[test]
2318 fn var_type_resolution_and_shadowing() {
2319 let mut a = JavaAst::new();
2320 let one = a.lit(Lit::Int(1));
2322 let b2_decl = a.var_decl("b2", JType::Int, Some(one));
2323 let inner_b2 = a.var("b2");
2324 let inner_stmt = a.expr_stmt(inner_b2);
2325 let inner = a.block(vec![b2_decl, inner_stmt]);
2326 let tv = a.lit(Lit::Bool(true));
2327 let decl_b = a.var_decl("b", JType::Bool, Some(tv));
2328 let outer_b = a.var("b");
2329 let outer_stmt = a.expr_stmt(outer_b);
2330 let body = a.block(vec![decl_b, inner, outer_stmt]);
2331 a.prepare(body);
2332 assert!(a.is_bool(outer_b));
2333 assert!(a.var_type(inner_b2).is_some());
2335 }
2336
2337 #[test]
2338 fn raw_and_foreach_semantics() {
2339 let mut a = JavaAst::new();
2340 let one = a.lit(Lit::Int(1));
2342 let dx = a.var_decl("x", JType::Int, Some(one));
2343 let r = a.raw("broken ~!@ code");
2344 let sv = a.var("s");
2345 let call = a.method_call(sv, "len", vec![]);
2346 let es = a.expr_stmt(call);
2347 let body = a.block(vec![es]);
2348 let lst = a.var("list");
2349 let fe = a.for_each("s", JType::Ref("String".into()), lst, body);
2350 let all = a.block(vec![dx, r, fe]);
2351 a.prepare(all);
2352 assert_eq!(a.kind(r), NodeKind::Raw);
2353 assert_eq!(a.effect(r), Effect::Unknown);
2354 assert!(a.var_type(sv).is_some()); }
2356
2357 #[test]
2358 fn num_raw_literal() {
2359 let mut a = JavaAst::new();
2360 let l = a.lit(Lit::NumRaw {
2361 text: "0x1F".into(),
2362 val: NumVal::Int(31),
2363 });
2364 a.prepare(l);
2365 assert!(a.is_exact_int(l));
2366 assert!(matches!(a.literal(l), Some(LitRef::Int(31))));
2367 }
2368}