1use super::analysis::*;
53use super::cfg::ControlFlowGraph;
54use super::liveness::{self, LivenessResult};
55use super::types::*;
56use crate::type_tracking::EscapeStatus;
57use datafrog::{Iteration, Relation, RelationLeaper};
58use std::collections::{HashMap, HashSet};
59
60pub type CalleeSummaries = HashMap<String, ReturnReferenceSummary>;
62
63#[derive(Debug, Default)]
65pub struct BorrowFacts {
66 pub loan_issued_at: Vec<(u32, u32)>,
68 pub cfg_edge: Vec<(u32, u32)>,
70 pub invalidates: Vec<(u32, u32)>,
72 pub use_of_loan: Vec<(u32, u32)>,
74 pub point_spans: HashMap<u32, shape_ast::ast::Span>,
76 pub loan_info: HashMap<u32, LoanInfo>,
78 pub potential_conflicts: Vec<(u32, u32)>, pub writes: Vec<(u32, Place, shape_ast::ast::Span)>,
82 pub reads: Vec<(u32, Place, shape_ast::ast::Span)>,
84 pub slot_escape_status: HashMap<SlotId, EscapeStatus>,
86 pub escaped_loans: Vec<(u32, shape_ast::ast::Span)>,
88 pub loan_sinks: Vec<LoanSink>,
90 pub task_boundary_loans: Vec<(u32, shape_ast::ast::Span)>,
92 pub closure_capture_loans: Vec<(u32, shape_ast::ast::Span)>,
94 pub array_store_loans: Vec<(u32, shape_ast::ast::Span)>,
96 pub object_store_loans: Vec<(u32, shape_ast::ast::Span)>,
98 pub enum_store_loans: Vec<(u32, shape_ast::ast::Span)>,
100 pub object_assignment_loans: Vec<(u32, shape_ast::ast::Span)>,
102 pub array_assignment_loans: Vec<(u32, shape_ast::ast::Span)>,
104 pub return_reference_candidates: Vec<(ReturnReferenceSummary, shape_ast::ast::Span)>,
106 pub non_reference_return_spans: Vec<shape_ast::ast::Span>,
108 pub non_sendable_task_boundary: Vec<(u32, shape_ast::ast::Span)>,
111}
112
113pub fn extract_facts(
115 mir: &MirFunction,
116 cfg: &ControlFlowGraph,
117 callee_summaries: &CalleeSummaries,
118) -> BorrowFacts {
119 let mut facts = BorrowFacts::default();
120 let mut next_loan = 0u32;
121 let mut slot_loans: HashMap<SlotId, Vec<u32>> = HashMap::new();
122 let mut slot_reference_origins: HashMap<SlotId, (BorrowKind, ReferenceOrigin)> =
123 HashMap::new();
124
125 let (all_captures, mutable_captures) =
128 super::storage_planning::collect_closure_captures(mir);
129 let closure_capture_slots: HashSet<SlotId> = mutable_captures;
130 facts.slot_escape_status.extend((0..mir.num_locals).map(|raw_slot| {
131 let slot = SlotId(raw_slot);
132 (
133 slot,
134 super::storage_planning::detect_escape_status(slot, mir, &all_captures),
135 )
136 }));
137 let param_reference_summaries: HashMap<SlotId, ReturnReferenceSummary> = mir
138 .param_slots
139 .iter()
140 .enumerate()
141 .filter_map(|(param_index, slot)| {
142 mir.param_reference_kinds
143 .get(param_index)
144 .copied()
145 .flatten()
146 .map(|kind| {
147 (
148 *slot,
149 ReturnReferenceSummary {
150 param_index,
151 kind,
152 projection: Some(Vec::new()),
153 },
154 )
155 })
156 })
157 .collect();
158 let mut slot_reference_summaries = param_reference_summaries.clone();
159
160 for block in &mir.blocks {
162 for i in 0..block.statements.len().saturating_sub(1) {
164 let from = block.statements[i].point.0;
165 let to = block.statements[i + 1].point.0;
166 facts.cfg_edge.push((from, to));
167 }
168
169 let last_point = block.statements.last().map(|s| s.point.0).unwrap_or(0);
171
172 for &succ_id in cfg.successors(block.id) {
173 let succ_block = mir.block(succ_id);
174 if let Some(first_stmt) = succ_block.statements.first() {
175 facts.cfg_edge.push((last_point, first_stmt.point.0));
176 }
177 }
178 }
179
180 for block in &mir.blocks {
182 for stmt in &block.statements {
183 facts.point_spans.insert(stmt.point.0, stmt.span);
184 match &stmt.kind {
185 StatementKind::Assign(dest, Rvalue::Borrow(kind, place)) => {
186 let loan_id = next_loan;
187 next_loan += 1;
188
189 facts.loan_issued_at.push((loan_id, stmt.point.0));
190 if let Place::Local(slot) = dest {
191 slot_loans.insert(*slot, vec![loan_id]);
192 slot_reference_origins.insert(
193 *slot,
194 (*kind, reference_origin_for_place(place, &mir.param_slots)),
195 );
196 if let Some(contract) = safe_reference_summary_for_borrow(
197 *kind,
198 place,
199 ¶m_reference_summaries,
200 ) {
201 slot_reference_summaries.insert(*slot, contract);
202 } else {
203 slot_reference_summaries.remove(slot);
204 }
205 if *slot == SlotId(0) {
206 if let Some(contract) = safe_reference_summary_for_borrow(
207 *kind,
208 place,
209 ¶m_reference_summaries,
210 ) {
211 facts
212 .return_reference_candidates
213 .push((contract, stmt.span));
214 } else {
215 facts.escaped_loans.push((loan_id, stmt.span));
216 facts.loan_sinks.push(LoanSink {
217 loan_id,
218 kind: LoanSinkKind::ReturnSlot,
219 sink_slot: Some(*slot),
220 span: stmt.span,
221 });
222 }
223 }
224 }
225 let region_depth = if mir.param_slots.contains(&place.root_local()) {
227 0 } else {
229 1 };
231 facts.loan_info.insert(
232 loan_id,
233 LoanInfo {
234 id: LoanId(loan_id),
235 borrowed_place: place.clone(),
236 kind: *kind,
237 issued_at: stmt.point,
238 span: stmt.span,
239 region_depth,
240 },
241 );
242 }
243 StatementKind::Assign(place, rvalue) => {
244 if let Place::Local(dest_slot) = place {
245 update_slot_loan_aliases(&mut slot_loans, *dest_slot, rvalue);
246 update_slot_reference_origins(
247 &mut slot_reference_origins,
248 *dest_slot,
249 rvalue,
250 );
251 update_slot_reference_summaries(
252 &mut slot_reference_summaries,
253 *dest_slot,
254 rvalue,
255 );
256 if *dest_slot == SlotId(0) {
257 let mut found_reference_return = false;
258 if let Some(contract) =
259 reference_summary_from_rvalue(&slot_reference_summaries, rvalue)
260 {
261 facts
262 .return_reference_candidates
263 .push((contract, stmt.span));
264 found_reference_return = true;
265 }
266 if let Some((borrow_kind, origin)) =
267 reference_origin_from_rvalue(&slot_reference_origins, rvalue)
268 {
269 if let Some(contract) =
270 reference_summary_from_origin(borrow_kind, &origin)
271 {
272 facts
273 .return_reference_candidates
274 .push((contract, stmt.span));
275 found_reference_return = true;
276 }
277 }
278 for loan_id in local_loans_from_rvalue(&slot_loans, rvalue) {
279 let info = &facts.loan_info[&loan_id];
280 if let Some(contract) = safe_reference_summary_for_borrow(
281 info.kind,
282 &info.borrowed_place,
283 ¶m_reference_summaries,
284 ) {
285 facts
286 .return_reference_candidates
287 .push((contract, stmt.span));
288 found_reference_return = true;
289 } else {
290 facts.escaped_loans.push((loan_id, stmt.span));
291 facts.loan_sinks.push(LoanSink {
292 loan_id,
293 kind: LoanSinkKind::ReturnSlot,
294 sink_slot: Some(*dest_slot),
295 span: stmt.span,
296 });
297 }
298 }
299 if !found_reference_return {
300 facts.non_reference_return_spans.push(stmt.span);
301 }
302 }
303 }
304 match place {
305 Place::Field(..) => {
306 for loan_id in local_loans_from_rvalue(&slot_loans, rvalue) {
307 facts.object_assignment_loans.push((loan_id, stmt.span));
308 facts.loan_sinks.push(LoanSink {
309 loan_id,
310 kind: LoanSinkKind::ObjectAssignment,
311 sink_slot: Some(place.root_local()),
312 span: stmt.span,
313 });
314 }
315 }
316 Place::Index(..) => {
317 for loan_id in local_loans_from_rvalue(&slot_loans, rvalue) {
318 facts.array_assignment_loans.push((loan_id, stmt.span));
319 facts.loan_sinks.push(LoanSink {
320 loan_id,
321 kind: LoanSinkKind::ArrayAssignment,
322 sink_slot: Some(place.root_local()),
323 span: stmt.span,
324 });
325 }
326 }
327 Place::Local(..) | Place::Deref(..) => {}
328 }
329 facts.writes.push((stmt.point.0, place.clone(), stmt.span));
330 for (lid, info) in &facts.loan_info {
332 if place.conflicts_with(&info.borrowed_place) {
333 facts.invalidates.push((stmt.point.0, *lid));
334 }
335 }
336 }
337 StatementKind::Drop(place) => {
338 for (lid, info) in &facts.loan_info {
340 if place.conflicts_with(&info.borrowed_place) {
341 facts.invalidates.push((stmt.point.0, *lid));
342 }
343 }
344 }
345 StatementKind::TaskBoundary(operands, kind) => {
346 for loan_id in local_loans_from_operands(&slot_loans, operands) {
347 let info = &facts.loan_info[&loan_id];
348 match kind {
349 TaskBoundaryKind::Detached => {
350 facts.task_boundary_loans.push((loan_id, stmt.span));
352 facts.loan_sinks.push(LoanSink {
353 loan_id,
354 kind: LoanSinkKind::DetachedTaskBoundary,
355 sink_slot: None,
356 span: stmt.span,
357 });
358 }
359 TaskBoundaryKind::Structured => {
360 if info.kind == BorrowKind::Exclusive {
362 facts.task_boundary_loans.push((loan_id, stmt.span));
363 facts.loan_sinks.push(LoanSink {
364 loan_id,
365 kind: LoanSinkKind::StructuredTaskBoundary,
366 sink_slot: None,
367 span: stmt.span,
368 });
369 }
370 }
371 }
372 }
373 if *kind == TaskBoundaryKind::Detached {
376 for op in operands {
377 if let Operand::Copy(Place::Local(slot))
378 | Operand::Move(Place::Local(slot)) = op
379 {
380 if closure_capture_slots.contains(slot) {
381 facts
382 .non_sendable_task_boundary
383 .push((slot.0 as u32, stmt.span));
384 }
385 }
386 }
387 }
388 }
389 StatementKind::ClosureCapture {
390 closure_slot,
391 operands,
392 } => {
393 for loan_id in local_loans_from_operands(&slot_loans, operands) {
394 facts.closure_capture_loans.push((loan_id, stmt.span));
395 facts.loan_sinks.push(LoanSink {
396 loan_id,
397 kind: LoanSinkKind::ClosureEnv,
398 sink_slot: Some(*closure_slot),
399 span: stmt.span,
400 });
401 }
402 }
403 StatementKind::ArrayStore {
404 container_slot,
405 operands,
406 } => {
407 for loan_id in local_loans_from_operands(&slot_loans, operands) {
408 facts.array_store_loans.push((loan_id, stmt.span));
409 facts.loan_sinks.push(LoanSink {
410 loan_id,
411 kind: LoanSinkKind::ArrayStore,
412 sink_slot: Some(*container_slot),
413 span: stmt.span,
414 });
415 }
416 }
417 StatementKind::ObjectStore {
418 container_slot,
419 operands,
420 } => {
421 for loan_id in local_loans_from_operands(&slot_loans, operands) {
422 facts.object_store_loans.push((loan_id, stmt.span));
423 facts.loan_sinks.push(LoanSink {
424 loan_id,
425 kind: LoanSinkKind::ObjectStore,
426 sink_slot: Some(*container_slot),
427 span: stmt.span,
428 });
429 }
430 }
431 StatementKind::EnumStore {
432 container_slot,
433 operands,
434 } => {
435 for loan_id in local_loans_from_operands(&slot_loans, operands) {
436 facts.enum_store_loans.push((loan_id, stmt.span));
437 facts.loan_sinks.push(LoanSink {
438 loan_id,
439 kind: LoanSinkKind::EnumStore,
440 sink_slot: Some(*container_slot),
441 span: stmt.span,
442 });
443 }
444 }
445 StatementKind::Nop => {}
446 }
447
448 for read_place in statement_read_places(&stmt.kind) {
449 facts
450 .reads
451 .push((stmt.point.0, read_place.clone(), stmt.span));
452 if let Place::Local(slot) = read_place {
453 if let Some(loans) = slot_loans.get(&slot) {
454 for loan_id in loans {
455 facts.use_of_loan.push((*loan_id, stmt.point.0));
456 }
457 }
458 }
459 }
460 }
461
462 if let TerminatorKind::Call { func, args, destination, .. } = &block.terminator.kind {
464 let call_point = block.statements.last().map(|s| s.point.0).unwrap_or(0);
465 let mut all_operands = vec![func];
467 all_operands.extend(args.iter());
468 for op in &all_operands {
469 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = op {
470 if let Some(loans) = slot_loans.get(&place.root_local()) {
471 for &loan_id in loans {
472 facts.use_of_loan.push((loan_id, call_point));
473 }
474 }
475 }
476 }
477 let dest_slot = destination.root_local();
479 slot_loans.remove(&dest_slot);
480 slot_reference_origins.remove(&dest_slot);
481 slot_reference_summaries.remove(&dest_slot);
482
483 if let Operand::Constant(MirConstant::Function(callee_name)) = func {
487 if let Some(callee_summary) = callee_summaries.get(callee_name.as_str()) {
488 if let Some(arg_operand) = args.get(callee_summary.param_index) {
489 if let Operand::Copy(arg_place)
490 | Operand::Move(arg_place)
491 | Operand::MoveExplicit(arg_place) = arg_operand
492 {
493 let arg_slot = arg_place.root_local();
494
495 if let Some(arg_loans) = slot_loans.get(&arg_slot).cloned() {
497 slot_loans.insert(dest_slot, arg_loans);
498 }
499
500 if let Some(arg_summary) =
502 slot_reference_summaries.get(&arg_slot).cloned()
503 {
504 let composed = compose_return_reference_summary(
505 &arg_summary,
506 callee_summary,
507 );
508
509 if composed.projection.is_some() {
513 if let Some((_, origin)) =
514 slot_reference_origins.get(&arg_slot).cloned()
515 {
516 if let Some(ref callee_proj) = callee_summary.projection {
518 let mut proj = origin.projection.clone();
519 proj.extend(callee_proj.iter().copied());
520 slot_reference_origins.insert(
521 dest_slot,
522 (
523 composed.kind,
524 ReferenceOrigin {
525 root: origin.root,
526 projection: proj,
527 },
528 ),
529 );
530 }
531 }
532 }
535 slot_reference_summaries.insert(dest_slot, composed);
538 }
539 }
540 }
541 }
542 }
543 }
544 }
545
546 let loan_ids: Vec<u32> = facts.loan_info.keys().copied().collect();
548 for i in 0..loan_ids.len() {
549 for j in (i + 1)..loan_ids.len() {
550 let a = loan_ids[i];
551 let b = loan_ids[j];
552 let info_a = &facts.loan_info[&a];
553 let info_b = &facts.loan_info[&b];
554
555 if info_a.borrowed_place.conflicts_with(&info_b.borrowed_place)
557 && (info_a.kind == BorrowKind::Exclusive || info_b.kind == BorrowKind::Exclusive)
558 {
559 facts.potential_conflicts.push((a, b));
560 }
561 }
562 }
563
564 facts
565}
566
567fn operand_read_places<'a>(operand: &'a Operand, reads: &mut Vec<Place>) {
568 match operand {
569 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
570 reads.push(place.clone());
571 place_nested_read_places(place, reads);
572 }
573 Operand::Constant(_) => {}
574 }
575}
576
577fn place_nested_read_places(place: &Place, reads: &mut Vec<Place>) {
578 match place {
579 Place::Local(_) => {}
580 Place::Field(base, _) | Place::Deref(base) => {
581 place_nested_read_places(base, reads);
582 }
583 Place::Index(base, index) => {
584 place_nested_read_places(base, reads);
585 operand_read_places(index, reads);
586 }
587 }
588}
589
590fn statement_read_places(kind: &StatementKind) -> Vec<Place> {
591 let mut reads = Vec::new();
592 match kind {
593 StatementKind::Assign(_, rvalue) => match rvalue {
594 Rvalue::Use(operand) | Rvalue::Clone(operand) => {
595 operand_read_places(operand, &mut reads)
596 }
597 Rvalue::Borrow(_, _) => {}
598 Rvalue::BinaryOp(_, lhs, rhs) => {
599 operand_read_places(lhs, &mut reads);
600 operand_read_places(rhs, &mut reads);
601 }
602 Rvalue::UnaryOp(_, operand) => operand_read_places(operand, &mut reads),
603 Rvalue::Aggregate(operands) => {
604 for operand in operands {
605 operand_read_places(operand, &mut reads);
606 }
607 }
608 },
609 StatementKind::Drop(place) => place_nested_read_places(place, &mut reads),
610 StatementKind::TaskBoundary(operands, _kind) => {
611 for operand in operands {
612 operand_read_places(operand, &mut reads);
613 }
614 }
615 StatementKind::ClosureCapture { operands, .. } => {
616 for operand in operands {
617 operand_read_places(operand, &mut reads);
618 }
619 }
620 StatementKind::ArrayStore { operands, .. } => {
621 for operand in operands {
622 operand_read_places(operand, &mut reads);
623 }
624 }
625 StatementKind::ObjectStore { operands, .. } => {
626 for operand in operands {
627 operand_read_places(operand, &mut reads);
628 }
629 }
630 StatementKind::EnumStore { operands, .. } => {
631 for operand in operands {
632 operand_read_places(operand, &mut reads);
633 }
634 }
635 StatementKind::Nop => {}
636 }
637 reads
638}
639
640fn local_loans_from_operand(slot_loans: &HashMap<SlotId, Vec<u32>>, operand: &Operand) -> Vec<u32> {
641 match operand {
642 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => slot_loans
643 .get(&place.root_local())
644 .cloned()
645 .unwrap_or_default(),
646 Operand::Constant(_) => Vec::new(),
647 }
648}
649
650fn local_loans_from_operands(
651 slot_loans: &HashMap<SlotId, Vec<u32>>,
652 operands: &[Operand],
653) -> Vec<u32> {
654 let mut loans = Vec::new();
655 let mut seen = HashSet::new();
656 for operand in operands {
657 for loan in local_loans_from_operand(slot_loans, operand) {
658 if seen.insert(loan) {
659 loans.push(loan);
660 }
661 }
662 }
663 loans
664}
665
666fn update_slot_loan_aliases(
667 slot_loans: &mut HashMap<SlotId, Vec<u32>>,
668 dest_slot: SlotId,
669 rvalue: &Rvalue,
670) {
671 match rvalue {
672 Rvalue::Borrow(_, _) => {}
673 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
674 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
675 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
676 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
677 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
678 if let Some(loans) = slot_loans.get(src_slot).cloned() {
679 slot_loans.insert(dest_slot, loans);
680 } else {
681 slot_loans.remove(&dest_slot);
682 }
683 }
684 _ => {
685 slot_loans.remove(&dest_slot);
686 }
687 }
688}
689
690fn local_loans_from_rvalue(slot_loans: &HashMap<SlotId, Vec<u32>>, rvalue: &Rvalue) -> Vec<u32> {
691 match rvalue {
692 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
693 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
694 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
695 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
696 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
697 slot_loans.get(src_slot).cloned().unwrap_or_default()
698 }
699 _ => Vec::new(),
700 }
701}
702
703fn update_slot_reference_summaries(
704 slot_reference_summaries: &mut HashMap<SlotId, ReturnReferenceSummary>,
705 dest_slot: SlotId,
706 rvalue: &Rvalue,
707) {
708 match rvalue {
709 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
710 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
711 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
712 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
713 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
714 if let Some(contract) = slot_reference_summaries.get(src_slot).cloned() {
715 slot_reference_summaries.insert(dest_slot, contract);
716 } else {
717 slot_reference_summaries.remove(&dest_slot);
718 }
719 }
720 _ => {
721 slot_reference_summaries.remove(&dest_slot);
722 }
723 }
724}
725
726fn reference_summary_from_rvalue(
727 slot_reference_summaries: &HashMap<SlotId, ReturnReferenceSummary>,
728 rvalue: &Rvalue,
729) -> Option<ReturnReferenceSummary> {
730 match rvalue {
731 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
732 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
733 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
734 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
735 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
736 slot_reference_summaries.get(src_slot).cloned()
737 }
738 _ => None,
739 }
740}
741
742fn update_slot_reference_origins(
743 slot_reference_origins: &mut HashMap<SlotId, (BorrowKind, ReferenceOrigin)>,
744 dest_slot: SlotId,
745 rvalue: &Rvalue,
746) {
747 match rvalue {
748 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
749 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
750 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
751 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
752 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
753 if let Some(origin) = slot_reference_origins.get(src_slot).cloned() {
754 slot_reference_origins.insert(dest_slot, origin);
755 } else {
756 slot_reference_origins.remove(&dest_slot);
757 }
758 }
759 _ => {
760 slot_reference_origins.remove(&dest_slot);
761 }
762 }
763}
764
765fn reference_origin_from_rvalue(
766 slot_reference_origins: &HashMap<SlotId, (BorrowKind, ReferenceOrigin)>,
767 rvalue: &Rvalue,
768) -> Option<(BorrowKind, ReferenceOrigin)> {
769 match rvalue {
770 Rvalue::Borrow(kind, place) => Some((
771 *kind,
772 reference_origin_for_place(place, &[]),
773 )),
774 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
775 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
776 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
777 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
778 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
779 slot_reference_origins.get(src_slot).cloned()
780 }
781 _ => None,
782 }
783}
784
785fn reference_origin_for_place(place: &Place, param_slots: &[SlotId]) -> ReferenceOrigin {
786 let root_slot = place.root_local();
787 let root = param_slots
788 .iter()
789 .position(|slot| *slot == root_slot)
790 .map(ReferenceOriginRoot::Param)
791 .unwrap_or(ReferenceOriginRoot::Local(root_slot));
792 ReferenceOrigin {
793 root,
794 projection: place.projection_steps(),
795 }
796}
797
798fn reference_summary_from_origin(
799 borrow_kind: BorrowKind,
800 origin: &ReferenceOrigin,
801) -> Option<ReturnReferenceSummary> {
802 match origin.root {
803 ReferenceOriginRoot::Param(param_index) => Some(ReturnReferenceSummary {
804 param_index,
805 kind: borrow_kind,
806 projection: Some(origin.projection.clone()),
807 }),
808 ReferenceOriginRoot::Local(_) => None,
809 }
810}
811
812fn safe_reference_summary_for_borrow(
813 borrow_kind: BorrowKind,
814 borrowed_place: &Place,
815 param_reference_summaries: &HashMap<SlotId, ReturnReferenceSummary>,
816) -> Option<ReturnReferenceSummary> {
817 let param_summary = param_reference_summaries.get(&borrowed_place.root_local())?;
820 Some(ReturnReferenceSummary {
821 param_index: param_summary.param_index,
822 kind: borrow_kind,
823 projection: Some(borrowed_place.projection_steps()),
824 })
825}
826
827fn compose_return_reference_summary(
835 arg_summary: &ReturnReferenceSummary,
836 callee_summary: &ReturnReferenceSummary,
837) -> ReturnReferenceSummary {
838 let projection = match (&arg_summary.projection, &callee_summary.projection) {
839 (Some(arg_proj), Some(callee_proj)) => {
840 if callee_proj
841 .iter()
842 .any(|step| matches!(step, ProjectionStep::Field(_)))
843 {
844 None } else {
846 let mut composed = arg_proj.clone();
847 composed.extend(callee_proj.iter().copied());
848 Some(composed)
849 }
850 }
851 _ => None, };
853 ReturnReferenceSummary {
854 param_index: arg_summary.param_index,
855 kind: callee_summary.kind,
856 projection,
857 }
858}
859
860fn resolve_return_reference_summary(
861 errors: &mut Vec<BorrowError>,
862 facts: &BorrowFacts,
863 loans_at_point: &HashMap<Point, Vec<LoanId>>,
864) -> Option<ReturnReferenceSummary> {
865 let mut merged_candidate: Option<ReturnReferenceSummary> = None;
866 let mut inconsistent = false;
867 for (candidate, _) in &facts.return_reference_candidates {
868 if let Some(existing) = merged_candidate.as_mut() {
869 if existing.param_index != candidate.param_index || existing.kind != candidate.kind {
870 inconsistent = true;
871 break;
872 }
873 if existing.projection != candidate.projection {
874 existing.projection = None;
875 }
876 } else {
877 merged_candidate = Some(candidate.clone());
878 }
879 }
880
881 if merged_candidate.is_none() {
882 return None;
883 }
884
885 let error_span = if inconsistent {
886 facts
887 .return_reference_candidates
888 .get(1)
889 .map(|(_, span)| *span)
890 } else {
891 facts.non_reference_return_spans.first().copied()
892 };
893
894 if let Some(span) = error_span {
895 let (conflicting_loan, loan_span, last_use_span) = facts
896 .return_reference_candidates
897 .first()
898 .and_then(|(candidate, candidate_span)| {
899 find_matching_loan_for_return_candidate(
900 candidate,
901 *candidate_span,
902 facts,
903 loans_at_point,
904 )
905 })
906 .unwrap_or((LoanId(0), span, None));
907 errors.push(BorrowError {
908 kind: BorrowErrorKind::InconsistentReferenceReturn,
909 span,
910 conflicting_loan,
911 loan_span,
912 last_use_span,
913 repairs: Vec::new(),
914 });
915 return None;
916 }
917
918 merged_candidate
919}
920
921fn find_matching_loan_for_return_candidate(
922 candidate: &ReturnReferenceSummary,
923 candidate_span: shape_ast::ast::Span,
924 facts: &BorrowFacts,
925 loans_at_point: &HashMap<Point, Vec<LoanId>>,
926) -> Option<(LoanId, shape_ast::ast::Span, Option<shape_ast::ast::Span>)> {
927 let point = facts
928 .point_spans
929 .iter()
930 .find_map(|(point, span)| (*span == candidate_span).then_some(Point(*point)))?;
931 let loans = loans_at_point.get(&point)?;
932 for loan in loans {
933 let info = facts.loan_info.get(&loan.0)?;
934 if info.kind == candidate.kind {
935 return Some((*loan, info.span, last_use_span_for_loan(facts, loan.0)));
936 }
937 }
938 None
939}
940
941pub fn solve(facts: &BorrowFacts) -> SolverResult {
943 let mut iteration = Iteration::new();
944
945 let cfg_edge: Relation<(u32, u32)> = facts.cfg_edge.iter().cloned().collect();
948 let invalidates_set: std::collections::HashSet<(u32, u32)> =
950 facts.invalidates.iter().cloned().collect();
951
952 let loan_live_at = iteration.variable::<(u32, u32)>("loan_live_at");
955
956 let seed: Vec<(u32, u32)> = facts
959 .loan_issued_at
960 .iter()
961 .map(|&(loan, point)| (point, loan))
962 .collect();
963 loan_live_at.extend(seed.iter().cloned());
964
965 while iteration.changed() {
971 loan_live_at.from_leapjoin(
975 &loan_live_at,
976 cfg_edge.extend_with(|&(point1, _loan)| point1),
977 |&(point1, loan), &point2| {
978 if invalidates_set.contains(&(point1, loan)) {
979 (u32::MAX, u32::MAX) } else {
983 (point2, loan)
984 }
985 },
986 );
987 }
988
989 let forward_live_points: Vec<(u32, u32)> = loan_live_at
991 .complete()
992 .iter()
993 .filter(|&&(p, l)| p != u32::MAX && l != u32::MAX)
994 .cloned()
995 .collect();
996 let (nll_live_set, loans_with_reachable_uses) = compute_nll_live_points(facts);
997 let loan_live_at_result: Vec<(u32, u32)> = forward_live_points
998 .into_iter()
999 .filter(|point_loan| {
1000 !loans_with_reachable_uses.contains(&point_loan.1) || nll_live_set.contains(point_loan)
1001 })
1002 .collect();
1003
1004 let mut loans_at_point: HashMap<Point, Vec<LoanId>> = HashMap::new();
1006 for &(point, loan) in &loan_live_at_result {
1007 loans_at_point
1008 .entry(Point(point))
1009 .or_default()
1010 .push(LoanId(loan));
1011 }
1012
1013 let mut loan_points: HashMap<u32, std::collections::HashSet<u32>> = HashMap::new();
1015 for &(point, loan) in &loan_live_at_result {
1016 loan_points.entry(loan).or_default().insert(point);
1017 }
1018
1019 let mut errors = Vec::new();
1021 let mut seen_conflicts = std::collections::HashSet::new();
1022 for &(loan_a, loan_b) in &facts.potential_conflicts {
1023 let key = (loan_a.min(loan_b), loan_a.max(loan_b));
1024 if !seen_conflicts.insert(key) {
1025 continue;
1026 }
1027
1028 let points_a = loan_points.get(&loan_a);
1029 let points_b = loan_points.get(&loan_b);
1030
1031 if let (Some(pa), Some(pb)) = (points_a, points_b) {
1032 let has_overlap = pa.iter().any(|p| pb.contains(p));
1034 if has_overlap {
1035 let info_a = &facts.loan_info[&loan_a];
1036 let info_b = &facts.loan_info[&loan_b];
1037 let kind = if info_a.kind == BorrowKind::Exclusive
1038 && info_b.kind == BorrowKind::Exclusive
1039 {
1040 BorrowErrorKind::ConflictExclusiveExclusive
1041 } else {
1042 BorrowErrorKind::ConflictSharedExclusive
1043 };
1044 errors.push(BorrowError {
1045 kind,
1046 span: info_b.span,
1047 conflicting_loan: LoanId(loan_a),
1048 loan_span: info_a.span,
1049 last_use_span: last_use_span_for_loan(facts, loan_a),
1050 repairs: Vec::new(),
1051 });
1052 }
1053 }
1054 }
1055
1056 let mut seen_writes = std::collections::HashSet::new();
1057 for (point, place, span) in &facts.writes {
1058 let point_key = Point(*point);
1059 let Some(loans) = loans_at_point.get(&point_key) else {
1060 continue;
1061 };
1062 for loan in loans {
1063 let info = &facts.loan_info[&loan.0];
1064 if !place.conflicts_with(&info.borrowed_place) {
1065 continue;
1066 }
1067 let key = (*point, loan.0);
1068 if !seen_writes.insert(key) {
1069 continue;
1070 }
1071 errors.push(BorrowError {
1072 kind: BorrowErrorKind::WriteWhileBorrowed,
1073 span: *span,
1074 conflicting_loan: *loan,
1075 loan_span: info.span,
1076 last_use_span: last_use_span_for_loan(facts, loan.0),
1077 repairs: Vec::new(),
1078 });
1079 break;
1080 }
1081 }
1082
1083 let mut seen_reads = std::collections::HashSet::new();
1084 for (point, place, span) in &facts.reads {
1085 let point_key = Point(*point);
1086 let Some(loans) = loans_at_point.get(&point_key) else {
1087 continue;
1088 };
1089 for loan in loans {
1090 let info = &facts.loan_info[&loan.0];
1091 if info.kind != BorrowKind::Exclusive || !place.conflicts_with(&info.borrowed_place) {
1092 continue;
1093 }
1094 let key = (*point, loan.0);
1095 if !seen_reads.insert(key) {
1096 continue;
1097 }
1098 errors.push(BorrowError {
1099 kind: BorrowErrorKind::ReadWhileExclusivelyBorrowed,
1100 span: *span,
1101 conflicting_loan: *loan,
1102 loan_span: info.span,
1103 last_use_span: last_use_span_for_loan(facts, loan.0),
1104 repairs: Vec::new(),
1105 });
1106 break;
1107 }
1108 }
1109
1110 let mut seen_escapes = std::collections::HashSet::new();
1111 for (loan_id, span) in &facts.escaped_loans {
1112 if !seen_escapes.insert((*loan_id, span.start, span.end)) {
1113 continue;
1114 }
1115 let info = &facts.loan_info[loan_id];
1116 errors.push(BorrowError {
1117 kind: BorrowErrorKind::ReferenceEscape,
1118 span: *span,
1119 conflicting_loan: LoanId(*loan_id),
1120 loan_span: info.span,
1121 last_use_span: last_use_span_for_loan(facts, *loan_id),
1122 repairs: Vec::new(),
1123 });
1124 }
1125
1126 let mut seen_sinks = std::collections::HashSet::new();
1127 for sink in &facts.loan_sinks {
1128 let key = (
1129 sink.loan_id,
1130 sink.kind,
1131 sink.span.start,
1132 sink.span.end,
1133 sink.sink_slot.map(|slot| slot.0),
1134 );
1135 if !seen_sinks.insert(key) {
1136 continue;
1137 }
1138
1139 let info = &facts.loan_info[&sink.loan_id];
1140 let sink_is_local = sink
1141 .sink_slot
1142 .and_then(|slot| facts.slot_escape_status.get(&slot).copied())
1143 == Some(EscapeStatus::Local);
1144
1145 let kind = match sink.kind {
1146 LoanSinkKind::ReturnSlot => continue,
1147 LoanSinkKind::ClosureEnv if sink_is_local => continue,
1148 LoanSinkKind::ClosureEnv => BorrowErrorKind::ReferenceEscapeIntoClosure,
1149 LoanSinkKind::ArrayStore | LoanSinkKind::ArrayAssignment if sink_is_local => continue,
1150 LoanSinkKind::ArrayStore | LoanSinkKind::ArrayAssignment => {
1151 BorrowErrorKind::ReferenceStoredInArray
1152 }
1153 LoanSinkKind::ObjectStore | LoanSinkKind::ObjectAssignment if sink_is_local => continue,
1154 LoanSinkKind::ObjectStore | LoanSinkKind::ObjectAssignment => {
1155 BorrowErrorKind::ReferenceStoredInObject
1156 }
1157 LoanSinkKind::EnumStore if sink_is_local => continue,
1158 LoanSinkKind::EnumStore => BorrowErrorKind::ReferenceStoredInEnum,
1159 LoanSinkKind::StructuredTaskBoundary => {
1160 BorrowErrorKind::ExclusiveRefAcrossTaskBoundary
1161 }
1162 LoanSinkKind::DetachedTaskBoundary if info.kind == BorrowKind::Exclusive => {
1163 BorrowErrorKind::ExclusiveRefAcrossTaskBoundary
1164 }
1165 LoanSinkKind::DetachedTaskBoundary => BorrowErrorKind::SharedRefAcrossDetachedTask,
1166 };
1167
1168 errors.push(BorrowError {
1169 kind,
1170 span: sink.span,
1171 conflicting_loan: LoanId(sink.loan_id),
1172 loan_span: info.span,
1173 last_use_span: last_use_span_for_loan(facts, sink.loan_id),
1174 repairs: Vec::new(),
1175 });
1176 }
1177
1178 let mut seen_non_sendable = std::collections::HashSet::new();
1180 for (slot_id, span) in &facts.non_sendable_task_boundary {
1181 if !seen_non_sendable.insert((*slot_id, span.start, span.end)) {
1182 continue;
1183 }
1184 errors.push(BorrowError {
1185 kind: BorrowErrorKind::NonSendableAcrossTaskBoundary,
1186 span: *span,
1187 conflicting_loan: LoanId(0),
1188 loan_span: *span,
1189 last_use_span: None,
1190 repairs: Vec::new(),
1191 });
1192 }
1193
1194 let return_reference_summary =
1195 resolve_return_reference_summary(&mut errors, facts, &loans_at_point);
1196
1197 SolverResult {
1198 loans_at_point,
1199 errors,
1200 loan_info: facts.loan_info.clone(),
1201 return_reference_summary,
1202 }
1203}
1204
1205fn compute_nll_live_points(facts: &BorrowFacts) -> (HashSet<(u32, u32)>, HashSet<u32>) {
1206 let mut predecessors: HashMap<u32, Vec<u32>> = HashMap::new();
1207 for (from, to) in &facts.cfg_edge {
1208 predecessors.entry(*to).or_default().push(*from);
1209 }
1210
1211 let issue_points: HashMap<u32, u32> = facts
1212 .loan_issued_at
1213 .iter()
1214 .map(|(loan_id, point)| (*loan_id, *point))
1215 .collect();
1216
1217 let mut invalidation_points: HashMap<u32, HashSet<u32>> = HashMap::new();
1218 for (point, loan_id) in &facts.invalidates {
1219 invalidation_points
1220 .entry(*loan_id)
1221 .or_default()
1222 .insert(*point);
1223 }
1224
1225 let mut use_points: HashMap<u32, Vec<u32>> = HashMap::new();
1226 for (loan_id, point) in &facts.use_of_loan {
1227 use_points.entry(*loan_id).or_default().push(*point);
1228 }
1229
1230 let mut live_points = HashSet::new();
1231 let mut loans_with_reachable_uses = HashSet::new();
1232 for (loan_id, issue_point) in issue_points {
1233 let mut worklist = use_points.get(&loan_id).cloned().unwrap_or_default();
1234 let invalidates = invalidation_points.get(&loan_id);
1235 let mut visited = HashSet::new();
1236 let mut loan_live_points = HashSet::new();
1237 let mut reached_issue = false;
1238
1239 while let Some(point) = worklist.pop() {
1240 if !visited.insert(point) {
1241 continue;
1242 }
1243
1244 loan_live_points.insert((point, loan_id));
1245
1246 if point == issue_point {
1247 reached_issue = true;
1248 continue;
1249 }
1250
1251 if invalidates.is_some_and(|points| points.contains(&point)) {
1252 continue;
1253 }
1254
1255 if let Some(preds) = predecessors.get(&point) {
1256 worklist.extend(preds.iter().copied());
1257 }
1258 }
1259
1260 if reached_issue {
1261 loans_with_reachable_uses.insert(loan_id);
1262 live_points.extend(loan_live_points);
1263 }
1264 }
1265
1266 (live_points, loans_with_reachable_uses)
1267}
1268
1269#[derive(Debug)]
1271pub struct SolverResult {
1272 pub loans_at_point: HashMap<Point, Vec<LoanId>>,
1273 pub errors: Vec<BorrowError>,
1274 pub loan_info: HashMap<u32, LoanInfo>,
1275 pub return_reference_summary: Option<ReturnReferenceSummary>,
1276}
1277
1278pub fn extract_borrow_summary(
1284 mir: &MirFunction,
1285 return_summary: Option<ReturnReferenceSummary>,
1286) -> FunctionBorrowSummary {
1287 let num_params = mir.param_slots.len();
1288 let mut param_borrows: Vec<Option<BorrowKind>> = mir
1289 .param_reference_kinds
1290 .iter()
1291 .cloned()
1292 .collect();
1293 while param_borrows.len() < num_params {
1295 param_borrows.push(None);
1296 }
1297
1298 let mut mutated_params: HashSet<usize> = HashSet::new();
1300 let mut read_params: HashSet<usize> = HashSet::new();
1301 for block in mir.iter_blocks() {
1302 for stmt in &block.statements {
1303 match &stmt.kind {
1304 StatementKind::Assign(dest, rvalue) => {
1305 let root = dest.root_local();
1307 if let Some(param_idx) = mir.param_slots.iter().position(|s| *s == root) {
1308 mutated_params.insert(param_idx);
1309 }
1310 for param_idx in 0..num_params {
1312 if rvalue_uses_param(rvalue, mir.param_slots[param_idx]) {
1313 read_params.insert(param_idx);
1314 }
1315 }
1316 }
1317 _ => {}
1318 }
1319 }
1320 if let TerminatorKind::Call { args, .. } = &block.terminator.kind {
1322 for arg in args {
1323 for param_idx in 0..num_params {
1324 if operand_uses_param(arg, mir.param_slots[param_idx]) {
1325 read_params.insert(param_idx);
1326 }
1327 }
1328 }
1329 }
1330 }
1331
1332 let mut effective_borrows: Vec<Option<BorrowKind>> = param_borrows.clone();
1335 for idx in 0..num_params {
1336 if effective_borrows[idx].is_none() {
1337 if mutated_params.contains(&idx) {
1338 effective_borrows[idx] = Some(BorrowKind::Exclusive);
1339 } else if read_params.contains(&idx) {
1340 effective_borrows[idx] = Some(BorrowKind::Shared);
1341 }
1342 }
1343 }
1344
1345 let mut conflict_pairs = Vec::new();
1348 for &mutated_idx in &mutated_params {
1349 for other_idx in 0..num_params {
1350 if other_idx == mutated_idx {
1351 continue;
1352 }
1353 if effective_borrows[other_idx].is_some() {
1355 conflict_pairs.push((mutated_idx, other_idx));
1356 }
1357 }
1358 }
1359 for i in 0..num_params {
1361 for j in (i + 1)..num_params {
1362 if effective_borrows[i] == Some(BorrowKind::Exclusive)
1363 && effective_borrows[j] == Some(BorrowKind::Exclusive)
1364 && !conflict_pairs.contains(&(i, j))
1365 && !conflict_pairs.contains(&(j, i))
1366 {
1367 conflict_pairs.push((i, j));
1368 }
1369 }
1370 }
1371
1372 FunctionBorrowSummary {
1373 param_borrows,
1374 conflict_pairs,
1375 return_summary,
1376 }
1377}
1378
1379fn rvalue_uses_param(rvalue: &Rvalue, param_slot: SlotId) -> bool {
1380 match rvalue {
1381 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
1382 operand_uses_param(op, param_slot)
1383 }
1384 Rvalue::Borrow(_, place) => place.root_local() == param_slot,
1385 Rvalue::BinaryOp(_, lhs, rhs) => {
1386 operand_uses_param(lhs, param_slot) || operand_uses_param(rhs, param_slot)
1387 }
1388 Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_param(op, param_slot)),
1389 }
1390}
1391
1392fn operand_uses_param(op: &Operand, param_slot: SlotId) -> bool {
1393 match op {
1394 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
1395 place.root_local() == param_slot
1396 }
1397 Operand::Constant(_) => false,
1398 }
1399}
1400
1401pub fn analyze(mir: &MirFunction, callee_summaries: &CalleeSummaries) -> BorrowAnalysis {
1402 let cfg = ControlFlowGraph::build(mir);
1403
1404 let liveness = liveness::compute_liveness(mir, &cfg);
1406
1407 let facts = extract_facts(mir, &cfg, callee_summaries);
1409
1410 let solver_result = solve(&facts);
1412
1413 let ownership_decisions = compute_ownership_decisions(mir, &liveness);
1415 let mut move_errors = compute_use_after_move_errors(mir, &cfg, &ownership_decisions);
1416
1417 let loans = solver_result
1419 .loan_info
1420 .into_iter()
1421 .map(|(id, info)| (LoanId(id), info))
1422 .collect();
1423 let mut errors = solver_result.errors;
1424 errors.append(&mut move_errors);
1425
1426 BorrowAnalysis {
1427 liveness,
1428 loans_at_point: solver_result.loans_at_point,
1429 loans,
1430 errors,
1431 ownership_decisions,
1432 mutability_errors: Vec::new(), return_reference_summary: solver_result.return_reference_summary,
1434 }
1435}
1436
1437fn compute_ownership_decisions(
1439 mir: &MirFunction,
1440 liveness: &LivenessResult,
1441) -> HashMap<Point, OwnershipDecision> {
1442 let mut decisions = HashMap::new();
1443
1444 for block in &mir.blocks {
1445 for (stmt_idx, stmt) in block.statements.iter().enumerate() {
1446 if let StatementKind::Assign(_, Rvalue::Use(Operand::Move(Place::Local(src_slot)))) =
1447 &stmt.kind
1448 {
1449 let src_type = mir
1451 .local_types
1452 .get(src_slot.0 as usize)
1453 .cloned()
1454 .unwrap_or(LocalTypeInfo::Unknown);
1455
1456 let decision = match src_type {
1457 LocalTypeInfo::Copy => OwnershipDecision::Copy,
1458 LocalTypeInfo::NonCopy => {
1459 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
1461 OwnershipDecision::Clone
1462 } else {
1463 OwnershipDecision::Move
1464 }
1465 }
1466 LocalTypeInfo::Unknown => {
1467 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
1469 OwnershipDecision::Clone
1470 } else {
1471 OwnershipDecision::Move
1472 }
1473 }
1474 };
1475
1476 decisions.insert(stmt.point, decision);
1477 }
1478 }
1479 }
1480
1481 decisions
1482}
1483
1484fn compute_use_after_move_errors(
1485 mir: &MirFunction,
1486 cfg: &ControlFlowGraph,
1487 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1488) -> Vec<BorrowError> {
1489 let mut in_states: HashMap<BasicBlockId, HashMap<Place, shape_ast::ast::Span>> = HashMap::new();
1490 let mut out_states: HashMap<BasicBlockId, HashMap<Place, shape_ast::ast::Span>> =
1491 HashMap::new();
1492
1493 for block in mir.iter_blocks() {
1494 in_states.insert(block.id, HashMap::new());
1495 out_states.insert(block.id, HashMap::new());
1496 }
1497
1498 let mut changed = true;
1499 while changed {
1500 changed = false;
1501 for &block_id in &cfg.reverse_postorder() {
1502 let mut block_in: Option<HashMap<Place, shape_ast::ast::Span>> = None;
1503 for &pred in cfg.predecessors(block_id) {
1504 if let Some(pred_out) = out_states.get(&pred) {
1505 if let Some(current) = block_in.as_mut() {
1506 intersect_moved_places(current, pred_out);
1507 } else {
1508 block_in = Some(pred_out.clone());
1509 }
1510 }
1511 }
1512 let block_in = block_in.unwrap_or_default();
1513
1514 let mut block_out = block_in.clone();
1515 let block = mir.block(block_id);
1516 for stmt in &block.statements {
1517 apply_move_transfer(&mut block_out, stmt, mir, ownership_decisions);
1518 }
1519 apply_terminator_move_transfer(&mut block_out, &block.terminator);
1521
1522 if in_states.get(&block_id) != Some(&block_in) {
1523 in_states.insert(block_id, block_in);
1524 changed = true;
1525 }
1526 if out_states.get(&block_id) != Some(&block_out) {
1527 out_states.insert(block_id, block_out);
1528 changed = true;
1529 }
1530 }
1531 }
1532
1533 let mut errors = Vec::new();
1534 let mut seen = HashSet::new();
1535 for block in mir.iter_blocks() {
1536 let mut moved_places = in_states.get(&block.id).cloned().unwrap_or_default();
1537 for stmt in &block.statements {
1538 for read_place in statement_read_places(&stmt.kind) {
1539 if let Some((moved_place, move_span)) =
1540 find_moved_place_conflict(&moved_places, &read_place)
1541 {
1542 let key = (stmt.point.0, format!("{}", moved_place));
1543 if seen.insert(key) {
1544 errors.push(BorrowError {
1545 kind: BorrowErrorKind::UseAfterMove,
1546 span: stmt.span,
1547 conflicting_loan: LoanId(0),
1548 loan_span: move_span,
1549 last_use_span: None,
1550 repairs: Vec::new(),
1551 });
1552 }
1553 break;
1554 }
1555 }
1556
1557 if let Some(borrowed_place) = statement_borrow_place(&stmt.kind)
1558 && let Some((moved_place, move_span)) =
1559 find_moved_place_conflict(&moved_places, borrowed_place)
1560 {
1561 let key = (stmt.point.0, format!("{}", moved_place));
1562 if seen.insert(key) {
1563 errors.push(BorrowError {
1564 kind: BorrowErrorKind::UseAfterMove,
1565 span: stmt.span,
1566 conflicting_loan: LoanId(0),
1567 loan_span: move_span,
1568 last_use_span: None,
1569 repairs: Vec::new(),
1570 });
1571 }
1572 }
1573
1574 if let Some(dest_place) = statement_dest_place(&stmt.kind)
1575 && let Some((moved_place, move_span)) = moved_places
1576 .iter()
1577 .find(|(moved_place, _)| {
1578 dest_place.conflicts_with(moved_place)
1579 && !reinitializes_moved_place(dest_place, moved_place)
1580 })
1581 .map(|(place, span)| (place.clone(), *span))
1582 {
1583 let key = (stmt.point.0, format!("{}", moved_place));
1584 if seen.insert(key) {
1585 errors.push(BorrowError {
1586 kind: BorrowErrorKind::UseAfterMove,
1587 span: stmt.span,
1588 conflicting_loan: LoanId(0),
1589 loan_span: move_span,
1590 last_use_span: None,
1591 repairs: Vec::new(),
1592 });
1593 }
1594 }
1595
1596 apply_move_transfer(&mut moved_places, stmt, mir, ownership_decisions);
1597 }
1598
1599 if let TerminatorKind::Call { func, args, destination, .. } = &block.terminator.kind {
1601 let term_key_point = block.terminator.span.start as u32;
1602 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = func {
1604 if let Some((moved_place, move_span)) = find_moved_place_conflict(&moved_places, place) {
1605 let key = (term_key_point, format!("{}", moved_place));
1606 if seen.insert(key) {
1607 errors.push(BorrowError {
1608 kind: BorrowErrorKind::UseAfterMove,
1609 span: block.terminator.span,
1610 conflicting_loan: LoanId(0),
1611 loan_span: move_span,
1612 last_use_span: None,
1613 repairs: Vec::new(),
1614 });
1615 }
1616 }
1617 }
1618 for arg in args {
1620 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = arg {
1621 if let Some((moved_place, move_span)) = find_moved_place_conflict(&moved_places, place) {
1622 let key = (term_key_point, format!("{}", moved_place));
1623 if seen.insert(key) {
1624 errors.push(BorrowError {
1625 kind: BorrowErrorKind::UseAfterMove,
1626 span: block.terminator.span,
1627 conflicting_loan: LoanId(0),
1628 loan_span: move_span,
1629 last_use_span: None,
1630 repairs: Vec::new(),
1631 });
1632 }
1633 }
1634 }
1635 }
1636 moved_places.retain(|moved_place, _| !reinitializes_moved_place(destination, moved_place));
1638 }
1639 }
1640
1641 errors
1642}
1643
1644fn intersect_moved_places(
1645 dest: &mut HashMap<Place, shape_ast::ast::Span>,
1646 incoming: &HashMap<Place, shape_ast::ast::Span>,
1647) {
1648 dest.retain(|place, span| {
1649 if let Some(incoming_span) = incoming.get(place) {
1650 if incoming_span.start < span.start {
1651 *span = *incoming_span;
1652 }
1653 true
1654 } else {
1655 false
1656 }
1657 });
1658}
1659
1660fn apply_move_transfer(
1661 moved_places: &mut HashMap<Place, shape_ast::ast::Span>,
1662 stmt: &MirStatement,
1663 mir: &MirFunction,
1664 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1665) {
1666 if let Some(dest_place) = statement_dest_place(&stmt.kind) {
1667 moved_places.retain(|moved_place, _| !reinitializes_moved_place(dest_place, moved_place));
1668 }
1669
1670 for moved_place in actual_move_places(stmt, mir, ownership_decisions) {
1671 moved_places.insert(moved_place, stmt.span);
1672 }
1673}
1674
1675fn apply_terminator_move_transfer(
1681 moved_places: &mut HashMap<Place, shape_ast::ast::Span>,
1682 terminator: &Terminator,
1683) {
1684 if let TerminatorKind::Call { destination, .. } = &terminator.kind {
1685 moved_places.retain(|moved_place, _| !reinitializes_moved_place(destination, moved_place));
1687 }
1688}
1689
1690fn statement_borrow_place(kind: &StatementKind) -> Option<&Place> {
1691 match kind {
1692 StatementKind::Assign(_, Rvalue::Borrow(_, place)) => Some(place),
1693 _ => None,
1694 }
1695}
1696
1697fn statement_dest_place(kind: &StatementKind) -> Option<&Place> {
1698 match kind {
1699 StatementKind::Assign(place, _) | StatementKind::Drop(place) => Some(place),
1700 StatementKind::TaskBoundary(..)
1701 | StatementKind::ClosureCapture { .. }
1702 | StatementKind::ArrayStore { .. }
1703 | StatementKind::ObjectStore { .. }
1704 | StatementKind::EnumStore { .. } => None,
1705 StatementKind::Nop => None,
1706 }
1707}
1708
1709fn actual_move_places(
1710 stmt: &MirStatement,
1711 mir: &MirFunction,
1712 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1713) -> Vec<Place> {
1714 match &stmt.kind {
1715 StatementKind::Assign(_, Rvalue::Use(Operand::Move(place)))
1716 if ownership_decisions.get(&stmt.point) == Some(&OwnershipDecision::Move) =>
1717 {
1718 vec![place.clone()]
1719 }
1720 StatementKind::Assign(_, Rvalue::Use(Operand::MoveExplicit(place)))
1721 if place_root_local_type(place, mir) != Some(LocalTypeInfo::Copy) =>
1722 {
1723 vec![place.clone()]
1724 }
1725 _ => Vec::new(),
1726 }
1727}
1728
1729fn place_root_local_type(place: &Place, mir: &MirFunction) -> Option<LocalTypeInfo> {
1730 mir.local_types.get(place.root_local().0 as usize).cloned()
1731}
1732
1733fn reinitializes_moved_place(dest_place: &Place, moved_place: &Place) -> bool {
1734 dest_place.is_prefix_of(moved_place)
1735}
1736
1737fn find_moved_place_conflict(
1738 moved_places: &HashMap<Place, shape_ast::ast::Span>,
1739 accessed_place: &Place,
1740) -> Option<(Place, shape_ast::ast::Span)> {
1741 moved_places
1742 .iter()
1743 .find(|(moved_place, _)| accessed_place.conflicts_with(moved_place))
1744 .map(|(place, span)| (place.clone(), *span))
1745}
1746
1747fn last_use_span_for_loan(facts: &BorrowFacts, loan_id: u32) -> Option<shape_ast::ast::Span> {
1748 facts
1749 .use_of_loan
1750 .iter()
1751 .filter(|(candidate, _)| *candidate == loan_id)
1752 .filter_map(|(_, point)| facts.point_spans.get(point).copied())
1753 .max_by_key(|span| span.start)
1754}
1755
1756#[cfg(test)]
1757mod tests {
1758 use super::*;
1759 use shape_ast::ast::Span;
1760
1761 fn span() -> Span {
1762 Span { start: 0, end: 1 }
1763 }
1764
1765 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
1766 MirStatement {
1767 kind,
1768 span: span(),
1769 point: Point(point),
1770 }
1771 }
1772
1773 fn make_terminator(kind: TerminatorKind) -> Terminator {
1774 Terminator { kind, span: span() }
1775 }
1776
1777 #[test]
1778 fn test_single_shared_borrow_no_error() {
1779 let mir = MirFunction {
1780 name: "test".to_string(),
1781 blocks: vec![BasicBlock {
1782 id: BasicBlockId(0),
1783 statements: vec![
1784 make_stmt(
1786 StatementKind::Assign(
1787 Place::Local(SlotId(0)),
1788 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1789 ),
1790 0,
1791 ),
1792 make_stmt(
1794 StatementKind::Assign(
1795 Place::Local(SlotId(1)),
1796 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
1797 ),
1798 1,
1799 ),
1800 ],
1801 terminator: make_terminator(TerminatorKind::Return),
1802 }],
1803 num_locals: 2,
1804 param_slots: vec![],
1805 param_reference_kinds: vec![],
1806 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
1807 span: span(),
1808 };
1809
1810 let analysis = analyze(&mir, &Default::default());
1811 assert!(analysis.errors.is_empty(), "expected no errors");
1812 }
1813
1814 #[test]
1815 fn test_conflicting_shared_and_exclusive_error() {
1816 let mir = MirFunction {
1820 name: "test".to_string(),
1821 blocks: vec![BasicBlock {
1822 id: BasicBlockId(0),
1823 statements: vec![
1824 make_stmt(
1825 StatementKind::Assign(
1826 Place::Local(SlotId(0)),
1827 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1828 ),
1829 0,
1830 ),
1831 make_stmt(
1832 StatementKind::Assign(
1833 Place::Local(SlotId(1)),
1834 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
1835 ),
1836 1,
1837 ),
1838 make_stmt(
1839 StatementKind::Assign(
1840 Place::Local(SlotId(2)),
1841 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
1842 ),
1843 2,
1844 ),
1845 ],
1846 terminator: make_terminator(TerminatorKind::Return),
1847 }],
1848 num_locals: 3,
1849 param_slots: vec![],
1850 param_reference_kinds: vec![],
1851 local_types: vec![
1852 LocalTypeInfo::NonCopy,
1853 LocalTypeInfo::NonCopy,
1854 LocalTypeInfo::NonCopy,
1855 ],
1856 span: span(),
1857 };
1858
1859 let analysis = analyze(&mir, &Default::default());
1860 assert!(
1861 !analysis.errors.is_empty(),
1862 "expected borrow conflict error"
1863 );
1864 assert_eq!(
1865 analysis.errors[0].kind,
1866 BorrowErrorKind::ConflictSharedExclusive
1867 );
1868 }
1869
1870 #[test]
1871 fn test_disjoint_field_borrows_no_conflict() {
1872 let mir = MirFunction {
1875 name: "test".to_string(),
1876 blocks: vec![BasicBlock {
1877 id: BasicBlockId(0),
1878 statements: vec![
1879 make_stmt(
1880 StatementKind::Assign(
1881 Place::Local(SlotId(0)),
1882 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
1883 ),
1884 0,
1885 ),
1886 make_stmt(
1887 StatementKind::Assign(
1888 Place::Local(SlotId(1)),
1889 Rvalue::Borrow(
1890 BorrowKind::Shared,
1891 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0)),
1892 ),
1893 ),
1894 1,
1895 ),
1896 make_stmt(
1897 StatementKind::Assign(
1898 Place::Local(SlotId(2)),
1899 Rvalue::Borrow(
1900 BorrowKind::Exclusive,
1901 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(1)),
1902 ),
1903 ),
1904 2,
1905 ),
1906 ],
1907 terminator: make_terminator(TerminatorKind::Return),
1908 }],
1909 num_locals: 3,
1910 param_slots: vec![],
1911 param_reference_kinds: vec![],
1912 local_types: vec![
1913 LocalTypeInfo::NonCopy,
1914 LocalTypeInfo::NonCopy,
1915 LocalTypeInfo::NonCopy,
1916 ],
1917 span: span(),
1918 };
1919
1920 let analysis = analyze(&mir, &Default::default());
1921 assert!(
1922 analysis.errors.is_empty(),
1923 "disjoint field borrows should not conflict, got: {:?}",
1924 analysis.errors
1925 );
1926 }
1927
1928 #[test]
1929 fn test_read_while_exclusive_borrow_error() {
1930 let mir = MirFunction {
1931 name: "test".to_string(),
1932 blocks: vec![BasicBlock {
1933 id: BasicBlockId(0),
1934 statements: vec![
1935 make_stmt(
1936 StatementKind::Assign(
1937 Place::Local(SlotId(0)),
1938 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1939 ),
1940 0,
1941 ),
1942 make_stmt(
1943 StatementKind::Assign(
1944 Place::Local(SlotId(1)),
1945 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
1946 ),
1947 1,
1948 ),
1949 make_stmt(
1950 StatementKind::Assign(
1951 Place::Local(SlotId(2)),
1952 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
1953 ),
1954 2,
1955 ),
1956 ],
1957 terminator: make_terminator(TerminatorKind::Return),
1958 }],
1959 num_locals: 3,
1960 param_slots: vec![],
1961 param_reference_kinds: vec![],
1962 local_types: vec![
1963 LocalTypeInfo::NonCopy,
1964 LocalTypeInfo::NonCopy,
1965 LocalTypeInfo::NonCopy,
1966 ],
1967 span: span(),
1968 };
1969
1970 let analysis = analyze(&mir, &Default::default());
1971 assert!(
1972 analysis
1973 .errors
1974 .iter()
1975 .any(|error| error.kind == BorrowErrorKind::ReadWhileExclusivelyBorrowed),
1976 "expected read-while-exclusive error, got {:?}",
1977 analysis.errors
1978 );
1979 }
1980
1981 #[test]
1982 fn test_reference_escape_error_for_returned_ref_alias() {
1983 let mir = MirFunction {
1984 name: "test".to_string(),
1985 blocks: vec![BasicBlock {
1986 id: BasicBlockId(0),
1987 statements: vec![
1988 make_stmt(
1989 StatementKind::Assign(
1990 Place::Local(SlotId(1)),
1991 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1992 ),
1993 0,
1994 ),
1995 make_stmt(
1996 StatementKind::Assign(
1997 Place::Local(SlotId(2)),
1998 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(1))),
1999 ),
2000 1,
2001 ),
2002 make_stmt(
2003 StatementKind::Assign(
2004 Place::Local(SlotId(3)),
2005 Rvalue::Use(Operand::Move(Place::Local(SlotId(2)))),
2006 ),
2007 2,
2008 ),
2009 make_stmt(
2010 StatementKind::Assign(
2011 Place::Local(SlotId(0)),
2012 Rvalue::Use(Operand::Move(Place::Local(SlotId(3)))),
2013 ),
2014 3,
2015 ),
2016 ],
2017 terminator: make_terminator(TerminatorKind::Return),
2018 }],
2019 num_locals: 4,
2020 param_slots: vec![],
2021 param_reference_kinds: vec![],
2022 local_types: vec![
2023 LocalTypeInfo::NonCopy,
2024 LocalTypeInfo::NonCopy,
2025 LocalTypeInfo::NonCopy,
2026 LocalTypeInfo::NonCopy,
2027 ],
2028 span: span(),
2029 };
2030
2031 let analysis = analyze(&mir, &Default::default());
2032 assert!(
2033 analysis
2034 .errors
2035 .iter()
2036 .any(|error| error.kind == BorrowErrorKind::ReferenceEscape),
2037 "expected reference-escape error, got {:?}",
2038 analysis.errors
2039 );
2040 }
2041
2042 #[test]
2043 fn test_use_after_explicit_move_error() {
2044 let mir = MirFunction {
2045 name: "test".to_string(),
2046 blocks: vec![BasicBlock {
2047 id: BasicBlockId(0),
2048 statements: vec![
2049 make_stmt(
2050 StatementKind::Assign(
2051 Place::Local(SlotId(0)),
2052 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2053 ),
2054 0,
2055 ),
2056 make_stmt(
2057 StatementKind::Assign(
2058 Place::Local(SlotId(1)),
2059 Rvalue::Use(Operand::MoveExplicit(Place::Local(SlotId(0)))),
2060 ),
2061 1,
2062 ),
2063 make_stmt(
2064 StatementKind::Assign(
2065 Place::Local(SlotId(2)),
2066 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
2067 ),
2068 2,
2069 ),
2070 ],
2071 terminator: make_terminator(TerminatorKind::Return),
2072 }],
2073 num_locals: 3,
2074 param_slots: vec![],
2075 param_reference_kinds: vec![],
2076 local_types: vec![
2077 LocalTypeInfo::NonCopy,
2078 LocalTypeInfo::NonCopy,
2079 LocalTypeInfo::NonCopy,
2080 ],
2081 span: span(),
2082 };
2083
2084 let analysis = analyze(&mir, &Default::default());
2085 assert!(
2086 analysis
2087 .errors
2088 .iter()
2089 .any(|error| error.kind == BorrowErrorKind::UseAfterMove),
2090 "expected use-after-move error, got {:?}",
2091 analysis.errors
2092 );
2093 }
2094
2095 #[test]
2096 fn test_move_vs_clone_decision() {
2097 let mir = MirFunction {
2100 name: "test".to_string(),
2101 blocks: vec![BasicBlock {
2102 id: BasicBlockId(0),
2103 statements: vec![
2104 make_stmt(
2105 StatementKind::Assign(
2106 Place::Local(SlotId(0)),
2107 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2108 ),
2109 0,
2110 ),
2111 make_stmt(
2112 StatementKind::Assign(
2113 Place::Local(SlotId(1)),
2114 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2115 ),
2116 1,
2117 ),
2118 ],
2119 terminator: make_terminator(TerminatorKind::Return),
2120 }],
2121 num_locals: 2,
2122 param_slots: vec![],
2123 param_reference_kinds: vec![],
2124 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
2125 span: span(),
2126 };
2127
2128 let analysis = analyze(&mir, &Default::default());
2129 assert_eq!(
2131 analysis.ownership_at(Point(1)),
2132 OwnershipDecision::Move,
2133 "source dead after → should be Move"
2134 );
2135 }
2136
2137 #[test]
2138 fn test_nll_borrow_scoping() {
2139 let mir = MirFunction {
2143 name: "test".to_string(),
2144 blocks: vec![
2145 BasicBlock {
2146 id: BasicBlockId(0),
2147 statements: vec![
2148 make_stmt(
2149 StatementKind::Assign(
2150 Place::Local(SlotId(0)),
2151 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2152 ),
2153 0,
2154 ),
2155 make_stmt(
2156 StatementKind::Assign(
2157 Place::Local(SlotId(1)),
2158 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
2159 ),
2160 1,
2161 ),
2162 make_stmt(
2164 StatementKind::Assign(
2165 Place::Local(SlotId(3)),
2166 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2167 ),
2168 2,
2169 ),
2170 ],
2171 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
2172 },
2173 BasicBlock {
2174 id: BasicBlockId(1),
2175 statements: vec![
2176 make_stmt(
2179 StatementKind::Assign(
2180 Place::Local(SlotId(2)),
2181 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
2182 ),
2183 3,
2184 ),
2185 ],
2186 terminator: make_terminator(TerminatorKind::Return),
2187 },
2188 ],
2189 num_locals: 4,
2190 param_slots: vec![],
2191 param_reference_kinds: vec![],
2192 local_types: vec![
2193 LocalTypeInfo::NonCopy,
2194 LocalTypeInfo::NonCopy,
2195 LocalTypeInfo::NonCopy,
2196 LocalTypeInfo::NonCopy,
2197 ],
2198 span: span(),
2199 };
2200
2201 let analysis = analyze(&mir, &Default::default());
2202 let _ = analysis;
2209 }
2210
2211 #[test]
2212 fn test_clone_decision_when_source_live_after() {
2213 let mir = MirFunction {
2217 name: "test".to_string(),
2218 blocks: vec![BasicBlock {
2219 id: BasicBlockId(0),
2220 statements: vec![
2221 make_stmt(
2222 StatementKind::Assign(
2223 Place::Local(SlotId(0)),
2224 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2225 ),
2226 0,
2227 ),
2228 make_stmt(
2229 StatementKind::Assign(
2230 Place::Local(SlotId(1)),
2231 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2232 ),
2233 1,
2234 ),
2235 make_stmt(
2236 StatementKind::Assign(
2237 Place::Local(SlotId(2)),
2238 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2239 ),
2240 2,
2241 ),
2242 ],
2243 terminator: make_terminator(TerminatorKind::Return),
2244 }],
2245 num_locals: 3,
2246 param_slots: vec![],
2247 param_reference_kinds: vec![],
2248 local_types: vec![
2249 LocalTypeInfo::NonCopy,
2250 LocalTypeInfo::NonCopy,
2251 LocalTypeInfo::NonCopy,
2252 ],
2253 span: span(),
2254 };
2255
2256 let analysis = analyze(&mir, &Default::default());
2257 assert_eq!(
2259 analysis.ownership_at(Point(1)),
2260 OwnershipDecision::Clone,
2261 "source live after → should be Clone"
2262 );
2263 assert_eq!(
2265 analysis.ownership_at(Point(2)),
2266 OwnershipDecision::Move,
2267 "source dead after → should be Move"
2268 );
2269 }
2270
2271 #[test]
2272 fn test_copy_type_always_copy_decision() {
2273 let mir = MirFunction {
2276 name: "test".to_string(),
2277 blocks: vec![BasicBlock {
2278 id: BasicBlockId(0),
2279 statements: vec![
2280 make_stmt(
2281 StatementKind::Assign(
2282 Place::Local(SlotId(0)),
2283 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2284 ),
2285 0,
2286 ),
2287 make_stmt(
2288 StatementKind::Assign(
2289 Place::Local(SlotId(1)),
2290 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2291 ),
2292 1,
2293 ),
2294 ],
2295 terminator: make_terminator(TerminatorKind::Return),
2296 }],
2297 num_locals: 2,
2298 param_slots: vec![],
2299 param_reference_kinds: vec![],
2300 local_types: vec![LocalTypeInfo::Copy, LocalTypeInfo::Copy],
2301 span: span(),
2302 };
2303
2304 let analysis = analyze(&mir, &Default::default());
2305 assert_eq!(
2306 analysis.ownership_at(Point(1)),
2307 OwnershipDecision::Copy,
2308 "Copy type → always Copy regardless of liveness"
2309 );
2310 }
2311
2312 #[test]
2317 fn test_compose_summary_identity() {
2318 let arg = ReturnReferenceSummary {
2320 param_index: 2,
2321 kind: BorrowKind::Shared,
2322 projection: Some(vec![]),
2323 };
2324 let callee = ReturnReferenceSummary {
2325 param_index: 0,
2326 kind: BorrowKind::Exclusive,
2327 projection: Some(vec![]),
2328 };
2329 let result = compose_return_reference_summary(&arg, &callee);
2330 assert_eq!(result.param_index, 2); assert_eq!(result.kind, BorrowKind::Exclusive); assert_eq!(result.projection, Some(vec![]));
2333 }
2334
2335 #[test]
2336 fn test_compose_summary_some_index_some_empty() {
2337 let arg = ReturnReferenceSummary {
2338 param_index: 0,
2339 kind: BorrowKind::Shared,
2340 projection: Some(vec![ProjectionStep::Index]),
2341 };
2342 let callee = ReturnReferenceSummary {
2343 param_index: 0,
2344 kind: BorrowKind::Shared,
2345 projection: Some(vec![]),
2346 };
2347 let result = compose_return_reference_summary(&arg, &callee);
2348 assert_eq!(result.projection, Some(vec![ProjectionStep::Index]));
2349 }
2350
2351 #[test]
2352 fn test_compose_summary_callee_field_loses_precision() {
2353 let arg = ReturnReferenceSummary {
2354 param_index: 0,
2355 kind: BorrowKind::Shared,
2356 projection: Some(vec![]),
2357 };
2358 let callee = ReturnReferenceSummary {
2359 param_index: 0,
2360 kind: BorrowKind::Shared,
2361 projection: Some(vec![ProjectionStep::Field(FieldIdx(0))]),
2362 };
2363 let result = compose_return_reference_summary(&arg, &callee);
2364 assert_eq!(result.projection, None); }
2366
2367 #[test]
2368 fn test_compose_summary_callee_index_composes() {
2369 let arg = ReturnReferenceSummary {
2370 param_index: 1,
2371 kind: BorrowKind::Shared,
2372 projection: Some(vec![ProjectionStep::Index]),
2373 };
2374 let callee = ReturnReferenceSummary {
2375 param_index: 0,
2376 kind: BorrowKind::Exclusive,
2377 projection: Some(vec![ProjectionStep::Index]),
2378 };
2379 let result = compose_return_reference_summary(&arg, &callee);
2380 assert_eq!(result.param_index, 1);
2381 assert_eq!(result.kind, BorrowKind::Exclusive);
2382 assert_eq!(
2383 result.projection,
2384 Some(vec![ProjectionStep::Index, ProjectionStep::Index])
2385 );
2386 }
2387
2388 #[test]
2389 fn test_compose_summary_arg_none() {
2390 let arg = ReturnReferenceSummary {
2391 param_index: 0,
2392 kind: BorrowKind::Shared,
2393 projection: None, };
2395 let callee = ReturnReferenceSummary {
2396 param_index: 0,
2397 kind: BorrowKind::Shared,
2398 projection: Some(vec![]),
2399 };
2400 let result = compose_return_reference_summary(&arg, &callee);
2401 assert_eq!(result.projection, None);
2402 }
2403
2404 #[test]
2405 fn test_compose_summary_callee_none() {
2406 let arg = ReturnReferenceSummary {
2407 param_index: 0,
2408 kind: BorrowKind::Shared,
2409 projection: Some(vec![ProjectionStep::Index]),
2410 };
2411 let callee = ReturnReferenceSummary {
2412 param_index: 0,
2413 kind: BorrowKind::Exclusive,
2414 projection: None,
2415 };
2416 let result = compose_return_reference_summary(&arg, &callee);
2417 assert_eq!(result.projection, None);
2418 }
2419
2420 #[test]
2425 fn test_call_composition_identity() {
2426 let mir = MirFunction {
2430 name: "caller".to_string(),
2431 blocks: vec![
2432 BasicBlock {
2433 id: BasicBlockId(0),
2434 statements: vec![
2435 MirStatement {
2436 kind: StatementKind::Assign(
2437 Place::Local(SlotId(2)),
2438 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2439 ),
2440 span: span(),
2441 point: Point(0),
2442 },
2443 ],
2444 terminator: Terminator {
2445 kind: TerminatorKind::Call {
2446 func: Operand::Constant(MirConstant::Function(
2447 "identity".to_string(),
2448 )),
2449 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2450 destination: Place::Local(SlotId(3)),
2451 next: BasicBlockId(1),
2452 },
2453 span: span(),
2454 },
2455 },
2456 BasicBlock {
2457 id: BasicBlockId(1),
2458 statements: vec![MirStatement {
2459 kind: StatementKind::Assign(
2460 Place::Local(SlotId(0)),
2461 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2462 ),
2463 span: span(),
2464 point: Point(1),
2465 }],
2466 terminator: Terminator {
2467 kind: TerminatorKind::Return,
2468 span: span(),
2469 },
2470 },
2471 ],
2472 num_locals: 4,
2473 param_slots: vec![SlotId(1)],
2474 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2475 local_types: vec![
2476 LocalTypeInfo::NonCopy,
2477 LocalTypeInfo::NonCopy,
2478 LocalTypeInfo::NonCopy,
2479 LocalTypeInfo::NonCopy,
2480 ],
2481 span: span(),
2482 };
2483
2484 let mut callee_summaries = CalleeSummaries::new();
2485 callee_summaries.insert(
2486 "identity".to_string(),
2487 ReturnReferenceSummary {
2488 param_index: 0,
2489 kind: BorrowKind::Shared,
2490 projection: Some(vec![]),
2491 },
2492 );
2493
2494 let analysis = analyze(&mir, &callee_summaries);
2495 assert!(
2496 analysis.return_reference_summary.is_some(),
2497 "expected return reference summary from composed call"
2498 );
2499 let summary = analysis.return_reference_summary.unwrap();
2500 assert_eq!(summary.param_index, 0);
2501 assert_eq!(summary.kind, BorrowKind::Shared);
2502 }
2503
2504 #[test]
2505 fn test_call_composition_unknown_callee() {
2506 let mir = MirFunction {
2508 name: "caller".to_string(),
2509 blocks: vec![
2510 BasicBlock {
2511 id: BasicBlockId(0),
2512 statements: vec![MirStatement {
2513 kind: StatementKind::Assign(
2514 Place::Local(SlotId(2)),
2515 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2516 ),
2517 span: span(),
2518 point: Point(0),
2519 }],
2520 terminator: Terminator {
2521 kind: TerminatorKind::Call {
2522 func: Operand::Constant(MirConstant::Function(
2523 "unknown_fn".to_string(),
2524 )),
2525 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2526 destination: Place::Local(SlotId(3)),
2527 next: BasicBlockId(1),
2528 },
2529 span: span(),
2530 },
2531 },
2532 BasicBlock {
2533 id: BasicBlockId(1),
2534 statements: vec![MirStatement {
2535 kind: StatementKind::Assign(
2536 Place::Local(SlotId(0)),
2537 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2538 ),
2539 span: span(),
2540 point: Point(1),
2541 }],
2542 terminator: Terminator {
2543 kind: TerminatorKind::Return,
2544 span: span(),
2545 },
2546 },
2547 ],
2548 num_locals: 4,
2549 param_slots: vec![SlotId(1)],
2550 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2551 local_types: vec![
2552 LocalTypeInfo::NonCopy,
2553 LocalTypeInfo::NonCopy,
2554 LocalTypeInfo::NonCopy,
2555 LocalTypeInfo::NonCopy,
2556 ],
2557 span: span(),
2558 };
2559
2560 let analysis = analyze(&mir, &Default::default());
2561 assert!(
2563 analysis.return_reference_summary.is_none(),
2564 "unknown callee should not produce return reference summary"
2565 );
2566 }
2567
2568 #[test]
2569 fn test_call_composition_indirect_call() {
2570 let mir = MirFunction {
2572 name: "caller".to_string(),
2573 blocks: vec![
2574 BasicBlock {
2575 id: BasicBlockId(0),
2576 statements: vec![MirStatement {
2577 kind: StatementKind::Assign(
2578 Place::Local(SlotId(2)),
2579 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2580 ),
2581 span: span(),
2582 point: Point(0),
2583 }],
2584 terminator: Terminator {
2585 kind: TerminatorKind::Call {
2586 func: Operand::Constant(MirConstant::Method(
2587 "identity".to_string(),
2588 )),
2589 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2590 destination: Place::Local(SlotId(3)),
2591 next: BasicBlockId(1),
2592 },
2593 span: span(),
2594 },
2595 },
2596 BasicBlock {
2597 id: BasicBlockId(1),
2598 statements: vec![MirStatement {
2599 kind: StatementKind::Assign(
2600 Place::Local(SlotId(0)),
2601 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2602 ),
2603 span: span(),
2604 point: Point(1),
2605 }],
2606 terminator: Terminator {
2607 kind: TerminatorKind::Return,
2608 span: span(),
2609 },
2610 },
2611 ],
2612 num_locals: 4,
2613 param_slots: vec![SlotId(1)],
2614 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2615 local_types: vec![
2616 LocalTypeInfo::NonCopy,
2617 LocalTypeInfo::NonCopy,
2618 LocalTypeInfo::NonCopy,
2619 LocalTypeInfo::NonCopy,
2620 ],
2621 span: span(),
2622 };
2623
2624 let mut callee_summaries = CalleeSummaries::new();
2625 callee_summaries.insert(
2626 "identity".to_string(),
2627 ReturnReferenceSummary {
2628 param_index: 0,
2629 kind: BorrowKind::Shared,
2630 projection: Some(vec![]),
2631 },
2632 );
2633
2634 let analysis = analyze(&mir, &callee_summaries);
2636 assert!(
2637 analysis.return_reference_summary.is_none(),
2638 "indirect (Method) call should not compose return summary"
2639 );
2640 }
2641
2642 #[test]
2643 fn test_call_composition_chain() {
2644 let mir = MirFunction {
2649 name: "caller".to_string(),
2650 blocks: vec![
2651 BasicBlock {
2652 id: BasicBlockId(0),
2653 statements: vec![MirStatement {
2654 kind: StatementKind::Assign(
2655 Place::Local(SlotId(2)),
2656 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2657 ),
2658 span: span(),
2659 point: Point(0),
2660 }],
2661 terminator: Terminator {
2662 kind: TerminatorKind::Call {
2663 func: Operand::Constant(MirConstant::Function(
2664 "inner".to_string(),
2665 )),
2666 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2667 destination: Place::Local(SlotId(3)),
2668 next: BasicBlockId(1),
2669 },
2670 span: span(),
2671 },
2672 },
2673 BasicBlock {
2674 id: BasicBlockId(1),
2675 statements: vec![MirStatement {
2676 kind: StatementKind::Nop,
2677 span: span(),
2678 point: Point(1),
2679 }],
2680 terminator: Terminator {
2681 kind: TerminatorKind::Call {
2682 func: Operand::Constant(MirConstant::Function(
2683 "outer".to_string(),
2684 )),
2685 args: vec![Operand::Copy(Place::Local(SlotId(3)))],
2686 destination: Place::Local(SlotId(4)),
2687 next: BasicBlockId(2),
2688 },
2689 span: span(),
2690 },
2691 },
2692 BasicBlock {
2693 id: BasicBlockId(2),
2694 statements: vec![MirStatement {
2695 kind: StatementKind::Assign(
2696 Place::Local(SlotId(0)),
2697 Rvalue::Use(Operand::Copy(Place::Local(SlotId(4)))),
2698 ),
2699 span: span(),
2700 point: Point(2),
2701 }],
2702 terminator: Terminator {
2703 kind: TerminatorKind::Return,
2704 span: span(),
2705 },
2706 },
2707 ],
2708 num_locals: 5,
2709 param_slots: vec![SlotId(1)],
2710 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2711 local_types: vec![
2712 LocalTypeInfo::NonCopy,
2713 LocalTypeInfo::NonCopy,
2714 LocalTypeInfo::NonCopy,
2715 LocalTypeInfo::NonCopy,
2716 LocalTypeInfo::NonCopy,
2717 ],
2718 span: span(),
2719 };
2720
2721 let mut callee_summaries = CalleeSummaries::new();
2722 callee_summaries.insert(
2723 "inner".to_string(),
2724 ReturnReferenceSummary {
2725 param_index: 0,
2726 kind: BorrowKind::Shared,
2727 projection: Some(vec![]),
2728 },
2729 );
2730 callee_summaries.insert(
2731 "outer".to_string(),
2732 ReturnReferenceSummary {
2733 param_index: 0,
2734 kind: BorrowKind::Exclusive,
2735 projection: Some(vec![]),
2736 },
2737 );
2738
2739 let analysis = analyze(&mir, &callee_summaries);
2740 assert!(
2741 analysis.return_reference_summary.is_some(),
2742 "chained composition should produce return reference summary"
2743 );
2744 let summary = analysis.return_reference_summary.unwrap();
2745 assert_eq!(summary.param_index, 0, "should trace to outermost param");
2746 assert_eq!(
2747 summary.kind,
2748 BorrowKind::Exclusive,
2749 "outer callee dictates the kind"
2750 );
2751 }
2752}