use std::collections::{HashMap, HashSet};
use cure_engine::analysis::{subtree_metrics, TreeMetrics};
use cure_engine::lang::ReassocOutcome;
use cure_engine::Lang;
pub use cure_tree::ChildList;
use cure_tree::{EventStore, NameTable};
pub use cure_engine::kind::{BinOp, LitRef, NodeKind, UnOp};
pub use cure_engine::Effect;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct JavaId(pub u32);
impl Default for JavaId {
fn default() -> Self {
JavaId(u32::MAX)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum JType {
Bool,
Byte,
Short,
Int,
Long,
Char,
Float,
Double,
Void,
Ref(String),
Array(Box<JType>),
Var,
}
impl JType {
pub fn is_ref(&self) -> bool {
matches!(self, JType::Ref(_) | JType::Array(_))
}
pub fn is_integral(&self) -> bool {
matches!(
self,
JType::Byte | JType::Short | JType::Int | JType::Long | JType::Char
)
}
pub fn is_bool(&self) -> bool {
matches!(self, JType::Bool)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum NumVal {
Int(i64),
Long(i64),
Float(f64),
Double(f64),
}
#[derive(Clone, PartialEq, Debug)]
pub enum Lit {
Bool(bool),
Int(i64),
Long(i64),
Float(f64),
Double(f64),
Char(char),
Str(String),
TextBlock(String),
NumRaw { text: String, val: NumVal },
Null,
}
#[derive(Clone, PartialEq, Debug)]
pub enum NodeData {
Block,
Group,
Empty,
Yield,
ExprStmt,
VarDecl { name: String, ty: JType, final_mod: bool },
Assign { op: Option<BinOp> },
If,
While,
DoWhile,
For { inits: u8, has_cond: bool, steps: u8 },
ForEach { name: String, ty: JType },
Return,
Break { label: Option<String> },
Continue { label: Option<String> },
Throw,
Try,
Catch { ty_raw: String, name: String },
Synchronized,
Switch,
Case { labels: u16, is_default: bool, arrow: bool },
Label { name: String },
Assert,
Raw { text: String },
Binary { op: BinOp },
Unary { op: UnOp },
Call,
Member { name: Sym },
Index,
Cast { ty: JType },
Paren,
Ternary,
New { ty: JType, anon_raw: Option<String> },
NewArray { ty: JType, dims: u16, sized: u16 },
ArrayLit,
VarRef { name: Sym },
Literal(Lit),
This,
Super,
InstanceOf { ty: JType, bind: Option<String> },
Lambda { params_raw: String },
MethodRef { name: Sym },
}
#[derive(Clone, PartialEq, Debug)]
pub struct Node {
pub data: NodeData,
pub children: ChildList<JavaId>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct Sym(pub u32);
fn lit_value_eq(x: &Lit, y: &Lit) -> bool {
fn r(l: &Lit) -> Option<cure_engine::kind::LitRef<'_>> {
Some(match l {
Lit::Bool(b) => LitRef::Bool(*b),
Lit::Int(v) => LitRef::Int(*v),
Lit::Long(v) => LitRef::Long(*v),
Lit::Float(v) => LitRef::Float(*v),
Lit::Double(v) => LitRef::Double(*v),
Lit::Char(c) => LitRef::Char(*c),
Lit::Str(s) => LitRef::Str(s.as_str()),
Lit::NumRaw { val, .. } => match val {
NumVal::Int(v) => LitRef::Int(*v),
NumVal::Long(v) => LitRef::Long(*v),
NumVal::Float(v) => LitRef::Float(*v),
NumVal::Double(v) => LitRef::Double(*v),
},
Lit::Null => LitRef::Null,
Lit::TextBlock(_) => return None,
})
}
match (r(x), r(y)) {
(Some(rx), Some(ry)) => {
if let (LitRef::Double(a), LitRef::Double(b)) = (rx, ry) {
return a == b && a.is_sign_negative() == b.is_sign_negative();
}
if let (LitRef::Float(a), LitRef::Float(b)) = (rx, ry) {
return a == b && a.is_sign_negative() == b.is_sign_negative();
}
rx == ry
}
_ => false,
}
}
#[derive(Clone, Debug)]
pub struct NoopCandidate {
pub params: Vec<JType>,
pub noop: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct VarFacts {
pub single_def: bool,
pub single_use: bool,
pub bool_valued: bool,
}
#[derive(Debug, Clone, Default)]
pub struct JavaFacts {
pub vars: HashMap<String, VarFacts>,
pub call_effects: HashMap<String, std::collections::HashSet<cure_engine::Effect>>,
}
impl JavaFacts {
pub fn var(&self, name: &str) -> VarFacts {
self.vars.get(name).copied().unwrap_or_default()
}
}
#[derive(Default, Debug)]
pub struct JavaAst {
pub(crate) nodes: Vec<Node>,
pub facts: Option<JavaFacts>,
effect_cache: Vec<Option<Effect>>,
var_types: Vec<Option<JType>>,
param_scope: Vec<(String, JType)>,
pub const_fields: HashMap<String, JavaId>,
pub const_scalars: HashSet<String>,
pub noop_private_methods: HashMap<String, HashMap<usize, Vec<NoopCandidate>>>,
pub field_types: HashMap<String, JType>,
pub unit_shadows_lang: bool,
pub raw_generic_names: std::collections::HashSet<String>,
pub final_fields: HashSet<String>,
pub inline_methods: HashMap<String, (Vec<String>, JavaId, String)>,
string_identity: std::cell::Cell<Option<(JavaId, bool)>>,
pub unit_string_identity: bool,
names: NameTable,
events: EventStore<JavaId, u32>,
prepared_root: Option<JavaId>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Param {
pub mods: String,
pub ty: JType,
pub varargs: bool,
pub name: String,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Declarator {
pub name: String,
pub extra_dims: u16,
pub init: Option<JavaId>,
}
#[derive(Clone, PartialEq, Debug)]
pub enum Member {
Field { mods: String, ty: JType, declarators: Vec<Declarator> },
Method {
mods: String,
ty_params: String,
ret: JType,
name: String,
params: Vec<Param>,
throws: Vec<String>,
body: Option<JavaId>,
},
Constructor {
mods: String,
ty_params: String,
name: String,
params: Vec<Param>,
throws: Vec<String>,
body: Option<JavaId>,
compact: bool,
},
Initializer { is_static: bool, body: JavaId },
Type(Box<TypeDecl>),
Raw(String),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TypeKind {
Class,
Interface,
Enum,
Record,
Annotation,
}
#[derive(Clone, PartialEq, Debug)]
pub struct TypeDecl {
pub kind: TypeKind,
pub mods: String,
pub name: String,
pub ty_params: String,
pub header: String,
pub extends: Vec<String>,
pub implements: Vec<String>,
pub permits: Vec<String>,
pub enum_constants: Vec<String>,
pub members: Vec<Member>,
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct CompilationUnit {
pub package: Option<String>,
pub package_annotations: String,
pub imports: Vec<String>,
pub types: Vec<TypeDecl>,
pub raws: Vec<String>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ParseError {
pub line: usize,
pub col: usize,
pub message: String,
}
impl JavaAst {
pub fn new() -> Self {
Self::default()
}
pub fn clone_node(&mut self, id: JavaId, children: Vec<JavaId>) -> JavaId {
let data = self.nodes[id.0 as usize].data.clone();
self.push(data, children)
}
fn push(&mut self, data: NodeData, children: Vec<JavaId>) -> JavaId {
let id = JavaId(self.nodes.len() as u32);
self.nodes.push(Node { data, children: ChildList::from_vec(children) });
id
}
pub fn node(&self, id: JavaId) -> &Node {
&self.nodes[id.0 as usize]
}
pub fn data(&self, id: JavaId) -> &NodeData {
&self.node(id).data
}
pub fn kind(&self, id: JavaId) -> NodeKind {
match &self.nodes[id.0 as usize].data {
NodeData::Block | NodeData::Group => NodeKind::Block,
NodeData::Empty => NodeKind::Empty,
NodeData::Yield => NodeKind::Yield,
NodeData::ExprStmt => NodeKind::ExprStmt,
NodeData::VarDecl { .. } => NodeKind::VarDecl,
NodeData::Assign { .. } => NodeKind::Assign,
NodeData::If => NodeKind::If,
NodeData::While => NodeKind::While,
NodeData::DoWhile => NodeKind::DoWhile,
NodeData::For { .. } => NodeKind::For,
NodeData::ForEach { .. } => NodeKind::ForEach,
NodeData::Return => NodeKind::Return,
NodeData::Break { .. } => NodeKind::Break,
NodeData::Continue { .. } => NodeKind::Continue,
NodeData::Throw => NodeKind::Throw,
NodeData::Try => NodeKind::Try,
NodeData::Catch { .. } => NodeKind::Catch,
NodeData::Synchronized => NodeKind::Synchronized,
NodeData::Switch => NodeKind::Switch,
NodeData::Case { .. } => NodeKind::Case,
NodeData::Label { .. } => NodeKind::Label,
NodeData::Assert => NodeKind::Assert,
NodeData::Raw { .. } => NodeKind::Raw,
NodeData::Binary { .. } => NodeKind::Binary,
NodeData::Unary { .. } => NodeKind::Unary,
NodeData::Call => NodeKind::Call,
NodeData::Member { .. } => NodeKind::Member,
NodeData::Index => NodeKind::Index,
NodeData::Cast { .. } => NodeKind::Cast,
NodeData::Paren => NodeKind::Paren,
NodeData::Ternary => NodeKind::Ternary,
NodeData::New { .. } => NodeKind::New,
NodeData::NewArray { .. } => NodeKind::NewArray,
NodeData::ArrayLit => NodeKind::ArrayLit,
NodeData::VarRef { .. } => NodeKind::VarRef,
NodeData::Literal(_) => NodeKind::Literal,
NodeData::This => NodeKind::This,
NodeData::Super => NodeKind::This,
NodeData::InstanceOf { .. } => NodeKind::InstanceOf,
NodeData::Lambda { .. } => NodeKind::Lambda,
NodeData::MethodRef { .. } => NodeKind::MethodRef,
}
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn children(&self, id: JavaId) -> &[JavaId] {
self.nodes[id.0 as usize].children.as_slice()
}
pub fn block(&mut self, stmts: Vec<JavaId>) -> JavaId {
self.push(NodeData::Block, stmts)
}
pub fn empty(&mut self) -> JavaId {
self.push(NodeData::Empty, vec![])
}
pub fn expr_stmt(&mut self, e: JavaId) -> JavaId {
self.push(NodeData::ExprStmt, vec![e])
}
pub fn var_decl(&mut self, name: &str, ty: JType, init: Option<JavaId>) -> JavaId {
self.var_decl_final(name, ty, init, false)
}
pub fn var_decl_final(
&mut self,
name: &str,
ty: JType,
init: Option<JavaId>,
final_mod: bool,
) -> JavaId {
self.push(
NodeData::VarDecl {
name: name.into(),
ty,
final_mod,
},
init.into_iter().collect(),
)
}
pub fn assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
self.push(NodeData::Assign { op: None }, vec![target, value])
}
pub fn assign_op(&mut self, op: BinOp, target: JavaId, value: JavaId) -> JavaId {
self.push(NodeData::Assign { op: Some(op) }, vec![target, value])
}
pub fn if_(&mut self, cond: JavaId, then: JavaId, els: Option<JavaId>) -> JavaId {
let mut ch = vec![cond, then];
ch.extend(els);
self.push(NodeData::If, ch)
}
pub fn while_(&mut self, cond: JavaId, body: JavaId) -> JavaId {
self.push(NodeData::While, vec![cond, body])
}
pub fn do_while(&mut self, body: JavaId, cond: JavaId) -> JavaId {
self.push(NodeData::DoWhile, vec![body, cond])
}
pub fn for_(
&mut self,
inits: Vec<JavaId>,
cond: Option<JavaId>,
steps: Vec<JavaId>,
body: JavaId,
) -> JavaId {
let ni = inits.len();
let ns = steps.len();
let mut ch = inits;
let has_cond = cond.is_some();
ch.extend(cond);
ch.extend(steps);
ch.push(body);
self.push(
NodeData::For {
inits: ni as u8,
has_cond,
steps: ns as u8,
},
ch,
)
}
pub fn for_each(&mut self, name: &str, ty: JType, iterable: JavaId, body: JavaId) -> JavaId {
self.push(
NodeData::ForEach {
name: name.into(),
ty,
},
vec![iterable, body],
)
}
pub fn yield_(&mut self, expr: JavaId) -> JavaId {
self.push(NodeData::Yield, vec![expr])
}
pub fn ret(&mut self, value: Option<JavaId>) -> JavaId {
self.push(NodeData::Return, value.into_iter().collect())
}
pub fn break_(&mut self, label: Option<&str>) -> JavaId {
self.push(
NodeData::Break {
label: label.map(Into::into),
},
vec![],
)
}
pub fn continue_(&mut self, label: Option<&str>) -> JavaId {
self.push(
NodeData::Continue {
label: label.map(Into::into),
},
vec![],
)
}
pub fn throw(&mut self, e: JavaId) -> JavaId {
self.push(NodeData::Throw, vec![e])
}
pub fn try_(
&mut self,
resources: Vec<JavaId>,
try_block: JavaId,
catches: Vec<JavaId>,
finally: Option<JavaId>,
) -> JavaId {
let mut ch = resources;
ch.push(try_block);
ch.extend(catches);
ch.extend(finally);
self.push(NodeData::Try, ch)
}
pub fn catch_(&mut self, ty_raw: &str, name: &str, block: JavaId) -> JavaId {
self.push(
NodeData::Catch {
ty_raw: ty_raw.into(),
name: name.into(),
},
vec![block],
)
}
pub fn synchronized(&mut self, lock: JavaId, block: JavaId) -> JavaId {
self.push(NodeData::Synchronized, vec![lock, block])
}
pub fn switch_(&mut self, subject: JavaId, cases: Vec<JavaId>) -> JavaId {
let mut ch = vec![subject];
ch.extend(cases);
self.push(NodeData::Switch, ch)
}
pub fn case_(
&mut self,
labels: Vec<JavaId>,
is_default: bool,
arrow: bool,
stmts: Vec<JavaId>,
) -> JavaId {
let n = labels.len() as u16;
let mut ch = labels;
ch.extend(stmts);
self.push(
NodeData::Case {
labels: n,
is_default,
arrow,
},
ch,
)
}
pub fn label(&mut self, name: &str, stmt: JavaId) -> JavaId {
self.push(NodeData::Label { name: name.into() }, vec![stmt])
}
pub fn assert_(&mut self, cond: JavaId, msg: Option<JavaId>) -> JavaId {
let mut ch = vec![cond];
ch.extend(msg);
self.push(NodeData::Assert, ch)
}
pub fn raw(&mut self, text: &str) -> JavaId {
self.push(NodeData::Raw { text: text.into() }, vec![])
}
pub fn bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
self.push(NodeData::Binary { op }, vec![l, r])
}
pub fn un(&mut self, op: UnOp, e: JavaId) -> JavaId {
self.push(NodeData::Unary { op }, vec![e])
}
pub fn call(&mut self, callee: JavaId, args: Vec<JavaId>) -> JavaId {
let mut ch = vec![callee];
ch.extend(args);
self.push(NodeData::Call, ch)
}
pub fn member(&mut self, obj: JavaId, name: &str) -> JavaId {
let sym = self.intern_sym(name);
self.push(NodeData::Member { name: sym }, vec![obj])
}
pub fn index(&mut self, arr: JavaId, idx: JavaId) -> JavaId {
self.push(NodeData::Index, vec![arr, idx])
}
pub fn cast(&mut self, ty: JType, e: JavaId) -> JavaId {
self.push(NodeData::Cast { ty }, vec![e])
}
pub fn paren(&mut self, e: JavaId) -> JavaId {
self.push(NodeData::Paren, vec![e])
}
pub fn ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
self.push(NodeData::Ternary, vec![c, a, b])
}
pub fn new_(&mut self, ty: JType, args: Vec<JavaId>) -> JavaId {
self.push(NodeData::New { ty, anon_raw: None }, args)
}
pub fn new_anon(&mut self, ty: JType, args: Vec<JavaId>, body_raw: String) -> JavaId {
self.push(
NodeData::New {
ty,
anon_raw: Some(body_raw),
},
args,
)
}
pub fn new_array(
&mut self,
ty: JType,
dims: u16,
sized: u16,
sizes: Vec<JavaId>,
init: Option<JavaId>,
) -> JavaId {
let mut ch = sizes;
ch.extend(init);
self.push(NodeData::NewArray { ty, dims, sized }, ch)
}
pub fn group(&mut self, stmts: Vec<JavaId>) -> JavaId {
self.push(NodeData::Group, stmts)
}
pub fn array_lit(&mut self, elems: Vec<JavaId>) -> JavaId {
self.push(NodeData::ArrayLit, elems)
}
pub fn var(&mut self, name: &str) -> JavaId {
let sym = self.intern_sym(name);
self.push(NodeData::VarRef { name: sym }, vec![])
}
pub fn lit(&mut self, l: Lit) -> JavaId {
self.push(NodeData::Literal(l), vec![])
}
fn build_int_impl(&mut self, v: i64, long: bool) -> JavaId {
if long {
self.lit(Lit::Long(v))
} else {
self.lit(Lit::Int(v))
}
}
pub fn this(&mut self) -> JavaId {
self.push(NodeData::This, vec![])
}
pub fn super_(&mut self) -> JavaId {
self.push(NodeData::Super, vec![])
}
pub fn instance_of(&mut self, e: JavaId, ty: JType, bind: Option<&str>) -> JavaId {
self.push(
NodeData::InstanceOf {
ty,
bind: bind.map(Into::into),
},
vec![e],
)
}
pub fn lambda(&mut self, params_raw: &str, body: JavaId) -> JavaId {
self.push(
NodeData::Lambda {
params_raw: params_raw.into(),
},
vec![body],
)
}
pub fn method_ref(&mut self, receiver: JavaId, name: &str) -> JavaId {
let sym = self.intern_sym(name);
self.push(NodeData::MethodRef { name: sym }, vec![receiver])
}
pub fn method_call(&mut self, obj: JavaId, name: &str, args: Vec<JavaId>) -> JavaId {
let m = self.member(obj, name);
self.call(m, args)
}
pub fn plain_call(&mut self, name: &str, args: Vec<JavaId>) -> JavaId {
let f = self.var(name);
self.call(f, args)
}
pub fn var_type(&self, id: JavaId) -> Option<&JType> {
self.var_types.get(id.0 as usize).and_then(|t| t.as_ref())
}
pub fn any_string_identity(&self, roots: &[JavaId]) -> bool {
roots.iter().any(|&r| {
let hit = Self::scan_string_identity(self, r);
if hit {
self.string_identity.set(Some((r, true)));
}
hit
})
}
pub fn has_string_identity_compare(&self, root: JavaId) -> bool {
if let Some((r, v)) = self.string_identity.get() {
if r == root {
return v;
}
}
let v = Self::scan_string_identity(self, root);
self.string_identity.set(Some((root, v)));
v
}
fn scan_string_identity(&self, root: JavaId) -> bool {
let mut stack = vec![root];
while let Some(id) = stack.pop() {
if let NodeData::Binary { op: BinOp::Eq | BinOp::Ne } = self.data(id) {
let ch = self.children(id);
if ch.len() == 2
&& self.expr_maybe_string(ch[0])
&& self.expr_maybe_string(ch[1])
{
return true;
}
}
for &c in self.children(id) {
stack.push(c);
}
}
false
}
fn expr_maybe_string(&self, id: JavaId) -> bool {
match self.data(id) {
NodeData::Literal(Lit::Str(_)) => true,
NodeData::Literal(_) | NodeData::This | NodeData::Super
| NodeData::NewArray { .. } | NodeData::ArrayLit | NodeData::InstanceOf { .. } => false,
NodeData::VarRef { .. } => self.var_type(id).map_or(true, Self::type_maybe_string),
NodeData::Cast { ty } => Self::type_maybe_string(ty),
NodeData::Paren => self
.children(id)
.first()
.map_or(true, |&c| self.expr_maybe_string(c)),
NodeData::Binary { op } if *op == BinOp::Add => {
self.children(id).iter().any(|&c| self.expr_maybe_string(c))
}
NodeData::Binary { .. } => false,
NodeData::Ternary => self
.children(id)
.get(1..)
.map_or(true, |cs| cs.iter().any(|&c| self.expr_maybe_string(c))),
_ => true,
}
}
fn type_maybe_string(t: &JType) -> bool {
match t {
JType::Bool | JType::Byte | JType::Short | JType::Int | JType::Long
| JType::Char | JType::Float | JType::Double | JType::Void
| JType::Array(_) => false, JType::Var => true, JType::Ref(n) => {
let base = n.split('<').next().unwrap_or(n).trim();
let last = base.rsplit('.').next().unwrap_or(base);
matches!(
last,
"String" | "Object" | "CharSequence" | "Comparable" | "Serializable"
) || n.contains('<') || (last.len() <= 2 && last.chars().next().is_some_and(|c| c.is_uppercase()))
}
}
}
pub fn set_param_scope(&mut self, params: &[(String, JType)]) {
self.param_scope = params.to_vec();
}
pub fn is_param_name(&self, name: &str) -> bool {
self.param_scope.iter().any(|(n, _)| n == name)
}
pub fn set_facts(&mut self, facts: JavaFacts) {
self.facts = Some(facts);
}
pub fn clear_facts(&mut self) {
self.facts = None;
}
pub fn name_of_key(&self, k: u32) -> Option<String> {
Some(self.names.name(k).to_string())
}
pub fn clear_param_scope(&mut self) {
self.param_scope.clear();
}
}
fn type_base_name(n: &str) -> &str {
let no_args = match n.find('<') {
Some(i) => &n[..i],
None => n,
};
no_args.rsplit('.').next().unwrap_or(no_args)
}
fn fold_i64(op: BinOp, a: i64, b: i64) -> Option<i64> {
use BinOp::*;
Some(match op {
Add => a.wrapping_add(b),
Sub => a.wrapping_sub(b),
Mul => a.wrapping_mul(b),
Div => {
if b == 0 {
return None;
}
a.wrapping_div(b)
}
Rem => {
if b == 0 {
return None;
}
a.wrapping_rem(b)
}
Shl => a.wrapping_shl((b as u64 & 63) as u32),
Shr => a.wrapping_shr((b as u64 & 63) as u32),
UShr => ((a as u64).wrapping_shr((b as u64 & 63) as u32)) as i64,
BitAnd => a & b,
BitXor => a ^ b,
BitOr => a | b,
_ => return None,
})
}
fn fold_i32(op: BinOp, a: i64, b: i64) -> Option<i64> {
use BinOp::*;
let (x, y) = (a as i32, b as i32);
Some(match op {
Add => x.wrapping_add(y) as i64,
Sub => x.wrapping_sub(y) as i64,
Mul => x.wrapping_mul(y) as i64,
Div => {
if y == 0 {
return None;
}
x.wrapping_div(y) as i64
}
Rem => {
if y == 0 {
return None;
}
x.wrapping_rem(y) as i64
}
Shl => x.wrapping_shl((y as u32) & 31) as i64,
Shr => x.wrapping_shr((y as u32) & 31) as i64,
UShr => (x as u32).wrapping_shr((y as u32) & 31) as i64,
BitAnd => (x & y) as i64,
BitXor => (x ^ y) as i64,
BitOr => (x | y) as i64,
_ => return None,
})
}
fn cmp_i64(op: BinOp, x: i64, y: i64) -> Option<bool> {
use BinOp::*;
Some(match op {
Lt => x < y,
Le => x <= y,
Gt => x > y,
Ge => x >= y,
Eq => x == y,
Ne => x != y,
_ => return None,
})
}
impl Lang for JavaAst {
type Id = JavaId;
type NameKey = u32;
fn kind(&self, id: JavaId) -> NodeKind {
JavaAst::kind(self, id)
}
fn children(&self, id: JavaId) -> &[JavaId] {
JavaAst::children(self, id)
}
fn set_child(&mut self, parent: JavaId, index: usize, new: JavaId) {
self.nodes[parent.0 as usize].children.set(index, new);
}
fn remove_child(&mut self, parent: JavaId, index: usize) {
self.nodes[parent.0 as usize].children.remove(index);
}
fn splice(&mut self, node: JavaId, index: usize, remove: usize, insert: Vec<JavaId>) {
self.nodes[node.0 as usize].children.splice(index, remove, insert);
}
fn build_return(&mut self, value: Option<JavaId>) -> JavaId {
self.ret(value)
}
fn build_block(&mut self, stmts: Vec<JavaId>) -> JavaId {
self.block(stmts)
}
fn build_unary(&mut self, op: UnOp, operand: JavaId) -> JavaId {
self.un(op, operand)
}
fn build_bool(&mut self, v: bool) -> JavaId {
self.lit(Lit::Bool(v))
}
fn build_int(&mut self, v: i64, long: bool) -> JavaId {
self.build_int_impl(v, long)
}
fn build_str(&mut self, s: &str) -> JavaId {
self.lit(Lit::Str(s.to_string()))
}
fn build_char(&mut self, c: char) -> JavaId {
self.lit(Lit::Char(c))
}
fn build_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> JavaId {
self.bin(op, l, r)
}
fn build_ternary(&mut self, c: JavaId, a: JavaId, b: JavaId) -> JavaId {
self.ternary(c, a, b)
}
fn build_assign(&mut self, target: JavaId, value: JavaId) -> JavaId {
self.assign(target, value)
}
fn fold_lit_bin(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<JavaId> {
let ll = self.literal(l)?;
let rr = self.literal(r)?;
if op == BinOp::Add {
let str_of = |x: LitRef<'_>| -> Option<String> {
match x {
LitRef::Str(s) => Some(s.to_string()),
LitRef::Char(c) => Some(c.to_string()),
LitRef::Int(v) => Some(v.to_string()),
LitRef::Long(v) => Some(v.to_string()),
LitRef::Bool(b) => Some(b.to_string()),
_ => None,
}
};
if matches!(ll, LitRef::Str(_)) || matches!(rr, LitRef::Str(_)) {
if let (Some(a), Some(b)) = (str_of(ll), str_of(rr)) {
return Some(self.build_str(&format!("{a}{b}")));
}
}
}
let int_of = |x: LitRef<'_>| -> Option<i64> {
match x {
LitRef::Int(v) | LitRef::Long(v) => Some(v),
LitRef::Char(c) => Some(c as u32 as i64),
_ => None,
}
};
let (a, b) = (int_of(ll)?, int_of(rr)?);
let wide = matches!(ll, LitRef::Long(_)) || matches!(rr, LitRef::Long(_));
let v = if wide { fold_i64(op, a, b) } else { fold_i32(op, a, b) }?;
Some(self.build_int(v, wide))
}
fn fold_lit_cmp(&mut self, op: BinOp, l: JavaId, r: JavaId) -> Option<bool> {
let ll = self.literal(l)?;
let rr = self.literal(r)?;
let int_of = |x: LitRef<'_>| -> Option<i64> {
match x {
LitRef::Int(v) | LitRef::Long(v) => Some(v),
LitRef::Char(c) => Some(c as u32 as i64),
_ => None,
}
};
if let (Some(x), Some(y)) = (int_of(ll), int_of(rr)) {
return cmp_i64(op, x, y);
}
if let (LitRef::Bool(x), LitRef::Bool(y)) = (ll, rr) {
if matches!(op, BinOp::Eq | BinOp::Ne) {
return Some(if op == BinOp::Eq { x == y } else { x != y });
}
}
None
}
fn fold_lit_neg(&mut self, v: i64, wide: bool) -> Option<i64> {
let n = -v;
if !wide && n > i32::MAX as i64 {
return None;
}
Some(n)
}
fn reassoc_delta(
&mut self,
op1: BinOp,
k1: i64,
op2: BinOp,
k2: i64,
wide: bool,
) -> Option<ReassocOutcome<JavaId>> {
use BinOp::*;
let raw: i64 = match (op1, op2) {
(BitXor, BitXor) => k1 ^ k2,
(a, b) if matches!(a, Add | Sub) && matches!(b, Add | Sub) => {
let sign = |o: BinOp| if o == Sub { -1i64 } else { 1i64 };
sign(a) * k1 + sign(b) * k2
}
_ => return None,
};
let d = if wide { raw } else { raw as i32 as i64 };
if d == 0 {
return Some(ReassocOutcome::Neutral);
}
if op1 == BitXor && op2 == BitXor {
return Some(ReassocOutcome::Lit(self.build_int(d, wide), BitXor));
}
if d > 0 {
Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
} else {
let nd = if wide { d.checked_neg().unwrap_or(d) } else { (d as i32).wrapping_neg() as i64 };
if nd > 0 {
Some(ReassocOutcome::Lit(self.build_int(nd, wide), Sub))
} else {
Some(ReassocOutcome::Lit(self.build_int(d, wide), Add))
}
}
}
fn copy_subtree(&mut self, id: JavaId) -> JavaId {
let old_children: Vec<JavaId> = self.nodes[id.0 as usize].children.as_slice().to_vec();
let mut children = Vec::with_capacity(old_children.len());
for c in old_children {
children.push(self.copy_subtree(c));
}
let data = self.nodes[id.0 as usize].data.clone();
self.push(data, children)
}
fn effect(&self, id: JavaId) -> Effect {
if let Some(e) = self.effect_cache.get(id.0 as usize).and_then(|x| *x) {
return e;
}
let mut e = self.own_effect(id);
for &c in self.nodes[id.0 as usize].children.as_slice() {
e = e.worst(self.effect(c));
}
e
}
fn invalidate_effect(&mut self, id: JavaId) {
if let Some(slot) = self.effect_cache.get_mut(id.0 as usize) {
*slot = None;
}
self.events.invalidate(id.0 as usize);
}
fn debug_verify_events(&self, stmt: JavaId) {
if !verify_events_on() {
return;
}
if let Some(indexed) = self.region_events(stmt) {
let fresh = cure_tree::collect_region_events(self, stmt);
let key = |e: &cure_engine::kind::RegionEvent<JavaId, u32>| {
(e.key, matches!(e.kind, cure_engine::kind::EventKind::Use), e.node.0)
};
let a: Vec<_> = indexed.iter().map(key).collect();
let b: Vec<_> = fresh.iter().map(key).collect();
if a != b {
eprintln!(
"[EVENTS-STALE] stmt #{} kind={:?}: indexed {} events, fresh {} events",
stmt.0, self.kind(stmt), a.len(), b.len()
);
}
}
}
fn region_events(
&self,
id: JavaId,
) -> Option<&[cure_engine::kind::RegionEvent<JavaId, u32>]> {
let v = self.events.slot(id.0 as usize);
if v.is_empty() {
None
} else {
Some(v)
}
}
fn var_key(&self, id: JavaId) -> Option<u32> {
self.names.var_key(id.0 as usize)
}
fn fact_single_def(&self, key: u32) -> bool {
match (&self.facts, self.names.name(key)) {
(Some(f), n) => f.var(n).single_def,
_ => false,
}
}
fn fact_single_use(&self, key: u32) -> bool {
match (&self.facts, self.names.name(key)) {
(Some(f), n) => f.var(n).single_use,
_ => false,
}
}
fn fact_bool_valued(&self, key: u32) -> bool {
match (&self.facts, self.names.name(key)) {
(Some(f), n) => f.var(n).bool_valued,
_ => false,
}
}
fn fact_call_effect(&self, name: &str) -> Option<cure_engine::Effect> {
self.facts
.as_ref()
.and_then(|f| {
f.call_effects.get(name).map(|set| {
set.iter().copied().fold(Effect::Pure, Effect::worst)
})
})
}
fn node_index(&self, id: JavaId) -> usize {
id.0 as usize
}
fn id_of_index(&self, idx: usize) -> JavaId {
JavaId(idx as u32)
}
fn is_opaque(&self, id: JavaId) -> bool {
match self.data(id) {
NodeData::Raw { .. } => true,
NodeData::New { anon_raw: Some(_), .. } => true,
_ => false,
}
}
fn own_effect(&self, id: JavaId) -> Effect {
match self.data(id) {
NodeData::Literal(_)
| NodeData::This
| NodeData::Super
| NodeData::Paren
| NodeData::Block
| NodeData::Group
| NodeData::Empty
| NodeData::VarDecl { .. }
| NodeData::Break { .. }
| NodeData::Continue { .. }
| NodeData::Try
| NodeData::Catch { .. }
| NodeData::Switch
| NodeData::Case { .. }
| NodeData::Label { .. }
| NodeData::ExprStmt
| NodeData::Ternary
| NodeData::ArrayLit
| NodeData::Lambda { .. }
| NodeData::MethodRef { .. }
| NodeData::InstanceOf { .. } => Effect::Pure,
NodeData::VarRef { .. } => Effect::MayRead,
NodeData::Member { .. } => Effect::MayThrow,
NodeData::Index => Effect::MayThrow,
NodeData::New { .. } | NodeData::NewArray { .. } => Effect::MayThrow,
NodeData::Call => Effect::Unknown,
NodeData::Assign { .. } => Effect::MayWrite,
NodeData::Unary { op } => {
if op.is_incdec() {
Effect::MayWrite
} else {
Effect::Pure
}
}
NodeData::Binary { op } => {
if op.may_div_zero() {
Effect::MayThrow
} else {
Effect::Pure
}
}
NodeData::Cast { ty } => {
if ty.is_ref() {
Effect::MayThrow
} else {
Effect::Pure
}
}
NodeData::For { .. } | NodeData::ForEach { .. } | NodeData::If => Effect::MayRead,
NodeData::While | NodeData::DoWhile => Effect::MayRead,
NodeData::Return | NodeData::Yield => Effect::MayThrow,
NodeData::Throw => Effect::MayThrow,
NodeData::Assert => Effect::MayThrow,
NodeData::Synchronized => Effect::MayWrite,
NodeData::Raw { .. } => Effect::Unknown,
}
}
fn is_bool(&self, id: JavaId) -> bool {
match self.data(id) {
NodeData::Literal(l) => matches!(l, Lit::Bool(_)),
NodeData::Unary { op } => *op == UnOp::Not,
NodeData::Binary { op } => {
op.is_comparison() || matches!(op, BinOp::And | BinOp::Or)
}
NodeData::Ternary => {
let ch = self.children(id);
self.is_bool(ch[1]) && self.is_bool(ch[2])
}
NodeData::Paren => self.is_bool(self.children(id)[0]),
NodeData::InstanceOf { .. } => true,
NodeData::VarRef { .. } => self
.var_types
.get(id.0 as usize)
.and_then(|t| t.as_ref())
.is_some_and(|t| t.is_bool()),
_ => false,
}
}
fn is_exact_int(&self, id: JavaId) -> bool {
match self.data(id) {
NodeData::Literal(l) => matches!(
l,
Lit::Int(_) | Lit::Long(_) | Lit::Char(_) | Lit::NumRaw { val: NumVal::Int(_) | NumVal::Long(_), .. }
),
NodeData::VarRef { .. } => {
self.var_types.get(id.0 as usize).and_then(|t| t.as_ref()).is_some_and(|t| t.is_integral())
}
NodeData::Binary { op } => {
let ch = self.children(id);
(op.is_arith() || op.is_comparison())
&& self.is_exact_int(ch[0])
&& self.is_exact_int(ch[1])
}
NodeData::Cast { ty } => ty.is_integral(),
NodeData::Paren => self.is_exact_int(self.children(id)[0]),
NodeData::Ternary => {
let ch = self.children(id);
self.is_exact_int(ch[1]) && self.is_exact_int(ch[2])
}
_ => false,
}
}
fn is_local_var(&self, id: JavaId) -> bool {
matches!(self.data(id), NodeData::VarRef { .. }) && self.var_type(id).is_some()
}
fn is_inferred_decl(&self, decl: JavaId) -> bool {
matches!(self.data(decl), NodeData::VarDecl { ty: JType::Var, .. })
}
fn is_char_decl(&self, decl: JavaId) -> bool {
matches!(self.data(decl), NodeData::VarDecl { ty: JType::Char, .. })
}
fn has_pattern_bindings(&self, root: JavaId) -> bool {
let mut stack = vec![root];
while let Some(n) = stack.pop() {
if let NodeData::InstanceOf { bind: Some(_), .. } = self.data(n) {
return true;
}
for &c in self.children(n) {
stack.push(c);
}
}
false
}
fn is_long_decl(&self, decl: JavaId) -> bool {
matches!(self.data(decl), NodeData::VarDecl { ty: JType::Long, .. })
}
fn is_wide_decl(&self, decl: JavaId) -> bool {
!matches!(
self.data(decl),
NodeData::VarDecl { ty: JType::Byte | JType::Short | JType::Char, .. }
)
}
fn receiver_propagation_unsound(
&self,
decl: JavaId,
value: JavaId,
use_parent: Option<JavaId>,
use_at_head: bool,
) -> bool {
let is_receiver = matches!(use_parent, Some(p) if {
matches!(self.data(p), NodeData::Member { .. }) && use_at_head
});
if is_receiver {
if self.receiver_boxing_unsound(decl, value) {
return true;
}
if self.receiver_new_type_unsound(decl, value) {
return true;
}
let raw_ref = match self.data(decl) {
NodeData::VarDecl { ty: JType::Ref(n), .. } => {
!n.contains('<')
&& n.rsplit('.').next().is_some_and(|base| {
self.raw_generic_names.contains(base)
})
}
_ => false,
};
if !raw_ref {
return false;
}
match self.data(value) {
NodeData::Call => true,
NodeData::New { ty, .. } => {
matches!(ty, JType::Ref(n) if n.contains('<'))
}
_ => false,
}
} else {
if matches!(use_parent, Some(p) if {
matches!(self.data(p), NodeData::Index) && use_at_head
}) {
return self.index_base_newarray_unsound(decl, value);
}
false
}
}
fn bin_op(&self, id: JavaId) -> Option<BinOp> {
match self.data(id) {
NodeData::Binary { op } => Some(*op),
_ => None,
}
}
fn un_op(&self, id: JavaId) -> Option<UnOp> {
match self.data(id) {
NodeData::Unary { op } => Some(*op),
_ => None,
}
}
fn literal(&self, id: JavaId) -> Option<LitRef<'_>> {
match self.data(id) {
NodeData::Literal(l) => Some(match l {
Lit::Bool(b) => LitRef::Bool(*b),
Lit::Int(v) => LitRef::Int(*v),
Lit::Long(v) => LitRef::Long(*v),
Lit::Float(v) => LitRef::Float(*v),
Lit::Double(v) => LitRef::Double(*v),
Lit::Char(c) => LitRef::Char(*c),
Lit::Str(s) => LitRef::Str(s),
Lit::TextBlock(_) => return None,
Lit::NumRaw { val, .. } => match val {
NumVal::Int(v) => LitRef::Int(*v),
NumVal::Long(v) => LitRef::Long(*v),
NumVal::Float(v) => LitRef::Float(*v),
NumVal::Double(v) => LitRef::Double(*v),
},
Lit::Null => LitRef::Null,
}),
_ => None,
}
}
fn var_name(&self, id: JavaId) -> Option<&str> {
match self.data(id) {
NodeData::VarRef { name } => Some(self.sn(*name)),
NodeData::VarDecl { name, .. } => Some(name),
NodeData::ForEach { name, .. } | NodeData::Catch { name, .. } => Some(name),
_ => None,
}
}
fn member_name(&self, id: JavaId) -> Option<&str> {
match self.data(id) {
NodeData::Member { name } => Some(self.sn(*name)),
NodeData::MethodRef { name } => Some(self.sn(*name)),
_ => None,
}
}
fn is_field_name(&self, name: &str) -> bool {
self.field_types.contains_key(name)
}
fn payload_equal(&self, a: JavaId, b: JavaId) -> bool {
if let (NodeData::Literal(x), NodeData::Literal(y)) = (self.data(a), self.data(b)) {
return lit_value_eq(x, y);
}
self.data(a) == self.data(b)
}
fn assign_op(&self, id: JavaId) -> Option<BinOp> {
match self.data(id) {
NodeData::Assign { op } => *op,
_ => None,
}
}
fn prepare(&mut self, root: JavaId) {
if cnt_prepare_on() {
PREPARE_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
PREPARE_NODES.fetch_add(self.nodes.len(), std::sync::atomic::Ordering::Relaxed);
}
let n_nodes = self.nodes.len();
let key_slots = self.names.key_slots();
if key_slots < n_nodes {
let names: Vec<Option<String>> = (key_slots..n_nodes)
.map(|i| self.var_name(JavaId(i as u32)).map(|n| n.to_string()))
.collect();
self.names.extend_owned(key_slots, &names);
}
self.string_identity.set(None);
self.events.resize(n_nodes);
if self.events.is_clean() {
if self.prepared_root == Some(root) {
return;
}
self.rebuild_var_types(root);
self.prepared_root = Some(root);
return;
}
self.prepared_root = Some(root);
self.events.mark_clean();
for sr in cure_tree::stmt_roots(self, root) {
let idx = sr.0 as usize;
if self.events.slot(idx).is_empty() {
let events = cure_tree::collect_region_events(self, sr);
self.events.put(idx, events);
}
}
let effects = cure_tree::rebuild_effects(self, n_nodes);
self.effect_cache = effects;
self.rebuild_var_types(root);
}
}
impl JavaAst {
pub(crate) fn rebuild_var_types(&mut self, root: JavaId) {
self.var_types.clear();
self.var_types.resize(self.nodes.len(), None);
#[derive(Clone)]
enum Frame {
Enter(JavaId),
Exit,
PopScope,
Bind(String, JType),
}
let mut base: HashMap<String, JType> = HashMap::new();
for (n, t) in &self.param_scope {
base.insert(n.clone(), t.clone());
}
let mut scopes: Vec<HashMap<String, JType>> = vec![base];
let mut stack = vec![Frame::Enter(root)];
while let Some(f) = stack.pop() {
match f {
Frame::Enter(id) => {
let node = &self.nodes[id.0 as usize];
enum Action {
None,
Scope,
Bind(String, JType),
}
let action = match &node.data {
NodeData::Block | NodeData::For { .. } | NodeData::Try => Action::Scope,
NodeData::VarDecl { name, ty, .. } => {
scopes.last_mut().unwrap().insert(name.clone(), ty.clone());
Action::None
}
NodeData::VarRef { name } => {
if let Some(t) = scopes
.iter()
.rev()
.find_map(|s| s.get(self.sn(*name)))
.cloned()
{
self.var_types[id.0 as usize] = Some(t);
}
Action::None
}
NodeData::Catch { name, .. } => {
Action::Bind(name.clone(), JType::Var)
}
NodeData::ForEach { name, ty } => {
Action::Bind(name.clone(), ty.clone())
}
_ => Action::None,
};
stack.push(Frame::Exit);
if matches!(action, Action::Scope | Action::Bind(_, _)) {
scopes.push(HashMap::new());
stack.push(Frame::PopScope);
}
for &c in node.children.as_slice().iter().rev() {
stack.push(Frame::Enter(c));
}
if let Action::Bind(n, t) = action {
stack.push(Frame::Bind(n, t));
}
}
Frame::Bind(name, ty) => {
scopes.last_mut().unwrap().insert(name, ty);
}
Frame::PopScope => {
scopes.pop();
}
Frame::Exit => {}
}
}
}
}
impl JavaAst {
fn receiver_new_type_unsound(&self, decl: JavaId, value: JavaId) -> bool {
let NodeData::New { ty, .. } = self.data(value) else {
return false;
};
let decl_ty = match self.data(decl) {
NodeData::VarDecl { ty, .. } => ty,
_ => return false,
};
if matches!(decl_ty, JType::Var) {
return false;
}
let name = match ty {
JType::Ref(n) => n.as_str(),
_ => return true,
};
if name.ends_with("<>") {
return true;
}
match decl_ty {
JType::Ref(d) => type_base_name(d) != type_base_name(name),
_ => true,
}
}
fn index_base_newarray_unsound(&self, decl: JavaId, value: JavaId) -> bool {
let NodeData::NewArray { ty, dims, .. } = self.data(value) else {
return false;
};
let decl_ty = match self.data(decl) {
NodeData::VarDecl { ty, .. } => ty,
_ => return false,
};
if matches!(decl_ty, JType::Var) {
return false;
}
let mut elem: &JType = decl_ty;
let mut nest = 0u16;
while let JType::Array(inner) = elem {
elem = inner.as_ref();
nest += 1;
}
nest != *dims || *elem != *ty
}
fn receiver_boxing_unsound(&self, decl: JavaId, value: JavaId) -> bool {
let declared_ref = match self.data(decl) {
NodeData::VarDecl { ty, .. } => ty.is_ref(),
_ => false,
};
if !declared_ref {
return false;
}
match self.data(value) {
NodeData::Literal(
Lit::Int(_) | Lit::Long(_) | Lit::Float(_) | Lit::Double(_) | Lit::Bool(_)
| Lit::Char(_),
) => true,
NodeData::VarRef { .. } => matches!(
self.var_type(value),
Some(
JType::Int
| JType::Long
| JType::Byte
| JType::Short
| JType::Char
| JType::Float
| JType::Double
| JType::Bool
)
),
NodeData::Unary { .. } | NodeData::Binary { .. } => true,
NodeData::Ternary => true,
NodeData::Cast { .. } => true,
NodeData::InstanceOf { .. } => true,
NodeData::Call | NodeData::Member { .. } | NodeData::Index => {
match self.data(decl) {
NodeData::VarDecl { ty: JType::Ref(n), .. } => {
let boxed = [
"Character", "Integer", "Long", "Boolean", "Double",
"Float", "Byte", "Short", "Number", "AtomicInteger",
"AtomicLong",
];
n.rsplit('.').next().is_some_and(|b| boxed.contains(&b))
}
_ => false,
}
}
_ => false,
}
}
pub fn intern_sym(&mut self, name: &str) -> Sym {
Sym(self.names.intern(name))
}
pub fn sn(&self, sym: Sym) -> &str {
self.names.name(sym.0)
}
}
pub fn unit_metrics(ast: &JavaAst, unit: &CompilationUnit) -> TreeMetrics {
let mut total = TreeMetrics::default();
for ty in &unit.types {
metrics_of_type(ast, ty, &mut total);
}
total
}
fn metrics_of_type(ast: &JavaAst, ty: &TypeDecl, total: &mut TreeMetrics) {
for m in &ty.members {
match m {
Member::Method { body: Some(b), .. } | Member::Constructor { body: Some(b), .. } => {
total.add(subtree_metrics(ast, *b));
}
Member::Initializer { body, .. } => {
total.add(subtree_metrics(ast, *body));
}
Member::Type(t) => metrics_of_type(ast, t, total),
_ => {}
}
}
}
static ENV_CACHED: std::sync::OnceLock<(bool, bool)> = std::sync::OnceLock::new();
fn env_flags() -> (bool, bool) {
*ENV_CACHED.get_or_init(|| {
(
std::env::var("CURE_CNT_PREPARE").is_ok(),
std::env::var("CURE_VERIFY_EVENTS").is_ok(),
)
})
}
fn cnt_prepare_on() -> bool { env_flags().0 }
fn verify_events_on() -> bool { env_flags().1 }
pub static PREPARE_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static PREPARE_NODES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub static COLLECT_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_smoke() {
let mut a = JavaAst::new();
let one = a.lit(Lit::Int(1));
let x = a.var_decl("x", JType::Int, Some(one));
let xv = a.var("x");
let r = a.ret(Some(xv));
let body = a.block(vec![x, r]);
assert_eq!(a.kind(body), NodeKind::Block);
assert_eq!(a.children(body).len(), 2);
}
#[test]
fn effect_aggregation() {
let mut a = JavaAst::new();
let f = a.plain_call("foo", vec![]);
let r = a.ret(Some(f));
let body = a.block(vec![r]);
a.prepare(body);
assert_eq!(a.effect(f), Effect::Unknown);
assert_eq!(a.effect(r), Effect::Unknown);
assert_eq!(a.effect(body), Effect::Unknown);
let mut b = JavaAst::new();
let two = b.lit(Lit::Int(2));
let three = b.lit(Lit::Int(3));
let add = b.bin(BinOp::Add, two, three);
let r2 = b.ret(Some(add));
let body2 = b.block(vec![r2]);
b.prepare(body2);
assert_eq!(b.effect(add), Effect::Pure);
assert_eq!(b.effect(r2), Effect::MayThrow);
assert_eq!(b.effect(body2), Effect::MayThrow);
}
#[test]
fn var_type_resolution_and_shadowing() {
let mut a = JavaAst::new();
let one = a.lit(Lit::Int(1));
let b2_decl = a.var_decl("b2", JType::Int, Some(one));
let inner_b2 = a.var("b2");
let inner_stmt = a.expr_stmt(inner_b2);
let inner = a.block(vec![b2_decl, inner_stmt]);
let tv = a.lit(Lit::Bool(true));
let decl_b = a.var_decl("b", JType::Bool, Some(tv));
let outer_b = a.var("b");
let outer_stmt = a.expr_stmt(outer_b);
let body = a.block(vec![decl_b, inner, outer_stmt]);
a.prepare(body);
assert!(a.is_bool(outer_b));
assert!(a.var_type(inner_b2).is_some());
}
#[test]
fn raw_and_foreach_semantics() {
let mut a = JavaAst::new();
let one = a.lit(Lit::Int(1));
let dx = a.var_decl("x", JType::Int, Some(one));
let r = a.raw("broken ~!@ code");
let sv = a.var("s");
let call = a.method_call(sv, "len", vec![]);
let es = a.expr_stmt(call);
let body = a.block(vec![es]);
let lst = a.var("list");
let fe = a.for_each("s", JType::Ref("String".into()), lst, body);
let all = a.block(vec![dx, r, fe]);
a.prepare(all);
assert_eq!(a.kind(r), NodeKind::Raw);
assert_eq!(a.effect(r), Effect::Unknown);
assert!(a.var_type(sv).is_some()); }
#[test]
fn num_raw_literal() {
let mut a = JavaAst::new();
let l = a.lit(Lit::NumRaw {
text: "0x1F".into(),
val: NumVal::Int(31),
});
a.prepare(l);
assert!(a.is_exact_int(l));
assert!(matches!(a.literal(l), Some(LitRef::Int(31))));
}
}