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 inline_methods: HashMap<String, (Vec<String>, JavaId, String)>,
342 string_identity: std::cell::Cell<Option<(JavaId, bool)>>,
345 pub unit_string_identity: bool,
350 names: NameTable,
354 events: EventStore<JavaId, u32>,
358 stmt_facts: Vec<Option<Box<[StmtFact]>>>,
360 stmt_kills: Vec<Option<Box<[StmtKillFact]>>>,
362 stmt_opq: Vec<Option<StmtOpq>>,
364 prepared_root: Option<JavaId>,
369}
370
371#[derive(Clone, PartialEq, Debug)]
377pub struct Param {
378 pub mods: String,
380 pub ty: JType,
381 pub varargs: bool,
383 pub name: String,
384}
385
386#[derive(Clone, PartialEq, Debug)]
388pub struct Declarator {
389 pub name: String,
390 pub extra_dims: u16,
392 pub init: Option<JavaId>,
394}
395
396#[derive(Clone, PartialEq, Debug)]
398pub enum Member {
399 Field { mods: String, ty: JType, declarators: Vec<Declarator> },
400 Method {
401 mods: String,
402 ty_params: String,
404 ret: JType,
405 name: String,
406 params: Vec<Param>,
407 throws: Vec<String>,
408 body: Option<JavaId>,
410 },
411 Constructor {
412 mods: String,
413 ty_params: String,
414 name: String,
415 params: Vec<Param>,
416 throws: Vec<String>,
417 body: Option<JavaId>,
418 compact: bool,
420 },
421 Initializer { is_static: bool, body: JavaId },
423 Type(Box<TypeDecl>),
425 Raw(String),
427}
428
429#[derive(Clone, Copy, PartialEq, Eq, Debug)]
430pub enum TypeKind {
431 Class,
432 Interface,
433 Enum,
434 Record,
435 Annotation,
436}
437
438#[derive(Clone, PartialEq, Debug)]
440pub struct TypeDecl {
441 pub kind: TypeKind,
442 pub mods: String,
444 pub name: String,
445 pub ty_params: String,
447 pub header: String,
449 pub extends: Vec<String>,
450 pub implements: Vec<String>,
451 pub permits: Vec<String>,
453 pub enum_constants: Vec<String>,
455 pub members: Vec<Member>,
456}
457
458#[derive(Clone, PartialEq, Debug, Default)]
460pub struct CompilationUnit {
461 pub package: Option<String>,
463 pub package_annotations: String,
466 pub header_comments: String,
469 pub imports: Vec<String>,
471 pub types: Vec<TypeDecl>,
472 pub raws: Vec<String>,
474}
475
476#[derive(Clone, PartialEq, Eq, Debug)]
478pub struct ParseError {
479 pub line: usize,
480 pub col: usize,
481 pub message: String,
482}
483
484impl JavaAst {
489 pub fn new() -> Self {
490 Self::default()
491 }
492
493 pub fn clone_node(&mut self, id: JavaId, children: Vec<JavaId>) -> JavaId {
495 let data = self.nodes[id.0 as usize].data.clone();
496 self.push(data, children)
497 }
498
499 fn push(&mut self, data: NodeData, children: Vec<JavaId>) -> JavaId {
500 let id = JavaId(self.nodes.len() as u32);
501 self.nodes.push(Node { data, children: ChildList::from_vec(children) });
502 id
503 }
504
505 pub fn node(&self, id: JavaId) -> &Node {
506 &self.nodes[id.0 as usize]
507 }
508 pub fn data(&self, id: JavaId) -> &NodeData {
509 &self.node(id).data
510 }
511
512 pub fn kind(&self, id: JavaId) -> NodeKind {
514 match &self.nodes[id.0 as usize].data {
515 NodeData::Block | NodeData::Group => NodeKind::Block,
516 NodeData::Empty => NodeKind::Empty,
517 NodeData::Yield => NodeKind::Yield,
518 NodeData::ExprStmt => NodeKind::ExprStmt,
519 NodeData::VarDecl { .. } => NodeKind::VarDecl,
520 NodeData::Assign { .. } => NodeKind::Assign,
521 NodeData::If => NodeKind::If,
522 NodeData::While => NodeKind::While,
523 NodeData::DoWhile => NodeKind::DoWhile,
524 NodeData::For { .. } => NodeKind::For,
525 NodeData::ForEach { .. } => NodeKind::ForEach,
526 NodeData::Return => NodeKind::Return,
527 NodeData::Break { .. } => NodeKind::Break,
528 NodeData::Continue { .. } => NodeKind::Continue,
529 NodeData::Throw => NodeKind::Throw,
530 NodeData::Try => NodeKind::Try,
531 NodeData::Catch { .. } => NodeKind::Catch,
532 NodeData::Synchronized => NodeKind::Synchronized,
533 NodeData::Switch => NodeKind::Switch,
534 NodeData::Case { .. } => NodeKind::Case,
535 NodeData::Label { .. } => NodeKind::Label,
536 NodeData::Assert => NodeKind::Assert,
537 NodeData::Raw { .. } => NodeKind::Raw,
538 NodeData::Binary { .. } => NodeKind::Binary,
539 NodeData::Unary { .. } => NodeKind::Unary,
540 NodeData::Call => NodeKind::Call,
541 NodeData::Member { .. } => NodeKind::Member,
542 NodeData::Index => NodeKind::Index,
543 NodeData::Cast { .. } => NodeKind::Cast,
544 NodeData::Paren => NodeKind::Paren,
545 NodeData::Ternary => NodeKind::Ternary,
546 NodeData::New { .. } => NodeKind::New,
547 NodeData::NewArray { .. } => NodeKind::NewArray,
548 NodeData::ArrayLit => NodeKind::ArrayLit,
549 NodeData::VarRef { .. } => NodeKind::VarRef,
550 NodeData::Literal(_) => NodeKind::Literal,
551 NodeData::This => NodeKind::This,
552 NodeData::Super => NodeKind::This,
553 NodeData::InstanceOf { .. } => NodeKind::InstanceOf,
554 NodeData::Lambda { .. } => NodeKind::Lambda,
555 NodeData::MethodRef { .. } => NodeKind::MethodRef,
556 }
557 }
558 pub fn node_count(&self) -> usize {
560 self.nodes.len()
561 }
562
563 pub fn children(&self, id: JavaId) -> &[JavaId] {
564 self.nodes[id.0 as usize].children.as_slice()
565 }
566
567 pub fn block(&mut self, stmts: Vec<JavaId>) -> JavaId {
570 self.push(NodeData::Block, stmts)
571 }
572 pub fn empty(&mut self) -> JavaId {
573 self.push(NodeData::Empty, vec![])
574 }
575 pub fn expr_stmt(&mut self, e: JavaId) -> JavaId {
576 self.push(NodeData::ExprStmt, vec![e])
577 }
578 pub fn var_decl(&mut self, name: &str, ty: JType, init: Option<JavaId>) -> JavaId {
579 self.var_decl_final(name, ty, init, false)
580 }
581 pub fn var_decl_final(
585 &mut self,
586 name: &str,
587 ty: JType,
588 init: Option<JavaId>,
589 final_mod: bool,
590 ) -> JavaId {
591 self.push(
592 NodeData::VarDecl {
593 name: name.into(),
594 ty,
595 final_mod,
596 },
597 init.into_iter().collect(),
598 )
599 }
600 pub fn assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
601 self.push(NodeData::Assign { op: None }, vec![target, value])
602 }
603 pub fn assign_op(&mut self, op: BinOp, target: JavaId, value: JavaId) -> JavaId {
604 self.push(NodeData::Assign { op: Some(op) }, vec![target, value])
605 }
606 pub fn if_(&mut self, cond: JavaId, then: JavaId, els: Option<JavaId>) -> JavaId {
607 let mut ch = vec![cond, then];
608 ch.extend(els);
609 self.push(NodeData::If, ch)
610 }
611 pub fn while_(&mut self, cond: JavaId, body: JavaId) -> JavaId {
612 self.push(NodeData::While, vec![cond, body])
613 }
614 pub fn do_while(&mut self, body: JavaId, cond: JavaId) -> JavaId {
615 self.push(NodeData::DoWhile, vec![body, cond])
616 }
617 pub fn for_(
618 &mut self,
619 inits: Vec<JavaId>,
620 cond: Option<JavaId>,
621 steps: Vec<JavaId>,
622 body: JavaId,
623 ) -> JavaId {
624 let ni = inits.len();
625 let ns = steps.len();
626 let mut ch = inits;
627 let has_cond = cond.is_some();
628 ch.extend(cond);
629 ch.extend(steps);
630 ch.push(body);
631 self.push(
632 NodeData::For {
633 inits: ni as u8,
634 has_cond,
635 steps: ns as u8,
636 },
637 ch,
638 )
639 }
640 pub fn for_each(&mut self, name: &str, ty: JType, iterable: JavaId, body: JavaId) -> JavaId {
641 self.push(
642 NodeData::ForEach {
643 name: name.into(),
644 ty,
645 },
646 vec![iterable, body],
647 )
648 }
649 pub fn yield_(&mut self, expr: JavaId) -> JavaId {
650 self.push(NodeData::Yield, vec![expr])
651 }
652 pub fn ret(&mut self, value: Option<JavaId>) -> JavaId {
653 self.push(NodeData::Return, value.into_iter().collect())
654 }
655 pub fn break_(&mut self, label: Option<&str>) -> JavaId {
656 self.push(
657 NodeData::Break {
658 label: label.map(Into::into),
659 },
660 vec![],
661 )
662 }
663 pub fn continue_(&mut self, label: Option<&str>) -> JavaId {
664 self.push(
665 NodeData::Continue {
666 label: label.map(Into::into),
667 },
668 vec![],
669 )
670 }
671 pub fn throw(&mut self, e: JavaId) -> JavaId {
672 self.push(NodeData::Throw, vec![e])
673 }
674 pub fn try_(
675 &mut self,
676 resources: Vec<JavaId>,
677 try_block: JavaId,
678 catches: Vec<JavaId>,
679 finally: Option<JavaId>,
680 ) -> JavaId {
681 let mut ch = resources;
682 ch.push(try_block);
683 ch.extend(catches);
684 ch.extend(finally);
685 self.push(NodeData::Try, ch)
686 }
687 pub fn catch_(&mut self, ty_raw: &str, name: &str, block: JavaId) -> JavaId {
688 self.push(
689 NodeData::Catch {
690 ty_raw: ty_raw.into(),
691 name: name.into(),
692 },
693 vec![block],
694 )
695 }
696 pub fn synchronized(&mut self, lock: JavaId, block: JavaId) -> JavaId {
697 self.push(NodeData::Synchronized, vec![lock, block])
698 }
699 pub fn switch_(&mut self, subject: JavaId, cases: Vec<JavaId>) -> JavaId {
700 let mut ch = vec![subject];
701 ch.extend(cases);
702 self.push(NodeData::Switch, ch)
703 }
704 pub fn case_(
705 &mut self,
706 labels: Vec<JavaId>,
707 is_default: bool,
708 arrow: bool,
709 stmts: Vec<JavaId>,
710 ) -> JavaId {
711 let n = labels.len() as u16;
712 let mut ch = labels;
713 ch.extend(stmts);
714 self.push(
715 NodeData::Case {
716 labels: n,
717 is_default,
718 arrow,
719 },
720 ch,
721 )
722 }
723 pub fn label(&mut self, name: &str, stmt: JavaId) -> JavaId {
724 self.push(NodeData::Label { name: name.into() }, vec![stmt])
725 }
726 pub fn assert_(&mut self, cond: JavaId, msg: Option<JavaId>) -> JavaId {
727 let mut ch = vec![cond];
728 ch.extend(msg);
729 self.push(NodeData::Assert, ch)
730 }
731 pub fn raw(&mut self, text: &str) -> JavaId {
732 self.push(NodeData::Raw { text: text.into() }, vec![])
733 }
734
735 pub fn bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
738 self.push(NodeData::Binary { op }, vec![l, r])
739 }
740 pub fn un(&mut self, op: UnOp, e: JavaId) -> JavaId {
741 self.push(NodeData::Unary { op }, vec![e])
742 }
743 pub fn call(&mut self, callee: JavaId, args: Vec<JavaId>) -> JavaId {
744 let mut ch = vec![callee];
745 ch.extend(args);
746 self.push(NodeData::Call, ch)
747 }
748 pub fn member(&mut self, obj: JavaId, name: &str) -> JavaId {
749 let sym = self.intern_sym(name);
750 self.push(NodeData::Member { name: sym }, vec![obj])
751 }
752 pub fn index(&mut self, arr: JavaId, idx: JavaId) -> JavaId {
753 self.push(NodeData::Index, vec![arr, idx])
754 }
755 pub fn cast(&mut self, ty: JType, e: JavaId) -> JavaId {
756 self.push(NodeData::Cast { ty }, vec![e])
757 }
758 pub fn paren(&mut self, e: JavaId) -> JavaId {
759 self.push(NodeData::Paren, vec![e])
760 }
761 pub fn ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
762 self.push(NodeData::Ternary, vec![c, a, b])
763 }
764 pub fn new_(&mut self, ty: JType, args: Vec<JavaId>) -> JavaId {
765 self.push(NodeData::New { ty, anon_raw: None }, args)
766 }
767 pub fn new_anon(&mut self, ty: JType, args: Vec<JavaId>, body_raw: String) -> JavaId {
768 self.push(
769 NodeData::New {
770 ty,
771 anon_raw: Some(body_raw),
772 },
773 args,
774 )
775 }
776 pub fn new_array(
777 &mut self,
778 ty: JType,
779 dims: u16,
780 sized: u16,
781 sizes: Vec<JavaId>,
782 init: Option<JavaId>,
783 ) -> JavaId {
784 let mut ch = sizes;
785 ch.extend(init);
786 self.push(NodeData::NewArray { ty, dims, sized }, ch)
787 }
788 pub fn group(&mut self, stmts: Vec<JavaId>) -> JavaId {
790 self.push(NodeData::Group, stmts)
791 }
792 pub fn array_lit(&mut self, elems: Vec<JavaId>) -> JavaId {
793 self.push(NodeData::ArrayLit, elems)
794 }
795 pub fn var(&mut self, name: &str) -> JavaId {
796 let sym = self.intern_sym(name);
797 self.push(NodeData::VarRef { name: sym }, vec![])
798 }
799 pub fn lit(&mut self, l: Lit) -> JavaId {
800 self.push(NodeData::Literal(l), vec![])
801 }
802 fn build_int_impl(&mut self, v: i64, long: bool) -> JavaId {
803 if long {
804 self.lit(Lit::Long(v))
805 } else {
806 self.lit(Lit::Int(v))
807 }
808 }
809 pub fn this(&mut self) -> JavaId {
810 self.push(NodeData::This, vec![])
811 }
812 pub fn super_(&mut self) -> JavaId {
813 self.push(NodeData::Super, vec![])
814 }
815 pub fn instance_of(&mut self, e: JavaId, ty: JType, bind: Option<&str>) -> JavaId {
816 self.push(
817 NodeData::InstanceOf {
818 ty,
819 bind: bind.map(Into::into),
820 },
821 vec![e],
822 )
823 }
824 pub fn lambda(&mut self, params_raw: &str, body: JavaId) -> JavaId {
825 self.push(
826 NodeData::Lambda {
827 params_raw: params_raw.into(),
828 },
829 vec![body],
830 )
831 }
832 pub fn method_ref(&mut self, receiver: JavaId, name: &str) -> JavaId {
833 let sym = self.intern_sym(name);
834 self.push(NodeData::MethodRef { name: sym }, vec![receiver])
835 }
836
837 pub fn method_call(&mut self, obj: JavaId, name: &str, args: Vec<JavaId>) -> JavaId {
841 let m = self.member(obj, name);
842 self.call(m, args)
843 }
844 pub fn plain_call(&mut self, name: &str, args: Vec<JavaId>) -> JavaId {
846 let f = self.var(name);
847 self.call(f, args)
848 }
849
850 pub fn var_type(&self, id: JavaId) -> Option<&JType> {
852 self.var_types.get(id.0 as usize).and_then(|t| t.as_ref())
853 }
854
855 pub fn any_string_identity(&self, roots: &[JavaId]) -> bool {
861 roots.iter().any(|&r| {
862 let hit = Self::scan_string_identity(self, r);
863 if hit {
864 self.string_identity.set(Some((r, true)));
865 }
866 hit
867 })
868 }
869
870 pub fn has_string_identity_compare(&self, root: JavaId) -> bool {
871 if let Some((r, v)) = self.string_identity.get() {
872 if r == root {
873 return v;
874 }
875 }
876 let v = Self::scan_string_identity(self, root);
877 self.string_identity.set(Some((root, v)));
878 v
879 }
880
881 fn scan_string_identity(&self, root: JavaId) -> bool {
882 let mut stack = vec![root];
883 while let Some(id) = stack.pop() {
884 if let NodeData::Binary { op: BinOp::Eq | BinOp::Ne } = self.data(id) {
885 let ch = self.children(id);
886 if ch.len() == 2
887 && self.expr_maybe_string(ch[0])
888 && self.expr_maybe_string(ch[1])
889 {
890 return true;
891 }
892 }
893 for &c in self.children(id) {
894 stack.push(c);
895 }
896 }
897 false
898 }
899
900 fn expr_maybe_string(&self, id: JavaId) -> bool {
902 match self.data(id) {
903 NodeData::Literal(Lit::Str(_)) => true,
904 NodeData::Literal(_) | NodeData::This | NodeData::Super
906 | NodeData::NewArray { .. } | NodeData::ArrayLit | NodeData::InstanceOf { .. } => false,
907 NodeData::VarRef { .. } => self.var_type(id).map_or(true, Self::type_maybe_string),
908 NodeData::Cast { ty } => Self::type_maybe_string(ty),
909 NodeData::Paren => self
910 .children(id)
911 .first()
912 .map_or(true, |&c| self.expr_maybe_string(c)),
913 NodeData::Binary { op } if *op == BinOp::Add => {
915 self.children(id).iter().any(|&c| self.expr_maybe_string(c))
916 }
917 NodeData::Binary { .. } => false,
918 NodeData::Ternary => self
919 .children(id)
920 .get(1..)
921 .map_or(true, |cs| cs.iter().any(|&c| self.expr_maybe_string(c))),
922 _ => true,
924 }
925 }
926
927 fn type_maybe_string(t: &JType) -> bool {
929 match t {
930 JType::Bool | JType::Byte | JType::Short | JType::Int | JType::Long
931 | JType::Char | JType::Float | JType::Double | JType::Void
932 | JType::Array(_) => false, JType::Var => true, JType::Ref(n) => {
935 let base = n.split('<').next().unwrap_or(n).trim();
936 let last = base.rsplit('.').next().unwrap_or(base);
937 matches!(
938 last,
939 "String" | "Object" | "CharSequence" | "Comparable" | "Serializable"
940 ) || n.contains('<') || (last.len() <= 2 && last.chars().next().is_some_and(|c| c.is_uppercase()))
943 }
944 }
945 }
946
947
948 pub fn set_param_scope(&mut self, params: &[(String, JType)]) {
950 self.param_scope = params.to_vec();
951 }
952
953 pub fn is_param_name(&self, name: &str) -> bool {
956 self.param_scope.iter().any(|(n, _)| n == name)
957 }
958
959 pub fn set_facts(&mut self, facts: JavaFacts) {
964 self.facts = Some(facts);
965 }
966 pub fn clear_facts(&mut self) {
968 self.facts = None;
969 }
970
971 pub fn name_of_key(&self, k: u32) -> Option<String> {
972 Some(self.names.name(k).to_string())
973 }
974 pub fn clear_param_scope(&mut self) {
975 self.param_scope.clear();
976 }
977}
978
979fn type_base_name(n: &str) -> &str {
987 let no_args = match n.find('<') {
988 Some(i) => &n[..i],
989 None => n,
990 };
991 no_args.rsplit('.').next().unwrap_or(no_args)
992}
993
994fn fold_i64(op: BinOp, a: i64, b: i64) -> Option<i64> {
1001 use BinOp::*;
1002 Some(match op {
1003 Add => a.wrapping_add(b),
1004 Sub => a.wrapping_sub(b),
1005 Mul => a.wrapping_mul(b),
1006 Div => {
1007 if b == 0 {
1008 return None;
1009 }
1010 a.wrapping_div(b)
1011 }
1012 Rem => {
1013 if b == 0 {
1014 return None;
1015 }
1016 a.wrapping_rem(b)
1017 }
1018 Shl => a.wrapping_shl((b as u64 & 63) as u32),
1019 Shr => a.wrapping_shr((b as u64 & 63) as u32),
1020 UShr => ((a as u64).wrapping_shr((b as u64 & 63) as u32)) as i64,
1021 BitAnd => a & b,
1022 BitXor => a ^ b,
1023 BitOr => a | b,
1024 _ => return None,
1025 })
1026}
1027
1028fn fold_i32(op: BinOp, a: i64, b: i64) -> Option<i64> {
1030 use BinOp::*;
1031 let (x, y) = (a as i32, b as i32);
1032 Some(match op {
1033 Add => x.wrapping_add(y) as i64,
1034 Sub => x.wrapping_sub(y) as i64,
1035 Mul => x.wrapping_mul(y) as i64,
1036 Div => {
1037 if y == 0 {
1038 return None;
1039 }
1040 x.wrapping_div(y) as i64
1041 }
1042 Rem => {
1043 if y == 0 {
1044 return None;
1045 }
1046 x.wrapping_rem(y) as i64
1047 }
1048 Shl => x.wrapping_shl((y as u32) & 31) as i64,
1049 Shr => x.wrapping_shr((y as u32) & 31) as i64,
1050 UShr => (x as u32).wrapping_shr((y as u32) & 31) as i64,
1051 BitAnd => (x & y) as i64,
1052 BitXor => (x ^ y) as i64,
1053 BitOr => (x | y) as i64,
1054 _ => return None,
1055 })
1056}
1057
1058fn cmp_i64(op: BinOp, x: i64, y: i64) -> Option<bool> {
1060 use BinOp::*;
1061 Some(match op {
1062 Lt => x < y,
1063 Le => x <= y,
1064 Gt => x > y,
1065 Ge => x >= y,
1066 Eq => x == y,
1067 Ne => x != y,
1068 _ => return None,
1069 })
1070}
1071
1072impl Lang for JavaAst {
1073 type Id = JavaId;
1074 type NameKey = u32;
1075
1076 fn kind(&self, id: JavaId) -> NodeKind {
1077 JavaAst::kind(self, id)
1078 }
1079
1080 fn children(&self, id: JavaId) -> &[JavaId] {
1081 JavaAst::children(self, id)
1082 }
1083
1084 fn set_child(&mut self, parent: JavaId, index: usize, new: JavaId) {
1085 self.nodes[parent.0 as usize].children.set(index, new);
1086 }
1087 fn remove_child(&mut self, parent: JavaId, index: usize) {
1088 self.nodes[parent.0 as usize].children.remove(index);
1089 }
1090 fn splice(&mut self, node: JavaId, index: usize, remove: usize, insert: Vec<JavaId>) {
1091 self.nodes[node.0 as usize].children.splice(index, remove, insert);
1092 }
1093
1094 fn build_return(&mut self, value: Option<JavaId>) -> JavaId {
1095 self.ret(value)
1096 }
1097 fn build_block(&mut self, stmts: Vec<JavaId>) -> JavaId {
1098 self.block(stmts)
1099 }
1100 fn build_unary(&mut self, op: UnOp, operand: JavaId) -> JavaId {
1101 self.un(op, operand)
1102 }
1103 fn build_bool(&mut self, v: bool) -> JavaId {
1104 self.lit(Lit::Bool(v))
1105 }
1106 fn build_int(&mut self, v: i64, long: bool) -> JavaId {
1107 self.build_int_impl(v, long)
1108 }
1109 fn build_str(&mut self, s: &str) -> JavaId {
1110 self.lit(Lit::Str(s.to_string()))
1111 }
1112 fn build_char(&mut self, c: char) -> JavaId {
1113 self.lit(Lit::Char(c))
1114 }
1115 fn build_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
1116 self.bin(op, l, r)
1117 }
1118 fn build_ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
1119 self.ternary(c, a, b)
1120 }
1121 fn build_assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
1122 self.assign(target, value)
1123 }
1124
1125 fn fold_lit_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<JavaId> {
1128 let ll = self.literal(l)?;
1129 let rr = self.literal(r)?;
1130 if op == BinOp::Add {
1134 let str_of = |x: LitRef<'_>| -> Option<String> {
1135 match x {
1136 LitRef::Str(s) => Some(s.to_string()),
1137 LitRef::Char(c) => Some(c.to_string()),
1138 LitRef::Int(v) => Some(v.to_string()),
1139 LitRef::Long(v) => Some(v.to_string()),
1140 LitRef::Bool(b) => Some(b.to_string()),
1141 _ => None,
1142 }
1143 };
1144 if matches!(ll, LitRef::Str(_)) || matches!(rr, LitRef::Str(_)) {
1145 if let (Some(a), Some(b)) = (str_of(ll), str_of(rr)) {
1146 return Some(self.build_str(&format!("{a}{b}")));
1147 }
1148 }
1149 }
1150 let int_of = |x: LitRef<'_>| -> Option<i64> {
1153 match x {
1154 LitRef::Int(v) | LitRef::Long(v) => Some(v),
1156 LitRef::Char(c) => Some(c as u32 as i64),
1157 _ => None,
1158 }
1159 };
1160 let (a, b) = (int_of(ll)?, int_of(rr)?);
1161 let wide = matches!(ll, LitRef::Long(_)) || matches!(rr, LitRef::Long(_));
1162 let v = if wide { fold_i64(op, a, b) } else { fold_i32(op, a, b) }?;
1163 Some(self.build_int(v, wide))
1164 }
1165
1166 fn fold_lit_cmp(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<bool> {
1167 let ll = self.literal(l)?;
1168 let rr = self.literal(r)?;
1169 let int_of = |x: LitRef<'_>| -> Option<i64> {
1171 match x {
1172 LitRef::Int(v) | LitRef::Long(v) => Some(v),
1173 LitRef::Char(c) => Some(c as u32 as i64),
1174 _ => None,
1175 }
1176 };
1177 if let (Some(x), Some(y)) = (int_of(ll), int_of(rr)) {
1178 return cmp_i64(op, x, y);
1179 }
1180 if let (LitRef::Bool(x), LitRef::Bool(y)) = (ll, rr) {
1183 if matches!(op, BinOp::Eq | BinOp::Ne) {
1184 return Some(if op == BinOp::Eq { x == y } else { x != y });
1185 }
1186 }
1187 None
1189 }
1190
1191 fn fold_lit_neg(&mut self, v: i64, wide: bool) -> Option<i64> {
1192 let n = -v;
1194 if !wide && n > i32::MAX as i64 {
1195 return None;
1196 }
1197 Some(n)
1198 }
1199
1200 fn reassoc_delta(
1201 &mut self,
1202 op1: BinOp,
1203 k1: i64,
1204 op2: BinOp,
1205 k2: i64,
1206 wide: bool,
1207 ) -> Option<ReassocOutcome<JavaId>> {
1208 use BinOp::*;
1209 let raw: i64 = match (op1, op2) {
1210 (BitXor, BitXor) => k1 ^ k2,
1211 (a, b) if matches!(a, Add | Sub) && matches!(b, Add | Sub) => {
1212 let sign = |o: BinOp| if o == Sub { -1i64 } else { 1i64 };
1213 sign(a)
1216 .wrapping_mul(k1)
1217 .wrapping_add(sign(b).wrapping_mul(k2))
1218 }
1219 _ => return None,
1220 };
1221 let d = if wide { raw } else { raw as i32 as i64 };
1224 if d == 0 {
1225 return Some(ReassocOutcome::Neutral);
1226 }
1227 if op1 == BitXor && op2 == BitXor {
1228 return Some(ReassocOutcome::Lit(self.build_int(d, wide), BitXor));
1229 }
1230 if d > 0 {
1231 Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
1232 } else {
1233 let nd = if wide { d.checked_neg().unwrap_or(d) } else { (d as i32).wrapping_neg() as i64 };
1237 if nd > 0 {
1238 Some(ReassocOutcome::Lit(self.build_int(nd, wide), Sub))
1239 } else {
1240 Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
1241 }
1242 }
1243 }
1244
1245 fn copy_subtree(&mut self, id: JavaId) -> JavaId {
1246 let old_children: Vec<JavaId> = self.nodes[id.0 as usize].children.as_slice().to_vec();
1247 let mut children = Vec::with_capacity(old_children.len());
1248 for c in old_children {
1249 children.push(self.copy_subtree(c));
1250 }
1251 let data = self.nodes[id.0 as usize].data.clone();
1252 self.push(data, children)
1253 }
1254
1255 fn effect(&self, id: JavaId) -> Effect {
1256 if let Some(e) = self.effect_cache.get(id.0 as usize).and_then(|x| *x) {
1257 return e;
1258 }
1259 let mut e = self.own_effect(id);
1261 for &c in self.nodes[id.0 as usize].children.as_slice() {
1262 e = e.worst(self.effect(c));
1263 }
1264 e
1265 }
1266
1267 fn invalidate_effect(&mut self, id: JavaId) {
1268 if let Some(slot) = self.effect_cache.get_mut(id.0 as usize) {
1269 *slot = None;
1270 }
1271 self.events.invalidate(id.0 as usize);
1274 if let Some(slot) = self.stmt_facts.get_mut(id.0 as usize) {
1275 *slot = None;
1276 }
1277 if let Some(slot) = self.stmt_kills.get_mut(id.0 as usize) {
1278 *slot = None;
1279 }
1280 if let Some(slot) = self.stmt_opq.get_mut(id.0 as usize) {
1281 *slot = None;
1282 }
1283 }
1284
1285 fn debug_verify_events(&self, stmt: JavaId) {
1286 if !verify_events_on() {
1287 return;
1288 }
1289 if let Some(indexed) = self.region_events(stmt) {
1290 let Some(fresh) = cure_tree::collect_region_events(self, stmt) else {
1291 return;
1292 };
1293 let key = |e: &cure_engine::kind::RegionEvent<JavaId, u32>| {
1295 (e.key, matches!(e.kind, cure_engine::kind::EventKind::Use), e.node.0)
1296 };
1297 let a: Vec<_> = indexed.iter().map(key).collect();
1298 let b: Vec<_> = fresh.iter().map(key).collect();
1299 if a != b {
1300 eprintln!(
1301 "[EVENTS-STALE] stmt #{} kind={:?}: indexed {} events, fresh {} events",
1302 stmt.0, self.kind(stmt), a.len(), b.len()
1303 );
1304 }
1305 }
1306 }
1307
1308 fn region_events(
1309 &self,
1310 id: JavaId,
1311 ) -> Option<&[cure_engine::kind::RegionEvent<JavaId, u32>]> {
1312 self.events.slot(id.0 as usize)
1314 }
1315
1316 fn var_key(&self, id: JavaId) -> Option<u32> {
1317 self.names.var_key(id.0 as usize)
1318 }
1319
1320 fn stmt_var_ref_count(&self, stmt: JavaId, key: u32) -> Option<u32> {
1321 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1322 Some(
1323 slot.binary_search_by_key(&key, |f| f.key)
1324 .ok()
1325 .map_or(0, |i| slot[i].refs),
1326 )
1327 }
1328
1329 fn stmt_lambda_captures(&self, stmt: JavaId, key: u32) -> Option<bool> {
1330 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1331 Some(
1332 slot.binary_search_by_key(&key, |f| f.key)
1333 .ok()
1334 .is_some_and(|i| slot[i].lambda_cap),
1335 )
1336 }
1337
1338 fn stmt_kill_event(&self, stmt: JavaId, key: u32) -> Option<StmtKill<JavaId>> {
1339 let slot = self.stmt_kills.get(stmt.0 as usize)?.as_deref()?;
1340 Some(match slot.binary_search_by_key(&key, |f| f.key) {
1341 Ok(i) => match slot[i].kind {
1342 1 => StmtKill::Read,
1343 2 => StmtKill::Shadow,
1344 _ => StmtKill::DomWrite(slot[i].assign),
1345 },
1346 Err(_) => StmtKill::None,
1347 })
1348 }
1349
1350 fn stmt_mentions(&self, stmt: JavaId, key: u32) -> Option<bool> {
1351 let slot = self.stmt_facts.get(stmt.0 as usize)?.as_deref()?;
1352 Some(
1353 slot.binary_search_by_key(&key, |f| f.key)
1354 .ok()
1355 .is_some_and(|i| slot[i].refs > 0 || slot[i].shadow),
1356 )
1357 }
1358
1359 fn stmt_opaque(&self, stmt: JavaId) -> Option<bool> {
1360 self.stmt_opq.get(stmt.0 as usize)?.map(|f| f.opaque)
1361 }
1362
1363 fn fact_single_def(&self, key: u32) -> bool {
1366 match (&self.facts, self.names.name(key)) {
1367 (Some(f), n) => f.var(n).single_def,
1368 _ => false,
1369 }
1370 }
1371 fn fact_single_use(&self, key: u32) -> bool {
1372 match (&self.facts, self.names.name(key)) {
1373 (Some(f), n) => f.var(n).single_use,
1374 _ => false,
1375 }
1376 }
1377 fn fact_bool_valued(&self, key: u32) -> bool {
1378 match (&self.facts, self.names.name(key)) {
1379 (Some(f), n) => f.var(n).bool_valued,
1380 _ => false,
1381 }
1382 }
1383 fn fact_call_effect(&self, name: &str) -> Option<cure_engine::Effect> {
1384 self.facts
1385 .as_ref()
1386 .and_then(|f| {
1387 f.call_effects.get(name).map(|set| {
1388 set.iter().copied().fold(Effect::Pure, Effect::worst)
1389 })
1390 })
1391 }
1392
1393 fn node_index(&self, id: JavaId) -> usize {
1394 id.0 as usize
1395 }
1396 fn id_of_index(&self, idx: usize) -> JavaId {
1397 JavaId(idx as u32)
1398 }
1399
1400 fn is_opaque(&self, id: JavaId) -> bool {
1401 match self.data(id) {
1402 NodeData::Raw { .. } => true,
1404 NodeData::New { anon_raw: Some(_), .. } => true,
1407 _ => false,
1408 }
1409 }
1410
1411 fn own_effect(&self, id: JavaId) -> Effect {
1412 match self.data(id) {
1413 NodeData::Literal(_)
1414 | NodeData::This
1415 | NodeData::Super
1416 | NodeData::Paren
1417 | NodeData::Block
1418 | NodeData::Group
1419 | NodeData::Empty
1420 | NodeData::VarDecl { .. }
1421 | NodeData::Break { .. }
1422 | NodeData::Continue { .. }
1423 | NodeData::Try
1424 | NodeData::Catch { .. }
1425 | NodeData::Switch
1426 | NodeData::Case { .. }
1427 | NodeData::Label { .. }
1428 | NodeData::ExprStmt
1429 | NodeData::Ternary
1430 | NodeData::ArrayLit
1431 | NodeData::Lambda { .. }
1432 | NodeData::MethodRef { .. }
1433 | NodeData::InstanceOf { .. } => Effect::Pure,
1434 NodeData::VarRef { .. } => Effect::MayRead,
1435 NodeData::Member { .. } => Effect::MayThrow,
1436 NodeData::Index => Effect::MayThrow,
1437 NodeData::New { .. } | NodeData::NewArray { .. } => Effect::MayThrow,
1438 NodeData::Call => Effect::Unknown,
1439 NodeData::Assign { .. } => Effect::MayWrite,
1440 NodeData::Unary { op } => {
1441 if op.is_incdec() {
1442 Effect::MayWrite
1443 } else {
1444 Effect::Pure
1445 }
1446 }
1447 NodeData::Binary { op } => {
1448 if op.may_div_zero() {
1449 Effect::MayThrow
1450 } else {
1451 Effect::Pure
1452 }
1453 }
1454 NodeData::Cast { ty } => {
1455 if ty.is_ref() {
1456 Effect::MayThrow
1458 } else {
1459 Effect::Pure
1460 }
1461 }
1462 NodeData::For { .. } | NodeData::ForEach { .. } | NodeData::If => Effect::MayRead,
1463 NodeData::While | NodeData::DoWhile => Effect::MayRead,
1464 NodeData::Return | NodeData::Yield => Effect::MayThrow,
1465 NodeData::Throw => Effect::MayThrow,
1466 NodeData::Assert => Effect::MayThrow,
1467 NodeData::Synchronized => Effect::MayWrite,
1468 NodeData::Raw { .. } => Effect::Unknown,
1469 }
1470 }
1471
1472 fn is_bool(&self, id: JavaId) -> bool {
1473 match self.data(id) {
1474 NodeData::Literal(l) => matches!(l, Lit::Bool(_)),
1475 NodeData::Unary { op } => *op == UnOp::Not,
1476 NodeData::Binary { op } => {
1477 op.is_comparison() || matches!(op, BinOp::And | BinOp::Or)
1478 }
1479 NodeData::Ternary => {
1480 let ch = self.children(id);
1481 self.is_bool(ch[1]) && self.is_bool(ch[2])
1482 }
1483 NodeData::Paren => self.is_bool(self.children(id)[0]),
1484 NodeData::InstanceOf { .. } => true,
1485 NodeData::VarRef { .. } => self
1486 .var_types
1487 .get(id.0 as usize)
1488 .and_then(|t| t.as_ref())
1489 .is_some_and(|t| t.is_bool()),
1490 _ => false,
1491 }
1492 }
1493
1494 fn is_exact_int(&self, id: JavaId) -> bool {
1495 match self.data(id) {
1496 NodeData::Literal(l) => matches!(
1497 l,
1498 Lit::Int(_) | Lit::Long(_) | Lit::Char(_) | Lit::NumRaw { val: NumVal::Int(_) | NumVal::Long(_), .. }
1499 ),
1500 NodeData::VarRef { .. } => {
1501 self.var_types.get(id.0 as usize).and_then(|t| t.as_ref()).is_some_and(|t| t.is_integral())
1502 }
1503 NodeData::Binary { op } => {
1504 let ch = self.children(id);
1505 (op.is_arith() || op.is_comparison())
1506 && self.is_exact_int(ch[0])
1507 && self.is_exact_int(ch[1])
1508 }
1509 NodeData::Cast { ty } => ty.is_integral(),
1510 NodeData::Paren => self.is_exact_int(self.children(id)[0]),
1511 NodeData::Ternary => {
1512 let ch = self.children(id);
1513 self.is_exact_int(ch[1]) && self.is_exact_int(ch[2])
1514 }
1515 _ => false,
1516 }
1517 }
1518
1519 fn is_local_var(&self, id: JavaId) -> bool {
1520 matches!(self.data(id), NodeData::VarRef { .. }) && self.var_type(id).is_some()
1526 }
1527
1528 fn is_inferred_decl(&self, decl: JavaId) -> bool {
1529 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Var, .. })
1530 }
1531
1532 fn is_char_decl(&self, decl: JavaId) -> bool {
1533 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Char, .. })
1534 }
1535
1536 fn has_pattern_bindings(&self, root: JavaId) -> bool {
1537 let mut stack = vec![root];
1538 while let Some(n) = stack.pop() {
1539 if let NodeData::InstanceOf { bind: Some(_), .. } = self.data(n) {
1540 return true;
1541 }
1542 for &c in self.children(n) {
1543 stack.push(c);
1544 }
1545 }
1546 false
1547 }
1548
1549 fn is_long_decl(&self, decl: JavaId) -> bool {
1550 matches!(self.data(decl), NodeData::VarDecl { ty: JType::Long, .. })
1551 }
1552 fn is_wide_decl(&self, decl: JavaId) -> bool {
1553 !matches!(
1556 self.data(decl),
1557 NodeData::VarDecl { ty: JType::Byte | JType::Short | JType::Char, .. }
1558 )
1559 }
1560
1561 fn receiver_propagation_unsound(
1562 &self,
1563 decl: JavaId,
1564 value: JavaId,
1565 use_parent: Option<JavaId>,
1566 use_at_head: bool,
1567 ) -> bool {
1568 let is_receiver = matches!(use_parent, Some(p) if {
1571 matches!(self.data(p), NodeData::Member { .. }) && use_at_head
1572 });
1573 if is_receiver {
1574 if self.receiver_boxing_unsound(decl, value) {
1576 return true;
1577 }
1578 if self.receiver_new_type_unsound(decl, value) {
1589 return true;
1590 }
1591 let raw_ref = match self.data(decl) {
1595 NodeData::VarDecl { ty: JType::Ref(n), .. } => {
1596 !n.contains('<')
1597 && n.rsplit('.').next().is_some_and(|base| {
1598 self.raw_generic_names.contains(base)
1599 })
1600 }
1601 _ => false,
1602 };
1603 if !raw_ref {
1604 return false;
1605 }
1606 match self.data(value) {
1608 NodeData::Call => true,
1609 NodeData::New { ty, .. } => {
1610 matches!(ty, JType::Ref(n) if n.contains('<'))
1611 }
1612 _ => false,
1613 }
1614 } else {
1615 if matches!(use_parent, Some(p) if {
1622 matches!(self.data(p), NodeData::Index) && use_at_head
1623 }) {
1624 return self.index_base_newarray_unsound(decl, value);
1625 }
1626 false
1627 }
1628 }
1629
1630 fn bin_op(&self, id: JavaId) -> Option<BinOp> {
1631 match self.data(id) {
1632 NodeData::Binary { op } => Some(*op),
1633 _ => None,
1634 }
1635 }
1636 fn un_op(&self, id: JavaId) -> Option<UnOp> {
1637 match self.data(id) {
1638 NodeData::Unary { op } => Some(*op),
1639 _ => None,
1640 }
1641 }
1642 fn literal(&self, id: JavaId) -> Option<LitRef<'_>> {
1643 match self.data(id) {
1644 NodeData::Literal(l) => Some(match l {
1645 Lit::Bool(b) => LitRef::Bool(*b),
1646 Lit::Int(v) => LitRef::Int(*v),
1647 Lit::Long(v) => LitRef::Long(*v),
1648 Lit::Float(v) => LitRef::Float(*v),
1649 Lit::Double(v) => LitRef::Double(*v),
1650 Lit::Char(c) => LitRef::Char(*c),
1651 Lit::Str(s) => LitRef::Str(s),
1652 Lit::TextBlock(_) => return None,
1658 Lit::NumRaw { val, .. } => match val {
1659 NumVal::Int(v) => LitRef::Int(*v),
1660 NumVal::Long(v) => LitRef::Long(*v),
1661 NumVal::Float(v) => LitRef::Float(*v),
1662 NumVal::Double(v) => LitRef::Double(*v),
1663 },
1664 Lit::Null => LitRef::Null,
1665 }),
1666 _ => None,
1667 }
1668 }
1669 fn var_name(&self, id: JavaId) -> Option<&str> {
1670 match self.data(id) {
1671 NodeData::VarRef { name } => Some(self.sn(*name)),
1672 NodeData::VarDecl { name, .. } => Some(name),
1673 NodeData::ForEach { name, .. } | NodeData::Catch { name, .. } => Some(name),
1674 _ => None,
1675 }
1676 }
1677 fn member_name(&self, id: JavaId) -> Option<&str> {
1678 match self.data(id) {
1679 NodeData::Member { name } => Some(self.sn(*name)),
1680 NodeData::MethodRef { name } => Some(self.sn(*name)),
1681 _ => None,
1682 }
1683 }
1684 fn double_not_sound(&self, _operand: JavaId) -> bool {
1685 true
1689 }
1690
1691 fn var_cast_position_ok(&self, value: JavaId, typed: JavaId) -> bool {
1692 if !matches!(self.data(value), NodeData::VarRef { .. }) {
1694 return true;
1695 }
1696 let Some(d) = self.var_type(value).cloned() else {
1700 return true;
1701 };
1702 let t = match self.data(typed) {
1703 NodeData::Cast { ty } => ty.clone(),
1704 NodeData::InstanceOf { ty, .. } => ty.clone(),
1705 _ => return true,
1706 };
1707 if d == t {
1708 return true;
1709 }
1710 let obj = |ty: &JType| {
1714 matches!(ty, JType::Ref(r) if r == "java.lang.Object" || r == "Object")
1715 };
1716 obj(&d) || obj(&t)
1717 }
1718 fn is_field_name(&self, name: &str) -> bool {
1719 self.field_types.contains_key(name)
1720 }
1721 fn payload_equal(&self, a: JavaId, b: JavaId) -> bool {
1722 if let (NodeData::Literal(x), NodeData::Literal(y)) = (self.data(a), self.data(b)) {
1725 return lit_value_eq(x, y);
1726 }
1727 self.data(a) == self.data(b)
1728 }
1729 fn assign_op(&self, id: JavaId) -> Option<BinOp> {
1730 match self.data(id) {
1731 NodeData::Assign { op } => *op,
1732 _ => None,
1733 }
1734 }
1735
1736 fn prepare(&mut self, root: JavaId) {
1740
1741 if cnt_prepare_on() {
1742 PREPARE_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1743 PREPARE_NODES.fetch_add(self.nodes.len(), std::sync::atomic::Ordering::Relaxed);
1744 }
1745 let n_nodes = self.nodes.len();
1749 let key_slots = self.names.key_slots();
1750 if key_slots < n_nodes {
1751 let names: Vec<Option<String>> = (key_slots..n_nodes)
1752 .map(|i| self.var_name(JavaId(i as u32)).map(|n| n.to_string()))
1753 .collect();
1754 self.names.extend_owned(key_slots, &names);
1755 }
1756 self.string_identity.set(None);
1758 self.events.resize(n_nodes);
1769 self.stmt_facts.resize_with(n_nodes, || None);
1770 self.stmt_kills.resize_with(n_nodes, || None);
1771 self.stmt_opq.resize_with(n_nodes, || None);
1772 if self.events.is_clean() {
1773 if self.prepared_root == Some(root) {
1774 return;
1775 }
1776 self.rebuild_var_types(root);
1779 self.prepared_root = Some(root);
1780 return;
1781 }
1782 self.prepared_root = Some(root);
1783 self.events.mark_clean();
1784 for sr in cure_tree::stmt_roots(self, root) {
1785 let idx = sr.0 as usize;
1786 if self.events.slot(idx).is_none() {
1787 if let Some(events) = cure_tree::collect_region_events(self, sr) {
1788 self.events.put(idx, events);
1789 }
1790 }
1791 if self.stmt_facts.get(idx).map_or(true, |f| f.is_none()) {
1792 let facts = self.collect_stmt_facts(sr);
1793 self.stmt_facts[idx] = Some(facts);
1794 }
1795 if self.stmt_kills.get(idx).map_or(true, |f| f.is_none()) {
1796 let kills = self.collect_stmt_kill_facts(sr);
1797 self.stmt_kills[idx] = Some(kills);
1798 }
1799 if self.stmt_opq.get(idx).map_or(true, |f| f.is_none()) {
1800 if let Some(opq) = self.stmt_opaque_fact(sr) {
1801 self.stmt_opq[idx] = Some(opq);
1802 }
1803 }
1804 }
1805 let effects = cure_tree::rebuild_effects(self, n_nodes);
1808 self.effect_cache = effects;
1809 self.rebuild_var_types(root);
1810 }
1811}
1812
1813impl JavaAst {
1814 fn collect_stmt_facts(&self, root: JavaId) -> Box<[StmtFact]> {
1818 let mut counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1819 let mut caps: std::collections::HashSet<u32> = std::collections::HashSet::new();
1820 let mut shadows: std::collections::HashSet<u32> = std::collections::HashSet::new();
1821 let mut stack: Vec<(JavaId, bool)> = vec![(root, false)];
1822 while let Some((id, in_lambda)) = stack.pop() {
1823 let node = &self.nodes[id.0 as usize];
1824 let is_lambda = matches!(node.data, NodeData::Lambda { .. });
1825 let is_named_decl = matches!(
1826 node.data,
1827 NodeData::VarDecl { .. } | NodeData::ForEach { .. } | NodeData::Catch { .. }
1828 );
1829 if matches!(node.data, NodeData::VarRef { .. }) {
1830 if let Some(key) = self.names.var_key(id.0 as usize) {
1831 *counts.entry(key).or_insert(0) += 1;
1832 if in_lambda {
1833 caps.insert(key);
1834 }
1835 }
1836 } else if is_named_decl {
1837 if let Some(key) = self.names.var_key(id.0 as usize) {
1838 shadows.insert(key);
1839 }
1840 }
1841 let child_in_lambda = in_lambda || is_lambda;
1842 for &c in node.children.as_slice() {
1843 stack.push((c, child_in_lambda));
1844 }
1845 }
1846 let mut keys: std::collections::HashSet<u32> = counts.keys().copied().collect();
1847 keys.extend(shadows.iter().copied());
1848 let mut facts: Vec<StmtFact> = keys
1849 .into_iter()
1850 .map(|key| StmtFact {
1851 key,
1852 refs: counts.get(&key).copied().unwrap_or(0),
1853 lambda_cap: caps.contains(&key),
1854 shadow: shadows.contains(&key),
1855 })
1856 .collect();
1857 facts.sort_unstable_by_key(|f| f.key);
1858 facts.into_boxed_slice()
1859 }
1860
1861 fn stmt_opaque_fact(&self, root: JavaId) -> Option<StmtOpq> {
1863 let mut stack = vec![root];
1864 while let Some(id) = stack.pop() {
1865 if self.is_opaque(id) {
1866 return Some(StmtOpq { opaque: true });
1867 }
1868 for &c in self.nodes[id.0 as usize].children.as_slice() {
1869 stack.push(c);
1870 }
1871 }
1872 Some(StmtOpq { opaque: false })
1873 }
1874
1875 fn collect_stmt_kill_facts(&self, root: JavaId) -> Box<[StmtKillFact]> {
1878 let mut facts: Vec<StmtKillFact> = cure_engine::rules::collect_stmt_kill_events(self, root)
1879 .into_iter()
1880 .map(|(key, event)| match event {
1881 StmtKill::None => unreachable!("collector only records real events"),
1882 StmtKill::Read => StmtKillFact {
1883 key,
1884 kind: 1,
1885 assign: JavaId(0),
1886 },
1887 StmtKill::Shadow => StmtKillFact {
1888 key,
1889 kind: 2,
1890 assign: JavaId(0),
1891 },
1892 StmtKill::DomWrite(assign) => StmtKillFact {
1893 key,
1894 kind: 3,
1895 assign,
1896 },
1897 })
1898 .collect();
1899 facts.sort_unstable_by_key(|f| f.key);
1900 facts.into_boxed_slice()
1901 }
1902
1903 pub(crate) fn rebuild_var_types(&mut self, root: JavaId) {
1905 self.var_types.clear();
1907 self.var_types.resize(self.nodes.len(), None);
1908 #[derive(Clone)]
1909 enum Frame {
1910 Enter(JavaId),
1911 Exit,
1912 PopScope,
1913 Bind(String, JType),
1914 }
1915 let mut base: HashMap<String, JType> = HashMap::new();
1916 for (n, t) in &self.param_scope {
1917 base.insert(n.clone(), t.clone());
1918 }
1919 let mut scopes: Vec<HashMap<String, JType>> = vec![base];
1920 let mut stack = vec![Frame::Enter(root)];
1921 while let Some(f) = stack.pop() {
1922 match f {
1923 Frame::Enter(id) => {
1924 let node = &self.nodes[id.0 as usize];
1925 enum Action {
1926 None,
1927 Scope,
1928 Bind(String, JType),
1929 }
1930 let action = match &node.data {
1931 NodeData::Block | NodeData::For { .. } | NodeData::Try => Action::Scope,
1932 NodeData::VarDecl { name, ty, .. } => {
1933 scopes.last_mut().unwrap().insert(name.clone(), ty.clone());
1934 Action::None
1935 }
1936 NodeData::VarRef { name } => {
1937 if let Some(t) = scopes
1938 .iter()
1939 .rev()
1940 .find_map(|s| s.get(self.sn(*name)))
1941 .cloned()
1942 {
1943 self.var_types[id.0 as usize] = Some(t);
1944 }
1945 Action::None
1946 }
1947 NodeData::Catch { name, .. } => {
1948 Action::Bind(name.clone(), JType::Var)
1949 }
1950 NodeData::ForEach { name, ty } => {
1951 Action::Bind(name.clone(), ty.clone())
1952 }
1953 _ => Action::None,
1954 };
1955 stack.push(Frame::Exit);
1958 if matches!(action, Action::Scope | Action::Bind(_, _)) {
1959 scopes.push(HashMap::new());
1960 stack.push(Frame::PopScope);
1961 }
1962 for &c in node.children.as_slice().iter().rev() {
1963 stack.push(Frame::Enter(c));
1964 }
1965 if let Action::Bind(n, t) = action {
1966 stack.push(Frame::Bind(n, t));
1967 }
1968 }
1969 Frame::Bind(name, ty) => {
1970 scopes.last_mut().unwrap().insert(name, ty);
1971 }
1972 Frame::PopScope => {
1973 scopes.pop();
1974 }
1975 Frame::Exit => {}
1976 }
1977 }
1978 }
1979}
1980
1981impl JavaAst {
1993 fn receiver_new_type_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2005 let NodeData::New { ty, .. } = self.data(value) else {
2006 return false;
2007 };
2008 let decl_ty = match self.data(decl) {
2009 NodeData::VarDecl { ty, .. } => ty,
2010 _ => return false,
2011 };
2012 if matches!(decl_ty, JType::Var) {
2015 return false;
2016 }
2017 let name = match ty {
2018 JType::Ref(n) => n.as_str(),
2019 _ => return true,
2021 };
2022 if name.ends_with("<>") {
2024 return true;
2025 }
2026 match decl_ty {
2027 JType::Ref(d) => type_base_name(d) != type_base_name(name),
2028 _ => true,
2030 }
2031 }
2032
2033 fn index_base_newarray_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2039 let NodeData::NewArray { ty, dims, .. } = self.data(value) else {
2040 return false;
2041 };
2042 let decl_ty = match self.data(decl) {
2043 NodeData::VarDecl { ty, .. } => ty,
2044 _ => return false,
2045 };
2046 if matches!(decl_ty, JType::Var) {
2047 return false;
2048 }
2049 let mut elem: &JType = decl_ty;
2051 let mut nest = 0u16;
2052 while let JType::Array(inner) = elem {
2053 elem = inner.as_ref();
2054 nest += 1;
2055 }
2056 nest != *dims || *elem != *ty
2057 }
2058
2059 fn receiver_boxing_unsound(&self, decl: JavaId, value: JavaId) -> bool {
2060 let declared_ref = match self.data(decl) {
2061 NodeData::VarDecl { ty, .. } => ty.is_ref(),
2062 _ => false,
2063 };
2064 if !declared_ref {
2065 return false;
2066 }
2067 match self.data(value) {
2068 NodeData::Literal(
2069 Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_) | Lit::Bool(_)
2070 | Lit::Char(_),
2071 ) => true,
2072 NodeData::VarRef { .. } => matches!(
2074 self.var_type(value),
2075 Some(
2076 JType::Int
2077 | JType::Long
2078 | JType::Byte
2079 | JType::Short
2080 | JType::Char
2081 | JType::Float
2082 | JType::Double
2083 | JType::Bool
2084 )
2085 ),
2086 NodeData::Unary { .. } | NodeData::Binary { .. } => true,
2088 NodeData::Ternary => true,
2092 NodeData::Cast { .. } => true,
2094 NodeData::InstanceOf { .. } => true,
2096 NodeData::Call | NodeData::Member { .. } | NodeData::Index => {
2102 match self.data(decl) {
2103 NodeData::VarDecl { ty: JType::Ref(n), .. } => {
2104 let boxed = [
2105 "Character", "Integer", "Long", "Boolean", "Double",
2106 "Float", "Byte", "Short", "Number", "AtomicInteger",
2107 "AtomicLong",
2108 ];
2109 n.rsplit('.').next().is_some_and(|b| boxed.contains(&b))
2110 }
2111 _ => false,
2112 }
2113 }
2114 _ => false,
2115 }
2116 }
2117
2118 pub fn intern_sym(&mut self, name: &str) -> Sym {
2121 Sym(self.names.intern(name))
2122 }
2123
2124 pub fn sn(&self, sym: Sym) -> &str {
2126 self.names.name(sym.0)
2127 }
2128
2129
2130
2131
2132}
2133
2134
2135pub fn unit_metrics(ast: &JavaAst, unit: &CompilationUnit) -> TreeMetrics {
2143 let mut total = TreeMetrics::default();
2144 for ty in &unit.types {
2145 metrics_of_type(ast, ty, &mut total);
2146 }
2147 total
2148}
2149
2150fn metrics_of_type(ast: &JavaAst, ty: &TypeDecl, total: &mut TreeMetrics) {
2151 for m in &ty.members {
2152 match m {
2153 Member::Method { body: Some(b), .. } | Member::Constructor { body: Some(b), .. } => {
2154 total.add(subtree_metrics(ast, *b));
2155 }
2156 Member::Initializer { body, .. } => {
2157 total.add(subtree_metrics(ast, *body));
2158 }
2159 Member::Type(t) => metrics_of_type(ast, t, total),
2160 _ => {}
2161 }
2162 }
2163}
2164
2165static ENV_CACHED: std::sync::OnceLock<(bool, bool)> = std::sync::OnceLock::new();
2166fn env_flags() -> (bool, bool) {
2167 *ENV_CACHED.get_or_init(|| {
2168 (
2169 std::env::var("CURE_CNT_PREPARE").is_ok(),
2170 std::env::var("CURE_VERIFY_EVENTS").is_ok(),
2171 )
2172 })
2173}
2174fn cnt_prepare_on() -> bool { env_flags().0 }
2175fn verify_events_on() -> bool { env_flags().1 }
2176
2177pub static PREPARE_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2178pub static PREPARE_NODES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2179
2180pub static COLLECT_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2181
2182#[cfg(test)]
2187mod tests {
2188 use super::*;
2189
2190 #[test]
2191 fn builder_smoke() {
2192 let mut a = JavaAst::new();
2193 let one = a.lit(Lit::Int(1));
2194 let x = a.var_decl("x", JType::Int, Some(one));
2195 let xv = a.var("x");
2196 let r = a.ret(Some(xv));
2197 let body = a.block(vec![x, r]);
2198 assert_eq!(a.kind(body), NodeKind::Block);
2199 assert_eq!(a.children(body).len(), 2);
2200 }
2201
2202 #[test]
2203 fn effect_aggregation() {
2204 let mut a = JavaAst::new();
2205 let f = a.plain_call("foo", vec![]);
2206 let r = a.ret(Some(f));
2207 let body = a.block(vec![r]);
2208 a.prepare(body);
2209 assert_eq!(a.effect(f), Effect::Unknown);
2210 assert_eq!(a.effect(r), Effect::Unknown);
2211 assert_eq!(a.effect(body), Effect::Unknown);
2212
2213 let mut b = JavaAst::new();
2214 let two = b.lit(Lit::Int(2));
2215 let three = b.lit(Lit::Int(3));
2216 let add = b.bin(BinOp::Add, two, three);
2217 let r2 = b.ret(Some(add));
2218 let body2 = b.block(vec![r2]);
2219 b.prepare(body2);
2220 assert_eq!(b.effect(add), Effect::Pure);
2221 assert_eq!(b.effect(r2), Effect::MayThrow);
2223 assert_eq!(b.effect(body2), Effect::MayThrow);
2224 }
2225
2226 #[test]
2227 fn var_type_resolution_and_shadowing() {
2228 let mut a = JavaAst::new();
2229 let one = a.lit(Lit::Int(1));
2231 let b2_decl = a.var_decl("b2", JType::Int, Some(one));
2232 let inner_b2 = a.var("b2");
2233 let inner_stmt = a.expr_stmt(inner_b2);
2234 let inner = a.block(vec![b2_decl, inner_stmt]);
2235 let tv = a.lit(Lit::Bool(true));
2236 let decl_b = a.var_decl("b", JType::Bool, Some(tv));
2237 let outer_b = a.var("b");
2238 let outer_stmt = a.expr_stmt(outer_b);
2239 let body = a.block(vec![decl_b, inner, outer_stmt]);
2240 a.prepare(body);
2241 assert!(a.is_bool(outer_b));
2242 assert!(a.var_type(inner_b2).is_some());
2244 }
2245
2246 #[test]
2247 fn raw_and_foreach_semantics() {
2248 let mut a = JavaAst::new();
2249 let one = a.lit(Lit::Int(1));
2251 let dx = a.var_decl("x", JType::Int, Some(one));
2252 let r = a.raw("broken ~!@ code");
2253 let sv = a.var("s");
2254 let call = a.method_call(sv, "len", vec![]);
2255 let es = a.expr_stmt(call);
2256 let body = a.block(vec![es]);
2257 let lst = a.var("list");
2258 let fe = a.for_each("s", JType::Ref("String".into()), lst, body);
2259 let all = a.block(vec![dx, r, fe]);
2260 a.prepare(all);
2261 assert_eq!(a.kind(r), NodeKind::Raw);
2262 assert_eq!(a.effect(r), Effect::Unknown);
2263 assert!(a.var_type(sv).is_some()); }
2265
2266 #[test]
2267 fn num_raw_literal() {
2268 let mut a = JavaAst::new();
2269 let l = a.lit(Lit::NumRaw {
2270 text: "0x1F".into(),
2271 val: NumVal::Int(31),
2272 });
2273 a.prepare(l);
2274 assert!(a.is_exact_int(l));
2275 assert!(matches!(a.literal(l), Some(LitRef::Int(31))));
2276 }
2277}