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 } => {
394 for loan_id in local_loans_from_operands(&slot_loans, operands) {
395 facts.closure_capture_loans.push((loan_id, stmt.span));
396 facts.loan_sinks.push(LoanSink {
397 loan_id,
398 kind: LoanSinkKind::ClosureEnv,
399 sink_slot: Some(*closure_slot),
400 span: stmt.span,
401 });
402 }
403 }
404 StatementKind::ArrayStore {
405 container_slot,
406 operands,
407 } => {
408 for loan_id in local_loans_from_operands(&slot_loans, operands) {
409 facts.array_store_loans.push((loan_id, stmt.span));
410 facts.loan_sinks.push(LoanSink {
411 loan_id,
412 kind: LoanSinkKind::ArrayStore,
413 sink_slot: Some(*container_slot),
414 span: stmt.span,
415 });
416 }
417 }
418 StatementKind::ObjectStore {
419 container_slot,
420 operands,
421 ..
422 } => {
423 for loan_id in local_loans_from_operands(&slot_loans, operands) {
424 facts.object_store_loans.push((loan_id, stmt.span));
425 facts.loan_sinks.push(LoanSink {
426 loan_id,
427 kind: LoanSinkKind::ObjectStore,
428 sink_slot: Some(*container_slot),
429 span: stmt.span,
430 });
431 }
432 }
433 StatementKind::EnumStore {
434 container_slot,
435 operands,
436 variant_name: _,
437 } => {
438 for loan_id in local_loans_from_operands(&slot_loans, operands) {
439 facts.enum_store_loans.push((loan_id, stmt.span));
440 facts.loan_sinks.push(LoanSink {
441 loan_id,
442 kind: LoanSinkKind::EnumStore,
443 sink_slot: Some(*container_slot),
444 span: stmt.span,
445 });
446 }
447 }
448 StatementKind::Nop => {}
449 }
450
451 for read_place in statement_read_places(&stmt.kind) {
452 facts
453 .reads
454 .push((stmt.point.0, read_place.clone(), stmt.span));
455 if let Place::Local(slot) = read_place {
456 if let Some(loans) = slot_loans.get(&slot) {
457 for loan_id in loans {
458 facts.use_of_loan.push((*loan_id, stmt.point.0));
459 }
460 }
461 }
462 }
463 }
464
465 if let TerminatorKind::Call { func, args, destination, .. } = &block.terminator.kind {
467 let call_point = block.statements.last().map(|s| s.point.0).unwrap_or(0);
468 let mut all_operands = vec![func];
470 all_operands.extend(args.iter());
471 for op in &all_operands {
472 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = op {
473 if let Some(loans) = slot_loans.get(&place.root_local()) {
474 for &loan_id in loans {
475 facts.use_of_loan.push((loan_id, call_point));
476 }
477 }
478 }
479 }
480 let dest_slot = destination.root_local();
482 slot_loans.remove(&dest_slot);
483 slot_reference_origins.remove(&dest_slot);
484 slot_reference_summaries.remove(&dest_slot);
485
486 if let Operand::Constant(MirConstant::Function(callee_name)) = func {
490 if let Some(callee_summary) = callee_summaries.get(callee_name.as_str()) {
491 if let Some(arg_operand) = args.get(callee_summary.param_index) {
492 if let Operand::Copy(arg_place)
493 | Operand::Move(arg_place)
494 | Operand::MoveExplicit(arg_place) = arg_operand
495 {
496 let arg_slot = arg_place.root_local();
497
498 if let Some(arg_loans) = slot_loans.get(&arg_slot).cloned() {
500 slot_loans.insert(dest_slot, arg_loans);
501 }
502
503 if let Some(arg_summary) =
505 slot_reference_summaries.get(&arg_slot).cloned()
506 {
507 let composed = compose_return_reference_summary(
508 &arg_summary,
509 callee_summary,
510 );
511
512 if composed.projection.is_some() {
516 if let Some((_, origin)) =
517 slot_reference_origins.get(&arg_slot).cloned()
518 {
519 if let Some(ref callee_proj) = callee_summary.projection {
521 let mut proj = origin.projection.clone();
522 proj.extend(callee_proj.iter().copied());
523 slot_reference_origins.insert(
524 dest_slot,
525 (
526 composed.kind,
527 ReferenceOrigin {
528 root: origin.root,
529 projection: proj,
530 },
531 ),
532 );
533 }
534 }
535 }
538 slot_reference_summaries.insert(dest_slot, composed);
541 }
542 }
543 }
544 }
545 }
546 }
547 }
548
549 let loan_ids: Vec<u32> = facts.loan_info.keys().copied().collect();
551 for i in 0..loan_ids.len() {
552 for j in (i + 1)..loan_ids.len() {
553 let a = loan_ids[i];
554 let b = loan_ids[j];
555 let info_a = &facts.loan_info[&a];
556 let info_b = &facts.loan_info[&b];
557
558 if info_a.borrowed_place.conflicts_with(&info_b.borrowed_place)
560 && (info_a.kind == BorrowKind::Exclusive || info_b.kind == BorrowKind::Exclusive)
561 {
562 facts.potential_conflicts.push((a, b));
563 }
564 }
565 }
566
567 facts
568}
569
570fn operand_read_places<'a>(operand: &'a Operand, reads: &mut Vec<Place>) {
571 match operand {
572 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
573 reads.push(place.clone());
574 place_nested_read_places(place, reads);
575 }
576 Operand::Constant(_) => {}
577 }
578}
579
580fn place_nested_read_places(place: &Place, reads: &mut Vec<Place>) {
581 match place {
582 Place::Local(_) => {}
583 Place::Field(base, _) | Place::Deref(base) => {
584 place_nested_read_places(base, reads);
585 }
586 Place::Index(base, index) => {
587 place_nested_read_places(base, reads);
588 operand_read_places(index, reads);
589 }
590 }
591}
592
593fn statement_read_places(kind: &StatementKind) -> Vec<Place> {
594 let mut reads = Vec::new();
595 match kind {
596 StatementKind::Assign(_, rvalue) => match rvalue {
597 Rvalue::Use(operand) | Rvalue::Clone(operand) => {
598 operand_read_places(operand, &mut reads)
599 }
600 Rvalue::Borrow(_, _) => {}
601 Rvalue::BinaryOp(_, lhs, rhs) => {
602 operand_read_places(lhs, &mut reads);
603 operand_read_places(rhs, &mut reads);
604 }
605 Rvalue::UnaryOp(_, operand) => operand_read_places(operand, &mut reads),
606 Rvalue::Aggregate(operands) => {
607 for operand in operands {
608 operand_read_places(operand, &mut reads);
609 }
610 }
611 Rvalue::EnumTest { operand, .. }
612 | Rvalue::EnumPayload { operand, .. }
613 | Rvalue::TypePatternTest { operand, .. }
614 | Rvalue::EnumDiscriminantTest { operand, .. } => {
615 operand_read_places(operand, &mut reads);
616 }
617 },
618 StatementKind::Drop(place) => place_nested_read_places(place, &mut reads),
619 StatementKind::TaskBoundary(operands, _kind) => {
620 for operand in operands {
621 operand_read_places(operand, &mut reads);
622 }
623 }
624 StatementKind::ClosureCapture { operands, .. } => {
625 for operand in operands {
626 operand_read_places(operand, &mut reads);
627 }
628 }
629 StatementKind::ArrayStore { operands, .. } => {
630 for operand in operands {
631 operand_read_places(operand, &mut reads);
632 }
633 }
634 StatementKind::ObjectStore { operands, .. } => {
635 for operand in operands {
636 operand_read_places(operand, &mut reads);
637 }
638 }
639 StatementKind::EnumStore { operands, .. } => {
640 for operand in operands {
641 operand_read_places(operand, &mut reads);
642 }
643 }
644 StatementKind::Nop => {}
645 }
646 reads
647}
648
649fn local_loans_from_operand(slot_loans: &HashMap<SlotId, Vec<u32>>, operand: &Operand) -> Vec<u32> {
650 match operand {
651 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => slot_loans
652 .get(&place.root_local())
653 .cloned()
654 .unwrap_or_default(),
655 Operand::Constant(_) => Vec::new(),
656 }
657}
658
659fn local_loans_from_operands(
660 slot_loans: &HashMap<SlotId, Vec<u32>>,
661 operands: &[Operand],
662) -> Vec<u32> {
663 let mut loans = Vec::new();
664 let mut seen = HashSet::new();
665 for operand in operands {
666 for loan in local_loans_from_operand(slot_loans, operand) {
667 if seen.insert(loan) {
668 loans.push(loan);
669 }
670 }
671 }
672 loans
673}
674
675fn update_slot_loan_aliases(
676 slot_loans: &mut HashMap<SlotId, Vec<u32>>,
677 dest_slot: SlotId,
678 rvalue: &Rvalue,
679) {
680 match rvalue {
681 Rvalue::Borrow(_, _) => {}
682 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
683 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
684 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
685 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
686 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
687 if let Some(loans) = slot_loans.get(src_slot).cloned() {
688 slot_loans.insert(dest_slot, loans);
689 } else {
690 slot_loans.remove(&dest_slot);
691 }
692 }
693 _ => {
694 slot_loans.remove(&dest_slot);
695 }
696 }
697}
698
699fn local_loans_from_rvalue(slot_loans: &HashMap<SlotId, Vec<u32>>, rvalue: &Rvalue) -> Vec<u32> {
700 match rvalue {
701 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
702 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
703 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
704 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
705 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
706 slot_loans.get(src_slot).cloned().unwrap_or_default()
707 }
708 _ => Vec::new(),
709 }
710}
711
712fn update_slot_reference_summaries(
713 slot_reference_summaries: &mut HashMap<SlotId, ReturnReferenceSummary>,
714 dest_slot: SlotId,
715 rvalue: &Rvalue,
716) {
717 match rvalue {
718 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
719 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
720 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
721 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
722 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
723 if let Some(contract) = slot_reference_summaries.get(src_slot).cloned() {
724 slot_reference_summaries.insert(dest_slot, contract);
725 } else {
726 slot_reference_summaries.remove(&dest_slot);
727 }
728 }
729 _ => {
730 slot_reference_summaries.remove(&dest_slot);
731 }
732 }
733}
734
735fn reference_summary_from_rvalue(
736 slot_reference_summaries: &HashMap<SlotId, ReturnReferenceSummary>,
737 rvalue: &Rvalue,
738) -> Option<ReturnReferenceSummary> {
739 match rvalue {
740 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
741 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
742 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
743 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
744 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
745 slot_reference_summaries.get(src_slot).cloned()
746 }
747 _ => None,
748 }
749}
750
751fn update_slot_reference_origins(
752 slot_reference_origins: &mut HashMap<SlotId, (BorrowKind, ReferenceOrigin)>,
753 dest_slot: SlotId,
754 rvalue: &Rvalue,
755) {
756 match rvalue {
757 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
758 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
759 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
760 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
761 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
762 if let Some(origin) = slot_reference_origins.get(src_slot).cloned() {
763 slot_reference_origins.insert(dest_slot, origin);
764 } else {
765 slot_reference_origins.remove(&dest_slot);
766 }
767 }
768 _ => {
769 slot_reference_origins.remove(&dest_slot);
770 }
771 }
772}
773
774fn reference_origin_from_rvalue(
775 slot_reference_origins: &HashMap<SlotId, (BorrowKind, ReferenceOrigin)>,
776 rvalue: &Rvalue,
777) -> Option<(BorrowKind, ReferenceOrigin)> {
778 match rvalue {
779 Rvalue::Borrow(kind, place) => Some((
780 *kind,
781 reference_origin_for_place(place, &[]),
782 )),
783 Rvalue::Use(Operand::Copy(Place::Local(src_slot)))
784 | Rvalue::Use(Operand::Move(Place::Local(src_slot)))
785 | Rvalue::Use(Operand::MoveExplicit(Place::Local(src_slot)))
786 | Rvalue::Clone(Operand::Copy(Place::Local(src_slot)))
787 | Rvalue::Clone(Operand::Move(Place::Local(src_slot))) => {
788 slot_reference_origins.get(src_slot).cloned()
789 }
790 _ => None,
791 }
792}
793
794fn reference_origin_for_place(place: &Place, param_slots: &[SlotId]) -> ReferenceOrigin {
795 let root_slot = place.root_local();
796 let root = param_slots
797 .iter()
798 .position(|slot| *slot == root_slot)
799 .map(ReferenceOriginRoot::Param)
800 .unwrap_or(ReferenceOriginRoot::Local(root_slot));
801 ReferenceOrigin {
802 root,
803 projection: place.projection_steps(),
804 }
805}
806
807fn reference_summary_from_origin(
808 borrow_kind: BorrowKind,
809 origin: &ReferenceOrigin,
810) -> Option<ReturnReferenceSummary> {
811 match origin.root {
812 ReferenceOriginRoot::Param(param_index) => Some(ReturnReferenceSummary {
813 param_index,
814 kind: borrow_kind,
815 projection: Some(origin.projection.clone()),
816 }),
817 ReferenceOriginRoot::Local(_) => None,
818 }
819}
820
821fn safe_reference_summary_for_borrow(
822 borrow_kind: BorrowKind,
823 borrowed_place: &Place,
824 param_reference_summaries: &HashMap<SlotId, ReturnReferenceSummary>,
825) -> Option<ReturnReferenceSummary> {
826 let param_summary = param_reference_summaries.get(&borrowed_place.root_local())?;
829 Some(ReturnReferenceSummary {
830 param_index: param_summary.param_index,
831 kind: borrow_kind,
832 projection: Some(borrowed_place.projection_steps()),
833 })
834}
835
836fn compose_return_reference_summary(
844 arg_summary: &ReturnReferenceSummary,
845 callee_summary: &ReturnReferenceSummary,
846) -> ReturnReferenceSummary {
847 let projection = match (&arg_summary.projection, &callee_summary.projection) {
848 (Some(arg_proj), Some(callee_proj)) => {
849 if callee_proj
850 .iter()
851 .any(|step| matches!(step, ProjectionStep::Field(_)))
852 {
853 None } else {
855 let mut composed = arg_proj.clone();
856 composed.extend(callee_proj.iter().copied());
857 Some(composed)
858 }
859 }
860 _ => None, };
862 ReturnReferenceSummary {
863 param_index: arg_summary.param_index,
864 kind: callee_summary.kind,
865 projection,
866 }
867}
868
869fn resolve_return_reference_summary(
870 errors: &mut Vec<BorrowError>,
871 facts: &BorrowFacts,
872 loans_at_point: &HashMap<Point, Vec<LoanId>>,
873) -> Option<ReturnReferenceSummary> {
874 let mut merged_candidate: Option<ReturnReferenceSummary> = None;
875 let mut inconsistent = false;
876 for (candidate, _) in &facts.return_reference_candidates {
877 if let Some(existing) = merged_candidate.as_mut() {
878 if existing.param_index != candidate.param_index || existing.kind != candidate.kind {
879 inconsistent = true;
880 break;
881 }
882 if existing.projection != candidate.projection {
883 existing.projection = None;
884 }
885 } else {
886 merged_candidate = Some(candidate.clone());
887 }
888 }
889
890 if merged_candidate.is_none() {
891 return None;
892 }
893
894 let error_span = if inconsistent {
895 facts
896 .return_reference_candidates
897 .get(1)
898 .map(|(_, span)| *span)
899 } else {
900 facts.non_reference_return_spans.first().copied()
901 };
902
903 if let Some(span) = error_span {
904 let (conflicting_loan, loan_span, last_use_span) = facts
905 .return_reference_candidates
906 .first()
907 .and_then(|(candidate, candidate_span)| {
908 find_matching_loan_for_return_candidate(
909 candidate,
910 *candidate_span,
911 facts,
912 loans_at_point,
913 )
914 })
915 .unwrap_or((LoanId(0), span, None));
916 errors.push(BorrowError {
917 kind: BorrowErrorKind::InconsistentReferenceReturn,
918 span,
919 conflicting_loan,
920 loan_span,
921 last_use_span,
922 repairs: Vec::new(),
923 });
924 return None;
925 }
926
927 merged_candidate
928}
929
930fn find_matching_loan_for_return_candidate(
931 candidate: &ReturnReferenceSummary,
932 candidate_span: shape_ast::ast::Span,
933 facts: &BorrowFacts,
934 loans_at_point: &HashMap<Point, Vec<LoanId>>,
935) -> Option<(LoanId, shape_ast::ast::Span, Option<shape_ast::ast::Span>)> {
936 let point = facts
937 .point_spans
938 .iter()
939 .find_map(|(point, span)| (*span == candidate_span).then_some(Point(*point)))?;
940 let loans = loans_at_point.get(&point)?;
941 for loan in loans {
942 let info = facts.loan_info.get(&loan.0)?;
943 if info.kind == candidate.kind {
944 return Some((*loan, info.span, last_use_span_for_loan(facts, loan.0)));
945 }
946 }
947 None
948}
949
950pub fn solve(facts: &BorrowFacts) -> SolverResult {
952 let mut iteration = Iteration::new();
953
954 let cfg_edge: Relation<(u32, u32)> = facts.cfg_edge.iter().cloned().collect();
957 let invalidates_set: std::collections::HashSet<(u32, u32)> =
959 facts.invalidates.iter().cloned().collect();
960
961 let loan_live_at = iteration.variable::<(u32, u32)>("loan_live_at");
964
965 let seed: Vec<(u32, u32)> = facts
968 .loan_issued_at
969 .iter()
970 .map(|&(loan, point)| (point, loan))
971 .collect();
972 loan_live_at.extend(seed.iter().cloned());
973
974 while iteration.changed() {
980 loan_live_at.from_leapjoin(
984 &loan_live_at,
985 cfg_edge.extend_with(|&(point1, _loan)| point1),
986 |&(point1, loan), &point2| {
987 if invalidates_set.contains(&(point1, loan)) {
988 (u32::MAX, u32::MAX) } else {
992 (point2, loan)
993 }
994 },
995 );
996 }
997
998 let forward_live_points: Vec<(u32, u32)> = loan_live_at
1000 .complete()
1001 .iter()
1002 .filter(|&&(p, l)| p != u32::MAX && l != u32::MAX)
1003 .cloned()
1004 .collect();
1005 let (nll_live_set, loans_with_reachable_uses) = compute_nll_live_points(facts);
1006 let loan_live_at_result: Vec<(u32, u32)> = forward_live_points
1007 .into_iter()
1008 .filter(|point_loan| {
1009 !loans_with_reachable_uses.contains(&point_loan.1) || nll_live_set.contains(point_loan)
1010 })
1011 .collect();
1012
1013 let mut loans_at_point: HashMap<Point, Vec<LoanId>> = HashMap::new();
1015 for &(point, loan) in &loan_live_at_result {
1016 loans_at_point
1017 .entry(Point(point))
1018 .or_default()
1019 .push(LoanId(loan));
1020 }
1021
1022 let mut loan_points: HashMap<u32, std::collections::HashSet<u32>> = HashMap::new();
1024 for &(point, loan) in &loan_live_at_result {
1025 loan_points.entry(loan).or_default().insert(point);
1026 }
1027
1028 let mut errors = Vec::new();
1030 let mut seen_conflicts = std::collections::HashSet::new();
1031 for &(loan_a, loan_b) in &facts.potential_conflicts {
1032 let key = (loan_a.min(loan_b), loan_a.max(loan_b));
1033 if !seen_conflicts.insert(key) {
1034 continue;
1035 }
1036
1037 let points_a = loan_points.get(&loan_a);
1038 let points_b = loan_points.get(&loan_b);
1039
1040 if let (Some(pa), Some(pb)) = (points_a, points_b) {
1041 let has_overlap = pa.iter().any(|p| pb.contains(p));
1043 if has_overlap {
1044 let info_a = &facts.loan_info[&loan_a];
1045 let info_b = &facts.loan_info[&loan_b];
1046 let kind = if info_a.kind == BorrowKind::Exclusive
1047 && info_b.kind == BorrowKind::Exclusive
1048 {
1049 BorrowErrorKind::ConflictExclusiveExclusive
1050 } else {
1051 BorrowErrorKind::ConflictSharedExclusive
1052 };
1053 errors.push(BorrowError {
1054 kind,
1055 span: info_b.span,
1056 conflicting_loan: LoanId(loan_a),
1057 loan_span: info_a.span,
1058 last_use_span: last_use_span_for_loan(facts, loan_a),
1059 repairs: Vec::new(),
1060 });
1061 }
1062 }
1063 }
1064
1065 let mut seen_writes = std::collections::HashSet::new();
1066 for (point, place, span) in &facts.writes {
1067 let point_key = Point(*point);
1068 let Some(loans) = loans_at_point.get(&point_key) else {
1069 continue;
1070 };
1071 for loan in loans {
1072 let info = &facts.loan_info[&loan.0];
1073 if !place.conflicts_with(&info.borrowed_place) {
1074 continue;
1075 }
1076 let key = (*point, loan.0);
1077 if !seen_writes.insert(key) {
1078 continue;
1079 }
1080 errors.push(BorrowError {
1081 kind: BorrowErrorKind::WriteWhileBorrowed,
1082 span: *span,
1083 conflicting_loan: *loan,
1084 loan_span: info.span,
1085 last_use_span: last_use_span_for_loan(facts, loan.0),
1086 repairs: Vec::new(),
1087 });
1088 break;
1089 }
1090 }
1091
1092 let mut seen_reads = std::collections::HashSet::new();
1093 for (point, place, span) in &facts.reads {
1094 let point_key = Point(*point);
1095 let Some(loans) = loans_at_point.get(&point_key) else {
1096 continue;
1097 };
1098 for loan in loans {
1099 let info = &facts.loan_info[&loan.0];
1100 if info.kind != BorrowKind::Exclusive || !place.conflicts_with(&info.borrowed_place) {
1101 continue;
1102 }
1103 let key = (*point, loan.0);
1104 if !seen_reads.insert(key) {
1105 continue;
1106 }
1107 errors.push(BorrowError {
1108 kind: BorrowErrorKind::ReadWhileExclusivelyBorrowed,
1109 span: *span,
1110 conflicting_loan: *loan,
1111 loan_span: info.span,
1112 last_use_span: last_use_span_for_loan(facts, loan.0),
1113 repairs: Vec::new(),
1114 });
1115 break;
1116 }
1117 }
1118
1119 let mut seen_escapes = std::collections::HashSet::new();
1120 for (loan_id, span) in &facts.escaped_loans {
1121 if !seen_escapes.insert((*loan_id, span.start, span.end)) {
1122 continue;
1123 }
1124 let info = &facts.loan_info[loan_id];
1125 errors.push(BorrowError {
1126 kind: BorrowErrorKind::ReferenceEscape,
1127 span: *span,
1128 conflicting_loan: LoanId(*loan_id),
1129 loan_span: info.span,
1130 last_use_span: last_use_span_for_loan(facts, *loan_id),
1131 repairs: Vec::new(),
1132 });
1133 }
1134
1135 let mut seen_sinks = std::collections::HashSet::new();
1136 for sink in &facts.loan_sinks {
1137 let key = (
1138 sink.loan_id,
1139 sink.kind,
1140 sink.span.start,
1141 sink.span.end,
1142 sink.sink_slot.map(|slot| slot.0),
1143 );
1144 if !seen_sinks.insert(key) {
1145 continue;
1146 }
1147
1148 let info = &facts.loan_info[&sink.loan_id];
1149 let sink_is_local = sink
1150 .sink_slot
1151 .and_then(|slot| facts.slot_escape_status.get(&slot).copied())
1152 == Some(EscapeStatus::Local);
1153
1154 let kind = match sink.kind {
1155 LoanSinkKind::ReturnSlot => continue,
1156 LoanSinkKind::ClosureEnv if sink_is_local => continue,
1157 LoanSinkKind::ClosureEnv => BorrowErrorKind::ReferenceEscapeIntoClosure,
1158 LoanSinkKind::ClosureEnvMut => continue,
1166 LoanSinkKind::ArrayStore | LoanSinkKind::ArrayAssignment if sink_is_local => continue,
1167 LoanSinkKind::ArrayStore | LoanSinkKind::ArrayAssignment => {
1168 BorrowErrorKind::ReferenceStoredInArray
1169 }
1170 LoanSinkKind::ObjectStore | LoanSinkKind::ObjectAssignment if sink_is_local => continue,
1171 LoanSinkKind::ObjectStore | LoanSinkKind::ObjectAssignment => {
1172 BorrowErrorKind::ReferenceStoredInObject
1173 }
1174 LoanSinkKind::EnumStore if sink_is_local => continue,
1175 LoanSinkKind::EnumStore => BorrowErrorKind::ReferenceStoredInEnum,
1176 LoanSinkKind::StructuredTaskBoundary => {
1177 BorrowErrorKind::ExclusiveRefAcrossTaskBoundary
1178 }
1179 LoanSinkKind::DetachedTaskBoundary if info.kind == BorrowKind::Exclusive => {
1180 BorrowErrorKind::ExclusiveRefAcrossTaskBoundary
1181 }
1182 LoanSinkKind::DetachedTaskBoundary => BorrowErrorKind::SharedRefAcrossDetachedTask,
1183 };
1184
1185 errors.push(BorrowError {
1186 kind,
1187 span: sink.span,
1188 conflicting_loan: LoanId(sink.loan_id),
1189 loan_span: info.span,
1190 last_use_span: last_use_span_for_loan(facts, sink.loan_id),
1191 repairs: Vec::new(),
1192 });
1193 }
1194
1195 let mut seen_non_sendable = std::collections::HashSet::new();
1197 for (slot_id, span) in &facts.non_sendable_task_boundary {
1198 if !seen_non_sendable.insert((*slot_id, span.start, span.end)) {
1199 continue;
1200 }
1201 errors.push(BorrowError {
1202 kind: BorrowErrorKind::NonSendableAcrossTaskBoundary,
1203 span: *span,
1204 conflicting_loan: LoanId(0),
1205 loan_span: *span,
1206 last_use_span: None,
1207 repairs: Vec::new(),
1208 });
1209 }
1210
1211 let return_reference_summary =
1212 resolve_return_reference_summary(&mut errors, facts, &loans_at_point);
1213
1214 SolverResult {
1215 loans_at_point,
1216 errors,
1217 loan_info: facts.loan_info.clone(),
1218 return_reference_summary,
1219 }
1220}
1221
1222fn compute_nll_live_points(facts: &BorrowFacts) -> (HashSet<(u32, u32)>, HashSet<u32>) {
1223 let mut predecessors: HashMap<u32, Vec<u32>> = HashMap::new();
1224 for (from, to) in &facts.cfg_edge {
1225 predecessors.entry(*to).or_default().push(*from);
1226 }
1227
1228 let issue_points: HashMap<u32, u32> = facts
1229 .loan_issued_at
1230 .iter()
1231 .map(|(loan_id, point)| (*loan_id, *point))
1232 .collect();
1233
1234 let mut invalidation_points: HashMap<u32, HashSet<u32>> = HashMap::new();
1235 for (point, loan_id) in &facts.invalidates {
1236 invalidation_points
1237 .entry(*loan_id)
1238 .or_default()
1239 .insert(*point);
1240 }
1241
1242 let mut use_points: HashMap<u32, Vec<u32>> = HashMap::new();
1243 for (loan_id, point) in &facts.use_of_loan {
1244 use_points.entry(*loan_id).or_default().push(*point);
1245 }
1246
1247 let mut live_points = HashSet::new();
1248 let mut loans_with_reachable_uses = HashSet::new();
1249 for (loan_id, issue_point) in issue_points {
1250 let mut worklist = use_points.get(&loan_id).cloned().unwrap_or_default();
1251 let invalidates = invalidation_points.get(&loan_id);
1252 let mut visited = HashSet::new();
1253 let mut loan_live_points = HashSet::new();
1254 let mut reached_issue = false;
1255
1256 while let Some(point) = worklist.pop() {
1257 if !visited.insert(point) {
1258 continue;
1259 }
1260
1261 loan_live_points.insert((point, loan_id));
1262
1263 if point == issue_point {
1264 reached_issue = true;
1265 continue;
1266 }
1267
1268 if invalidates.is_some_and(|points| points.contains(&point)) {
1269 continue;
1270 }
1271
1272 if let Some(preds) = predecessors.get(&point) {
1273 worklist.extend(preds.iter().copied());
1274 }
1275 }
1276
1277 if reached_issue {
1278 loans_with_reachable_uses.insert(loan_id);
1279 live_points.extend(loan_live_points);
1280 }
1281 }
1282
1283 (live_points, loans_with_reachable_uses)
1284}
1285
1286#[derive(Debug)]
1288pub struct SolverResult {
1289 pub loans_at_point: HashMap<Point, Vec<LoanId>>,
1290 pub errors: Vec<BorrowError>,
1291 pub loan_info: HashMap<u32, LoanInfo>,
1292 pub return_reference_summary: Option<ReturnReferenceSummary>,
1293}
1294
1295pub fn extract_borrow_summary(
1301 mir: &MirFunction,
1302 return_summary: Option<ReturnReferenceSummary>,
1303) -> FunctionBorrowSummary {
1304 let num_params = mir.param_slots.len();
1305 let mut param_borrows: Vec<Option<BorrowKind>> = mir
1306 .param_reference_kinds
1307 .iter()
1308 .cloned()
1309 .collect();
1310 while param_borrows.len() < num_params {
1312 param_borrows.push(None);
1313 }
1314
1315 let mut mutated_params: HashSet<usize> = HashSet::new();
1317 let mut read_params: HashSet<usize> = HashSet::new();
1318 for block in mir.iter_blocks() {
1319 for stmt in &block.statements {
1320 match &stmt.kind {
1321 StatementKind::Assign(dest, rvalue) => {
1322 let root = dest.root_local();
1324 if let Some(param_idx) = mir.param_slots.iter().position(|s| *s == root) {
1325 mutated_params.insert(param_idx);
1326 }
1327 for param_idx in 0..num_params {
1329 if rvalue_uses_param(rvalue, mir.param_slots[param_idx]) {
1330 read_params.insert(param_idx);
1331 }
1332 }
1333 }
1334 _ => {}
1335 }
1336 }
1337 if let TerminatorKind::Call { args, .. } = &block.terminator.kind {
1339 for arg in args {
1340 for param_idx in 0..num_params {
1341 if operand_uses_param(arg, mir.param_slots[param_idx]) {
1342 read_params.insert(param_idx);
1343 }
1344 }
1345 }
1346 }
1347 }
1348
1349 let mut effective_borrows: Vec<Option<BorrowKind>> = param_borrows.clone();
1352 for idx in 0..num_params {
1353 if effective_borrows[idx].is_none() {
1354 if mutated_params.contains(&idx) {
1355 effective_borrows[idx] = Some(BorrowKind::Exclusive);
1356 } else if read_params.contains(&idx) {
1357 effective_borrows[idx] = Some(BorrowKind::Shared);
1358 }
1359 }
1360 }
1361
1362 let mut conflict_pairs = Vec::new();
1365 for &mutated_idx in &mutated_params {
1366 for other_idx in 0..num_params {
1367 if other_idx == mutated_idx {
1368 continue;
1369 }
1370 if effective_borrows[other_idx].is_some() {
1372 conflict_pairs.push((mutated_idx, other_idx));
1373 }
1374 }
1375 }
1376 for i in 0..num_params {
1378 for j in (i + 1)..num_params {
1379 if effective_borrows[i] == Some(BorrowKind::Exclusive)
1380 && effective_borrows[j] == Some(BorrowKind::Exclusive)
1381 && !conflict_pairs.contains(&(i, j))
1382 && !conflict_pairs.contains(&(j, i))
1383 {
1384 conflict_pairs.push((i, j));
1385 }
1386 }
1387 }
1388
1389 let closure_param_escapes = compute_closure_param_escapes(mir);
1390
1391 FunctionBorrowSummary {
1392 param_borrows,
1393 conflict_pairs,
1394 return_summary,
1395 return_ownership_mode: super::return_ownership::infer_return_ownership_mode(
1396 mir,
1397 &HashMap::new(),
1398 ),
1399 closure_param_escapes,
1400 }
1401}
1402
1403fn compute_closure_param_escapes(mir: &MirFunction) -> Vec<bool> {
1426 let num_params = mir.param_slots.len();
1427 let mut result = vec![true; num_params];
1428
1429 for (param_idx, ¶m_slot) in mir.param_slots.iter().enumerate() {
1430 result[param_idx] = param_slot_escapes(param_slot, mir);
1431 }
1432
1433 result
1434}
1435
1436fn param_slot_escapes(param_slot: SlotId, mir: &MirFunction) -> bool {
1444 let mut tracked: HashSet<SlotId> = HashSet::new();
1445 tracked.insert(param_slot);
1446
1447 let mut changed = true;
1450 while changed {
1451 changed = false;
1452
1453 for block in mir.iter_blocks() {
1454 for stmt in &block.statements {
1455 match &stmt.kind {
1456 StatementKind::Assign(place, rvalue) => {
1457 let reads_tracked = rvalue_uses_any(rvalue, &tracked);
1460 match place {
1461 Place::Local(dest) => {
1462 if reads_tracked && tracked.insert(*dest) {
1463 changed = true;
1464 }
1465 if *dest == SlotId(0) && reads_tracked {
1466 return true;
1467 }
1468 }
1469 Place::Field(..) | Place::Index(..) | Place::Deref(..) => {
1470 if reads_tracked {
1471 return true;
1472 }
1473 }
1474 }
1475
1476 if let Rvalue::Aggregate(ops) = rvalue {
1478 if ops.iter().any(|op| operand_uses_any(op, &tracked)) {
1479 return true;
1480 }
1481 }
1482 }
1483 StatementKind::ArrayStore { operands, .. }
1484 | StatementKind::ObjectStore { operands, .. }
1485 | StatementKind::EnumStore { operands, .. }
1486 | StatementKind::TaskBoundary(operands, _)
1487 | StatementKind::ClosureCapture { operands, .. } => {
1488 if operands.iter().any(|op| operand_uses_any(op, &tracked)) {
1489 return true;
1490 }
1491 }
1492 StatementKind::Drop(_) | StatementKind::Nop => {}
1493 }
1494 }
1495
1496 if let TerminatorKind::Call { args, .. } = &block.terminator.kind {
1497 if args.iter().any(|op| operand_uses_any(op, &tracked)) {
1501 return true;
1502 }
1503 }
1504 }
1505 }
1506
1507 false
1508}
1509
1510fn rvalue_uses_any(rvalue: &Rvalue, slots: &HashSet<SlotId>) -> bool {
1511 match rvalue {
1512 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
1513 operand_uses_any(op, slots)
1514 }
1515 Rvalue::Borrow(_, place) => slots.contains(&place.root_local()),
1516 Rvalue::BinaryOp(_, lhs, rhs) => {
1517 operand_uses_any(lhs, slots) || operand_uses_any(rhs, slots)
1518 }
1519 Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_any(op, slots)),
1520 Rvalue::EnumTest { operand, .. }
1521 | Rvalue::EnumPayload { operand, .. }
1522 | Rvalue::TypePatternTest { operand, .. }
1523 | Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_any(operand, slots),
1524 }
1525}
1526
1527fn operand_uses_any(op: &Operand, slots: &HashSet<SlotId>) -> bool {
1528 match op {
1529 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
1530 slots.contains(&place.root_local())
1531 }
1532 Operand::Constant(_) => false,
1533 }
1534}
1535
1536pub fn extract_borrow_summary_with_callees(
1539 mir: &MirFunction,
1540 return_summary: Option<ReturnReferenceSummary>,
1541 callee_modes: &HashMap<String, ReturnOwnershipMode>,
1542) -> FunctionBorrowSummary {
1543 let mut summary = extract_borrow_summary(mir, return_summary);
1544 summary.return_ownership_mode =
1545 super::return_ownership::infer_return_ownership_mode(mir, callee_modes);
1546 summary
1547}
1548
1549fn rvalue_uses_param(rvalue: &Rvalue, param_slot: SlotId) -> bool {
1550 match rvalue {
1551 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
1552 operand_uses_param(op, param_slot)
1553 }
1554 Rvalue::Borrow(_, place) => place.root_local() == param_slot,
1555 Rvalue::BinaryOp(_, lhs, rhs) => {
1556 operand_uses_param(lhs, param_slot) || operand_uses_param(rhs, param_slot)
1557 }
1558 Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_param(op, param_slot)),
1559 Rvalue::EnumTest { operand, .. }
1560 | Rvalue::EnumPayload { operand, .. }
1561 | Rvalue::TypePatternTest { operand, .. }
1562 | Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_param(operand, param_slot),
1563 }
1564}
1565
1566fn operand_uses_param(op: &Operand, param_slot: SlotId) -> bool {
1567 match op {
1568 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
1569 place.root_local() == param_slot
1570 }
1571 Operand::Constant(_) => false,
1572 }
1573}
1574
1575pub fn analyze(mir: &MirFunction, callee_summaries: &CalleeSummaries) -> BorrowAnalysis {
1576 let cfg = ControlFlowGraph::build(mir);
1577
1578 let liveness = liveness::compute_liveness(mir, &cfg);
1580
1581 let facts = extract_facts(mir, &cfg, callee_summaries);
1583
1584 let solver_result = solve(&facts);
1586
1587 let ownership_decisions = compute_ownership_decisions(mir, &liveness);
1589 let mut move_errors = compute_use_after_move_errors(mir, &cfg, &ownership_decisions);
1590
1591 let loans = solver_result
1593 .loan_info
1594 .into_iter()
1595 .map(|(id, info)| (LoanId(id), info))
1596 .collect();
1597 let mut errors = solver_result.errors;
1598 errors.append(&mut move_errors);
1599
1600 BorrowAnalysis {
1601 liveness,
1602 loans_at_point: solver_result.loans_at_point,
1603 loans,
1604 errors,
1605 ownership_decisions,
1606 mutability_errors: Vec::new(), return_reference_summary: solver_result.return_reference_summary,
1608 }
1609}
1610
1611fn compute_ownership_decisions(
1613 mir: &MirFunction,
1614 liveness: &LivenessResult,
1615) -> HashMap<Point, OwnershipDecision> {
1616 let mut decisions = HashMap::new();
1617
1618 for block in &mir.blocks {
1619 for (stmt_idx, stmt) in block.statements.iter().enumerate() {
1620 let src_slot = match &stmt.kind {
1623 StatementKind::Assign(_, Rvalue::Use(Operand::Move(Place::Local(s)))) => s,
1624 StatementKind::Assign(_, Rvalue::Use(Operand::Copy(Place::Local(s)))) => s,
1625 _ => continue,
1626 };
1627 {
1628 let src_type = mir
1630 .local_types
1631 .get(src_slot.0 as usize)
1632 .cloned()
1633 .unwrap_or(LocalTypeInfo::Unknown);
1634
1635 let decision = match src_type {
1636 LocalTypeInfo::Copy => OwnershipDecision::Copy,
1637 LocalTypeInfo::NonCopy => {
1638 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
1640 OwnershipDecision::Clone
1641 } else {
1642 OwnershipDecision::Move
1643 }
1644 }
1645 LocalTypeInfo::Unknown => {
1646 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
1648 OwnershipDecision::Clone
1649 } else {
1650 OwnershipDecision::Move
1651 }
1652 }
1653 };
1654
1655 decisions.insert(stmt.point, decision);
1656 }
1657 }
1658 }
1659
1660 decisions
1661}
1662
1663fn compute_use_after_move_errors(
1664 mir: &MirFunction,
1665 cfg: &ControlFlowGraph,
1666 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1667) -> Vec<BorrowError> {
1668 let mut in_states: HashMap<BasicBlockId, HashMap<Place, shape_ast::ast::Span>> = HashMap::new();
1669 let mut out_states: HashMap<BasicBlockId, HashMap<Place, shape_ast::ast::Span>> =
1670 HashMap::new();
1671
1672 for block in mir.iter_blocks() {
1673 in_states.insert(block.id, HashMap::new());
1674 out_states.insert(block.id, HashMap::new());
1675 }
1676
1677 let mut changed = true;
1678 while changed {
1679 changed = false;
1680 for &block_id in &cfg.reverse_postorder() {
1681 let mut block_in: Option<HashMap<Place, shape_ast::ast::Span>> = None;
1682 for &pred in cfg.predecessors(block_id) {
1683 if let Some(pred_out) = out_states.get(&pred) {
1684 if let Some(current) = block_in.as_mut() {
1685 intersect_moved_places(current, pred_out);
1686 } else {
1687 block_in = Some(pred_out.clone());
1688 }
1689 }
1690 }
1691 let block_in = block_in.unwrap_or_default();
1692
1693 let mut block_out = block_in.clone();
1694 let block = mir.block(block_id);
1695 for stmt in &block.statements {
1696 apply_move_transfer(&mut block_out, stmt, mir, ownership_decisions);
1697 }
1698 apply_terminator_move_transfer(&mut block_out, &block.terminator);
1700
1701 if in_states.get(&block_id) != Some(&block_in) {
1702 in_states.insert(block_id, block_in);
1703 changed = true;
1704 }
1705 if out_states.get(&block_id) != Some(&block_out) {
1706 out_states.insert(block_id, block_out);
1707 changed = true;
1708 }
1709 }
1710 }
1711
1712 let mut errors = Vec::new();
1713 let mut seen = HashSet::new();
1714 for block in mir.iter_blocks() {
1715 let mut moved_places = in_states.get(&block.id).cloned().unwrap_or_default();
1716 for stmt in &block.statements {
1717 for read_place in statement_read_places(&stmt.kind) {
1718 if let Some((moved_place, move_span)) =
1719 find_moved_place_conflict(&moved_places, &read_place)
1720 {
1721 let key = (stmt.point.0, format!("{}", moved_place));
1722 if seen.insert(key) {
1723 errors.push(BorrowError {
1724 kind: BorrowErrorKind::UseAfterMove,
1725 span: stmt.span,
1726 conflicting_loan: LoanId(0),
1727 loan_span: move_span,
1728 last_use_span: None,
1729 repairs: Vec::new(),
1730 });
1731 }
1732 break;
1733 }
1734 }
1735
1736 if let Some(borrowed_place) = statement_borrow_place(&stmt.kind)
1737 && let Some((moved_place, move_span)) =
1738 find_moved_place_conflict(&moved_places, borrowed_place)
1739 {
1740 let key = (stmt.point.0, format!("{}", moved_place));
1741 if seen.insert(key) {
1742 errors.push(BorrowError {
1743 kind: BorrowErrorKind::UseAfterMove,
1744 span: stmt.span,
1745 conflicting_loan: LoanId(0),
1746 loan_span: move_span,
1747 last_use_span: None,
1748 repairs: Vec::new(),
1749 });
1750 }
1751 }
1752
1753 if let Some(dest_place) = statement_dest_place(&stmt.kind)
1754 && let Some((moved_place, move_span)) = moved_places
1755 .iter()
1756 .find(|(moved_place, _)| {
1757 dest_place.conflicts_with(moved_place)
1758 && !reinitializes_moved_place(dest_place, moved_place)
1759 })
1760 .map(|(place, span)| (place.clone(), *span))
1761 {
1762 let key = (stmt.point.0, format!("{}", moved_place));
1763 if seen.insert(key) {
1764 errors.push(BorrowError {
1765 kind: BorrowErrorKind::UseAfterMove,
1766 span: stmt.span,
1767 conflicting_loan: LoanId(0),
1768 loan_span: move_span,
1769 last_use_span: None,
1770 repairs: Vec::new(),
1771 });
1772 }
1773 }
1774
1775 apply_move_transfer(&mut moved_places, stmt, mir, ownership_decisions);
1776 }
1777
1778 if let TerminatorKind::Call { func, args, destination, .. } = &block.terminator.kind {
1780 let term_key_point = block.terminator.span.start as u32;
1781 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = func {
1783 if let Some((moved_place, move_span)) = find_moved_place_conflict(&moved_places, place) {
1784 let key = (term_key_point, format!("{}", moved_place));
1785 if seen.insert(key) {
1786 errors.push(BorrowError {
1787 kind: BorrowErrorKind::UseAfterMove,
1788 span: block.terminator.span,
1789 conflicting_loan: LoanId(0),
1790 loan_span: move_span,
1791 last_use_span: None,
1792 repairs: Vec::new(),
1793 });
1794 }
1795 }
1796 }
1797 for arg in args {
1799 if let Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) = arg {
1800 if let Some((moved_place, move_span)) = find_moved_place_conflict(&moved_places, place) {
1801 let key = (term_key_point, format!("{}", moved_place));
1802 if seen.insert(key) {
1803 errors.push(BorrowError {
1804 kind: BorrowErrorKind::UseAfterMove,
1805 span: block.terminator.span,
1806 conflicting_loan: LoanId(0),
1807 loan_span: move_span,
1808 last_use_span: None,
1809 repairs: Vec::new(),
1810 });
1811 }
1812 }
1813 }
1814 }
1815 moved_places.retain(|moved_place, _| !reinitializes_moved_place(destination, moved_place));
1817 }
1818 }
1819
1820 errors
1821}
1822
1823fn intersect_moved_places(
1824 dest: &mut HashMap<Place, shape_ast::ast::Span>,
1825 incoming: &HashMap<Place, shape_ast::ast::Span>,
1826) {
1827 dest.retain(|place, span| {
1828 if let Some(incoming_span) = incoming.get(place) {
1829 if incoming_span.start < span.start {
1830 *span = *incoming_span;
1831 }
1832 true
1833 } else {
1834 false
1835 }
1836 });
1837}
1838
1839fn apply_move_transfer(
1840 moved_places: &mut HashMap<Place, shape_ast::ast::Span>,
1841 stmt: &MirStatement,
1842 mir: &MirFunction,
1843 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1844) {
1845 if let Some(dest_place) = statement_dest_place(&stmt.kind) {
1846 moved_places.retain(|moved_place, _| !reinitializes_moved_place(dest_place, moved_place));
1847 }
1848
1849 for moved_place in actual_move_places(stmt, mir, ownership_decisions) {
1850 moved_places.insert(moved_place, stmt.span);
1851 }
1852}
1853
1854fn apply_terminator_move_transfer(
1860 moved_places: &mut HashMap<Place, shape_ast::ast::Span>,
1861 terminator: &Terminator,
1862) {
1863 if let TerminatorKind::Call { destination, .. } = &terminator.kind {
1864 moved_places.retain(|moved_place, _| !reinitializes_moved_place(destination, moved_place));
1866 }
1867}
1868
1869fn statement_borrow_place(kind: &StatementKind) -> Option<&Place> {
1870 match kind {
1871 StatementKind::Assign(_, Rvalue::Borrow(_, place)) => Some(place),
1872 _ => None,
1873 }
1874}
1875
1876fn statement_dest_place(kind: &StatementKind) -> Option<&Place> {
1877 match kind {
1878 StatementKind::Assign(place, _) | StatementKind::Drop(place) => Some(place),
1879 StatementKind::TaskBoundary(..)
1880 | StatementKind::ClosureCapture { .. }
1881 | StatementKind::ArrayStore { .. }
1882 | StatementKind::ObjectStore { .. }
1883 | StatementKind::EnumStore { .. } => None,
1884 StatementKind::Nop => None,
1885 }
1886}
1887
1888fn actual_move_places(
1889 stmt: &MirStatement,
1890 mir: &MirFunction,
1891 ownership_decisions: &HashMap<Point, OwnershipDecision>,
1892) -> Vec<Place> {
1893 match &stmt.kind {
1894 StatementKind::Assign(_, Rvalue::Use(Operand::Move(place)))
1895 if ownership_decisions.get(&stmt.point) == Some(&OwnershipDecision::Move) =>
1896 {
1897 vec![place.clone()]
1898 }
1899 StatementKind::Assign(_, Rvalue::Use(Operand::MoveExplicit(place)))
1900 if place_root_local_type(place, mir) != Some(LocalTypeInfo::Copy) =>
1901 {
1902 vec![place.clone()]
1903 }
1904 _ => Vec::new(),
1905 }
1906}
1907
1908fn place_root_local_type(place: &Place, mir: &MirFunction) -> Option<LocalTypeInfo> {
1909 mir.local_types.get(place.root_local().0 as usize).cloned()
1910}
1911
1912fn reinitializes_moved_place(dest_place: &Place, moved_place: &Place) -> bool {
1913 dest_place.is_prefix_of(moved_place)
1914}
1915
1916fn find_moved_place_conflict(
1917 moved_places: &HashMap<Place, shape_ast::ast::Span>,
1918 accessed_place: &Place,
1919) -> Option<(Place, shape_ast::ast::Span)> {
1920 moved_places
1921 .iter()
1922 .find(|(moved_place, _)| accessed_place.conflicts_with(moved_place))
1923 .map(|(place, span)| (place.clone(), *span))
1924}
1925
1926fn last_use_span_for_loan(facts: &BorrowFacts, loan_id: u32) -> Option<shape_ast::ast::Span> {
1927 facts
1928 .use_of_loan
1929 .iter()
1930 .filter(|(candidate, _)| *candidate == loan_id)
1931 .filter_map(|(_, point)| facts.point_spans.get(point).copied())
1932 .max_by_key(|span| span.start)
1933}
1934
1935#[cfg(test)]
1936mod tests {
1937 use super::*;
1938 use shape_ast::ast::Span;
1939
1940 fn span() -> Span {
1941 Span { start: 0, end: 1 }
1942 }
1943
1944 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
1945 MirStatement {
1946 kind,
1947 span: span(),
1948 point: Point(point),
1949 }
1950 }
1951
1952 fn make_terminator(kind: TerminatorKind) -> Terminator {
1953 Terminator { kind, span: span() }
1954 }
1955
1956 #[test]
1957 fn test_single_shared_borrow_no_error() {
1958 let mir = MirFunction {
1959 name: "test".to_string(),
1960 blocks: vec![BasicBlock {
1961 id: BasicBlockId(0),
1962 statements: vec![
1963 make_stmt(
1965 StatementKind::Assign(
1966 Place::Local(SlotId(0)),
1967 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1968 ),
1969 0,
1970 ),
1971 make_stmt(
1973 StatementKind::Assign(
1974 Place::Local(SlotId(1)),
1975 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
1976 ),
1977 1,
1978 ),
1979 ],
1980 terminator: make_terminator(TerminatorKind::Return),
1981 }],
1982 num_locals: 2,
1983 param_slots: vec![],
1984 param_reference_kinds: vec![],
1985 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
1986 span: span(),
1987 field_name_table: std::collections::HashMap::new(),
1988 local_struct_type_names: std::collections::HashMap::new(),
1989 local_typed_array_element_types: std::collections::HashMap::new(),
1990 local_declared_scalar_types: std::collections::HashMap::new(),
1991 };
1992
1993 let analysis = analyze(&mir, &Default::default());
1994 assert!(analysis.errors.is_empty(), "expected no errors");
1995 }
1996
1997 #[test]
1998 fn test_conflicting_shared_and_exclusive_error() {
1999 let mir = MirFunction {
2003 name: "test".to_string(),
2004 blocks: vec![BasicBlock {
2005 id: BasicBlockId(0),
2006 statements: vec![
2007 make_stmt(
2008 StatementKind::Assign(
2009 Place::Local(SlotId(0)),
2010 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2011 ),
2012 0,
2013 ),
2014 make_stmt(
2015 StatementKind::Assign(
2016 Place::Local(SlotId(1)),
2017 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
2018 ),
2019 1,
2020 ),
2021 make_stmt(
2022 StatementKind::Assign(
2023 Place::Local(SlotId(2)),
2024 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
2025 ),
2026 2,
2027 ),
2028 ],
2029 terminator: make_terminator(TerminatorKind::Return),
2030 }],
2031 num_locals: 3,
2032 param_slots: vec![],
2033 param_reference_kinds: vec![],
2034 local_types: vec![
2035 LocalTypeInfo::NonCopy,
2036 LocalTypeInfo::NonCopy,
2037 LocalTypeInfo::NonCopy,
2038 ],
2039 span: span(),
2040 field_name_table: std::collections::HashMap::new(),
2041 local_struct_type_names: std::collections::HashMap::new(),
2042 local_typed_array_element_types: std::collections::HashMap::new(),
2043 local_declared_scalar_types: std::collections::HashMap::new(),
2044 };
2045
2046 let analysis = analyze(&mir, &Default::default());
2047 assert!(
2048 !analysis.errors.is_empty(),
2049 "expected borrow conflict error"
2050 );
2051 assert_eq!(
2052 analysis.errors[0].kind,
2053 BorrowErrorKind::ConflictSharedExclusive
2054 );
2055 }
2056
2057 #[test]
2058 fn test_disjoint_field_borrows_no_conflict() {
2059 let mir = MirFunction {
2062 name: "test".to_string(),
2063 blocks: vec![BasicBlock {
2064 id: BasicBlockId(0),
2065 statements: vec![
2066 make_stmt(
2067 StatementKind::Assign(
2068 Place::Local(SlotId(0)),
2069 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
2070 ),
2071 0,
2072 ),
2073 make_stmt(
2074 StatementKind::Assign(
2075 Place::Local(SlotId(1)),
2076 Rvalue::Borrow(
2077 BorrowKind::Shared,
2078 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0)),
2079 ),
2080 ),
2081 1,
2082 ),
2083 make_stmt(
2084 StatementKind::Assign(
2085 Place::Local(SlotId(2)),
2086 Rvalue::Borrow(
2087 BorrowKind::Exclusive,
2088 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(1)),
2089 ),
2090 ),
2091 2,
2092 ),
2093 ],
2094 terminator: make_terminator(TerminatorKind::Return),
2095 }],
2096 num_locals: 3,
2097 param_slots: vec![],
2098 param_reference_kinds: vec![],
2099 local_types: vec![
2100 LocalTypeInfo::NonCopy,
2101 LocalTypeInfo::NonCopy,
2102 LocalTypeInfo::NonCopy,
2103 ],
2104 span: span(),
2105 field_name_table: std::collections::HashMap::new(),
2106 local_struct_type_names: std::collections::HashMap::new(),
2107 local_typed_array_element_types: std::collections::HashMap::new(),
2108 local_declared_scalar_types: std::collections::HashMap::new(),
2109 };
2110
2111 let analysis = analyze(&mir, &Default::default());
2112 assert!(
2113 analysis.errors.is_empty(),
2114 "disjoint field borrows should not conflict, got: {:?}",
2115 analysis.errors
2116 );
2117 }
2118
2119 #[test]
2120 fn test_read_while_exclusive_borrow_error() {
2121 let mir = MirFunction {
2122 name: "test".to_string(),
2123 blocks: vec![BasicBlock {
2124 id: BasicBlockId(0),
2125 statements: vec![
2126 make_stmt(
2127 StatementKind::Assign(
2128 Place::Local(SlotId(0)),
2129 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2130 ),
2131 0,
2132 ),
2133 make_stmt(
2134 StatementKind::Assign(
2135 Place::Local(SlotId(1)),
2136 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
2137 ),
2138 1,
2139 ),
2140 make_stmt(
2141 StatementKind::Assign(
2142 Place::Local(SlotId(2)),
2143 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
2144 ),
2145 2,
2146 ),
2147 ],
2148 terminator: make_terminator(TerminatorKind::Return),
2149 }],
2150 num_locals: 3,
2151 param_slots: vec![],
2152 param_reference_kinds: vec![],
2153 local_types: vec![
2154 LocalTypeInfo::NonCopy,
2155 LocalTypeInfo::NonCopy,
2156 LocalTypeInfo::NonCopy,
2157 ],
2158 span: span(),
2159 field_name_table: std::collections::HashMap::new(),
2160 local_struct_type_names: std::collections::HashMap::new(),
2161 local_typed_array_element_types: std::collections::HashMap::new(),
2162 local_declared_scalar_types: std::collections::HashMap::new(),
2163 };
2164
2165 let analysis = analyze(&mir, &Default::default());
2166 assert!(
2167 analysis
2168 .errors
2169 .iter()
2170 .any(|error| error.kind == BorrowErrorKind::ReadWhileExclusivelyBorrowed),
2171 "expected read-while-exclusive error, got {:?}",
2172 analysis.errors
2173 );
2174 }
2175
2176 #[test]
2177 fn test_reference_escape_error_for_returned_ref_alias() {
2178 let mir = MirFunction {
2179 name: "test".to_string(),
2180 blocks: vec![BasicBlock {
2181 id: BasicBlockId(0),
2182 statements: vec![
2183 make_stmt(
2184 StatementKind::Assign(
2185 Place::Local(SlotId(1)),
2186 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2187 ),
2188 0,
2189 ),
2190 make_stmt(
2191 StatementKind::Assign(
2192 Place::Local(SlotId(2)),
2193 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(1))),
2194 ),
2195 1,
2196 ),
2197 make_stmt(
2198 StatementKind::Assign(
2199 Place::Local(SlotId(3)),
2200 Rvalue::Use(Operand::Move(Place::Local(SlotId(2)))),
2201 ),
2202 2,
2203 ),
2204 make_stmt(
2205 StatementKind::Assign(
2206 Place::Local(SlotId(0)),
2207 Rvalue::Use(Operand::Move(Place::Local(SlotId(3)))),
2208 ),
2209 3,
2210 ),
2211 ],
2212 terminator: make_terminator(TerminatorKind::Return),
2213 }],
2214 num_locals: 4,
2215 param_slots: vec![],
2216 param_reference_kinds: vec![],
2217 local_types: vec![
2218 LocalTypeInfo::NonCopy,
2219 LocalTypeInfo::NonCopy,
2220 LocalTypeInfo::NonCopy,
2221 LocalTypeInfo::NonCopy,
2222 ],
2223 span: span(),
2224 field_name_table: std::collections::HashMap::new(),
2225 local_struct_type_names: std::collections::HashMap::new(),
2226 local_typed_array_element_types: std::collections::HashMap::new(),
2227 local_declared_scalar_types: std::collections::HashMap::new(),
2228 };
2229
2230 let analysis = analyze(&mir, &Default::default());
2231 assert!(
2232 analysis
2233 .errors
2234 .iter()
2235 .any(|error| error.kind == BorrowErrorKind::ReferenceEscape),
2236 "expected reference-escape error, got {:?}",
2237 analysis.errors
2238 );
2239 }
2240
2241 #[test]
2242 fn test_use_after_explicit_move_error() {
2243 let mir = MirFunction {
2244 name: "test".to_string(),
2245 blocks: vec![BasicBlock {
2246 id: BasicBlockId(0),
2247 statements: vec![
2248 make_stmt(
2249 StatementKind::Assign(
2250 Place::Local(SlotId(0)),
2251 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2252 ),
2253 0,
2254 ),
2255 make_stmt(
2256 StatementKind::Assign(
2257 Place::Local(SlotId(1)),
2258 Rvalue::Use(Operand::MoveExplicit(Place::Local(SlotId(0)))),
2259 ),
2260 1,
2261 ),
2262 make_stmt(
2263 StatementKind::Assign(
2264 Place::Local(SlotId(2)),
2265 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
2266 ),
2267 2,
2268 ),
2269 ],
2270 terminator: make_terminator(TerminatorKind::Return),
2271 }],
2272 num_locals: 3,
2273 param_slots: vec![],
2274 param_reference_kinds: vec![],
2275 local_types: vec![
2276 LocalTypeInfo::NonCopy,
2277 LocalTypeInfo::NonCopy,
2278 LocalTypeInfo::NonCopy,
2279 ],
2280 span: span(),
2281 field_name_table: std::collections::HashMap::new(),
2282 local_struct_type_names: std::collections::HashMap::new(),
2283 local_typed_array_element_types: std::collections::HashMap::new(),
2284 local_declared_scalar_types: std::collections::HashMap::new(),
2285 };
2286
2287 let analysis = analyze(&mir, &Default::default());
2288 assert!(
2289 analysis
2290 .errors
2291 .iter()
2292 .any(|error| error.kind == BorrowErrorKind::UseAfterMove),
2293 "expected use-after-move error, got {:?}",
2294 analysis.errors
2295 );
2296 }
2297
2298 #[test]
2299 fn test_move_vs_clone_decision() {
2300 let mir = MirFunction {
2303 name: "test".to_string(),
2304 blocks: vec![BasicBlock {
2305 id: BasicBlockId(0),
2306 statements: vec![
2307 make_stmt(
2308 StatementKind::Assign(
2309 Place::Local(SlotId(0)),
2310 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2311 ),
2312 0,
2313 ),
2314 make_stmt(
2315 StatementKind::Assign(
2316 Place::Local(SlotId(1)),
2317 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2318 ),
2319 1,
2320 ),
2321 ],
2322 terminator: make_terminator(TerminatorKind::Return),
2323 }],
2324 num_locals: 2,
2325 param_slots: vec![],
2326 param_reference_kinds: vec![],
2327 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
2328 span: span(),
2329 field_name_table: std::collections::HashMap::new(),
2330 local_struct_type_names: std::collections::HashMap::new(),
2331 local_typed_array_element_types: std::collections::HashMap::new(),
2332 local_declared_scalar_types: std::collections::HashMap::new(),
2333 };
2334
2335 let analysis = analyze(&mir, &Default::default());
2336 assert_eq!(
2338 analysis.ownership_at(Point(1)),
2339 OwnershipDecision::Move,
2340 "source dead after → should be Move"
2341 );
2342 }
2343
2344 #[test]
2345 fn test_nll_borrow_scoping() {
2346 let mir = MirFunction {
2350 name: "test".to_string(),
2351 blocks: vec![
2352 BasicBlock {
2353 id: BasicBlockId(0),
2354 statements: vec![
2355 make_stmt(
2356 StatementKind::Assign(
2357 Place::Local(SlotId(0)),
2358 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2359 ),
2360 0,
2361 ),
2362 make_stmt(
2363 StatementKind::Assign(
2364 Place::Local(SlotId(1)),
2365 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
2366 ),
2367 1,
2368 ),
2369 make_stmt(
2371 StatementKind::Assign(
2372 Place::Local(SlotId(3)),
2373 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2374 ),
2375 2,
2376 ),
2377 ],
2378 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
2379 },
2380 BasicBlock {
2381 id: BasicBlockId(1),
2382 statements: vec![
2383 make_stmt(
2386 StatementKind::Assign(
2387 Place::Local(SlotId(2)),
2388 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
2389 ),
2390 3,
2391 ),
2392 ],
2393 terminator: make_terminator(TerminatorKind::Return),
2394 },
2395 ],
2396 num_locals: 4,
2397 param_slots: vec![],
2398 param_reference_kinds: vec![],
2399 local_types: vec![
2400 LocalTypeInfo::NonCopy,
2401 LocalTypeInfo::NonCopy,
2402 LocalTypeInfo::NonCopy,
2403 LocalTypeInfo::NonCopy,
2404 ],
2405 span: span(),
2406 field_name_table: std::collections::HashMap::new(),
2407 local_struct_type_names: std::collections::HashMap::new(),
2408 local_typed_array_element_types: std::collections::HashMap::new(),
2409 local_declared_scalar_types: std::collections::HashMap::new(),
2410 };
2411
2412 let analysis = analyze(&mir, &Default::default());
2413 let _ = analysis;
2420 }
2421
2422 #[test]
2423 fn test_clone_decision_when_source_live_after() {
2424 let mir = MirFunction {
2428 name: "test".to_string(),
2429 blocks: vec![BasicBlock {
2430 id: BasicBlockId(0),
2431 statements: vec![
2432 make_stmt(
2433 StatementKind::Assign(
2434 Place::Local(SlotId(0)),
2435 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2436 ),
2437 0,
2438 ),
2439 make_stmt(
2440 StatementKind::Assign(
2441 Place::Local(SlotId(1)),
2442 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2443 ),
2444 1,
2445 ),
2446 make_stmt(
2447 StatementKind::Assign(
2448 Place::Local(SlotId(2)),
2449 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2450 ),
2451 2,
2452 ),
2453 ],
2454 terminator: make_terminator(TerminatorKind::Return),
2455 }],
2456 num_locals: 3,
2457 param_slots: vec![],
2458 param_reference_kinds: vec![],
2459 local_types: vec![
2460 LocalTypeInfo::NonCopy,
2461 LocalTypeInfo::NonCopy,
2462 LocalTypeInfo::NonCopy,
2463 ],
2464 span: span(),
2465 field_name_table: std::collections::HashMap::new(),
2466 local_struct_type_names: std::collections::HashMap::new(),
2467 local_typed_array_element_types: std::collections::HashMap::new(),
2468 local_declared_scalar_types: std::collections::HashMap::new(),
2469 };
2470
2471 let analysis = analyze(&mir, &Default::default());
2472 assert_eq!(
2474 analysis.ownership_at(Point(1)),
2475 OwnershipDecision::Clone,
2476 "source live after → should be Clone"
2477 );
2478 assert_eq!(
2480 analysis.ownership_at(Point(2)),
2481 OwnershipDecision::Move,
2482 "source dead after → should be Move"
2483 );
2484 }
2485
2486 #[test]
2487 fn test_copy_type_always_copy_decision() {
2488 let mir = MirFunction {
2491 name: "test".to_string(),
2492 blocks: vec![BasicBlock {
2493 id: BasicBlockId(0),
2494 statements: vec![
2495 make_stmt(
2496 StatementKind::Assign(
2497 Place::Local(SlotId(0)),
2498 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2499 ),
2500 0,
2501 ),
2502 make_stmt(
2503 StatementKind::Assign(
2504 Place::Local(SlotId(1)),
2505 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
2506 ),
2507 1,
2508 ),
2509 ],
2510 terminator: make_terminator(TerminatorKind::Return),
2511 }],
2512 num_locals: 2,
2513 param_slots: vec![],
2514 param_reference_kinds: vec![],
2515 local_types: vec![LocalTypeInfo::Copy, LocalTypeInfo::Copy],
2516 span: span(),
2517 field_name_table: std::collections::HashMap::new(),
2518 local_struct_type_names: std::collections::HashMap::new(),
2519 local_typed_array_element_types: std::collections::HashMap::new(),
2520 local_declared_scalar_types: std::collections::HashMap::new(),
2521 };
2522
2523 let analysis = analyze(&mir, &Default::default());
2524 assert_eq!(
2525 analysis.ownership_at(Point(1)),
2526 OwnershipDecision::Copy,
2527 "Copy type → always Copy regardless of liveness"
2528 );
2529 }
2530
2531 #[test]
2536 fn test_compose_summary_identity() {
2537 let arg = ReturnReferenceSummary {
2539 param_index: 2,
2540 kind: BorrowKind::Shared,
2541 projection: Some(vec![]),
2542 };
2543 let callee = ReturnReferenceSummary {
2544 param_index: 0,
2545 kind: BorrowKind::Exclusive,
2546 projection: Some(vec![]),
2547 };
2548 let result = compose_return_reference_summary(&arg, &callee);
2549 assert_eq!(result.param_index, 2); assert_eq!(result.kind, BorrowKind::Exclusive); assert_eq!(result.projection, Some(vec![]));
2552 }
2553
2554 #[test]
2555 fn test_compose_summary_some_index_some_empty() {
2556 let arg = ReturnReferenceSummary {
2557 param_index: 0,
2558 kind: BorrowKind::Shared,
2559 projection: Some(vec![ProjectionStep::Index]),
2560 };
2561 let callee = ReturnReferenceSummary {
2562 param_index: 0,
2563 kind: BorrowKind::Shared,
2564 projection: Some(vec![]),
2565 };
2566 let result = compose_return_reference_summary(&arg, &callee);
2567 assert_eq!(result.projection, Some(vec![ProjectionStep::Index]));
2568 }
2569
2570 #[test]
2571 fn test_compose_summary_callee_field_loses_precision() {
2572 let arg = ReturnReferenceSummary {
2573 param_index: 0,
2574 kind: BorrowKind::Shared,
2575 projection: Some(vec![]),
2576 };
2577 let callee = ReturnReferenceSummary {
2578 param_index: 0,
2579 kind: BorrowKind::Shared,
2580 projection: Some(vec![ProjectionStep::Field(FieldIdx(0))]),
2581 };
2582 let result = compose_return_reference_summary(&arg, &callee);
2583 assert_eq!(result.projection, None); }
2585
2586 #[test]
2587 fn test_compose_summary_callee_index_composes() {
2588 let arg = ReturnReferenceSummary {
2589 param_index: 1,
2590 kind: BorrowKind::Shared,
2591 projection: Some(vec![ProjectionStep::Index]),
2592 };
2593 let callee = ReturnReferenceSummary {
2594 param_index: 0,
2595 kind: BorrowKind::Exclusive,
2596 projection: Some(vec![ProjectionStep::Index]),
2597 };
2598 let result = compose_return_reference_summary(&arg, &callee);
2599 assert_eq!(result.param_index, 1);
2600 assert_eq!(result.kind, BorrowKind::Exclusive);
2601 assert_eq!(
2602 result.projection,
2603 Some(vec![ProjectionStep::Index, ProjectionStep::Index])
2604 );
2605 }
2606
2607 #[test]
2608 fn test_compose_summary_arg_none() {
2609 let arg = ReturnReferenceSummary {
2610 param_index: 0,
2611 kind: BorrowKind::Shared,
2612 projection: None, };
2614 let callee = ReturnReferenceSummary {
2615 param_index: 0,
2616 kind: BorrowKind::Shared,
2617 projection: Some(vec![]),
2618 };
2619 let result = compose_return_reference_summary(&arg, &callee);
2620 assert_eq!(result.projection, None);
2621 }
2622
2623 #[test]
2624 fn test_compose_summary_callee_none() {
2625 let arg = ReturnReferenceSummary {
2626 param_index: 0,
2627 kind: BorrowKind::Shared,
2628 projection: Some(vec![ProjectionStep::Index]),
2629 };
2630 let callee = ReturnReferenceSummary {
2631 param_index: 0,
2632 kind: BorrowKind::Exclusive,
2633 projection: None,
2634 };
2635 let result = compose_return_reference_summary(&arg, &callee);
2636 assert_eq!(result.projection, None);
2637 }
2638
2639 #[test]
2644 fn test_call_composition_identity() {
2645 let mir = MirFunction {
2649 name: "caller".to_string(),
2650 blocks: vec![
2651 BasicBlock {
2652 id: BasicBlockId(0),
2653 statements: vec![
2654 MirStatement {
2655 kind: StatementKind::Assign(
2656 Place::Local(SlotId(2)),
2657 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2658 ),
2659 span: span(),
2660 point: Point(0),
2661 },
2662 ],
2663 terminator: Terminator {
2664 kind: TerminatorKind::Call {
2665 func: Operand::Constant(MirConstant::Function(
2666 "identity".to_string(),
2667 )),
2668 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2669 destination: Place::Local(SlotId(3)),
2670 next: BasicBlockId(1),
2671 },
2672 span: span(),
2673 },
2674 },
2675 BasicBlock {
2676 id: BasicBlockId(1),
2677 statements: vec![MirStatement {
2678 kind: StatementKind::Assign(
2679 Place::Local(SlotId(0)),
2680 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2681 ),
2682 span: span(),
2683 point: Point(1),
2684 }],
2685 terminator: Terminator {
2686 kind: TerminatorKind::Return,
2687 span: span(),
2688 },
2689 },
2690 ],
2691 num_locals: 4,
2692 param_slots: vec![SlotId(1)],
2693 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2694 local_types: vec![
2695 LocalTypeInfo::NonCopy,
2696 LocalTypeInfo::NonCopy,
2697 LocalTypeInfo::NonCopy,
2698 LocalTypeInfo::NonCopy,
2699 ],
2700 span: span(),
2701 field_name_table: std::collections::HashMap::new(),
2702 local_struct_type_names: std::collections::HashMap::new(),
2703 local_typed_array_element_types: std::collections::HashMap::new(),
2704 local_declared_scalar_types: std::collections::HashMap::new(),
2705 };
2706
2707 let mut callee_summaries = CalleeSummaries::new();
2708 callee_summaries.insert(
2709 "identity".to_string(),
2710 ReturnReferenceSummary {
2711 param_index: 0,
2712 kind: BorrowKind::Shared,
2713 projection: Some(vec![]),
2714 },
2715 );
2716
2717 let analysis = analyze(&mir, &callee_summaries);
2718 assert!(
2719 analysis.return_reference_summary.is_some(),
2720 "expected return reference summary from composed call"
2721 );
2722 let summary = analysis.return_reference_summary.unwrap();
2723 assert_eq!(summary.param_index, 0);
2724 assert_eq!(summary.kind, BorrowKind::Shared);
2725 }
2726
2727 #[test]
2728 fn test_call_composition_unknown_callee() {
2729 let mir = MirFunction {
2731 name: "caller".to_string(),
2732 blocks: vec![
2733 BasicBlock {
2734 id: BasicBlockId(0),
2735 statements: vec![MirStatement {
2736 kind: StatementKind::Assign(
2737 Place::Local(SlotId(2)),
2738 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2739 ),
2740 span: span(),
2741 point: Point(0),
2742 }],
2743 terminator: Terminator {
2744 kind: TerminatorKind::Call {
2745 func: Operand::Constant(MirConstant::Function(
2746 "unknown_fn".to_string(),
2747 )),
2748 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2749 destination: Place::Local(SlotId(3)),
2750 next: BasicBlockId(1),
2751 },
2752 span: span(),
2753 },
2754 },
2755 BasicBlock {
2756 id: BasicBlockId(1),
2757 statements: vec![MirStatement {
2758 kind: StatementKind::Assign(
2759 Place::Local(SlotId(0)),
2760 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2761 ),
2762 span: span(),
2763 point: Point(1),
2764 }],
2765 terminator: Terminator {
2766 kind: TerminatorKind::Return,
2767 span: span(),
2768 },
2769 },
2770 ],
2771 num_locals: 4,
2772 param_slots: vec![SlotId(1)],
2773 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2774 local_types: vec![
2775 LocalTypeInfo::NonCopy,
2776 LocalTypeInfo::NonCopy,
2777 LocalTypeInfo::NonCopy,
2778 LocalTypeInfo::NonCopy,
2779 ],
2780 span: span(),
2781 field_name_table: std::collections::HashMap::new(),
2782 local_struct_type_names: std::collections::HashMap::new(),
2783 local_typed_array_element_types: std::collections::HashMap::new(),
2784 local_declared_scalar_types: std::collections::HashMap::new(),
2785 };
2786
2787 let analysis = analyze(&mir, &Default::default());
2788 assert!(
2790 analysis.return_reference_summary.is_none(),
2791 "unknown callee should not produce return reference summary"
2792 );
2793 }
2794
2795 #[test]
2796 fn test_call_composition_indirect_call() {
2797 let mir = MirFunction {
2799 name: "caller".to_string(),
2800 blocks: vec![
2801 BasicBlock {
2802 id: BasicBlockId(0),
2803 statements: vec![MirStatement {
2804 kind: StatementKind::Assign(
2805 Place::Local(SlotId(2)),
2806 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2807 ),
2808 span: span(),
2809 point: Point(0),
2810 }],
2811 terminator: Terminator {
2812 kind: TerminatorKind::Call {
2813 func: Operand::Constant(MirConstant::Method(
2814 "identity".to_string(),
2815 )),
2816 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2817 destination: Place::Local(SlotId(3)),
2818 next: BasicBlockId(1),
2819 },
2820 span: span(),
2821 },
2822 },
2823 BasicBlock {
2824 id: BasicBlockId(1),
2825 statements: vec![MirStatement {
2826 kind: StatementKind::Assign(
2827 Place::Local(SlotId(0)),
2828 Rvalue::Use(Operand::Copy(Place::Local(SlotId(3)))),
2829 ),
2830 span: span(),
2831 point: Point(1),
2832 }],
2833 terminator: Terminator {
2834 kind: TerminatorKind::Return,
2835 span: span(),
2836 },
2837 },
2838 ],
2839 num_locals: 4,
2840 param_slots: vec![SlotId(1)],
2841 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2842 local_types: vec![
2843 LocalTypeInfo::NonCopy,
2844 LocalTypeInfo::NonCopy,
2845 LocalTypeInfo::NonCopy,
2846 LocalTypeInfo::NonCopy,
2847 ],
2848 span: span(),
2849 field_name_table: std::collections::HashMap::new(),
2850 local_struct_type_names: std::collections::HashMap::new(),
2851 local_typed_array_element_types: std::collections::HashMap::new(),
2852 local_declared_scalar_types: std::collections::HashMap::new(),
2853 };
2854
2855 let mut callee_summaries = CalleeSummaries::new();
2856 callee_summaries.insert(
2857 "identity".to_string(),
2858 ReturnReferenceSummary {
2859 param_index: 0,
2860 kind: BorrowKind::Shared,
2861 projection: Some(vec![]),
2862 },
2863 );
2864
2865 let analysis = analyze(&mir, &callee_summaries);
2867 assert!(
2868 analysis.return_reference_summary.is_none(),
2869 "indirect (Method) call should not compose return summary"
2870 );
2871 }
2872
2873 #[test]
2874 fn test_call_composition_chain() {
2875 let mir = MirFunction {
2880 name: "caller".to_string(),
2881 blocks: vec![
2882 BasicBlock {
2883 id: BasicBlockId(0),
2884 statements: vec![MirStatement {
2885 kind: StatementKind::Assign(
2886 Place::Local(SlotId(2)),
2887 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2888 ),
2889 span: span(),
2890 point: Point(0),
2891 }],
2892 terminator: Terminator {
2893 kind: TerminatorKind::Call {
2894 func: Operand::Constant(MirConstant::Function(
2895 "inner".to_string(),
2896 )),
2897 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
2898 destination: Place::Local(SlotId(3)),
2899 next: BasicBlockId(1),
2900 },
2901 span: span(),
2902 },
2903 },
2904 BasicBlock {
2905 id: BasicBlockId(1),
2906 statements: vec![MirStatement {
2907 kind: StatementKind::Nop,
2908 span: span(),
2909 point: Point(1),
2910 }],
2911 terminator: Terminator {
2912 kind: TerminatorKind::Call {
2913 func: Operand::Constant(MirConstant::Function(
2914 "outer".to_string(),
2915 )),
2916 args: vec![Operand::Copy(Place::Local(SlotId(3)))],
2917 destination: Place::Local(SlotId(4)),
2918 next: BasicBlockId(2),
2919 },
2920 span: span(),
2921 },
2922 },
2923 BasicBlock {
2924 id: BasicBlockId(2),
2925 statements: vec![MirStatement {
2926 kind: StatementKind::Assign(
2927 Place::Local(SlotId(0)),
2928 Rvalue::Use(Operand::Copy(Place::Local(SlotId(4)))),
2929 ),
2930 span: span(),
2931 point: Point(2),
2932 }],
2933 terminator: Terminator {
2934 kind: TerminatorKind::Return,
2935 span: span(),
2936 },
2937 },
2938 ],
2939 num_locals: 5,
2940 param_slots: vec![SlotId(1)],
2941 param_reference_kinds: vec![Some(BorrowKind::Shared)],
2942 local_types: vec![
2943 LocalTypeInfo::NonCopy,
2944 LocalTypeInfo::NonCopy,
2945 LocalTypeInfo::NonCopy,
2946 LocalTypeInfo::NonCopy,
2947 LocalTypeInfo::NonCopy,
2948 ],
2949 span: span(),
2950 field_name_table: std::collections::HashMap::new(),
2951 local_struct_type_names: std::collections::HashMap::new(),
2952 local_typed_array_element_types: std::collections::HashMap::new(),
2953 local_declared_scalar_types: std::collections::HashMap::new(),
2954 };
2955
2956 let mut callee_summaries = CalleeSummaries::new();
2957 callee_summaries.insert(
2958 "inner".to_string(),
2959 ReturnReferenceSummary {
2960 param_index: 0,
2961 kind: BorrowKind::Shared,
2962 projection: Some(vec![]),
2963 },
2964 );
2965 callee_summaries.insert(
2966 "outer".to_string(),
2967 ReturnReferenceSummary {
2968 param_index: 0,
2969 kind: BorrowKind::Exclusive,
2970 projection: Some(vec![]),
2971 },
2972 );
2973
2974 let analysis = analyze(&mir, &callee_summaries);
2975 assert!(
2976 analysis.return_reference_summary.is_some(),
2977 "chained composition should produce return reference summary"
2978 );
2979 let summary = analysis.return_reference_summary.unwrap();
2980 assert_eq!(summary.param_index, 0, "should trace to outermost param");
2981 assert_eq!(
2982 summary.kind,
2983 BorrowKind::Exclusive,
2984 "outer callee dictates the kind"
2985 );
2986 }
2987
2988 fn make_param_mir(
2992 name: &str,
2993 num_params: u16,
2994 body: Vec<MirStatement>,
2995 num_locals: u16,
2996 ) -> MirFunction {
2997 let param_slots: Vec<SlotId> = (1..=num_params).map(SlotId).collect();
2999 MirFunction {
3000 name: name.to_string(),
3001 blocks: vec![BasicBlock {
3002 id: BasicBlockId(0),
3003 statements: body,
3004 terminator: make_terminator(TerminatorKind::Return),
3005 }],
3006 num_locals,
3007 param_slots,
3008 param_reference_kinds: vec![None; num_params as usize],
3009 local_types: vec![LocalTypeInfo::Unknown; num_locals as usize],
3010 span: span(),
3011 field_name_table: std::collections::HashMap::new(),
3012 local_struct_type_names: std::collections::HashMap::new(),
3013 local_typed_array_element_types: std::collections::HashMap::new(),
3014 local_declared_scalar_types: std::collections::HashMap::new(),
3015 }
3016 }
3017
3018 #[test]
3019 fn test_closure_param_escapes_pure_body_marks_non_escaping() {
3020 let mir = make_param_mir(
3023 "pure_param",
3024 1,
3025 vec![make_stmt(
3026 StatementKind::Assign(
3027 Place::Local(SlotId(2)),
3028 Rvalue::BinaryOp(
3029 BinOp::Add,
3030 Operand::Copy(Place::Local(SlotId(1))),
3031 Operand::Constant(MirConstant::Int(1)),
3032 ),
3033 ),
3034 0,
3035 )],
3036 3,
3037 );
3038
3039 let summary = extract_borrow_summary(&mir, None);
3040 assert_eq!(
3041 summary.closure_param_escapes,
3042 vec![false],
3043 "pure arithmetic body does not escape the param"
3044 );
3045 }
3046
3047 #[test]
3048 fn test_closure_param_escapes_return_marks_escaping() {
3049 let mir = make_param_mir(
3051 "return_param",
3052 1,
3053 vec![make_stmt(
3054 StatementKind::Assign(
3055 Place::Local(SlotId(0)),
3056 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
3057 ),
3058 0,
3059 )],
3060 2,
3061 );
3062
3063 let summary = extract_borrow_summary(&mir, None);
3064 assert_eq!(
3065 summary.closure_param_escapes,
3066 vec![true],
3067 "param returned from function must be marked as escaping"
3068 );
3069 }
3070
3071 #[test]
3072 fn test_closure_param_escapes_captured_marks_escaping() {
3073 let mir = make_param_mir(
3075 "capture_param",
3076 1,
3077 vec![make_stmt(
3078 StatementKind::ClosureCapture {
3079 closure_slot: SlotId(2),
3080 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
3081 function_id: None,
3082 },
3083 0,
3084 )],
3085 3,
3086 );
3087
3088 let summary = extract_borrow_summary(&mir, None);
3089 assert_eq!(
3090 summary.closure_param_escapes,
3091 vec![true],
3092 "param captured into a closure must be marked as escaping"
3093 );
3094 }
3095
3096 #[test]
3097 fn test_closure_param_escapes_mixed_params() {
3098 let mir = make_param_mir(
3101 "mixed_params",
3102 2,
3103 vec![
3104 make_stmt(
3105 StatementKind::Assign(
3106 Place::Local(SlotId(3)),
3107 Rvalue::BinaryOp(
3108 BinOp::Add,
3109 Operand::Copy(Place::Local(SlotId(1))),
3110 Operand::Constant(MirConstant::Int(1)),
3111 ),
3112 ),
3113 0,
3114 ),
3115 make_stmt(
3116 StatementKind::Assign(
3117 Place::Local(SlotId(0)),
3118 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
3119 ),
3120 1,
3121 ),
3122 ],
3123 4,
3124 );
3125
3126 let summary = extract_borrow_summary(&mir, None);
3127 assert_eq!(
3128 summary.closure_param_escapes,
3129 vec![false, true],
3130 "a non-escaping, b escaping — verifies per-param resolution"
3131 );
3132 }
3133}