1use crate::ir::{IrProgram, IrFunction, OwnershipState, BorrowKind};
2use crate::parser::HeaderCache;
3use std::collections::{HashMap, HashSet};
4use crate::debug_println;
5
6fn is_system_header(file_path: &str) -> bool {
9 let system_paths = [
11 "/usr/include",
12 "/usr/local/include",
13 "/opt/homebrew/include",
14 "/Library/Developer",
15 "C:\\Program Files",
16 "/Applications/Xcode.app",
17 ];
18
19 for path in &system_paths {
20 if file_path.starts_with(path) {
21 return true;
22 }
23 }
24
25 if file_path.contains("/include/c++/") ||
27 file_path.contains("/bits/") ||
28 file_path.contains("/ext/") ||
29 file_path.contains("stl_") ||
30 file_path.contains("/lib/gcc/") {
31 return true;
32 }
33
34 if file_path.contains("/include/rusty/") || file_path.contains("/include/unified_") {
37 return true;
38 }
39
40 false
41}
42
43fn is_primitive_type(type_name: &str) -> bool {
45 let base_type = type_name
47 .split('<').next().unwrap_or(type_name)
48 .trim();
49
50 matches!(base_type,
51 "int" | "char" | "bool" | "float" | "double" |
52 "long" | "short" | "unsigned" | "signed" |
53 "int8_t" | "int16_t" | "int32_t" | "int64_t" |
54 "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" |
55 "size_t" | "ptrdiff_t" | "void"
56 )
57}
58
59pub mod ownership;
60pub mod borrows;
61pub mod lifetimes;
62pub mod lifetime_checker;
63pub mod scope_lifetime;
64pub mod lifetime_inference;
65pub mod pointer_safety;
66pub mod unsafe_propagation;
67pub mod this_tracking;
68pub mod liveness;
69pub mod mutable_checker;
70pub mod lambda_capture_safety;
71pub mod raii_tracking;
72pub mod inheritance_safety;
73pub mod function_pointer_safety;
74
75#[derive(Debug, Clone)]
76#[allow(dead_code)]
77pub struct BorrowCheckError {
78 pub kind: ErrorKind,
79 pub location: String,
80 pub message: String,
81}
82
83#[derive(Debug, Clone)]
84#[allow(dead_code)]
85pub enum ErrorKind {
86 UseAfterMove,
87 DoubleBorrow,
88 MutableBorrowWhileImmutable,
89 DanglingReference,
90 LifetimeViolation,
91}
92
93#[allow(dead_code)]
94pub fn check_borrows(program: IrProgram) -> Result<Vec<String>, String> {
95 let mut errors = Vec::new();
96
97 for function in &program.functions {
98 let function_errors = check_function(function)?;
99 errors.extend(function_errors);
100 }
101
102 Ok(errors)
103}
104
105#[allow(dead_code)]
106pub fn check_borrows_with_annotations_and_safety(
107 program: IrProgram,
108 header_cache: HeaderCache,
109 file_safe: bool
110) -> Result<Vec<String>, String> {
111 if !file_safe && !has_any_safe_functions(&program, &header_cache) {
113 return Ok(Vec::new()); }
115
116 check_borrows_with_annotations(program, header_cache)
117}
118
119pub fn check_borrows_with_safety_context(
120 program: IrProgram,
121 header_cache: HeaderCache,
122 safety_context: crate::parser::safety_annotations::SafetyContext
123) -> Result<Vec<String>, String> {
124 use crate::parser::safety_annotations::SafetyMode;
125
126 if safety_context.file_default != SafetyMode::Safe &&
128 !safety_context.function_overrides.iter().any(|(_, mode)| *mode == SafetyMode::Safe) &&
129 !has_any_safe_functions(&program, &header_cache) {
130 return Ok(Vec::new()); }
132
133 let mut errors = Vec::new();
134
135 let annotation_errors = check_lifetime_annotation_requirements(&program, &header_cache, &safety_context)?;
137 errors.extend(annotation_errors);
138
139 for function in &program.functions {
141 debug_println!("DEBUG: Checking function '{}'", function.name);
142
143 if is_system_header(&function.source_file) {
146 debug_println!("DEBUG: Skipping system header function '{}' from {}", function.name, function.source_file);
147 continue;
148 }
149
150 if !safety_context.should_check_function(&function.name) {
152 debug_println!("DEBUG: Skipping unsafe function '{}'", function.name);
153 continue; }
155 debug_println!("DEBUG: Function '{}' is safe, checking...", function.name);
156
157 let function_errors = check_function_with_header_cache(function, &header_cache)?;
159 errors.extend(function_errors);
160 }
161
162 for function in &program.functions {
164 if is_system_header(&function.source_file) {
166 continue;
167 }
168
169 if safety_context.should_check_function(&function.name) {
170 let inference_errors = lifetime_inference::infer_and_validate_lifetimes(function)?;
171 errors.extend(inference_errors);
172
173 let raii_errors = raii_tracking::check_raii_issues(function, &header_cache)?;
175 errors.extend(raii_errors);
176 }
177 }
178
179 if header_cache.has_signatures() {
181 let lifetime_errors = lifetime_checker::check_lifetimes_with_annotations(&program, &header_cache, &safety_context)?;
183 errors.extend(lifetime_errors);
184
185 let scope_errors = scope_lifetime::check_scoped_lifetimes(&program, &header_cache, &safety_context)?;
187 errors.extend(scope_errors);
188 }
189
190 Ok(errors)
191}
192
193fn has_any_safe_functions(program: &IrProgram, header_cache: &HeaderCache) -> bool {
194 use crate::parser::annotations::SafetyAnnotation;
195
196 for function in &program.functions {
197 if let Some(sig) = header_cache.get_signature(&function.name) {
198 if let Some(SafetyAnnotation::Safe) = sig.safety {
199 return true;
200 }
201 }
202 }
203 false
204}
205
206fn returns_reference(return_type: &str) -> bool {
208 return_type.contains('&') && !return_type.contains("&&") }
212
213fn check_lifetime_annotation_requirements(
215 program: &IrProgram,
216 header_cache: &HeaderCache,
217 safety_context: &crate::parser::safety_annotations::SafetyContext
218) -> Result<Vec<String>, String> {
219 let mut errors = Vec::new();
220
221 for function in &program.functions {
222 if is_system_header(&function.source_file) {
224 continue;
225 }
226
227 if !safety_context.should_check_function(&function.name) {
229 continue;
230 }
231
232 let has_lifetime_annotation = if let Some(sig) = header_cache.get_signature(&function.name) {
238 sig.return_lifetime.is_some()
239 } else {
240 false
241 };
242
243 let returns_ref = check_if_function_returns_reference(function);
245
246 if returns_ref && !has_lifetime_annotation {
247 errors.push(format!(
248 "Safe function '{}' returns a reference but has no @lifetime annotation",
249 function.name
250 ));
251 }
252 }
253
254 Ok(errors)
255}
256
257fn check_if_function_returns_reference(function: &IrFunction) -> bool {
259 function.return_type.contains('&') && !function.return_type.contains("&&")
262}
263
264#[allow(dead_code)]
265pub fn check_borrows_with_annotations(program: IrProgram, header_cache: HeaderCache) -> Result<Vec<String>, String> {
266 use crate::parser::annotations::SafetyAnnotation;
267 use crate::parser::safety_annotations::SafetyContext;
268 let mut errors = Vec::new();
269
270 let mut safety_context = SafetyContext::new();
272 safety_context.merge_header_annotations(&header_cache);
273
274 for function in &program.functions {
276 let is_unsafe = if let Some(sig) = header_cache.get_signature(&function.name) {
278 matches!(sig.safety, Some(SafetyAnnotation::Unsafe))
279 } else {
280 false
281 };
282
283 if !is_unsafe {
285 let function_errors = check_function(function)?;
286 errors.extend(function_errors);
287 }
288 }
289
290 for function in &program.functions {
292 let inference_errors = lifetime_inference::infer_and_validate_lifetimes(function)?;
293 errors.extend(inference_errors);
294 }
295
296 if header_cache.has_signatures() {
298 let lifetime_errors = lifetime_checker::check_lifetimes_with_annotations(&program, &header_cache, &safety_context)?;
300 errors.extend(lifetime_errors);
301
302 let scope_errors = scope_lifetime::check_scoped_lifetimes(&program, &header_cache, &safety_context)?;
304 errors.extend(scope_errors);
305 }
306
307 Ok(errors)
308}
309
310fn check_function(function: &IrFunction) -> Result<Vec<String>, String> {
312 let empty_cache = HeaderCache::new();
314 check_function_with_header_cache(function, &empty_cache)
315}
316
317fn check_function_with_header_cache(function: &IrFunction, header_cache: &HeaderCache) -> Result<Vec<String>, String> {
319 let mut errors = Vec::new();
320
321 let mut liveness_analyzer = liveness::LivenessAnalyzer::new();
323 let last_uses = liveness_analyzer.analyze(function);
324
325 let mut ownership_tracker = OwnershipTracker::with_liveness(last_uses);
327
328 let mut this_tracker = if function.is_method {
330 Some(this_tracking::ThisPointerTracker::new(function.method_qualifier.clone()))
331 } else {
332 None
333 };
334
335 for (name, var_info) in &function.variables {
337 ownership_tracker.set_ownership(name.clone(), var_info.ownership.clone());
338
339 match &var_info.ty {
341 crate::ir::VariableType::Reference(_) => {
342 ownership_tracker.mark_as_reference(name.clone(), false);
343 }
344 crate::ir::VariableType::MutableReference(_) => {
345 ownership_tracker.mark_as_reference(name.clone(), true);
346 }
347 _ => {}
348 }
349 }
350
351 for node_idx in function.cfg.node_indices() {
353 let block = &function.cfg[node_idx];
354
355 let mut i = 0;
357 while i < block.statements.len() {
358 let statement = &block.statements[i];
359
360 if matches!(statement, crate::ir::IrStatement::EnterLoop) {
362 let mut loop_end = i + 1;
364 let mut loop_depth = 1;
365 while loop_end < block.statements.len() && loop_depth > 0 {
366 match &block.statements[loop_end] {
367 crate::ir::IrStatement::EnterLoop => loop_depth += 1,
368 crate::ir::IrStatement::ExitLoop => loop_depth -= 1,
369 _ => {}
370 }
371 loop_end += 1;
372 }
373
374 let loop_body = &block.statements[i+1..loop_end-1];
376
377 ownership_tracker.enter_loop();
379
380 let mut loop_local_vars = HashSet::new();
382 collect_loop_local_vars(&loop_body, &mut loop_local_vars);
383
384 for (loop_idx, loop_stmt) in loop_body.iter().enumerate() {
386 process_statement(loop_stmt, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
387
388 ownership_tracker.check_and_clear_last_uses(i + 1 + loop_idx);
391 }
392
393 let state_after_first = ownership_tracker.ownership.clone();
395
396 ownership_tracker.clear_loop_locals(&loop_local_vars);
398
399 for (loop_idx, loop_stmt) in loop_body.iter().enumerate() {
401 check_statement_for_loop_errors(loop_stmt, &state_after_first, &loop_local_vars, &mut errors);
404 process_statement(loop_stmt, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
405
406 ownership_tracker.check_and_clear_last_uses(i + 1 + loop_idx);
408 }
409
410 ownership_tracker.clear_loop_locals(&loop_local_vars);
412
413 ownership_tracker.exit_loop();
414
415 i = loop_end;
417 } else {
418 process_statement(statement, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
420
421 ownership_tracker.check_and_clear_last_uses(i);
423
424 i += 1;
425 }
426 }
427 }
428
429 Ok(errors)
430}
431
432fn collect_loop_local_vars(statements: &[crate::ir::IrStatement], loop_local_vars: &mut HashSet<String>) {
435 for stmt in statements {
436 match stmt {
437 crate::ir::IrStatement::VarDecl { name, .. } => {
438 loop_local_vars.insert(name.clone());
439 }
440 crate::ir::IrStatement::Borrow { to, .. } => {
441 loop_local_vars.insert(to.clone());
442 }
443 crate::ir::IrStatement::Move { to, .. } => {
444 loop_local_vars.insert(to.clone());
445 }
446 crate::ir::IrStatement::Assign { lhs, .. } => {
447 loop_local_vars.insert(lhs.clone());
448 }
449 crate::ir::IrStatement::CallExpr { result: Some(var), .. } => {
450 loop_local_vars.insert(var.clone());
451 }
452 crate::ir::IrStatement::If { then_branch, else_branch } => {
454 collect_loop_local_vars(then_branch, loop_local_vars);
455 if let Some(else_stmts) = else_branch {
456 collect_loop_local_vars(else_stmts, loop_local_vars);
457 }
458 }
459 _ => {}
460 }
461 }
462}
463
464fn check_statement_for_loop_errors(
467 statement: &crate::ir::IrStatement,
468 state_after_first: &HashMap<String, OwnershipState>,
469 loop_local_vars: &HashSet<String>,
470 errors: &mut Vec<String>,
471) {
472 match statement {
473 crate::ir::IrStatement::Move { from, .. } => {
474 if loop_local_vars.contains(from) {
478 return;
479 }
480 if let Some(state) = state_after_first.get(from) {
481 if *state == OwnershipState::Moved {
482 errors.push(format!(
483 "Use after move in loop: variable '{}' was moved in first iteration and used again in second iteration",
484 from
485 ));
486 }
487 }
488 }
489 crate::ir::IrStatement::Assign { rhs, .. } => {
490 if let crate::ir::IrExpression::Variable(var) = rhs {
491 if loop_local_vars.contains(var) {
493 return;
494 }
495 if let Some(state) = state_after_first.get(var) {
496 if *state == OwnershipState::Moved {
497 errors.push(format!(
498 "Use after move in loop: variable '{}' was moved in first iteration and used again in second iteration",
499 var
500 ));
501 }
502 }
503 }
504 }
505 crate::ir::IrStatement::If { then_branch, else_branch } => {
507 for stmt in then_branch {
508 check_statement_for_loop_errors(stmt, state_after_first, loop_local_vars, errors);
509 }
510 if let Some(else_stmts) = else_branch {
511 for stmt in else_stmts {
512 check_statement_for_loop_errors(stmt, state_after_first, loop_local_vars, errors);
513 }
514 }
515 }
516 _ => {}
517 }
518}
519
520fn check_borrow_conflicts(
522 from: &str,
523 kind: &BorrowKind,
524 ownership_tracker: &OwnershipTracker,
525 errors: &mut Vec<String>,
526) -> bool {
527 let current_borrows = ownership_tracker.get_borrows(from);
528
529 match kind {
530 BorrowKind::Immutable => {
531 if current_borrows.has_mutable {
533 errors.push(format!(
534 "Cannot create immutable reference to '{}': already mutably borrowed",
535 from
536 ));
537 return false;
538 }
539 }
540 BorrowKind::Mutable => {
541 if current_borrows.immutable_count > 0 {
543 errors.push(format!(
544 "Cannot create mutable reference to '{}': already immutably borrowed",
545 from
546 ));
547 return false;
548 } else if current_borrows.has_mutable {
549 errors.push(format!(
550 "Cannot create mutable reference to '{}': already mutably borrowed",
551 from
552 ));
553 return false;
554 }
555 }
556 }
557
558 true
559}
560
561fn check_field_borrow_conflicts(
564 object: &str,
565 field: &str,
566 kind: &BorrowKind,
567 ownership_tracker: &OwnershipTracker,
568 errors: &mut Vec<String>,
569) -> bool {
570 let whole_object_borrows = ownership_tracker.get_borrows(object);
572 if whole_object_borrows.has_mutable {
573 errors.push(format!(
574 "Cannot borrow field '{}.{}': '{}' is already mutably borrowed",
575 object, field, object
576 ));
577 return false;
578 }
579 if whole_object_borrows.immutable_count > 0 && *kind == BorrowKind::Mutable {
580 errors.push(format!(
581 "Cannot mutably borrow field '{}.{}': '{}' is already immutably borrowed",
582 object, field, object
583 ));
584 return false;
585 }
586
587 let field_borrows = ownership_tracker.get_field_borrows(object, field);
589
590 match kind {
591 BorrowKind::Immutable => {
592 if field_borrows.has_mutable {
594 errors.push(format!(
595 "Cannot create immutable reference to '{}.{}': already mutably borrowed",
596 object, field
597 ));
598 return false;
599 }
600 }
601 BorrowKind::Mutable => {
602 if field_borrows.immutable_count > 0 {
604 errors.push(format!(
605 "Cannot create mutable reference to '{}.{}': already immutably borrowed",
606 object, field
607 ));
608 return false;
609 } else if field_borrows.has_mutable {
610 errors.push(format!(
611 "Cannot create mutable reference to '{}.{}': already mutably borrowed",
612 object, field
613 ));
614 return false;
615 }
616 }
617 }
618
619 true
620}
621
622fn check_whole_object_vs_field_borrows(
624 object: &str,
625 kind: &BorrowKind,
626 ownership_tracker: &OwnershipTracker,
627 errors: &mut Vec<String>,
628) -> bool {
629 let borrowed_fields = ownership_tracker.get_borrowed_fields(object);
631
632 if !borrowed_fields.is_empty() {
633 let field_list: Vec<String> = borrowed_fields.iter().map(|(f, _)| f.clone()).collect();
634
635 match kind {
636 BorrowKind::Mutable => {
637 errors.push(format!(
638 "Cannot mutably borrow '{}': fields are already borrowed ({})",
639 object, field_list.join(", ")
640 ));
641 return false;
642 }
643 BorrowKind::Immutable => {
644 let any_mutable = borrowed_fields.iter().any(|(_, is_mut)| *is_mut);
646 if any_mutable {
647 errors.push(format!(
648 "Cannot immutably borrow '{}': field is already mutably borrowed",
649 object
650 ));
651 return false;
652 }
653 }
655 }
656 }
657
658 true
659}
660
661fn process_statement(
664 statement: &crate::ir::IrStatement,
665 ownership_tracker: &mut OwnershipTracker,
666 this_tracker: &mut Option<this_tracking::ThisPointerTracker>,
667 errors: &mut Vec<String>,
668 header_cache: &HeaderCache, function: &IrFunction, ) {
671 match statement {
672 crate::ir::IrStatement::Move { from, to } => {
673 debug_println!("DEBUG ANALYSIS: Processing Move from '{}' to '{}'", from, to);
674 if ownership_tracker.is_in_unsafe_block() {
676 ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
678 ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
679 return;
680 }
681
682 let from_state = ownership_tracker.get_ownership(from);
684 debug_println!("DEBUG ANALYSIS: '{}' state: {:?}", from, from_state);
685
686 if ownership_tracker.is_reference(from) {
688 errors.push(format!(
689 "Cannot move out of '{}' because it is behind a reference",
690 from
691 ));
692 return;
693 }
694
695 if ownership_tracker.is_transitively_borrowed(from) {
698 let borrowers = ownership_tracker.get_transitive_borrowers(from);
699 errors.push(format!(
700 "Cannot move '{}' because it is borrowed by: {}",
701 from,
702 borrowers.join(", ")
703 ));
704 return;
705 }
706
707 if let Some(borrows) = ownership_tracker.get_active_borrows(to) {
711 if !borrows.is_empty() {
712 let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
713 errors.push(format!(
714 "Cannot assign to '{}' because it is borrowed by: {} (assignment would drop the old value)",
715 to,
716 borrower_names.join(", ")
717 ));
718 return;
719 }
720 }
721
722 if from_state == Some(&OwnershipState::Moved) {
723 errors.push(format!(
724 "Use after move: variable '{}' has already been moved",
725 from
726 ));
727 }
728
729 if ownership_tracker.has_moved_fields(from) {
731 let moved_fields = ownership_tracker.get_moved_fields(from);
732 errors.push(format!(
733 "Cannot move '{}' because it has been partially moved (moved fields: {})",
734 from,
735 moved_fields.join(", ")
736 ));
737 return;
738 }
739
740 if to.starts_with("_temp_move_") || to.starts_with("_moved_") {
742 ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
744 } else {
745 ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
747 ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
748 }
749 }
750
751 crate::ir::IrStatement::MoveField { object, field, to } => {
753 debug_println!("DEBUG ANALYSIS: Processing MoveField from '{}.{}' to '{}'", object, field, to);
754
755 if ownership_tracker.is_in_unsafe_block() {
757 ownership_tracker.mark_field_moved(object.clone(), field.clone());
758 ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
759 return;
760 }
761
762 let object_state = ownership_tracker.get_ownership(object);
764 if object_state == Some(&OwnershipState::Moved) {
765 errors.push(format!(
766 "Cannot move field '{}' from '{}' because '{}' has been moved",
767 field, object, object
768 ));
769 return;
770 }
771
772 let field_state = ownership_tracker.get_field_ownership(object, field);
774 if field_state == OwnershipState::Moved {
775 errors.push(format!(
776 "Use after move: field '{}.{}' has already been moved",
777 object, field
778 ));
779 return;
780 }
781
782 if ownership_tracker.is_transitively_borrowed(object) {
784 let borrowers = ownership_tracker.get_transitive_borrowers(object);
785 errors.push(format!(
786 "Cannot move field '{}.{}' because '{}' is borrowed by: {}",
787 object, field, object, borrowers.join(", ")
788 ));
789 return;
790 }
791
792 if let Some(tracker) = this_tracker {
795 if object == "this" {
796 if let Err(err) = tracker.can_move_member(field) {
797 errors.push(err);
798 return;
799 }
800 }
801 }
802
803 ownership_tracker.mark_field_moved(object.clone(), field.clone());
805 ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
806
807 if let Some(tracker) = this_tracker {
809 if object == "this" {
810 tracker.mark_field_moved(field.clone());
811 }
812 }
813 }
814
815 crate::ir::IrStatement::UseField { object, field, operation } => {
816 debug_println!("DEBUG ANALYSIS: UseField object='{}', field='{}', operation='{}'", object, field, operation);
817
818 if ownership_tracker.is_in_unsafe_block() {
820 return;
821 }
822
823 let object_state = ownership_tracker.get_ownership(object);
825 if object_state == Some(&OwnershipState::Moved) {
826 errors.push(format!(
827 "Cannot {} field '{}.{}' because '{}' has been moved",
828 operation, object, field, object
829 ));
830 return;
831 }
832
833 let field_state = ownership_tracker.get_field_ownership(object, field);
835 if field_state == OwnershipState::Moved {
836 errors.push(format!(
837 "Cannot {} field '{}.{}' because it has been moved",
838 operation, object, field
839 ));
840 return;
841 }
842
843 if let Some(tracker) = this_tracker {
845 if object == "this" {
846 if operation == "read" {
848 if let Err(err) = tracker.can_read_member(field) {
849 errors.push(err);
850 return;
851 }
852 }
853 else if operation == "write" {
855 if let Err(err) = tracker.can_modify_member(field) {
856 errors.push(err);
857 return;
858 }
859 }
860 }
861 }
862 }
863
864 crate::ir::IrStatement::BorrowField { object, field, to, kind } => {
865 debug_println!("DEBUG ANALYSIS: BorrowField from '{}.{}' to '{}'", object, field, to);
866
867 if ownership_tracker.is_in_unsafe_block() {
869 ownership_tracker.add_field_borrow(object.clone(), field.clone(), to.clone(), kind.clone());
871 ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
872 return;
873 }
874
875 let object_state = ownership_tracker.get_ownership(object);
877 if object_state == Some(&OwnershipState::Moved) {
878 errors.push(format!(
879 "Cannot borrow field '{}.{}' because '{}' has been moved",
880 field, object, object
881 ));
882 return;
883 }
884
885 let field_state = ownership_tracker.get_field_ownership(object, field);
887 if field_state == OwnershipState::Moved {
888 errors.push(format!(
889 "Cannot borrow field '{}.{}' because it has been moved",
890 object, field
891 ));
892 return;
893 }
894
895 if let Some(tracker) = this_tracker {
897 if object == "this" {
898 if let Err(err) = tracker.can_borrow_member(field, kind.clone()) {
899 errors.push(err);
900 return;
901 }
902 }
903 }
904
905 if !check_field_borrow_conflicts(object, field, kind, ownership_tracker, errors) {
907 return;
908 }
909
910 ownership_tracker.add_field_borrow(object.clone(), field.clone(), to.clone(), kind.clone());
912 ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
913
914 if let Some(tracker) = this_tracker {
916 if object == "this" {
917 tracker.mark_field_borrowed(field.clone(), kind.clone());
918 }
919 }
920 }
921
922 crate::ir::IrStatement::Borrow { from, to, kind } => {
923 if ownership_tracker.is_in_unsafe_block() {
925 ownership_tracker.add_borrow(from.clone(), to.clone(), kind.clone());
927 ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
928 return;
929 }
930
931 let from_state = ownership_tracker.get_ownership(from);
933
934 if from_state == Some(&OwnershipState::Moved) {
935 errors.push(format!(
936 "Cannot borrow '{}' because it has been moved",
937 from
938 ));
939 return;
940 }
941
942 let from_is_mutable_ref = ownership_tracker.is_mutable_reference(from);
946 let from_is_immutable_ref = ownership_tracker.is_reference(from) && !from_is_mutable_ref;
947
948 if !from_is_mutable_ref && !from_is_immutable_ref {
951 if !check_borrow_conflicts(from, kind, ownership_tracker, errors) {
952 return;
953 }
954
955 if !check_whole_object_vs_field_borrows(from, kind, ownership_tracker, errors) {
957 return;
958 }
959 }
960
961 ownership_tracker.add_borrow(from.clone(), to.clone(), kind.clone());
963 ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
964
965 if from_is_mutable_ref {
968 ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
969 }
970 }
972
973 crate::ir::IrStatement::Assign { lhs, rhs } => {
974 if ownership_tracker.is_in_unsafe_block() {
976 return;
977 }
978
979 if ownership_tracker.is_reference(lhs) && !ownership_tracker.is_mutable_reference(lhs) {
981 errors.push(format!(
982 "Cannot assign to '{}' through const reference",
983 lhs
984 ));
985 }
986
987 if let crate::ir::IrExpression::Variable(rhs_var) = rhs {
989 if ownership_tracker.get_ownership(rhs_var) == Some(&OwnershipState::Moved) {
990 errors.push(format!(
991 "Use after move: variable '{}' has been moved",
992 rhs_var
993 ));
994 }
995 }
996
997 if !ownership_tracker.is_reference(lhs) {
1001 ownership_tracker.set_ownership(lhs.clone(), OwnershipState::Owned);
1002 }
1003 }
1004
1005 crate::ir::IrStatement::Drop(var) => {
1006 debug_println!("DEBUG ANALYSIS: Processing explicit Drop for '{}'", var);
1007 if ownership_tracker.is_in_unsafe_block() {
1009 return;
1011 }
1012
1013 if let Some(borrows) = ownership_tracker.get_active_borrows(var) {
1016 if !borrows.is_empty() {
1017 let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
1018 errors.push(format!(
1019 "Cannot assign to '{}' because it is borrowed by: {} (assignment would drop the old value)",
1020 var,
1021 borrower_names.join(", ")
1022 ));
1023 return;
1024 }
1025 }
1026
1027 let state = ownership_tracker.get_ownership(var);
1029 if state == Some(&OwnershipState::Moved) {
1030 debug_println!("DEBUG ANALYSIS: Skipping drop check for '{}' - already moved", var);
1031 }
1033
1034 debug_println!("DEBUG ANALYSIS: Drop check passed for '{}' - subsequent assignment will transfer ownership", var);
1039 }
1040
1041 crate::ir::IrStatement::ImplicitDrop { var, has_destructor, .. } => {
1042 debug_println!("DEBUG ANALYSIS: Processing ImplicitDrop for '{}' (has_destructor={})", var, has_destructor);
1043 if ownership_tracker.is_in_unsafe_block() {
1045 if *has_destructor {
1047 ownership_tracker.set_ownership(var.clone(), OwnershipState::Moved);
1048 }
1049 ownership_tracker.clear_borrows_from(var);
1051 return;
1052 }
1053
1054 if *has_destructor {
1057 if let Some(borrows) = ownership_tracker.get_active_borrows(var) {
1060 if !borrows.is_empty() {
1061 let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
1062 errors.push(format!(
1063 "Cannot drop '{}' because it is borrowed by: {} (implicit drop at scope end)",
1064 var,
1065 borrower_names.join(", ")
1066 ));
1067 return;
1068 }
1069 }
1070
1071 let state = ownership_tracker.get_ownership(var);
1073 if state == Some(&OwnershipState::Moved) {
1074 debug_println!("DEBUG ANALYSIS: Skipping implicit drop for '{}' - already moved", var);
1075 return; }
1077
1078 debug_println!("DEBUG ANALYSIS: Marking '{}' as dropped (implicit drop)", var);
1080 ownership_tracker.set_ownership(var.clone(), OwnershipState::Moved);
1081 }
1082
1083 debug_println!("DEBUG ANALYSIS: Clearing borrows from '{}'", var);
1088 ownership_tracker.clear_borrows_from(var);
1089 }
1090
1091 crate::ir::IrStatement::EnterScope => {
1092 ownership_tracker.enter_scope();
1093 }
1094
1095 crate::ir::IrStatement::ExitScope => {
1096 let current_scope = ownership_tracker.scope_stack.len();
1102
1103 for (var_name, var_info) in &function.variables {
1105 if var_info.scope_level == current_scope {
1106 if let Some(active_borrows) = ownership_tracker.active_borrows.get(var_name) {
1108 for borrow in active_borrows {
1109 if borrow.scope < current_scope {
1111 errors.push(format!(
1112 "Dangling reference: '{}' borrows from '{}' which goes out of scope",
1113 borrow.borrower, var_name
1114 ));
1115 }
1116 }
1117 }
1118 }
1119 }
1120
1121 ownership_tracker.exit_scope();
1122 }
1123
1124 crate::ir::IrStatement::EnterLoop => {
1125 }
1127
1128 crate::ir::IrStatement::ExitLoop => {
1129 }
1131
1132 crate::ir::IrStatement::EnterUnsafe => {
1133 ownership_tracker.unsafe_depth += 1;
1134 }
1135
1136 crate::ir::IrStatement::ExitUnsafe => {
1137 if ownership_tracker.unsafe_depth > 0 {
1138 ownership_tracker.unsafe_depth -= 1;
1139 }
1140 }
1141
1142 crate::ir::IrStatement::If { then_branch, else_branch } => {
1143 if ownership_tracker.is_in_unsafe_block() {
1145 return;
1146 }
1147 let state_before_if = ownership_tracker.clone_state();
1150
1151 for stmt in then_branch {
1153 process_statement(stmt, ownership_tracker, this_tracker, errors, header_cache, function);
1154 }
1155 let state_after_then = ownership_tracker.clone_state();
1156
1157 ownership_tracker.restore_state(&state_before_if);
1159
1160 if let Some(else_stmts) = else_branch {
1161 for stmt in else_stmts {
1162 process_statement(stmt, ownership_tracker, this_tracker, errors, header_cache, function);
1163 }
1164 let state_after_else = ownership_tracker.clone_state();
1165
1166 ownership_tracker.merge_states(&state_after_then, &state_after_else);
1168 } else {
1169 ownership_tracker.merge_states(&state_after_then, &state_before_if);
1172 }
1173 }
1174
1175 crate::ir::IrStatement::UseVariable { var, operation } => {
1176 debug_println!("DEBUG ANALYSIS: UseVariable var='{}', operation='{}'", var, operation);
1177
1178 if ownership_tracker.is_in_unsafe_block() {
1180 debug_println!("DEBUG ANALYSIS: Skipping check - in unsafe block");
1181 return;
1182 }
1183
1184 let var_state = ownership_tracker.get_ownership(var);
1186 debug_println!("DEBUG ANALYSIS: var_state for '{}' = {:?}", var, var_state);
1187
1188 if var_state == Some(&OwnershipState::Moved) {
1189 errors.push(format!(
1190 "Use after move: cannot {} variable '{}' because it has been moved",
1191 operation, var
1192 ));
1193 }
1194 }
1195
1196 crate::ir::IrStatement::Return { value } => {
1197 if ownership_tracker.is_in_unsafe_block() {
1199 return;
1200 }
1201
1202 if let Some(val) = value {
1203 let var_state = ownership_tracker.get_ownership(val);
1205
1206 if var_state == Some(&OwnershipState::Moved) {
1207 errors.push(format!(
1208 "Cannot return '{}' because it has been moved",
1209 val
1210 ));
1211 }
1212 }
1213 }
1214
1215 crate::ir::IrStatement::PackExpansion { pack_name, operation } => {
1216 debug_println!("DEBUG ANALYSIS: PackExpansion pack='{}', operation='{}'", pack_name, operation);
1218
1219 if ownership_tracker.is_in_unsafe_block() {
1221 debug_println!("DEBUG ANALYSIS: Skipping pack check - in unsafe block");
1222 return;
1223 }
1224
1225 let pack_state = ownership_tracker.get_ownership(pack_name);
1227 debug_println!("DEBUG ANALYSIS: pack_state for '{}' = {:?}", pack_name, pack_state);
1228
1229 if pack_state == Some(&OwnershipState::Moved) {
1230 errors.push(format!(
1231 "Use after move: cannot use pack '{}' because it has been moved",
1232 pack_name
1233 ));
1234 return;
1235 }
1236
1237 match operation.as_str() {
1239 "move" | "forward" => {
1240 debug_println!("DEBUG ANALYSIS: Pack '{}' is being moved/forwarded", pack_name);
1242 ownership_tracker.set_ownership(pack_name.clone(), OwnershipState::Moved);
1243 }
1244 "use" => {
1245 debug_println!("DEBUG ANALYSIS: Pack '{}' is being used (immutable)", pack_name);
1248 }
1250 _ => {
1251 debug_println!("DEBUG ANALYSIS: Unknown pack operation '{}'", operation);
1252 }
1253 }
1254 }
1255
1256 crate::ir::IrStatement::CallExpr { func, args, result } => {
1258 debug_println!("DEBUG ANALYSIS PHASE2: CallExpr func='{}', args={:?}, result={:?}", func, args, result);
1259
1260 if ownership_tracker.is_in_unsafe_block() {
1262 return;
1263 }
1264
1265 let result_var = match result {
1267 Some(r) => r,
1268 None => return,
1269 };
1270
1271 debug_println!("DEBUG ANALYSIS PHASE2: Processing call result '{}'", result_var);
1272
1273 if let Some(signature) = header_cache.get_signature(func) {
1276 debug_println!("DEBUG ANALYSIS PHASE2: Found signature for function '{}'", func);
1277
1278 if !signature.param_lifetimes.is_empty() || signature.return_lifetime.is_some() {
1280 debug_println!("DEBUG ANALYSIS PHASE2: Function '{}' has lifetime annotations", func);
1281
1282 if let Some(ret_lifetime) = &signature.return_lifetime {
1284 debug_println!("DEBUG ANALYSIS PHASE2: Return lifetime annotation found");
1285
1286 for (param_idx, param_lifetime_opt) in signature.param_lifetimes.iter().enumerate() {
1288 if let Some(param_lifetime) = param_lifetime_opt {
1289 let ret_lifetime_name = match ret_lifetime {
1291 crate::parser::annotations::LifetimeAnnotation::Ref(name) |
1292 crate::parser::annotations::LifetimeAnnotation::MutRef(name) |
1293 crate::parser::annotations::LifetimeAnnotation::Lifetime(name) => Some(name),
1294 _ => None,
1295 };
1296
1297 let param_lifetime_name = match param_lifetime {
1298 crate::parser::annotations::LifetimeAnnotation::Ref(name) |
1299 crate::parser::annotations::LifetimeAnnotation::MutRef(name) |
1300 crate::parser::annotations::LifetimeAnnotation::Lifetime(name) => Some(name),
1301 _ => None,
1302 };
1303
1304 if ret_lifetime_name.is_some() && ret_lifetime_name == param_lifetime_name {
1306 debug_println!("DEBUG ANALYSIS PHASE2: Found matching lifetime '{}' between return and param {}",
1307 ret_lifetime_name.unwrap(), param_idx);
1308
1309 if param_idx < args.len() {
1311 let borrowed_var = &args[param_idx];
1312 debug_println!("DEBUG ANALYSIS PHASE2: Return value '{}' borrows from parameter '{}'",
1313 result_var, borrowed_var);
1314
1315 if borrowed_var.starts_with("_temp_literal_") || borrowed_var.starts_with("_temp_expr_") {
1319 debug_println!("DEBUG ANALYSIS: Detected dangling reference from temporary argument");
1320 errors.push(format!(
1321 "Dangling reference: function '{}' returns reference tied to temporary argument",
1322 func
1323 ));
1324 break; }
1326
1327 let borrow_kind = match ret_lifetime {
1331 crate::parser::annotations::LifetimeAnnotation::MutRef(_) => BorrowKind::Mutable,
1332 _ => {
1333 if let Some(var_info) = function.variables.get(result_var) {
1335 if matches!(var_info.ty, crate::ir::VariableType::MutableReference(_)) {
1336 BorrowKind::Mutable
1337 } else {
1338 BorrowKind::Immutable
1339 }
1340 } else {
1341 BorrowKind::Immutable
1342 }
1343 }
1344 };
1345
1346 let borrow_source = BorrowSource::MethodReturnValue {
1348 method: func.clone(),
1349 receiver: borrowed_var.clone(),
1350 };
1351
1352 debug_println!("DEBUG ANALYSIS PHASE2: Checking if should create borrow for '{}'", result_var);
1357 debug_println!("DEBUG ANALYSIS PHASE2: ret_lifetime.is_owned() = {}", ret_lifetime.is_owned());
1358
1359 let should_create_borrow = if !ret_lifetime.is_owned() {
1360 if let Some(var_info) = function.variables.get(result_var) {
1362 debug_println!("DEBUG ANALYSIS PHASE2: Found var_info for '{}', type = {:?}", result_var, var_info.ty);
1363 let is_ref = matches!(var_info.ty,
1364 crate::ir::VariableType::Reference(_) |
1365 crate::ir::VariableType::MutableReference(_));
1366 debug_println!("DEBUG ANALYSIS PHASE2: is_ref = {}", is_ref);
1367
1368 let is_complex = matches!(&var_info.ty, crate::ir::VariableType::Owned(type_name)
1369 if !is_primitive_type(type_name));
1370 debug_println!("DEBUG ANALYSIS PHASE2: is_complex = {}", is_complex);
1371
1372 is_ref || is_complex
1373 } else {
1374 debug_println!("DEBUG ANALYSIS PHASE2: No var_info found for '{}', assuming complex", result_var);
1375 true
1377 }
1378 } else {
1379 debug_println!("DEBUG ANALYSIS PHASE2: Return type is owned - no borrow");
1380 false
1382 };
1383
1384 debug_println!("DEBUG ANALYSIS PHASE2: should_create_borrow = {}", should_create_borrow);
1385
1386 if should_create_borrow {
1387 debug_println!("DEBUG ANALYSIS PHASE2: Adding {} borrow: '{}' -> '{}' (result is reference type)",
1388 if borrow_kind == BorrowKind::Mutable { "mutable" } else { "immutable" },
1389 borrowed_var, result_var);
1390
1391 if !check_borrow_conflicts(borrowed_var, &borrow_kind, ownership_tracker, errors) {
1393 debug_println!("DEBUG ANALYSIS PHASE3: Borrow conflict detected for '{}'", borrowed_var);
1394 break; }
1396
1397 let is_mutable = borrow_kind == BorrowKind::Mutable;
1398
1399 ownership_tracker.add_borrow_with_source(
1400 borrowed_var.clone(),
1401 result_var.clone(),
1402 borrow_kind,
1403 borrow_source
1404 );
1405
1406 ownership_tracker.mark_as_reference(
1408 result_var.clone(),
1409 is_mutable
1410 );
1411 } else {
1412 debug_println!("DEBUG ANALYSIS PHASE2: Skipping borrow creation for '{}' - result is value type, not reference",
1413 result_var);
1414 }
1415
1416 break;
1418 }
1419 }
1420 }
1421 }
1422 }
1423 }
1424 } else {
1425 debug_println!("DEBUG ANALYSIS PHASE2: No signature found for function '{}'", func);
1426 }
1427 }
1428
1429 _ => {}
1430 }
1431}
1432
1433struct OwnershipTracker {
1434 ownership: HashMap<String, OwnershipState>,
1435 borrows: HashMap<String, BorrowInfo>,
1436 reference_info: HashMap<String, ReferenceInfo>,
1437 scope_stack: Vec<ScopeInfo>,
1439 loop_depth: usize,
1441 loop_entry_states: Vec<LoopEntryState>,
1443 unsafe_depth: usize,
1445 active_borrows: HashMap<String, Vec<ActiveBorrow>>,
1448 field_ownership: HashMap<String, HashMap<String, OwnershipState>>,
1451 field_borrows: HashMap<String, HashMap<String, BorrowInfo>>,
1454 this_context: Option<ThisContext>,
1456 last_use_map: HashMap<String, usize>,
1459}
1460
1461#[derive(Clone)]
1462struct TrackerState {
1463 ownership: HashMap<String, OwnershipState>,
1464 borrows: HashMap<String, BorrowInfo>,
1465 reference_info: HashMap<String, ReferenceInfo>,
1466 active_borrows: HashMap<String, Vec<ActiveBorrow>>,
1467 field_ownership: HashMap<String, HashMap<String, OwnershipState>>,
1469 field_borrows: HashMap<String, HashMap<String, BorrowInfo>>,
1471}
1472
1473#[derive(Clone)]
1474struct LoopEntryState {
1475 ownership: HashMap<String, OwnershipState>,
1476 #[allow(dead_code)]
1477 borrows: HashMap<String, BorrowInfo>,
1478}
1479
1480#[derive(Default, Clone)]
1481struct ScopeInfo {
1482 local_borrows: HashSet<String>,
1484}
1485
1486#[derive(Default, Clone)]
1487struct BorrowInfo {
1488 immutable_count: usize,
1489 has_mutable: bool,
1490 borrowers: HashSet<String>,
1491}
1492
1493#[derive(Clone)]
1494struct ReferenceInfo {
1495 is_reference: bool,
1496 is_mutable: bool,
1497}
1498
1499#[derive(Clone, Debug)]
1502struct ActiveBorrow {
1503 borrower: String, borrowed_from: String, kind: BorrowKind,
1506 scope: usize, source: BorrowSource,
1509}
1510
1511#[derive(Clone, Debug, PartialEq)]
1513enum BorrowSource {
1514 DirectReference, MethodReturnValue { method: String, receiver: String, },
1519 FieldAccess { object: String,
1521 field: String,
1522 },
1523}
1524
1525#[derive(Clone, Debug, PartialEq)]
1527enum ThisContext {
1528 Borrowed, MutBorrowed, ConstBorrowed, Consumed, }
1533
1534impl OwnershipTracker {
1535 fn new() -> Self {
1536 Self::with_liveness(HashMap::new())
1537 }
1538
1539 fn with_liveness(last_use_map: HashMap<String, usize>) -> Self {
1540 let mut tracker = Self {
1541 ownership: HashMap::new(),
1542 borrows: HashMap::new(),
1543 reference_info: HashMap::new(),
1544 scope_stack: Vec::new(),
1545 loop_depth: 0,
1546 loop_entry_states: Vec::new(),
1547 unsafe_depth: 0,
1548 active_borrows: HashMap::new(),
1549 field_ownership: HashMap::new(), field_borrows: HashMap::new(), this_context: None, last_use_map, };
1554 tracker.scope_stack.push(ScopeInfo::default());
1556 tracker
1557 }
1558
1559 fn is_in_unsafe_block(&self) -> bool {
1560 self.unsafe_depth > 0
1561 }
1562
1563 fn set_ownership(&mut self, var: String, state: OwnershipState) {
1564 self.ownership.insert(var, state);
1565 }
1566
1567 fn get_ownership(&self, var: &str) -> Option<&OwnershipState> {
1568 self.ownership.get(var)
1569 }
1570
1571 fn get_borrows(&self, var: &str) -> BorrowInfo {
1572 self.borrows.get(var).cloned().unwrap_or_default()
1573 }
1574
1575 fn add_borrow_with_source(&mut self, from: String, to: String, kind: BorrowKind, source: BorrowSource) {
1577 let borrow_info = self.borrows.entry(from.clone()).or_default();
1578 borrow_info.borrowers.insert(to.clone());
1579
1580 if let Some(current_scope) = self.scope_stack.last_mut() {
1582 current_scope.local_borrows.insert(to.clone());
1583 }
1584
1585 match kind {
1586 BorrowKind::Immutable => borrow_info.immutable_count += 1,
1587 BorrowKind::Mutable => borrow_info.has_mutable = true,
1588 }
1589
1590 let current_scope_level = self.scope_stack.len();
1592 let active_borrow = ActiveBorrow {
1593 borrower: to,
1594 borrowed_from: from.clone(),
1595 kind,
1596 scope: current_scope_level,
1597 source, };
1599 self.active_borrows.entry(from).or_default().push(active_borrow);
1600 }
1601
1602 fn add_borrow(&mut self, from: String, to: String, kind: BorrowKind) {
1604 self.add_borrow_with_source(from, to, kind, BorrowSource::DirectReference);
1605 }
1606
1607 fn get_active_borrows(&self, var: &str) -> Option<&Vec<ActiveBorrow>> {
1609 self.active_borrows.get(var)
1610 }
1611
1612 fn is_transitively_borrowed(&self, var: &str) -> bool {
1616 if let Some(borrows) = self.active_borrows.get(var) {
1618 if !borrows.is_empty() {
1619 for borrow in borrows {
1621 if self.is_transitively_borrowed(&borrow.borrower) {
1623 return true;
1624 }
1625 }
1626 return true;
1628 }
1629 }
1630 false
1632 }
1633
1634 fn get_transitive_borrowers(&self, var: &str) -> Vec<String> {
1637 let mut result = Vec::new();
1638
1639 if let Some(borrows) = self.active_borrows.get(var) {
1640 for borrow in borrows {
1641 result.push(borrow.borrower.clone());
1642 let nested = self.get_transitive_borrowers(&borrow.borrower);
1644 result.extend(nested);
1645 }
1646 }
1647
1648 result
1649 }
1650
1651 fn clear_borrows_from(&mut self, var: &str) {
1654 debug_println!("LIVENESS: Clearing borrows from '{}'", var);
1655
1656 for (_borrowed_var, borrows) in &mut self.active_borrows {
1658 borrows.retain(|b| {
1659 if b.borrower == var {
1660 debug_println!("LIVENESS: Removing borrow: '{}' → '{}'", var, _borrowed_var);
1661 false
1662 } else {
1663 true
1664 }
1665 });
1666 }
1667
1668 self.active_borrows.retain(|_, borrows| !borrows.is_empty());
1670 }
1671
1672 fn check_and_clear_last_uses(&mut self, statement_idx: usize) {
1675 let vars_to_clear: Vec<String> = self.last_use_map.iter()
1676 .filter(|(_, &last_use_idx)| last_use_idx == statement_idx)
1677 .map(|(var, _)| var.clone())
1678 .collect();
1679
1680 for var in vars_to_clear {
1681 debug_println!("LIVENESS: Variable '{}' reached its last use at statement {}", var, statement_idx);
1682 self.clear_borrows_from(&var);
1683 }
1684 }
1685
1686 fn get_field_ownership(&self, object: &str, field: &str) -> OwnershipState {
1690 self.field_ownership
1691 .get(object)
1692 .and_then(|fields| fields.get(field))
1693 .cloned()
1694 .unwrap_or(OwnershipState::Owned)
1695 }
1696
1697 fn mark_field_moved(&mut self, object: String, field: String) {
1699 self.field_ownership
1700 .entry(object)
1701 .or_default()
1702 .insert(field, OwnershipState::Moved);
1703 }
1704
1705 fn has_moved_fields(&self, object: &str) -> bool {
1709 if self.field_ownership
1711 .get(object)
1712 .map(|fields| fields.values().any(|s| *s == OwnershipState::Moved))
1713 .unwrap_or(false) {
1714 return true;
1715 }
1716
1717 let prefix = format!("{}.", object);
1719 for (key, fields) in &self.field_ownership {
1720 if key.starts_with(&prefix) {
1721 if fields.values().any(|s| *s == OwnershipState::Moved) {
1722 return true;
1723 }
1724 }
1725 }
1726
1727 false
1728 }
1729
1730 fn get_moved_fields(&self, object: &str) -> Vec<String> {
1732 let mut result = Vec::new();
1733
1734 if let Some(fields) = self.field_ownership.get(object) {
1736 for (field, state) in fields.iter() {
1737 if *state == OwnershipState::Moved {
1738 result.push(field.clone());
1739 }
1740 }
1741 }
1742
1743 let prefix = format!("{}.", object);
1745 for (key, fields) in &self.field_ownership {
1746 if key.starts_with(&prefix) {
1747 for (field, state) in fields.iter() {
1748 if *state == OwnershipState::Moved {
1749 let nested_path = &key[prefix.len()..];
1752 result.push(format!("{}.{}", nested_path, field));
1753 }
1754 }
1755 }
1756 }
1757
1758 result
1759 }
1760
1761 fn get_field_borrows(&self, object: &str, field: &str) -> BorrowInfo {
1765 self.field_borrows
1766 .get(object)
1767 .and_then(|fields| fields.get(field))
1768 .cloned()
1769 .unwrap_or_default()
1770 }
1771
1772 fn add_field_borrow(&mut self, object: String, field: String, borrower: String, kind: BorrowKind) {
1774 let field_map = self.field_borrows.entry(object).or_default();
1775 let borrow_info = field_map.entry(field).or_default();
1776 borrow_info.borrowers.insert(borrower.clone());
1777
1778 match kind {
1779 BorrowKind::Immutable => borrow_info.immutable_count += 1,
1780 BorrowKind::Mutable => borrow_info.has_mutable = true,
1781 }
1782
1783 if let Some(current_scope) = self.scope_stack.last_mut() {
1785 current_scope.local_borrows.insert(borrower);
1786 }
1787 }
1788
1789 fn has_any_field_borrowed(&self, object: &str) -> bool {
1791 if let Some(fields) = self.field_borrows.get(object) {
1792 for borrow_info in fields.values() {
1793 if borrow_info.immutable_count > 0 || borrow_info.has_mutable {
1794 return true;
1795 }
1796 }
1797 }
1798 false
1799 }
1800
1801 fn get_borrowed_fields(&self, object: &str) -> Vec<(String, bool)> {
1803 let mut result = Vec::new();
1804 if let Some(fields) = self.field_borrows.get(object) {
1805 for (field, borrow_info) in fields {
1806 if borrow_info.has_mutable {
1807 result.push((field.clone(), true)); } else if borrow_info.immutable_count > 0 {
1809 result.push((field.clone(), false)); }
1811 }
1812 }
1813 result
1814 }
1815
1816 fn can_move_from_this(&self) -> bool {
1818 match &self.this_context {
1819 Some(ThisContext::Consumed) => true, Some(_) => false, None => true, }
1823 }
1824
1825 fn set_this_context(&mut self, context: Option<ThisContext>) {
1827 self.this_context = context;
1828 }
1829
1830 fn enter_scope(&mut self) {
1831 self.scope_stack.push(ScopeInfo::default());
1832 }
1833
1834 fn exit_scope(&mut self) {
1835 if let Some(scope) = self.scope_stack.pop() {
1836 let _current_scope_level = self.scope_stack.len() + 1; for borrow_name in &scope.local_borrows {
1840 self.reference_info.remove(borrow_name);
1842
1843 for borrow_info in self.borrows.values_mut() {
1845 borrow_info.borrowers.remove(borrow_name);
1846 }
1849
1850 for active_borrows in self.active_borrows.values_mut() {
1853 active_borrows.retain(|b| &b.borrower != borrow_name);
1854 }
1855
1856 for field_map in self.field_borrows.values_mut() {
1859 for borrow_info in field_map.values_mut() {
1860 if borrow_info.borrowers.remove(borrow_name) {
1861 if borrow_info.borrowers.is_empty() {
1866 borrow_info.has_mutable = false;
1867 borrow_info.immutable_count = 0;
1868 }
1869 }
1870 }
1871 }
1872 }
1873
1874 self.borrows.retain(|_, info| !info.borrowers.is_empty());
1876
1877 self.active_borrows.retain(|_, borrows| !borrows.is_empty());
1879
1880 for field_map in self.field_borrows.values_mut() {
1882 field_map.retain(|_, info| !info.borrowers.is_empty());
1883 }
1884 self.field_borrows.retain(|_, fields| !fields.is_empty());
1885 }
1886 }
1887
1888 fn mark_as_reference(&mut self, var: String, is_mutable: bool) {
1889 self.reference_info.insert(var, ReferenceInfo {
1890 is_reference: true,
1891 is_mutable,
1892 });
1893 }
1894
1895 fn is_reference(&self, var: &str) -> bool {
1896 self.reference_info
1897 .get(var)
1898 .map(|info| info.is_reference)
1899 .unwrap_or(false)
1900 }
1901
1902 fn is_mutable_reference(&self, var: &str) -> bool {
1903 self.reference_info
1904 .get(var)
1905 .map(|info| info.is_reference && info.is_mutable)
1906 .unwrap_or(false)
1907 }
1908
1909 fn enter_loop(&mut self) {
1910 self.loop_entry_states.push(LoopEntryState {
1914 ownership: self.ownership.clone(),
1915 borrows: self.borrows.clone(),
1916 });
1917 self.loop_depth += 1;
1918 }
1919
1920 fn exit_loop(&mut self) {
1921 if self.loop_depth > 0 {
1922 self.loop_depth -= 1;
1923
1924 if let Some(entry_state) = self.loop_entry_states.pop() {
1928 for (var, current_state) in &self.ownership {
1933 if *current_state == OwnershipState::Moved {
1934 if let Some(entry_ownership) = entry_state.ownership.get(var) {
1938 if *entry_ownership == OwnershipState::Owned {
1939 }
1944 }
1945 }
1946 }
1947 }
1948 }
1949 }
1950
1951 fn clone_state(&self) -> TrackerState {
1952 TrackerState {
1953 ownership: self.ownership.clone(),
1954 borrows: self.borrows.clone(),
1955 reference_info: self.reference_info.clone(),
1956 active_borrows: self.active_borrows.clone(),
1957 field_ownership: self.field_ownership.clone(), field_borrows: self.field_borrows.clone(), }
1960 }
1961
1962 fn restore_state(&mut self, state: &TrackerState) {
1963 self.ownership = state.ownership.clone();
1964 self.borrows = state.borrows.clone();
1965 self.reference_info = state.reference_info.clone();
1966 self.active_borrows = state.active_borrows.clone();
1967 self.field_ownership = state.field_ownership.clone(); self.field_borrows = state.field_borrows.clone(); }
1970
1971 fn merge_states(&mut self, then_state: &TrackerState, else_state: &TrackerState) {
1972 for (var, then_ownership) in &then_state.ownership {
1975 if let Some(else_ownership) = else_state.ownership.get(var) {
1976 if *then_ownership == OwnershipState::Moved || *else_ownership == OwnershipState::Moved {
1977 self.ownership.insert(var.clone(), OwnershipState::Moved);
1980 } else {
1981 self.ownership.insert(var.clone(), then_ownership.clone());
1983 }
1984 }
1985 }
1986
1987 self.borrows.clear();
1990 for (var, then_borrow) in &then_state.borrows {
1991 if let Some(else_borrow) = else_state.borrows.get(var) {
1992 let mut merged_borrow = then_borrow.clone();
1994 merged_borrow.borrowers.retain(|b| else_borrow.borrowers.contains(b));
1996 merged_borrow.immutable_count = merged_borrow.immutable_count.min(else_borrow.immutable_count);
1998 merged_borrow.has_mutable = merged_borrow.has_mutable && else_borrow.has_mutable;
1999
2000 if !merged_borrow.borrowers.is_empty() {
2001 self.borrows.insert(var.clone(), merged_borrow);
2002 }
2003 }
2004 }
2006
2007 let mut refs_to_keep = HashSet::new();
2009 for (var, _) in &then_state.reference_info {
2010 if else_state.reference_info.contains_key(var) {
2011 refs_to_keep.insert(var.clone());
2012 }
2013 }
2014 self.reference_info.retain(|var, _| refs_to_keep.contains(var));
2015
2016 self.active_borrows.clear();
2019 for (var, then_borrows) in &then_state.active_borrows {
2020 if let Some(else_borrows) = else_state.active_borrows.get(var) {
2021 let then_borrowers: HashSet<String> = then_borrows.iter().map(|b| b.borrower.clone()).collect();
2023 let else_borrowers: HashSet<String> = else_borrows.iter().map(|b| b.borrower.clone()).collect();
2024
2025 let common_borrowers: Vec<ActiveBorrow> = then_borrows.iter()
2026 .filter(|b| else_borrowers.contains(&b.borrower))
2027 .cloned()
2028 .collect();
2029
2030 if !common_borrowers.is_empty() {
2031 self.active_borrows.insert(var.clone(), common_borrowers);
2032 }
2033 }
2034 }
2035
2036 self.field_ownership.clear();
2038 let mut all_objects: HashSet<String> = HashSet::new();
2040 all_objects.extend(then_state.field_ownership.keys().cloned());
2041 all_objects.extend(else_state.field_ownership.keys().cloned());
2042
2043 for object in all_objects {
2044 let then_fields = then_state.field_ownership.get(&object);
2045 let else_fields = else_state.field_ownership.get(&object);
2046
2047 match (then_fields, else_fields) {
2048 (Some(then_f), Some(else_f)) => {
2049 let mut merged_fields = HashMap::new();
2051
2052 let mut all_field_names: HashSet<String> = HashSet::new();
2054 all_field_names.extend(then_f.keys().cloned());
2055 all_field_names.extend(else_f.keys().cloned());
2056
2057 for field in all_field_names {
2058 let then_state = then_f.get(&field);
2059 let else_state = else_f.get(&field);
2060
2061 match (then_state, else_state) {
2062 (Some(t), Some(e)) => {
2063 if *t == OwnershipState::Moved || *e == OwnershipState::Moved {
2065 merged_fields.insert(field, OwnershipState::Moved);
2066 } else {
2067 merged_fields.insert(field, t.clone());
2068 }
2069 }
2070 (Some(t), None) | (None, Some(t)) => {
2071 merged_fields.insert(field, t.clone());
2073 }
2074 (None, None) => unreachable!(),
2075 }
2076 }
2077
2078 if !merged_fields.is_empty() {
2079 self.field_ownership.insert(object, merged_fields);
2080 }
2081 }
2082 (Some(fields), None) | (None, Some(fields)) => {
2083 self.field_ownership.insert(object, fields.clone());
2085 }
2086 (None, None) => unreachable!(),
2087 }
2088 }
2089
2090 self.field_borrows.clear();
2092 let mut all_borrow_objects: HashSet<String> = HashSet::new();
2094 all_borrow_objects.extend(then_state.field_borrows.keys().cloned());
2095 all_borrow_objects.extend(else_state.field_borrows.keys().cloned());
2096
2097 for object in all_borrow_objects {
2098 let then_fields = then_state.field_borrows.get(&object);
2099 let else_fields = else_state.field_borrows.get(&object);
2100
2101 match (then_fields, else_fields) {
2102 (Some(then_f), Some(else_f)) => {
2103 let mut merged_fields = HashMap::new();
2105
2106 for (field, then_borrow) in then_f {
2107 if let Some(else_borrow) = else_f.get(field) {
2108 let mut merged = then_borrow.clone();
2110 merged.borrowers.retain(|b| else_borrow.borrowers.contains(b));
2111 merged.immutable_count = merged.immutable_count.min(else_borrow.immutable_count);
2112 merged.has_mutable = merged.has_mutable && else_borrow.has_mutable;
2113
2114 if !merged.borrowers.is_empty() || merged.immutable_count > 0 || merged.has_mutable {
2115 merged_fields.insert(field.clone(), merged);
2116 }
2117 }
2118 }
2119
2120 if !merged_fields.is_empty() {
2121 self.field_borrows.insert(object, merged_fields);
2122 }
2123 }
2124 _ => {
2125 }
2127 }
2128 }
2129 }
2130
2131 fn clear_loop_locals(&mut self, loop_locals: &HashSet<String>) {
2132 for local_var in loop_locals {
2134 self.reference_info.remove(local_var);
2136
2137 for borrow_info in self.borrows.values_mut() {
2139 borrow_info.borrowers.remove(local_var);
2140 }
2143
2144 self.ownership.remove(local_var);
2146 }
2147
2148 for (_, borrow_info) in self.borrows.iter_mut() {
2150 if borrow_info.borrowers.is_empty() {
2154 borrow_info.immutable_count = 0;
2155 borrow_info.has_mutable = false;
2156 }
2157 }
2158
2159 self.borrows.retain(|_, info| !info.borrowers.is_empty());
2161 }
2162}
2163
2164#[cfg(test)]
2165mod tests {
2166 use super::*;
2167 use crate::ir::{IrProgram, IrFunction, BasicBlock, IrStatement};
2168 use petgraph::graph::DiGraph;
2169
2170 fn create_test_program() -> IrProgram {
2171 IrProgram {
2172 functions: vec![],
2173 ownership_graph: DiGraph::new(),
2174 user_defined_raii_types: std::collections::HashSet::new(),
2175 }
2176 }
2177
2178 fn create_test_function(name: &str) -> IrFunction {
2179 let mut cfg = DiGraph::new();
2180 let block = BasicBlock {
2181 id: 0,
2182 statements: vec![],
2183 terminator: None,
2184 };
2185 cfg.add_node(block);
2186
2187 IrFunction {
2188 name: name.to_string(),
2189 cfg,
2190 variables: HashMap::new(),
2191 return_type: "void".to_string(),
2192 source_file: "test.cpp".to_string(),
2193 is_method: false,
2194 method_qualifier: None,
2195 class_name: None,
2196 template_parameters: vec![],
2197 lifetime_params: HashMap::new(),
2198 param_lifetimes: Vec::new(),
2199 return_lifetime: None,
2200 lifetime_constraints: Vec::new(),
2201 }
2202 }
2203
2204 #[test]
2205 fn test_empty_program_passes() {
2206 let program = create_test_program();
2207 let result = check_borrows(program);
2208
2209 assert!(result.is_ok());
2210 let errors = result.unwrap();
2211 assert_eq!(errors.len(), 0);
2212 }
2213
2214 #[test]
2215 fn test_ownership_tracker_initialization() {
2216 let mut tracker = OwnershipTracker::new();
2217 tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2218
2219 assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Owned));
2220 assert_eq!(tracker.get_ownership("y"), None);
2221 }
2222
2223 #[test]
2224 fn test_ownership_state_transitions() {
2225 let mut tracker = OwnershipTracker::new();
2226
2227 tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2229 assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Owned));
2230
2231 tracker.set_ownership("x".to_string(), OwnershipState::Moved);
2233 tracker.set_ownership("y".to_string(), OwnershipState::Owned);
2234
2235 assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Moved));
2236 assert_eq!(tracker.get_ownership("y"), Some(&OwnershipState::Owned));
2237 }
2238
2239 #[test]
2240 fn test_borrow_tracking() {
2241 let mut tracker = OwnershipTracker::new();
2242 tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2243
2244 tracker.add_borrow("x".to_string(), "ref1".to_string(), BorrowKind::Immutable);
2246 let borrows = tracker.get_borrows("x");
2247 assert_eq!(borrows.immutable_count, 1);
2248 assert!(!borrows.has_mutable);
2249
2250 tracker.add_borrow("x".to_string(), "ref2".to_string(), BorrowKind::Immutable);
2252 let borrows = tracker.get_borrows("x");
2253 assert_eq!(borrows.immutable_count, 2);
2254 assert!(!borrows.has_mutable);
2255 }
2256
2257 #[test]
2258 fn test_mutable_borrow_tracking() {
2259 let mut tracker = OwnershipTracker::new();
2260 tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2261
2262 tracker.add_borrow("x".to_string(), "mut_ref".to_string(), BorrowKind::Mutable);
2264 let borrows = tracker.get_borrows("x");
2265 assert_eq!(borrows.immutable_count, 0);
2266 assert!(borrows.has_mutable);
2267 }
2268
2269 #[test]
2270 fn test_use_after_move_detection() {
2271 let mut program = create_test_program();
2272 let mut func = create_test_function("test");
2273
2274 func.variables.insert(
2276 "x".to_string(),
2277 crate::ir::VariableInfo {
2278 name: "x".to_string(),
2279 ty: crate::ir::VariableType::Owned("int".to_string()),
2280 ownership: OwnershipState::Owned,
2281 lifetime: None,
2282 is_parameter: false,
2283 is_static: false,
2284 scope_level: 0,
2285 has_destructor: false,
2286 declaration_index: 0,
2287 },
2288 );
2289
2290 func.variables.insert(
2291 "y".to_string(),
2292 crate::ir::VariableInfo {
2293 name: "y".to_string(),
2294 ty: crate::ir::VariableType::Owned("int".to_string()),
2295 ownership: OwnershipState::Owned,
2296 lifetime: None,
2297 is_parameter: false,
2298 is_static: false,
2299 scope_level: 0,
2300 has_destructor: false,
2301 declaration_index: 0,
2302 },
2303 );
2304
2305 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2307 block.statements.push(IrStatement::Move {
2308 from: "x".to_string(),
2309 to: "y".to_string(),
2310 });
2311
2312 block.statements.push(IrStatement::Move {
2314 from: "x".to_string(),
2315 to: "z".to_string(),
2316 });
2317
2318 program.functions.push(func);
2319
2320 let result = check_borrows(program);
2321 assert!(result.is_ok());
2322
2323 let errors = result.unwrap();
2324 assert_eq!(errors.len(), 1);
2325 assert!(errors[0].contains("Use after move"));
2326 }
2327
2328 #[test]
2329 fn test_multiple_immutable_borrows_allowed() {
2330 let mut program = create_test_program();
2331 let mut func = create_test_function("test");
2332
2333 func.variables.insert(
2334 "x".to_string(),
2335 crate::ir::VariableInfo {
2336 name: "x".to_string(),
2337 ty: crate::ir::VariableType::Owned("int".to_string()),
2338 ownership: OwnershipState::Owned,
2339 lifetime: None,
2340 is_parameter: false,
2341 is_static: false,
2342 scope_level: 0,
2343 has_destructor: false,
2344 declaration_index: 0,
2345 },
2346 );
2347
2348 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2349 block.statements.push(IrStatement::Borrow {
2350 from: "x".to_string(),
2351 to: "ref1".to_string(),
2352 kind: BorrowKind::Immutable,
2353 });
2354
2355 block.statements.push(IrStatement::Borrow {
2356 from: "x".to_string(),
2357 to: "ref2".to_string(),
2358 kind: BorrowKind::Immutable,
2359 });
2360
2361 program.functions.push(func);
2362
2363 let result = check_borrows(program);
2364 assert!(result.is_ok());
2365
2366 let errors = result.unwrap();
2367 assert_eq!(errors.len(), 0); }
2369
2370 #[test]
2371 fn test_mutable_borrow_while_immutable_fails() {
2372 let mut program = create_test_program();
2373 let mut func = create_test_function("test");
2374
2375 func.variables.insert(
2376 "x".to_string(),
2377 crate::ir::VariableInfo {
2378 name: "x".to_string(),
2379 ty: crate::ir::VariableType::Owned("int".to_string()),
2380 ownership: OwnershipState::Owned,
2381 lifetime: None,
2382 is_parameter: false,
2383 is_static: false,
2384 scope_level: 0,
2385 has_destructor: false,
2386 declaration_index: 0,
2387 },
2388 );
2389
2390 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2391
2392 block.statements.push(IrStatement::Borrow {
2394 from: "x".to_string(),
2395 to: "ref1".to_string(),
2396 kind: BorrowKind::Immutable,
2397 });
2398
2399 block.statements.push(IrStatement::Borrow {
2401 from: "x".to_string(),
2402 to: "mut_ref".to_string(),
2403 kind: BorrowKind::Mutable,
2404 });
2405
2406 program.functions.push(func);
2407
2408 let result = check_borrows(program);
2409 assert!(result.is_ok());
2410
2411 let errors = result.unwrap();
2412 assert_eq!(errors.len(), 1);
2413 assert!(errors[0].contains("Cannot"));
2414 assert!(errors[0].contains("mutable"));
2415 }
2416
2417 #[test]
2418 fn test_const_reference_cannot_modify() {
2419 let mut program = create_test_program();
2420 let mut func = create_test_function("test");
2421
2422 func.variables.insert(
2424 "value".to_string(),
2425 crate::ir::VariableInfo {
2426 name: "value".to_string(),
2427 ty: crate::ir::VariableType::Owned("int".to_string()),
2428 ownership: OwnershipState::Owned,
2429 lifetime: None,
2430 is_parameter: false,
2431 is_static: false,
2432 scope_level: 0,
2433 has_destructor: false,
2434 declaration_index: 0,
2435 },
2436 );
2437
2438 func.variables.insert(
2439 "const_ref".to_string(),
2440 crate::ir::VariableInfo {
2441 name: "const_ref".to_string(),
2442 ty: crate::ir::VariableType::Reference("int".to_string()),
2443 ownership: OwnershipState::Borrowed(BorrowKind::Immutable),
2444 lifetime: None,
2445 is_parameter: false,
2446 is_static: false,
2447 scope_level: 0,
2448 has_destructor: false,
2449 declaration_index: 0,
2450 },
2451 );
2452
2453 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2454
2455 block.statements.push(IrStatement::Borrow {
2457 from: "value".to_string(),
2458 to: "const_ref".to_string(),
2459 kind: BorrowKind::Immutable,
2460 });
2461
2462 block.statements.push(IrStatement::Assign {
2464 lhs: "const_ref".to_string(),
2465 rhs: crate::ir::IrExpression::Variable("other".to_string()),
2466 });
2467
2468 program.functions.push(func);
2469
2470 let result = check_borrows(program);
2471 assert!(result.is_ok());
2472
2473 let errors = result.unwrap();
2474 assert_eq!(errors.len(), 1);
2475 assert!(errors[0].contains("Cannot assign"));
2476 assert!(errors[0].contains("const reference"));
2477 }
2478
2479 #[test]
2480 fn test_mutable_reference_can_modify() {
2481 let mut program = create_test_program();
2482 let mut func = create_test_function("test");
2483
2484 func.variables.insert(
2486 "value".to_string(),
2487 crate::ir::VariableInfo {
2488 name: "value".to_string(),
2489 ty: crate::ir::VariableType::Owned("int".to_string()),
2490 ownership: OwnershipState::Owned,
2491 lifetime: None,
2492 is_parameter: false,
2493 is_static: false,
2494 scope_level: 0,
2495 has_destructor: false,
2496 declaration_index: 0,
2497 },
2498 );
2499
2500 func.variables.insert(
2501 "mut_ref".to_string(),
2502 crate::ir::VariableInfo {
2503 name: "mut_ref".to_string(),
2504 ty: crate::ir::VariableType::MutableReference("int".to_string()),
2505 ownership: OwnershipState::Borrowed(BorrowKind::Mutable),
2506 lifetime: None,
2507 is_parameter: false,
2508 is_static: false,
2509 scope_level: 0,
2510 has_destructor: false,
2511 declaration_index: 0,
2512 },
2513 );
2514
2515 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2516
2517 block.statements.push(IrStatement::Borrow {
2519 from: "value".to_string(),
2520 to: "mut_ref".to_string(),
2521 kind: BorrowKind::Mutable,
2522 });
2523
2524 block.statements.push(IrStatement::Assign {
2526 lhs: "mut_ref".to_string(),
2527 rhs: crate::ir::IrExpression::Variable("other".to_string()),
2528 });
2529
2530 program.functions.push(func);
2531
2532 let result = check_borrows(program);
2533 assert!(result.is_ok());
2534
2535 let errors = result.unwrap();
2536 assert_eq!(errors.len(), 0); }
2538
2539 #[test]
2540 fn test_cannot_move_from_reference() {
2541 let mut program = create_test_program();
2542 let mut func = create_test_function("test");
2543
2544 func.variables.insert(
2546 "ref_var".to_string(),
2547 crate::ir::VariableInfo {
2548 name: "ref_var".to_string(),
2549 ty: crate::ir::VariableType::Reference("int".to_string()),
2550 ownership: OwnershipState::Borrowed(BorrowKind::Immutable),
2551 lifetime: None,
2552 is_parameter: false,
2553 is_static: false,
2554 scope_level: 0,
2555 has_destructor: false,
2556 declaration_index: 0,
2557 },
2558 );
2559
2560 func.variables.insert(
2561 "dest".to_string(),
2562 crate::ir::VariableInfo {
2563 name: "dest".to_string(),
2564 ty: crate::ir::VariableType::Owned("int".to_string()),
2565 ownership: OwnershipState::Owned,
2566 lifetime: None,
2567 is_parameter: false,
2568 is_static: false,
2569 scope_level: 0,
2570 has_destructor: false,
2571 declaration_index: 0,
2572 },
2573 );
2574
2575 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2576
2577 block.statements.push(IrStatement::Move {
2579 from: "ref_var".to_string(),
2580 to: "dest".to_string(),
2581 });
2582
2583 program.functions.push(func);
2584
2585 let result = check_borrows(program);
2586 assert!(result.is_ok());
2587
2588 let errors = result.unwrap();
2589 assert_eq!(errors.len(), 1);
2590 assert!(errors[0].contains("Cannot move"));
2591 assert!(errors[0].contains("reference"));
2592 }
2593
2594 #[test]
2595 fn test_multiple_const_references_allowed() {
2596 let mut program = create_test_program();
2597 let mut func = create_test_function("test");
2598
2599 func.variables.insert(
2600 "value".to_string(),
2601 crate::ir::VariableInfo {
2602 name: "value".to_string(),
2603 ty: crate::ir::VariableType::Owned("int".to_string()),
2604 ownership: OwnershipState::Owned,
2605 lifetime: None,
2606 is_parameter: false,
2607 is_static: false,
2608 scope_level: 0,
2609 has_destructor: false,
2610 declaration_index: 0,
2611 },
2612 );
2613
2614 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2615
2616 block.statements.push(IrStatement::Borrow {
2618 from: "value".to_string(),
2619 to: "const_ref1".to_string(),
2620 kind: BorrowKind::Immutable,
2621 });
2622
2623 block.statements.push(IrStatement::Borrow {
2624 from: "value".to_string(),
2625 to: "const_ref2".to_string(),
2626 kind: BorrowKind::Immutable,
2627 });
2628
2629 block.statements.push(IrStatement::Borrow {
2630 from: "value".to_string(),
2631 to: "const_ref3".to_string(),
2632 kind: BorrowKind::Immutable,
2633 });
2634
2635 program.functions.push(func);
2636
2637 let result = check_borrows(program);
2638 assert!(result.is_ok());
2639
2640 let errors = result.unwrap();
2641 assert_eq!(errors.len(), 0); }
2643
2644 #[test]
2645 fn test_cannot_borrow_moved_value() {
2646 let mut program = create_test_program();
2647 let mut func = create_test_function("test");
2648
2649 func.variables.insert(
2650 "value".to_string(),
2651 crate::ir::VariableInfo {
2652 name: "value".to_string(),
2653 ty: crate::ir::VariableType::UniquePtr("int".to_string()),
2654 ownership: OwnershipState::Owned,
2655 lifetime: None,
2656 is_parameter: false,
2657 is_static: false,
2658 scope_level: 0,
2659 has_destructor: false,
2660 declaration_index: 0,
2661 },
2662 );
2663
2664 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2665
2666 block.statements.push(IrStatement::Move {
2668 from: "value".to_string(),
2669 to: "other".to_string(),
2670 });
2671
2672 block.statements.push(IrStatement::Borrow {
2674 from: "value".to_string(),
2675 to: "ref".to_string(),
2676 kind: BorrowKind::Immutable,
2677 });
2678
2679 program.functions.push(func);
2680
2681 let result = check_borrows(program);
2682 assert!(result.is_ok());
2683
2684 let errors = result.unwrap();
2685 assert!(errors.len() > 0);
2686 assert!(errors.iter().any(|e| e.contains("moved")));
2687 }
2688
2689 #[test]
2690 fn test_multiple_functions_with_references() {
2691 let mut program = create_test_program();
2692
2693 let mut func1 = create_test_function("func1");
2695 func1.variables.insert(
2696 "x".to_string(),
2697 crate::ir::VariableInfo {
2698 name: "x".to_string(),
2699 ty: crate::ir::VariableType::Owned("int".to_string()),
2700 ownership: OwnershipState::Owned,
2701 lifetime: None,
2702 is_parameter: false,
2703 is_static: false,
2704 scope_level: 0,
2705 has_destructor: false,
2706 declaration_index: 0,
2707 },
2708 );
2709
2710 let block1 = &mut func1.cfg[petgraph::graph::NodeIndex::new(0)];
2711 block1.statements.push(IrStatement::Borrow {
2712 from: "x".to_string(),
2713 to: "ref1".to_string(),
2714 kind: BorrowKind::Immutable,
2715 });
2716 block1.statements.push(IrStatement::Borrow {
2717 from: "x".to_string(),
2718 to: "ref2".to_string(),
2719 kind: BorrowKind::Immutable,
2720 });
2721
2722 let mut func2 = create_test_function("func2");
2724 func2.variables.insert(
2725 "y".to_string(),
2726 crate::ir::VariableInfo {
2727 name: "y".to_string(),
2728 ty: crate::ir::VariableType::Owned("int".to_string()),
2729 ownership: OwnershipState::Owned,
2730 lifetime: None,
2731 is_parameter: false,
2732 is_static: false,
2733 scope_level: 0,
2734 has_destructor: false,
2735 declaration_index: 0,
2736 },
2737 );
2738
2739 let block2 = &mut func2.cfg[petgraph::graph::NodeIndex::new(0)];
2740 block2.statements.push(IrStatement::Borrow {
2741 from: "y".to_string(),
2742 to: "mut1".to_string(),
2743 kind: BorrowKind::Mutable,
2744 });
2745 block2.statements.push(IrStatement::Borrow {
2746 from: "y".to_string(),
2747 to: "mut2".to_string(),
2748 kind: BorrowKind::Mutable,
2749 });
2750
2751 program.functions.push(func1);
2752 program.functions.push(func2);
2753
2754 let result = check_borrows(program);
2755 assert!(result.is_ok());
2756
2757 let errors = result.unwrap();
2758 assert_eq!(errors.len(), 1); assert!(errors[0].contains("already mutably borrowed"));
2760 }
2761
2762 #[test]
2763 fn test_complex_borrow_chain() {
2764 let mut program = create_test_program();
2765 let mut func = create_test_function("test");
2766
2767 func.variables.insert(
2769 "a".to_string(),
2770 crate::ir::VariableInfo {
2771 name: "a".to_string(),
2772 ty: crate::ir::VariableType::Owned("int".to_string()),
2773 ownership: OwnershipState::Owned,
2774 lifetime: None,
2775 is_parameter: false,
2776 is_static: false,
2777 scope_level: 0,
2778 has_destructor: false,
2779 declaration_index: 0,
2780 },
2781 );
2782
2783 func.variables.insert(
2784 "b".to_string(),
2785 crate::ir::VariableInfo {
2786 name: "b".to_string(),
2787 ty: crate::ir::VariableType::Owned("int".to_string()),
2788 ownership: OwnershipState::Owned,
2789 lifetime: None,
2790 is_parameter: false,
2791 is_static: false,
2792 scope_level: 0,
2793 has_destructor: false,
2794 declaration_index: 0,
2795 },
2796 );
2797
2798 let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2799
2800 block.statements.push(IrStatement::Borrow {
2802 from: "a".to_string(),
2803 to: "ref_a1".to_string(),
2804 kind: BorrowKind::Immutable,
2805 });
2806 block.statements.push(IrStatement::Borrow {
2807 from: "a".to_string(),
2808 to: "ref_a2".to_string(),
2809 kind: BorrowKind::Immutable,
2810 });
2811
2812 block.statements.push(IrStatement::Borrow {
2814 from: "b".to_string(),
2815 to: "mut_b".to_string(),
2816 kind: BorrowKind::Mutable,
2817 });
2818
2819 block.statements.push(IrStatement::Borrow {
2821 from: "b".to_string(),
2822 to: "ref_b".to_string(),
2823 kind: BorrowKind::Immutable,
2824 });
2825
2826 program.functions.push(func);
2827
2828 let result = check_borrows(program);
2829 assert!(result.is_ok());
2830
2831 let errors = result.unwrap();
2832 assert_eq!(errors.len(), 1);
2833 assert!(errors[0].contains("'b'"));
2834 assert!(errors[0].contains("already mutably borrowed"));
2835 }
2836}
2837#[cfg(test)]
2838mod scope_tests {
2839 use super::*;
2840 use crate::ir::{BasicBlock, IrFunction, IrProgram, IrStatement, BorrowKind};
2841 use petgraph::graph::Graph;
2842 use std::collections::HashMap;
2843
2844 fn create_test_function_with_statements(statements: Vec<IrStatement>) -> IrFunction {
2845 let mut cfg = Graph::new();
2846 let block = BasicBlock {
2847 id: 0,
2848 statements,
2849 terminator: None,
2850 };
2851 cfg.add_node(block);
2852
2853 IrFunction {
2854 name: "test".to_string(),
2855 cfg,
2856 variables: HashMap::new(),
2857 return_type: "void".to_string(),
2858 source_file: "test.cpp".to_string(),
2859 is_method: false,
2860 method_qualifier: None,
2861 class_name: None,
2862 template_parameters: vec![],
2863 lifetime_params: HashMap::new(),
2864 param_lifetimes: Vec::new(),
2865 return_lifetime: None,
2866 lifetime_constraints: Vec::new(),
2867 }
2868 }
2869
2870 #[test]
2871 fn test_scope_cleanup_simple() {
2872 let statements = vec![
2873 IrStatement::EnterScope,
2874 IrStatement::Borrow {
2875 from: "value".to_string(),
2876 to: "ref1".to_string(),
2877 kind: BorrowKind::Mutable,
2878 },
2879 IrStatement::ExitScope,
2880 IrStatement::Borrow {
2882 from: "value".to_string(),
2883 to: "ref2".to_string(),
2884 kind: BorrowKind::Mutable,
2885 },
2886 ];
2887
2888 let func = create_test_function_with_statements(statements);
2889 let mut program = IrProgram {
2890 functions: vec![func],
2891 ownership_graph: petgraph::graph::DiGraph::new(),
2892 user_defined_raii_types: std::collections::HashSet::new(),
2893 };
2894
2895 let result = check_borrows(program);
2896 assert!(result.is_ok());
2897 let errors = result.unwrap();
2898 assert_eq!(errors.len(), 0, "Should not report errors for borrows in different scopes");
2899 }
2900
2901 #[test]
2902 fn test_nested_scopes() {
2903 let statements = vec![
2904 IrStatement::EnterScope,
2905 IrStatement::Borrow {
2906 from: "value".to_string(),
2907 to: "ref1".to_string(),
2908 kind: BorrowKind::Immutable,
2909 },
2910 IrStatement::EnterScope,
2911 IrStatement::Borrow {
2913 from: "value".to_string(),
2914 to: "ref2".to_string(),
2915 kind: BorrowKind::Immutable,
2916 },
2917 IrStatement::ExitScope,
2918 IrStatement::ExitScope,
2920 IrStatement::Borrow {
2922 from: "value".to_string(),
2923 to: "ref3".to_string(),
2924 kind: BorrowKind::Mutable,
2925 },
2926 ];
2927
2928 let func = create_test_function_with_statements(statements);
2929 let mut program = IrProgram {
2930 functions: vec![func],
2931 ownership_graph: petgraph::graph::DiGraph::new(),
2932 user_defined_raii_types: std::collections::HashSet::new(),
2933 };
2934
2935 let result = check_borrows(program);
2936 assert!(result.is_ok());
2937 let errors = result.unwrap();
2938 assert_eq!(errors.len(), 0, "Nested scopes should work correctly");
2939 }
2940
2941 #[test]
2942 fn test_scope_doesnt_affect_moves() {
2943 let statements = vec![
2944 IrStatement::EnterScope,
2945 IrStatement::Move {
2946 from: "x".to_string(),
2947 to: "y".to_string(),
2948 },
2949 IrStatement::ExitScope,
2950 IrStatement::Move {
2952 from: "x".to_string(),
2953 to: "z".to_string(),
2954 },
2955 ];
2956
2957 let func = create_test_function_with_statements(statements);
2958 let mut program = IrProgram {
2959 functions: vec![func],
2960 ownership_graph: petgraph::graph::DiGraph::new(),
2961 user_defined_raii_types: std::collections::HashSet::new(),
2962 };
2963
2964 let result = check_borrows(program);
2965 assert!(result.is_ok());
2966 let errors = result.unwrap();
2967 assert!(errors.len() > 0, "Should still detect use-after-move across scopes");
2968 assert!(errors[0].contains("already been moved") || errors[0].contains("Use after move"));
2969 }
2970
2971 #[test]
2972 fn test_multiple_sequential_scopes() {
2973 let statements = vec![
2974 IrStatement::EnterScope,
2976 IrStatement::Borrow {
2977 from: "value".to_string(),
2978 to: "ref1".to_string(),
2979 kind: BorrowKind::Mutable,
2980 },
2981 IrStatement::ExitScope,
2982
2983 IrStatement::EnterScope,
2985 IrStatement::Borrow {
2986 from: "value".to_string(),
2987 to: "ref2".to_string(),
2988 kind: BorrowKind::Mutable,
2989 },
2990 IrStatement::ExitScope,
2991
2992 IrStatement::EnterScope,
2994 IrStatement::Borrow {
2995 from: "value".to_string(),
2996 to: "ref3".to_string(),
2997 kind: BorrowKind::Mutable,
2998 },
2999 IrStatement::ExitScope,
3000 ];
3001
3002 let func = create_test_function_with_statements(statements);
3003 let mut program = IrProgram {
3004 functions: vec![func],
3005 ownership_graph: petgraph::graph::DiGraph::new(),
3006 user_defined_raii_types: std::collections::HashSet::new(),
3007 };
3008
3009 let result = check_borrows(program);
3010 assert!(result.is_ok());
3011 let errors = result.unwrap();
3012 assert_eq!(errors.len(), 0, "Sequential scopes should not conflict");
3013 }
3014
3015 #[test]
3016 fn test_error_still_caught_in_same_scope() {
3017 let statements = vec![
3018 IrStatement::EnterScope,
3019 IrStatement::Borrow {
3020 from: "value".to_string(),
3021 to: "ref1".to_string(),
3022 kind: BorrowKind::Mutable,
3023 },
3024 IrStatement::Borrow {
3026 from: "value".to_string(),
3027 to: "ref2".to_string(),
3028 kind: BorrowKind::Mutable,
3029 },
3030 IrStatement::ExitScope,
3031 ];
3032
3033 let func = create_test_function_with_statements(statements);
3034 let mut program = IrProgram {
3035 functions: vec![func],
3036 ownership_graph: petgraph::graph::DiGraph::new(),
3037 user_defined_raii_types: std::collections::HashSet::new(),
3038 };
3039
3040 let result = check_borrows(program);
3041 assert!(result.is_ok());
3042 let errors = result.unwrap();
3043 assert!(errors.len() > 0, "Should still catch errors within the same scope");
3044 assert!(errors[0].contains("already mutably borrowed"));
3045 }
3046}