1use crate::parser::annotations::{LifetimeAnnotation, FunctionSignature, LifetimeBound};
2use crate::parser::HeaderCache;
3use crate::parser::safety_annotations::SafetyContext;
4use crate::ir::{IrProgram, IrStatement, IrFunction, VariableType};
5use std::collections::{HashMap, HashSet};
6use crate::debug_println;
7
8#[derive(Debug, Clone)]
10pub struct LifetimeScope {
11 variable_lifetimes: HashMap<String, String>,
13 constraints: Vec<LifetimeBound>,
15 owned_variables: HashSet<String>,
17}
18
19impl LifetimeScope {
20 pub fn new() -> Self {
21 Self {
22 variable_lifetimes: HashMap::new(),
23 constraints: Vec::new(),
24 owned_variables: HashSet::new(),
25 }
26 }
27
28 pub fn set_lifetime(&mut self, var: String, lifetime: String) {
30 self.variable_lifetimes.insert(var, lifetime);
31 }
32
33 pub fn mark_owned(&mut self, var: String) {
35 self.owned_variables.insert(var);
36 }
37
38 pub fn get_lifetime(&self, var: &str) -> Option<&String> {
40 self.variable_lifetimes.get(var)
41 }
42
43 pub fn is_owned(&self, var: &str) -> bool {
45 self.owned_variables.contains(var)
46 }
47
48 #[allow(dead_code)]
50 pub fn add_constraint(&mut self, constraint: LifetimeBound) {
51 self.constraints.push(constraint);
52 }
53
54 pub fn check_outlives(&self, longer: &str, shorter: &str) -> bool {
56 if longer == shorter {
58 return true;
59 }
60
61 if let (Some(longer_scope), Some(shorter_scope)) = (
64 longer.strip_prefix("'scope_"),
65 shorter.strip_prefix("'scope_")
66 ) {
67 if let (Ok(longer_depth), Ok(shorter_depth)) = (
68 longer_scope.parse::<usize>(),
69 shorter_scope.parse::<usize>()
70 ) {
71 return longer_depth <= shorter_depth;
72 }
73 }
74
75 for constraint in &self.constraints {
77 if constraint.longer == longer && constraint.shorter == shorter {
78 return true;
79 }
80 }
81
82 self.check_outlives_transitive(longer, shorter, &mut HashSet::new())
85 }
86
87 fn check_outlives_transitive(&self, longer: &str, shorter: &str, visited: &mut HashSet<String>) -> bool {
89 if visited.contains(longer) {
91 return false;
92 }
93 visited.insert(longer.to_string());
94
95 for constraint in &self.constraints {
97 if constraint.longer == longer {
98 if constraint.shorter == shorter {
100 return true;
101 }
102
103 if self.check_outlives_transitive(&constraint.shorter, shorter, visited) {
105 return true;
106 }
107 }
108 }
109
110 false
111 }
112}
113
114fn is_system_header(file_path: &str) -> bool {
117 let system_paths = [
118 "/usr/include",
119 "/usr/local/include",
120 "/opt/homebrew/include",
121 "/Library/Developer",
122 "C:\\Program Files",
123 "/Applications/Xcode.app",
124 ];
125
126 for path in &system_paths {
127 if file_path.starts_with(path) {
128 return true;
129 }
130 }
131
132 if file_path.contains("/include/c++/") ||
134 file_path.contains("/bits/") ||
135 file_path.contains("/ext/") ||
136 file_path.contains("stl_") ||
137 file_path.contains("/lib/gcc/") {
138 return true;
139 }
140
141 if file_path.contains("/include/rusty/") || file_path.contains("/include/unified_") {
143 return true;
144 }
145
146 false
147}
148
149pub fn check_lifetimes_with_annotations(
150 program: &IrProgram,
151 header_cache: &HeaderCache,
152 safety_context: &SafetyContext
153) -> Result<Vec<String>, String> {
154 let mut errors = Vec::new();
155
156 for function in &program.functions {
157 if is_system_header(&function.source_file) {
159 continue;
160 }
161
162 if !safety_context.should_check_function(&function.name) {
165 continue;
166 }
167
168 let mut scope = LifetimeScope::new();
169 let function_errors = check_function_lifetimes(function, &mut scope, header_cache)?;
170 errors.extend(function_errors);
171 }
172
173 Ok(errors)
174}
175
176fn check_function_lifetimes(
177 function: &IrFunction,
178 scope: &mut LifetimeScope,
179 header_cache: &HeaderCache
180) -> Result<Vec<String>, String> {
181 let mut errors = Vec::new();
182
183 for (name, var_info) in &function.variables {
186 match &var_info.ty {
187 crate::ir::VariableType::Reference(_) |
188 crate::ir::VariableType::MutableReference(_) => {
189 scope.set_lifetime(name.clone(), format!("'{}", name));
191 }
192 _ => {
193 scope.mark_owned(name.clone());
195 }
196 }
197 }
198
199 let mut scope_depth = 0;
201 let mut variable_scopes: HashMap<String, usize> = HashMap::new();
202
203 for node_idx in function.cfg.node_indices() {
205 let block = &function.cfg[node_idx];
206
207 for statement in &block.statements {
208 match statement {
209 IrStatement::EnterScope => {
210 scope_depth += 1;
211 }
212 IrStatement::ExitScope => {
213 if scope_depth > 0 {
214 scope_depth -= 1;
215 }
216 }
217 IrStatement::Assign { lhs, .. } |
218 IrStatement::Borrow { to: lhs, .. } => {
219 if !variable_scopes.contains_key(lhs) {
221 variable_scopes.insert(lhs.clone(), scope_depth);
222 }
223 }
224 IrStatement::CallExpr { args, result, .. } => {
225 if let Some(lhs) = result {
227 if !variable_scopes.contains_key(lhs) {
228 variable_scopes.insert(lhs.clone(), scope_depth);
229 }
230 }
231 for arg in args {
233 if !variable_scopes.contains_key(arg) && scope.is_owned(arg) {
234 variable_scopes.insert(arg.clone(), scope_depth);
235 }
236 }
237 }
238 _ => {}
239 }
240 }
241 }
242
243 for (var_name, var_info) in &function.variables {
247 if scope.is_owned(var_name) {
248 if let Some(&depth) = variable_scopes.get(var_name) {
250 let lifetime = format!("'scope_{}", depth);
252 scope.set_lifetime(var_name.clone(), lifetime.clone());
253 } else {
254 let lifetime = "'scope_0".to_string();
257 scope.set_lifetime(var_name.clone(), lifetime.clone());
258 }
259 }
260 }
261
262 scope_depth = 0;
264
265 for node_idx in function.cfg.node_indices() {
267 let block = &function.cfg[node_idx];
268
269 for (idx, statement) in block.statements.iter().enumerate() {
270 match statement {
271 IrStatement::EnterScope => {
272 scope_depth += 1;
273 }
274 IrStatement::ExitScope => {
275 if scope_depth > 0 {
276 scope_depth -= 1;
277 }
278 }
279 IrStatement::CallExpr { func, args, result } => {
280 if let Some(signature) = header_cache.get_signature(func) {
282 let call_errors = check_function_call(
283 func,
284 args,
285 result.as_ref(),
286 signature,
287 scope
288 );
289 errors.extend(call_errors);
290 }
291 }
292
293 IrStatement::Borrow { from, to, .. } => {
294 if let Some(from_lifetime) = scope.get_lifetime(from) {
297 scope.set_lifetime(to.clone(), from_lifetime.clone());
298 } else if scope.is_owned(from) {
299 scope.set_lifetime(to.clone(), format!("'{}", to));
301 }
302 }
303
304 IrStatement::Return { value } => {
305 if let Some(value) = value {
307 let return_errors = check_return_lifetime(value, function, scope);
308 errors.extend(return_errors);
309 }
310 }
311
312 _ => {}
313 }
314 }
315 }
316
317 Ok(errors)
318}
319
320fn check_function_call(
321 func_name: &str,
322 args: &[String],
323 result: Option<&String>,
324 signature: &FunctionSignature,
325 scope: &LifetimeScope
326) -> Vec<String> {
327 let mut errors = Vec::new();
328
329 if args.len() != signature.param_lifetimes.len() {
331 return errors;
333 }
334
335 let mut arg_lifetimes = Vec::new();
337 for (i, arg) in args.iter().enumerate() {
338 if let Some(lifetime) = scope.get_lifetime(arg) {
339 arg_lifetimes.push(Some(lifetime.clone()));
340 } else if scope.is_owned(arg) {
341 arg_lifetimes.push(None); } else {
343 arg_lifetimes.push(Some(format!("'arg{}", i)));
344 }
345 }
346
347 for (i, (arg, expected)) in args.iter().zip(&signature.param_lifetimes).enumerate() {
351 if let Some(expected_lifetime) = expected {
352 match expected_lifetime {
353 LifetimeAnnotation::Ref(_expected) | LifetimeAnnotation::MutRef(_expected) => {
354 }
358 LifetimeAnnotation::Owned => {
359 if !scope.is_owned(arg) {
361 errors.push(format!(
362 "Function '{}' expects ownership of parameter {}, but '{}' is a reference",
363 func_name, i + 1, arg
364 ));
365 }
366 }
367 _ => {}
368 }
369 }
370 }
371
372 for bound in &signature.lifetime_bounds {
374 let longer_lifetime = map_lifetime_to_actual(&bound.longer, &arg_lifetimes);
376 let shorter_lifetime = map_lifetime_to_actual(&bound.shorter, &arg_lifetimes);
377
378 if let (Some(longer), Some(shorter)) = (longer_lifetime, shorter_lifetime) {
379 let outlives = scope.check_outlives(&longer, &shorter);
380 if !outlives {
381 errors.push(format!(
382 "Lifetime constraint violated in call to '{}': '{}' must outlive '{}'",
383 func_name, longer, shorter
384 ));
385 }
386 }
387 }
388
389 if let (Some(_result_var), Some(return_lifetime)) = (result, &signature.return_lifetime) {
391 match return_lifetime {
392 LifetimeAnnotation::Ref(ret_lifetime) | LifetimeAnnotation::MutRef(ret_lifetime) => {
393 let actual_lifetime = map_lifetime_to_actual(ret_lifetime, &arg_lifetimes);
396 if let Some(_lifetime) = actual_lifetime {
397 }
401 }
402 LifetimeAnnotation::Owned => {
403 }
405 _ => {}
406 }
407 }
408
409 errors
410}
411
412fn check_return_lifetime(
413 value: &str,
414 function: &IrFunction,
415 scope: &LifetimeScope
416) -> Vec<String> {
417 let mut errors = Vec::new();
418
419 if let Some(var_info) = function.variables.get(value) {
422 match &var_info.ty {
423 VariableType::Reference(_) | VariableType::MutableReference(_) => {
424 if let Some(lifetime) = scope.get_lifetime(value) {
427 for (var_name, other_var_info) in &function.variables {
429 if var_name == value {
431 continue;
432 }
433 let is_owned_type = matches!(
437 other_var_info.ty,
438 VariableType::Owned(_)
439 );
440 if is_owned_type && lifetime.contains(var_name) && !is_parameter(var_name, function) {
441 errors.push(format!(
442 "Returning reference to local variable '{}' - this will create a dangling reference",
443 var_name
444 ));
445 }
446 }
447 }
448 }
449 VariableType::Owned(_) => {
450 }
454 _ => {}
457 }
458 }
459
460 errors
461}
462
463fn is_parameter(var_name: &str, function: &IrFunction) -> bool {
464 function.variables.get(var_name)
466 .map(|var_info| var_info.is_parameter)
467 .unwrap_or(false)
468}
469
470fn map_lifetime_to_actual(lifetime_name: &str, arg_lifetimes: &[Option<String>]) -> Option<String> {
471 match lifetime_name {
473 "a" => arg_lifetimes.get(0).and_then(|l| l.clone()),
474 "b" => arg_lifetimes.get(1).and_then(|l| l.clone()),
475 "c" => arg_lifetimes.get(2).and_then(|l| l.clone()),
476 _ => Some(format!("'{}", lifetime_name)),
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483
484 #[test]
485 fn test_lifetime_scope() {
486 let mut scope = LifetimeScope::new();
487
488 scope.set_lifetime("ref1".to_string(), "'a".to_string());
489 scope.mark_owned("value".to_string());
490
491 assert_eq!(scope.get_lifetime("ref1"), Some(&"'a".to_string()));
492 assert!(scope.is_owned("value"));
493 assert!(!scope.is_owned("ref1"));
494 }
495
496 #[test]
497 fn test_outlives_checking() {
498 let mut scope = LifetimeScope::new();
499
500 scope.add_constraint(LifetimeBound {
501 longer: "a".to_string(),
502 shorter: "b".to_string(),
503 });
504
505 assert!(scope.check_outlives("a", "b"));
506 assert!(scope.check_outlives("a", "a")); assert!(!scope.check_outlives("b", "a")); }
509
510 #[test]
511 fn test_check_return_lifetime_pointer_is_safe() {
512 use crate::ir::{VariableInfo, OwnershipState, ControlFlowGraph};
513 use std::collections::HashMap;
514
515 let mut variables = HashMap::new();
517 variables.insert("p".to_string(), VariableInfo {
518 name: "p".to_string(),
519 ty: VariableType::Raw("void*".to_string()),
520 ownership: OwnershipState::Owned,
521 lifetime: None,
522 is_parameter: false,
523 is_static: false,
524 scope_level: 1,
525 has_destructor: false,
526 declaration_index: 0,
527 });
528
529 let function = IrFunction {
530 name: "allocate".to_string(),
531 cfg: ControlFlowGraph::new(),
532 variables,
533 return_type: "void*".to_string(),
534 source_file: "test.cpp".to_string(),
535 is_method: false,
536 method_qualifier: None,
537 class_name: None,
538 template_parameters: vec![],
539 lifetime_params: HashMap::new(),
540 param_lifetimes: vec![],
541 return_lifetime: None,
542 lifetime_constraints: vec![],
543 };
544
545 let mut scope = LifetimeScope::new();
546 scope.set_lifetime("p".to_string(), "'p".to_string());
547
548 let errors = check_return_lifetime("p", &function, &scope);
550 assert!(errors.is_empty(), "Returning pointer value should be safe, got: {:?}", errors);
551 }
552
553 #[test]
554 fn test_check_return_lifetime_reference_is_unsafe() {
555 use crate::ir::{VariableInfo, OwnershipState, ControlFlowGraph};
556 use std::collections::HashMap;
557
558 let mut variables = HashMap::new();
560 variables.insert("local".to_string(), VariableInfo {
561 name: "local".to_string(),
562 ty: VariableType::Owned("int".to_string()),
563 ownership: OwnershipState::Owned,
564 lifetime: None,
565 is_parameter: false,
566 is_static: false,
567 scope_level: 1,
568 has_destructor: false,
569 declaration_index: 0,
570 });
571 variables.insert("ref".to_string(), VariableInfo {
572 name: "ref".to_string(),
573 ty: VariableType::Reference("int".to_string()),
574 ownership: OwnershipState::Owned,
575 lifetime: None,
576 is_parameter: false,
577 is_static: false,
578 scope_level: 1,
579 has_destructor: false,
580 declaration_index: 1,
581 });
582
583 let function = IrFunction {
584 name: "bad_return".to_string(),
585 cfg: ControlFlowGraph::new(),
586 variables,
587 return_type: "int&".to_string(),
588 source_file: "test.cpp".to_string(),
589 is_method: false,
590 method_qualifier: None,
591 class_name: None,
592 template_parameters: vec![],
593 lifetime_params: HashMap::new(),
594 param_lifetimes: vec![],
595 return_lifetime: None,
596 lifetime_constraints: vec![],
597 };
598
599 let mut scope = LifetimeScope::new();
600 scope.set_lifetime("ref".to_string(), "'local".to_string());
602
603 let errors = check_return_lifetime("ref", &function, &scope);
605 assert!(!errors.is_empty(), "Returning reference to local should be flagged as unsafe");
606 assert!(errors[0].contains("local"), "Error should mention the local variable");
607 }
608
609 #[test]
610 fn test_check_return_lifetime_owned_is_safe() {
611 use crate::ir::{VariableInfo, OwnershipState, ControlFlowGraph};
612 use std::collections::HashMap;
613
614 let mut variables = HashMap::new();
616 variables.insert("ptr".to_string(), VariableInfo {
617 name: "ptr".to_string(),
618 ty: VariableType::UniquePtr("int".to_string()),
619 ownership: OwnershipState::Owned,
620 lifetime: None,
621 is_parameter: false,
622 is_static: false,
623 scope_level: 1,
624 has_destructor: true,
625 declaration_index: 0,
626 });
627
628 let function = IrFunction {
629 name: "create".to_string(),
630 cfg: ControlFlowGraph::new(),
631 variables,
632 return_type: "std::unique_ptr<int>".to_string(),
633 source_file: "test.cpp".to_string(),
634 is_method: false,
635 method_qualifier: None,
636 class_name: None,
637 template_parameters: vec![],
638 lifetime_params: HashMap::new(),
639 param_lifetimes: vec![],
640 return_lifetime: None,
641 lifetime_constraints: vec![],
642 };
643
644 let mut scope = LifetimeScope::new();
645 scope.set_lifetime("ptr".to_string(), "'ptr".to_string());
646
647 let errors = check_return_lifetime("ptr", &function, &scope);
649 assert!(errors.is_empty(), "Returning owned value should be safe, got: {:?}", errors);
650 }
651}