1use std::collections::{HashMap, HashSet};
2use crate::ir::{IrFunction, IrStatement, BorrowKind};
3use crate::debug_println;
4
5fn returns_reference(return_type: &str) -> bool {
7 return_type.contains('&') && !return_type.contains("&&") }
10
11#[derive(Debug, Clone, PartialEq)]
13pub struct InferredLifetime {
14 pub name: String,
15 pub start: usize,
17 pub end: usize,
19 pub dependencies: HashSet<String>,
21}
22
23pub struct LifetimeInferencer {
25 lifetimes: HashMap<String, InferredLifetime>,
27 last_use: HashMap<String, usize>,
29 first_def: HashMap<String, usize>,
31 lifetime_counter: usize,
33}
34
35impl LifetimeInferencer {
36 pub fn new() -> Self {
37 Self {
38 lifetimes: HashMap::new(),
39 last_use: HashMap::new(),
40 first_def: HashMap::new(),
41 lifetime_counter: 0,
42 }
43 }
44
45 fn gen_lifetime(&mut self) -> String {
47 let name = format!("'inferred_{}", self.lifetime_counter);
48 self.lifetime_counter += 1;
49 name
50 }
51
52 pub fn infer_function_lifetimes(&mut self, function: &IrFunction) -> HashMap<String, InferredLifetime> {
54 for (var_name, _var_info) in &function.variables {
58 self.first_def.entry(var_name.clone()).or_insert(0);
59 self.last_use.entry(var_name.clone()).or_insert(0);
60 }
61
62 let mut statement_index = 0;
64
65 for node_idx in function.cfg.node_indices() {
66 let block = &function.cfg[node_idx];
67
68 for statement in &block.statements {
69 self.process_statement(statement, statement_index);
70 statement_index += 1;
71 }
72 }
73
74 let first_def_clone = self.first_def.clone();
76 for (var, &first_def_idx) in &first_def_clone {
77 let last_use_idx = self.last_use.get(var).copied().unwrap_or(first_def_idx);
78
79 let lifetime = InferredLifetime {
80 name: self.gen_lifetime(),
81 start: first_def_idx,
82 end: last_use_idx,
83 dependencies: HashSet::new(),
84 };
85
86 self.lifetimes.insert(var.clone(), lifetime);
87 }
88
89 statement_index = 0;
91 for node_idx in function.cfg.node_indices() {
92 let block = &function.cfg[node_idx];
93
94 for statement in &block.statements {
95 self.infer_dependencies(statement, statement_index);
96 statement_index += 1;
97 }
98 }
99
100 self.lifetimes.clone()
101 }
102
103 fn process_statement(&mut self, statement: &IrStatement, index: usize) {
105 match statement {
106 IrStatement::Assign { lhs, rhs } => {
107 self.first_def.entry(lhs.clone()).or_insert(index);
108 self.last_use.insert(lhs.clone(), index);
109 if let crate::ir::IrExpression::Variable(rhs_var) = rhs {
112 self.last_use.insert(rhs_var.clone(), index);
113 }
114 }
115
116 IrStatement::Move { from, to } => {
117 self.last_use.insert(from.clone(), index);
118 self.first_def.entry(to.clone()).or_insert(index);
119 self.last_use.insert(to.clone(), index);
120 }
121
122 IrStatement::Borrow { from, to, .. } => {
123 self.last_use.insert(from.clone(), index);
124 self.first_def.entry(to.clone()).or_insert(index);
125 self.last_use.insert(to.clone(), index);
126 }
127
128 IrStatement::CallExpr { args, result, .. } => {
129 for arg in args {
130 self.last_use.insert(arg.clone(), index);
131 }
132 if let Some(res) = result {
133 self.first_def.entry(res.clone()).or_insert(index);
134 self.last_use.insert(res.clone(), index);
135 }
136 }
137
138 IrStatement::Return { value } => {
139 if let Some(val) = value {
140 self.last_use.insert(val.clone(), index);
141 }
142 }
143
144 _ => {}
145 }
146 }
147
148 fn infer_dependencies(&mut self, statement: &IrStatement, _index: usize) {
150 match statement {
151 IrStatement::Borrow { from, to, kind } => {
152 if let Some(to_lifetime) = self.lifetimes.get_mut(to) {
154 to_lifetime.dependencies.insert(from.clone());
155
156 if matches!(kind, BorrowKind::Mutable) {
158 }
161 }
162 }
163
164 IrStatement::Move { from, to } => {
165 if let Some(from_lifetime) = self.lifetimes.get(from).cloned() {
167 if let Some(to_lifetime) = self.lifetimes.get_mut(to) {
168 to_lifetime.dependencies.extend(from_lifetime.dependencies);
170 }
171 }
172 }
173
174 _ => {}
175 }
176 }
177
178 pub fn lifetimes_overlap(&self, a: &str, b: &str) -> bool {
180 if let (Some(lifetime_a), Some(lifetime_b)) = (self.lifetimes.get(a), self.lifetimes.get(b)) {
181 !(lifetime_a.end < lifetime_b.start || lifetime_b.end < lifetime_a.start)
183 } else {
184 false
185 }
186 }
187
188 #[allow(dead_code)]
190 pub fn get_lifetime(&self, var: &str) -> Option<&InferredLifetime> {
191 self.lifetimes.get(var)
192 }
193
194 pub fn is_alive_at(&self, var: &str, point: usize) -> bool {
196 if let Some(lifetime) = self.lifetimes.get(var) {
197 point >= lifetime.start && point <= lifetime.end
198 } else {
199 false
200 }
201 }
202}
203
204pub fn infer_and_validate_lifetimes(function: &IrFunction) -> Result<Vec<String>, String> {
206 let mut inferencer = LifetimeInferencer::new();
207 let lifetimes = inferencer.infer_function_lifetimes(function);
208 let mut errors = Vec::new();
209
210 let mut statement_index = 0;
212 for node_idx in function.cfg.node_indices() {
213 let block = &function.cfg[node_idx];
214
215 for statement in &block.statements {
216 match statement {
217 IrStatement::Borrow { from, to, kind } => {
218 if inferencer.lifetimes.contains_key(from) && !inferencer.is_alive_at(from, statement_index) {
221 errors.push(format!(
222 "Cannot borrow '{}': variable is not alive at this point",
223 from
224 ));
225 }
226
227 if matches!(kind, BorrowKind::Mutable) {
229 for (other_var, other_lifetime) in &lifetimes {
231 if other_var != to && other_lifetime.dependencies.contains(from) {
232 if inferencer.lifetimes_overlap(to, other_var) {
233 errors.push(format!(
236 "Cannot create mutable borrow '{}': '{}' is already borrowed by '{}'",
237 to, from, other_var
238 ));
239 }
240 }
241 }
242 }
243 }
244
245 IrStatement::Move { from, to } => {
246 if inferencer.lifetimes.contains_key(from) && !inferencer.is_alive_at(from, statement_index) {
248 errors.push(format!(
249 "Cannot move '{}': variable is not alive at this point",
250 from
251 ));
252 }
253 }
254
255 IrStatement::Return { value } => {
256 if value.is_none() && returns_reference(&function.return_type) {
259 errors.push(format!(
260 "Cannot return reference to temporary value in function '{}'",
261 function.name
262 ));
263 }
264
265 if let Some(val) = value {
266 if returns_reference(&function.return_type) {
268 if let Some(var_info) = function.variables.get(val) {
269 let is_param = is_parameter(val, function);
270
271 match &var_info.ty {
273 crate::ir::VariableType::Reference(_) |
274 crate::ir::VariableType::MutableReference(_) => {
275 if let Some(lifetime) = lifetimes.get(val) {
278 for dep in &lifetime.dependencies {
280 if !is_parameter(dep, function) && !var_info.is_static {
281 errors.push(format!(
282 "Potential dangling reference: returning '{}' which depends on local variable '{}'",
283 val, dep
284 ));
285 }
286 }
287 }
288 }
291 _ => {
292 if !is_param && !var_info.is_static {
295 errors.push(format!(
296 "Cannot return reference to local variable '{}'",
297 val
298 ));
299 }
300 }
301 }
302 }
303 }
304 }
305 }
306
307 _ => {}
308 }
309
310 statement_index += 1;
311 }
312 }
313
314 Ok(errors)
315}
316
317fn is_parameter(var_name: &str, function: &IrFunction) -> bool {
318 function.variables.get(var_name)
320 .map(|var_info| var_info.is_parameter)
321 .unwrap_or(false)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::ir::BasicBlock;
328 use petgraph::graph::Graph;
329
330 #[test]
331 fn test_lifetime_inference() {
332 let mut inferencer = LifetimeInferencer::new();
333
334 inferencer.process_statement(&IrStatement::Assign {
336 lhs: "x".to_string(),
337 rhs: crate::ir::IrExpression::Variable("temp".to_string()),
338 }, 0);
339
340 inferencer.process_statement(&IrStatement::Borrow {
341 from: "x".to_string(),
342 to: "ref_x".to_string(),
343 kind: BorrowKind::Immutable,
344 }, 1);
345
346 inferencer.process_statement(&IrStatement::Return {
347 value: Some("ref_x".to_string()),
348 }, 2);
349
350 let mut cfg = Graph::new();
352 let block = BasicBlock {
353 id: 0,
354 statements: vec![],
355 terminator: None,
356 };
357 cfg.add_node(block);
358
359 let function = IrFunction {
360 name: "test".to_string(),
361 cfg,
362 variables: HashMap::new(),
363 return_type: "void".to_string(),
364 source_file: "test.cpp".to_string(),
365 is_method: false,
366 method_qualifier: None,
367 class_name: None,
368 template_parameters: vec![],
369 lifetime_params: HashMap::new(),
370 param_lifetimes: Vec::new(),
371 return_lifetime: None,
372 lifetime_constraints: Vec::new(),
373 };
374
375 let lifetimes = inferencer.infer_function_lifetimes(&function);
376
377 assert!(inferencer.first_def.contains_key("x"));
379 assert!(inferencer.first_def.contains_key("ref_x"));
380 assert_eq!(inferencer.last_use.get("x"), Some(&1));
381 assert_eq!(inferencer.last_use.get("ref_x"), Some(&2));
382 }
383
384 #[test]
385 fn test_lifetime_overlap() {
386 let mut inferencer = LifetimeInferencer::new();
387
388 inferencer.lifetimes.insert("a".to_string(), InferredLifetime {
389 name: "'a".to_string(),
390 start: 0,
391 end: 5,
392 dependencies: HashSet::new(),
393 });
394
395 inferencer.lifetimes.insert("b".to_string(), InferredLifetime {
396 name: "'b".to_string(),
397 start: 3,
398 end: 7,
399 dependencies: HashSet::new(),
400 });
401
402 inferencer.lifetimes.insert("c".to_string(), InferredLifetime {
403 name: "'c".to_string(),
404 start: 6,
405 end: 10,
406 dependencies: HashSet::new(),
407 });
408
409 assert!(inferencer.lifetimes_overlap("a", "b")); assert!(!inferencer.lifetimes_overlap("a", "c")); assert!(inferencer.lifetimes_overlap("b", "c")); }
413}