1use std::collections::{HashMap, HashSet, VecDeque};
2use crate::ir::{IrFunction, IrStatement, IrProgram, BasicBlock};
3use crate::parser::HeaderCache;
4use crate::parser::annotations::{FunctionSignature, LifetimeAnnotation};
5use crate::parser::safety_annotations::SafetyContext;
6use petgraph::graph::NodeIndex;
7use petgraph::Direction;
8
9#[derive(Debug, Clone)]
11pub struct Scope {
12 #[allow(dead_code)]
13 pub id: usize,
14 pub parent: Option<usize>,
15 #[allow(dead_code)]
16 pub kind: ScopeKind,
17 pub local_variables: HashSet<String>,
19 pub local_references: HashSet<String>,
21 #[allow(dead_code)]
23 pub entry_block: Option<NodeIndex>,
24 #[allow(dead_code)]
26 pub exit_blocks: Vec<NodeIndex>,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30#[allow(dead_code)]
31pub enum ScopeKind {
32 Function,
33 Block,
34 Loop,
35 Conditional,
36}
37
38#[derive(Debug)]
40pub struct ScopedLifetimeTracker {
41 scopes: HashMap<usize, Scope>,
43 variable_scope: HashMap<String, usize>,
45 lifetime_scopes: HashMap<String, (usize, usize)>, constraints: Vec<LifetimeConstraint>,
49 next_scope_id: usize,
51}
52
53#[derive(Debug, Clone)]
54pub struct LifetimeConstraint {
55 pub kind: ConstraintKind,
56 pub location: String,
57}
58
59#[derive(Debug, Clone)]
60#[allow(dead_code)]
61pub enum ConstraintKind {
62 Outlives { longer: String, shorter: String },
63 Equal { a: String, b: String },
64 BorrowedFrom { reference: String, source: String },
65 MustLiveUntil { lifetime: String, scope_id: usize },
66}
67
68impl ScopedLifetimeTracker {
69 pub fn new() -> Self {
70 Self {
71 scopes: HashMap::new(),
72 variable_scope: HashMap::new(),
73 lifetime_scopes: HashMap::new(),
74 constraints: Vec::new(),
75 next_scope_id: 0,
76 }
77 }
78
79 pub fn push_scope(&mut self, kind: ScopeKind, parent: Option<usize>) -> usize {
81 let id = self.next_scope_id;
82 self.next_scope_id += 1;
83
84 self.scopes.insert(id, Scope {
85 id,
86 parent,
87 kind,
88 local_variables: HashSet::new(),
89 local_references: HashSet::new(),
90 entry_block: None,
91 exit_blocks: Vec::new(),
92 });
93
94 id
95 }
96
97 pub fn declare_variable(&mut self, var: String, scope_id: usize) {
99 if let Some(scope) = self.scopes.get_mut(&scope_id) {
100 scope.local_variables.insert(var.clone());
101 self.variable_scope.insert(var, scope_id);
102 }
103 }
104
105 pub fn declare_reference(&mut self, ref_name: String, scope_id: usize) {
107 if let Some(scope) = self.scopes.get_mut(&scope_id) {
108 scope.local_references.insert(ref_name.clone());
109 self.variable_scope.insert(ref_name, scope_id);
110 }
111 }
112
113 pub fn check_outlives(&self, longer: &str, shorter: &str) -> bool {
115 let longer_range = self.lifetime_scopes.get(longer);
117 let shorter_range = self.lifetime_scopes.get(shorter);
118
119 match (longer_range, shorter_range) {
120 (Some((l_start, l_end)), Some((s_start, s_end))) => {
121 l_start <= s_start && l_end >= s_end
124 }
125 _ => {
126 longer == shorter
128 }
129 }
130 }
131
132 pub fn is_alive_in_scope(&self, var: &str, scope_id: usize) -> bool {
134 if let Some(var_scope) = self.variable_scope.get(var) {
135 self.is_ancestor_scope(*var_scope, scope_id)
137 } else {
138 false
139 }
140 }
141
142 fn is_ancestor_scope(&self, scope_a: usize, scope_b: usize) -> bool {
144 if scope_a == scope_b {
145 return true;
146 }
147
148 let mut current = scope_b;
149 while let Some(scope) = self.scopes.get(¤t) {
150 if let Some(parent) = scope.parent {
151 if parent == scope_a {
152 return true;
153 }
154 current = parent;
155 } else {
156 break;
157 }
158 }
159
160 false
161 }
162
163 pub fn add_constraint(&mut self, constraint: LifetimeConstraint) {
165 self.constraints.push(constraint);
166 }
167
168 pub fn validate_constraints(&self) -> Vec<String> {
170 let mut errors = Vec::new();
171
172 for constraint in &self.constraints {
173 match &constraint.kind {
174 ConstraintKind::Outlives { longer, shorter } => {
175 if !self.check_outlives(longer, shorter) {
176 errors.push(format!(
177 "Lifetime '{}' does not outlive '{}' at {}",
178 longer, shorter, constraint.location
179 ));
180 }
181 }
182 ConstraintKind::BorrowedFrom { reference, source } => {
183 let is_function_call_result = source.starts_with("operator") ||
186 source.contains("::") || source.starts_with("__") || source.starts_with("temp_"); if !is_function_call_result {
192 if let Some(ref_scope) = self.variable_scope.get(reference) {
193 if !self.is_alive_in_scope(source, *ref_scope) {
194 errors.push(format!(
195 "Reference '{}' borrows from '{}' which is not alive at {}",
196 reference, source, constraint.location
197 ));
198 }
199 }
200 }
201 }
202 ConstraintKind::MustLiveUntil { lifetime, scope_id } => {
203 if let Some((_, end_scope)) = self.lifetime_scopes.get(lifetime) {
204 if !self.is_ancestor_scope(*end_scope, *scope_id) && *end_scope != *scope_id {
205 errors.push(format!(
206 "Lifetime '{}' must live until scope {} but ends at scope {} at {}",
207 lifetime, scope_id, end_scope, constraint.location
208 ));
209 }
210 }
211 }
212 _ => {}
213 }
214 }
215
216 errors
217 }
218}
219
220pub fn analyze_function_scopes(
222 function: &IrFunction,
223 header_cache: &HeaderCache,
224) -> Result<Vec<String>, String> {
225 let mut tracker = ScopedLifetimeTracker::new();
226 let mut errors = Vec::new();
227
228 let func_scope = tracker.push_scope(ScopeKind::Function, None);
230
231 for (name, var_info) in &function.variables {
233 tracker.declare_variable(name.clone(), func_scope);
234
235 match &var_info.ty {
237 crate::ir::VariableType::Reference(_) |
238 crate::ir::VariableType::MutableReference(_) => {
239 tracker.declare_reference(name.clone(), func_scope);
240 let lifetime = format!("'param_{}", name);
242 tracker.lifetime_scopes.insert(lifetime, (func_scope, func_scope));
243 }
244 _ => {}
245 }
246 }
247
248 let mut visited = HashSet::new();
250 let mut queue = VecDeque::new();
251
252 for node in function.cfg.node_indices() {
254 if function.cfg.edges_directed(node, Direction::Incoming).count() == 0 {
255 queue.push_back((node, func_scope));
256 }
257 }
258
259 while let Some((node_idx, current_scope)) = queue.pop_front() {
260 if visited.contains(&node_idx) {
261 continue;
262 }
263 visited.insert(node_idx);
264
265 let block = &function.cfg[node_idx];
266 let block_errors = analyze_block(
267 block,
268 current_scope,
269 &mut tracker,
270 function,
271 header_cache,
272 )?;
273 errors.extend(block_errors);
274
275 for neighbor in function.cfg.neighbors_directed(node_idx, Direction::Outgoing) {
277 queue.push_back((neighbor, current_scope));
278 }
279 }
280
281 errors.extend(tracker.validate_constraints());
283
284 Ok(errors)
285}
286
287fn analyze_block(
288 block: &BasicBlock,
289 scope_id: usize,
290 tracker: &mut ScopedLifetimeTracker,
291 function: &IrFunction,
292 header_cache: &HeaderCache,
293) -> Result<Vec<String>, String> {
294 let mut errors = Vec::new();
295
296 for statement in &block.statements {
297 match statement {
298 IrStatement::Borrow { from, to, .. } => {
299 let is_function_call_result = from.starts_with("operator") ||
303 from.contains("::") || from.starts_with("__") || from.starts_with("temp_"); if !is_function_call_result && !tracker.is_alive_in_scope(from, scope_id) {
309 errors.push(format!(
310 "Cannot borrow from '{}': variable is not alive in current scope",
311 from
312 ));
313 } else {
314 tracker.add_constraint(LifetimeConstraint {
316 kind: ConstraintKind::BorrowedFrom {
317 reference: to.clone(),
318 source: from.clone(),
319 },
320 location: format!("borrow of {} from {}", to, from),
321 });
322
323 tracker.declare_reference(to.clone(), scope_id);
325 }
326 }
327
328 IrStatement::Return { value } => {
329 if let Some(val) = value {
330 if let Some(var_scope) = tracker.variable_scope.get(val) {
332 if *var_scope != 0 && !is_parameter(val, function) {
335 if let Some(var_info) = function.variables.get(val) {
337 if !var_info.is_static { match var_info.ty {
339 crate::ir::VariableType::Reference(_) |
340 crate::ir::VariableType::MutableReference(_) => {
341 }
345 crate::ir::VariableType::Owned(_) => {
346 errors.push(format!(
349 "Returning reference to local variable '{}' - will create dangling reference",
350 val
351 ));
352 }
353 _ => {} }
355 }
356 }
357 }
358 }
359 }
360 }
361
362 IrStatement::CallExpr { func, args, result } => {
363 if let Some(signature) = header_cache.get_signature(func) {
365 let call_errors = check_call_lifetimes(
366 func,
367 args,
368 result.as_ref(),
369 signature,
370 scope_id,
371 tracker,
372 );
373 errors.extend(call_errors);
374 }
375 }
376
377 _ => {}
378 }
379 }
380
381 Ok(errors)
382}
383
384fn check_call_lifetimes(
385 func_name: &str,
386 _args: &[String],
387 result: Option<&String>,
388 signature: &FunctionSignature,
389 scope_id: usize,
390 tracker: &mut ScopedLifetimeTracker,
391) -> Vec<String> {
392 let errors = Vec::new();
393
394 for bound in &signature.lifetime_bounds {
396 tracker.add_constraint(LifetimeConstraint {
399 kind: ConstraintKind::Outlives {
400 longer: bound.longer.clone(),
401 shorter: bound.shorter.clone(),
402 },
403 location: format!("call to {}", func_name),
404 });
405 }
406
407 if let (Some(result_var), Some(return_lifetime)) = (result, &signature.return_lifetime) {
409 match return_lifetime {
410 LifetimeAnnotation::Ref(_) | LifetimeAnnotation::MutRef(_) => {
411 tracker.declare_reference(result_var.clone(), scope_id);
413 }
414 LifetimeAnnotation::Owned => {
415 tracker.declare_variable(result_var.clone(), scope_id);
417 }
418 _ => {}
419 }
420 }
421
422 errors
423}
424
425fn is_parameter(var_name: &str, function: &IrFunction) -> bool {
426 var_name.starts_with("param") || var_name.starts_with("arg") ||
430 (function.variables.get(var_name).map_or(false, |info| {
431 matches!(info.ownership, crate::ir::OwnershipState::Owned)
434 }))
435}
436
437fn is_system_header(file_path: &str) -> bool {
440 let system_paths = [
441 "/usr/include",
442 "/usr/local/include",
443 "/opt/homebrew/include",
444 "/Library/Developer",
445 "C:\\Program Files",
446 "/Applications/Xcode.app",
447 ];
448
449 for path in &system_paths {
450 if file_path.starts_with(path) {
451 return true;
452 }
453 }
454
455 if file_path.contains("/include/c++/") ||
457 file_path.contains("/bits/") ||
458 file_path.contains("/ext/") ||
459 file_path.contains("stl_") ||
460 file_path.contains("/lib/gcc/") {
461 return true;
462 }
463
464 if file_path.contains("/include/rusty/") || file_path.contains("/include/unified_") {
466 return true;
467 }
468
469 false
470}
471
472pub fn check_scoped_lifetimes(
473 program: &IrProgram,
474 header_cache: &HeaderCache,
475 safety_context: &SafetyContext,
476) -> Result<Vec<String>, String> {
477 let mut all_errors = Vec::new();
478
479 for function in &program.functions {
480 if is_system_header(&function.source_file) {
482 continue;
483 }
484
485 if !safety_context.should_check_function(&function.name) {
488 continue;
489 }
490
491 let errors = analyze_function_scopes(function, header_cache)?;
492 all_errors.extend(errors);
493 }
494
495 Ok(all_errors)
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn test_scope_tracking() {
504 let mut tracker = ScopedLifetimeTracker::new();
505
506 let func_scope = tracker.push_scope(ScopeKind::Function, None);
507 let block_scope = tracker.push_scope(ScopeKind::Block, Some(func_scope));
508
509 tracker.declare_variable("x".to_string(), func_scope);
510 tracker.declare_variable("y".to_string(), block_scope);
511
512 assert!(tracker.is_alive_in_scope("x", block_scope));
513 assert!(tracker.is_alive_in_scope("y", block_scope));
514 assert!(!tracker.is_alive_in_scope("y", func_scope));
515 }
516
517 #[test]
518 fn test_outlives_checking() {
519 let mut tracker = ScopedLifetimeTracker::new();
520
521 tracker.lifetime_scopes.insert("'a".to_string(), (0, 2));
522 tracker.lifetime_scopes.insert("'b".to_string(), (1, 2));
523
524 assert!(tracker.check_outlives("'a", "'b"));
525 assert!(!tracker.check_outlives("'b", "'a"));
526 }
527}