Skip to main content

rapx/verify/call_summary/
mod.rs

1//! Interprocedural call summaries for the staged verifier.
2//!
3//! The backward visitor needs dependency information: when a call result is
4//! relevant, which call arguments should become relevant too?  The forward
5//! visitor needs effect information: after a retained call, what facts about the
6//! return value or arguments can be added or forgotten?
7//!
8//! This module keeps those summaries in one place.  Standard unsafe/std APIs
9//! are summarized by name.  Local callees can additionally use the existing
10//! dataflow graph to approximate which arguments flow into the return value.
11pub(crate) mod builtin_models;
12pub(crate) mod interprocedural;
13
14#[cfg(rapx_has_attr_ir)]
15use rustc_attr_ir::LangItem;
16#[cfg(all(not(rapx_has_attr_ir), not(rapx_ge_100)))]
17use rustc_hir::LangItem;
18#[cfg(all(not(rapx_has_attr_ir), rapx_ge_100))]
19use rustc_hir::attrs::lang_items::LangItem;
20use rustc_hir::def_id::DefId;
21use rustc_middle::{
22    mir::{Local, Operand},
23    ty::{Ty, TyCtxt, TyKind},
24};
25
26use crate::compat::FxHashMap;
27use crate::helpers::mir_utils;
28use crate::verify::api_classify::is_std_vec;
29
30/// Caller constraints that affect a callee's path feasibility, propagated down
31/// the call chain during must-write summarization. Only concrete literal
32/// arguments are carried for now; symbolic path conditions come later.
33#[derive(Clone, Debug, Default)]
34pub(crate) struct CallContext {
35    /// Concrete literal argument values, keyed by 0-based argument index
36    /// (matching `trace_to_callee_arg`).
37    pub concrete: FxHashMap<usize, i128>,
38}
39
40/// Dependency summary consumed by the backward visitor.
41#[derive(Clone, Debug)]
42pub(crate) struct CallDependencySummary {
43    /// If the call destination is relevant, these call arguments are relevant.
44    pub return_depends_on_args: Vec<usize>,
45    /// Arguments definitely written on every return path (the must-write
46    /// intersection, from `local_must_write_args`). The backward slicer keeps
47    /// these relevant so the write effect is applied. A conditionally-written
48    /// argument is *not* listed here — that is the "may-write" set, which this
49    /// summary does not compute.
50    pub must_write_args: Vec<usize>,
51    /// True when this summary is conservative rather than precise.
52    pub unsupported: bool,
53}
54
55impl CallDependencySummary {
56    /// Build a conservative summary that keeps all arguments relevant.
57    fn unknown(arg_count: usize) -> Self {
58        Self {
59            return_depends_on_args: (0..arg_count).collect(),
60            must_write_args: Vec::new(),
61            unsupported: true,
62        }
63    }
64}
65
66/// Effect summary consumed by the forward visitor.
67#[derive(Clone, Debug)]
68pub(crate) struct CallEffectSummary {
69    /// Effects that can be applied to the path-local abstract state.
70    pub effects: Vec<CallEffect>,
71    /// True when this summary is conservative rather than precise.
72    pub unsupported: bool,
73}
74
75impl CallEffectSummary {
76    /// Build a conservative summary for an unsupported call.
77    fn unknown() -> Self {
78        Self {
79            effects: Vec::new(),
80            unsupported: true,
81        }
82    }
83}
84
85/// Path-local effect produced by a retained call.
86#[derive(Clone, Debug)]
87pub(crate) enum CallEffect {
88    /// The return value aliases or is a direct value flow from an argument.
89    ReturnAliasArg { arg: usize },
90    /// `select_unpredictable(cond, x, y)` returns either `x` or `y`: the result
91    /// is one of the two candidate values (`args[1]`/`args[2]`), non-deterministic
92    /// since the boolean selector (`args[0]`) is unpredictable.
93    SelectUnpredictable,
94    /// The return value is a pointer extracted from an aggregate/reference arg.
95    ReturnPointerFromArg { arg: usize },
96    /// The return value is `base + offset * stride`.
97    ReturnPointerAdd {
98        base_arg: usize,
99        offset_arg: usize,
100        stride: Option<u64>,
101        /// Whether the result is a *dereferenceable* pointer (its contract
102        /// guarantees strict in-bounds, e.g. `SliceIndex::get_unchecked`), as
103        /// opposed to plain `add` arithmetic whose result may be one-past-end.
104        dereferenceable: bool,
105    },
106    /// The return value is `base - offset * stride`.
107    ReturnPointerSub {
108        base_arg: usize,
109        offset_arg: usize,
110        stride: Option<u64>,
111    },
112    /// The return value is known to be non-zero.
113    ReturnNonZero,
114    /// The return value is known to satisfy a concrete alignment.
115    ReturnAligned,
116    /// The return value is a concrete layout/numeric constant.
117    ReturnConst { value: u64 },
118    /// The call writes one initialized element through a pointer argument.
119    WriteMemory { pointer_arg: usize },
120    /// The return value is a pointer backed by a fresh allocation of
121    /// `size_arg` elements × `elem_size` bytes. The base address is taken
122    /// from `pointer_arg`. Used for `from_raw_parts(ptr, len)`.
123    ReturnFreshAllocation {
124        pointer_arg: usize,
125        size_arg: usize,
126        elem_size: u64,
127    },
128    /// `alloc::alloc::exchange_malloc(size, align)` — a fresh allocation of
129    /// `size_arg` bytes, returned as `*mut u8`.
130    ReturnExchangeMalloc { size_arg: usize },
131    /// The return value is the length of an aggregate argument.
132    ReturnLengthOfArg { arg: usize },
133    /// The return value is field `field` of the pointee of argument `arg`
134    /// (models `Vec::len` and any `(*self).field` getter; the field index is
135    /// derived straight from the callee's MIR).
136    ReturnFieldOfArg { arg: usize, field: usize },
137    /// The return value is field `field` of the pointee of argument `arg`,
138    /// minus `offset` elements. Models an iterator's `next_back_unchecked`,
139    /// which mutates its `end_or_len` field via `pre_dec_end(offset)` before
140    /// returning it — so the returned pointer is `field - offset` elements past
141    /// the stored field value.
142    ReturnFieldOfArgSub {
143        arg: usize,
144        field: usize,
145        offset: u64,
146    },
147    /// The return value is `min(lhs_arg, rhs_arg)`, satisfying
148    /// `return <= lhs_arg` and `return <= rhs_arg`.
149    ReturnMin { lhs_arg: usize, rhs_arg: usize },
150    /// The return value is `max(lhs_arg, rhs_arg)`.
151    ReturnMax { lhs_arg: usize, rhs_arg: usize },
152    /// The return value is `clamp(value_arg, min_arg, max_arg)`.
153    ReturnClamp {
154        value_arg: usize,
155        min_arg: usize,
156        max_arg: usize,
157    },
158    /// The return value is the absolute value of `arg` (`ite(arg >= 0, arg, -arg)`).
159    ReturnAbs { arg: usize },
160    /// The return value is the negation of `arg` (`-arg`).
161    ReturnNeg { arg: usize },
162    /// The return value is `lhs_arg + rhs_arg`.
163    ReturnAdd { lhs_arg: usize, rhs_arg: usize },
164    /// The return value is `lhs_arg * rhs_arg`.
165    ReturnMul { lhs_arg: usize, rhs_arg: usize },
166    /// The call returns `Option<T>` whose `Some` payload is `lhs_arg + rhs_arg`
167    /// (models `checked_add`; the payload is non-zero whenever `lhs_arg` is).
168    ReturnOptionSomeAdd { lhs_arg: usize, rhs_arg: usize },
169    /// The call returns `Option<T>` whose `Some` payload is `lhs_arg * rhs_arg`
170    /// (models `checked_mul`; the payload is non-zero whenever both args are).
171    ReturnOptionSomeMul { lhs_arg: usize, rhs_arg: usize },
172    /// The return value is non-zero *iff* `arg` is non-zero (models bit-preserving
173    /// operations like `rotate_left`/`swap_bytes`/`count_ones`/`isqrt`, which map
174    /// `0` to `0` and non-zero to non-zero).
175    ReturnNonZeroIff { arg: usize },
176    /// The call returns `Option<T>` whose `Some` payload is non-zero *iff* `arg`
177    /// is non-zero (models `checked_pow`).
178    ReturnOptionSomeNonZeroIff { arg: usize },
179    /// The call returns `Option<T>` whose `Some` payload is unconditionally
180    /// non-zero (models `checked_next_power_of_two`, where the next power of
181    /// two is always positive regardless of the argument).
182    ReturnOptionSomeNonZero,
183    /// A specific field of the returned tuple is known to be non-zero (e.g.
184    /// `overflowing_abs`/`overflowing_neg` return `(result, overflow)` where
185    /// `result != 0`). Used to discharge a downstream `ValidNum(result != 0)`.
186    ReturnTupleFieldNonZero { field: usize },
187    /// A specific field of the returned tuple carries the length of a given
188    /// argument (e.g. split_at(mid) returns (left, right) where left.len() == mid).
189    ReturnTupleFieldLength { field: usize, from_arg: usize },
190    /// The return value is a pointer backed by a fresh heap allocation of
191    /// `size_arg` elements × `elem_size` bytes. Unlike ReturnFreshAllocation
192    /// this does not require a pointer argument — used for constructors like
193    /// `Vec::from_elem(init, count)` that allocate fresh memory.
194    ReturnNewAllocation { size_arg: usize, elem_size: u64 },
195    /// `Box::new` / `Box::new_in` / `Box::new_uninit` / `Box::new_uninit_in`
196    /// (and `try_` variants): allocate a fresh heap buffer of `size_of::<T>()`
197    /// bytes and return a `Box` whose pointer field backs it.  These have MIR
198    /// available but carry a `match` on the allocator's `Result`, which the
199    /// inline heuristic rejects as a semantic branch, so a direct effect is the
200    /// only way the fresh allocation's provenance reaches the `NonNull`.
201    ReturnBoxAllocation,
202    /// Like ReturnNewAllocation but the length is carried by the argument
203    /// itself (a Box fat pointer) rather than a separate count argument.
204    /// Used for `into_vec` / `box_assume_init_into_vec_unsafe`.
205    ReturnNewAllocationFromBox,
206    /// Like `ReturnNewAllocation`, but the argument is the *capacity*: the
207    /// returned Vec starts empty (`len == 0`) with `cap == cap_arg` (models
208    /// `Vec::with_capacity`).
209    ReturnNewAllocationFromCap { cap_arg: usize, elem_size: u64 },
210    /// The return value is a non-zero power of two (models `Layout::align`).
211    ReturnPowerOfTwo,
212    /// The call transfers a Vec's backing allocation into a Box (e.g.
213    /// `Vec::into_boxed_slice`). Looks up the current heap allocation from the
214    /// argument's owning pointer field via its stack provenance.
215    ReturnBoxFromVec { arg: usize },
216    /// The return value is known to own initialized memory of the type pointed
217    /// to by the indicated argument (e.g. `Box::from_raw(p)` owns one initialized
218    /// `T` element reached through `p`).
219    OwnsInitMemory { arg: usize },
220    /// The call validates that every element of the array argument `indices_arg`
221    /// is `< args[len_arg]` and that the elements are pairwise distinct, returning
222    /// `Err` otherwise.  On the `Ok` continuation the caller may assume
223    /// `InBound(slice_of(len_arg), indices_arg)` and
224    /// `NonOverlap(indices_arg)`.  (A trusted interprocedural summary, like the
225    /// std-primitive summaries — the validator's body is not re-proved here.)
226    ChecksIndexBoundsDisjoint { indices_arg: usize, len_arg: usize },
227    /// The call returns `Option<usize>` whose `Some` payload is a scan index
228    /// into the iterator argument `self_arg` (models `Iterator::position` /
229    /// `Iterator::find`): `Some(i)` satisfies `0 <= i < self.len()` where
230    /// `self` is the Iter/IterMut struct produced by `into_iter`/`iter`.
231    ReturnOptionSomeScanIndex { self_arg: usize },
232    /// The call returns `Option<usize>` whose `Some` payload `i` is an index
233    /// into the slice argument `arg`: `i < args[arg].len()`.  Detected from the
234    /// callee's MIR shape (`while i < arg.len() { ... return Some(i); ... }`,
235    /// i.e. `memchr`-style search).  Lets a caller re-prove a numeric invariant
236    /// like `finger <= finger_back` after `finger += i + 1`.
237    ReturnOptionSomeIndexLtArgLen { arg: usize },
238    /// The call returns `Option<(.., usize, ..)>` whose tuple field `field` (a
239    /// byte length) is `<= args[arg].len()`.  Detected from a UTF-8-decoder
240    /// shape: each `Some((.., len))` return is guarded by `slice.get(len - 1)?`.
241    ReturnOptionSomeTupleFieldLeArgLen { field: usize, arg: usize },
242    /// The call is `Try::branch`: `Option<T>` -> `ControlFlow<Option<!>, T>`,
243    /// so the result's `Continue` payload (field 0) equals the input's `Some`
244    /// payload (field 0).  Models the `?` operator's `if let Some(..) = expr?`
245    /// unwrap so the payload's provenance survives the branch.
246    ReturnBranchPayload { arg: usize },
247    /// The call returns the length of a nul-terminated string (models
248    /// `strlen`): `0 <= len < isize::MAX`, so `len + 1` (the byte length with
249    /// the terminator) fits in `isize::MAX` — discharging the
250    /// `from_raw_parts` `ValidNum(size_of(T)*(len+1) <= isize::MAX)` bound.
251    ReturnScanLength,
252    /// `ptr.align_offset(align)` returns an offset such that
253    /// `(ptr + offset) % align == 0` and `0 <= offset < align` (or `usize::MAX`
254    /// when no such offset exists). Models `*const T::align_offset` /
255    /// `*mut T::align_offset` by recording the alignment path-condition so
256    /// downstream `ptr.add(offset)` dereferences can discharge `Align`.
257    ReturnAlignOffset { ptr_arg: usize, align_arg: usize },
258    /// A local `align_to`-style wrapper (`align_to_ext`/`align_to_mut_ext`)
259    /// returns `(prefix, body, suffix)` where `body` is `align_of::<U>()`-aligned.
260    /// Models the tuple by creating three sub-slices whose lengths/offsets obey
261    /// `prefix.len() = offset` and `len - suffix.len() = offset + k*size_of::<U>()`,
262    /// and records `(ptr + offset) % align_of::<U>() == 0` so downstream
263    /// `ptr.add(offset - k)` dereferences can discharge `Align`.
264    ReturnAlignTo { receiver_arg: usize },
265    /// `IntoIterator::into_iter` on `&[T]` / `&mut [T]` returns an
266    /// `Iter`/`IterMut` whose `ptr` (field 0) and `end_or_len` (field 1) share
267    /// the source slice's allocation. Models the constructor by materializing
268    /// those two pointer fields so downstream `Iterator::next` / `len` /
269    /// `is_empty` can resolve the iterator's provenance and element type.
270    ReturnIter { receiver_arg: usize },
271    /// `<ManuallyDrop<T> as Deref>::deref` / `MaybeDangling::as_ref` return a
272    /// reference to the inner value at the *same* address (transparent
273    /// wrappers).  The return aliases `arg` (a `&T` pointing at `arg`'s
274    /// pointee) and its pointee field values are the argument's field values
275    /// with the leading `peel` transparent field-0 hops stripped.
276    ReturnTransparentDeref { arg: usize, peel: usize },
277    /// `slice::range(range, bounds)` returns `Range { start, end }` satisfying
278    /// `0 <= start <= end <= bounds.end`. Models the range normalizer whose
279    /// `start_bound`/`end_bound` trait dispatch cannot be inlined.
280    ReturnRange { bounds_arg: usize },
281    /// `mem::replace(dest, src)` returns `*dest` (the old value), so the return
282    /// is the *pointee* of the reference argument, not the reference itself.
283    ReturnDerefArg { arg: usize },
284    /// The call *frees* the heap allocation behind `pointer_arg` (a `&mut`
285    /// reference to a `Box`/`Vec`/`String` pointee). Models `ManuallyDrop::drop`.
286    DropMemory { pointer_arg: usize },
287}
288
289/// Return dependency information for a MIR call terminator.
290pub(crate) fn dependency_summary<'tcx>(
291    tcx: TyCtxt<'tcx>,
292    func: &Operand<'tcx>,
293    arg_count: usize,
294    context: &CallContext,
295) -> CallDependencySummary {
296    let callee = mir_utils::dep_callee_def_id(func);
297
298    // MIR dataflow first: works for local and cross-crate (`#[inline]`)
299    // callees alike, no hand-written table needed.
300    if let Some(callee) = callee {
301        if tcx.intrinsic(callee).is_some() || mir_utils::is_drop_in_place(callee) {
302            return CallDependencySummary::unknown(arg_count);
303        }
304        if let Some(must_write_args) = interprocedural::local_must_write_args(tcx, callee, context)
305        {
306            if !must_write_args.is_empty() {
307                return CallDependencySummary {
308                    return_depends_on_args: Vec::new(),
309                    must_write_args: must_write_args
310                        .into_iter()
311                        .filter(|index| *index < arg_count)
312                        .collect(),
313                    unsupported: false,
314                };
315            }
316        }
317        // A memchr/decode-style callee's return payload is bounded by a slice
318        // argument's length, so the return value depends on that slice argument.
319        // The dataflow analyzer can't see this through the loop, so detect it
320        // from the MIR shape and keep the slice argument relevant.
321        if let Some(effect) = interprocedural::try_slice_bounded_return_effect(tcx, callee) {
322            if let CallEffect::ReturnOptionSomeIndexLtArgLen { arg } = effect {
323                if arg < arg_count {
324                    return CallDependencySummary {
325                        return_depends_on_args: vec![arg],
326                        must_write_args: Vec::new(),
327                        unsupported: false,
328                    };
329                }
330            }
331        }
332        if let Some(effect) = interprocedural::try_decode_length_return_effect(tcx, callee) {
333            if let CallEffect::ReturnOptionSomeTupleFieldLeArgLen { arg, .. } = effect {
334                if arg < arg_count {
335                    return CallDependencySummary {
336                        return_depends_on_args: vec![arg],
337                        must_write_args: Vec::new(),
338                        unsupported: false,
339                    };
340                }
341            }
342        }
343        // `Try::branch` (`Option<T>` -> `ControlFlow<Option<!>, T>`): the
344        // `Continue` payload is the input's `Some` payload, so the return value
345        // depends on the input.  Detect by name (the trait method's `self` type
346        // is generic, so the type check below is skipped here).
347        if mir_utils::call_name(tcx, func).ends_with("::branch") {
348            return CallDependencySummary {
349                return_depends_on_args: vec![0],
350                must_write_args: Vec::new(),
351                unsupported: false,
352            };
353        }
354        if let Some(return_deps) = interprocedural::local_return_dependencies(tcx, callee) {
355            return CallDependencySummary {
356                return_depends_on_args: return_deps
357                    .into_iter()
358                    .filter(|index| *index < arg_count)
359                    .collect(),
360                must_write_args: Vec::new(),
361                unsupported: false,
362            };
363        }
364    }
365
366    CallDependencySummary::unknown(arg_count)
367}
368
369/// Return effect information for a MIR call terminator.
370pub(crate) fn effect_summary<'tcx>(
371    tcx: TyCtxt<'tcx>,
372    caller: DefId,
373    func: &Operand<'tcx>,
374    destination: Local,
375    context: &CallContext,
376) -> CallEffectSummary {
377    let callee = mir_utils::dep_callee_def_id(func);
378    let name = mir_utils::call_name(tcx, func);
379
380    if let Some(summary) =
381        builtin_models::lookup_effect(tcx, caller, callee, func, destination)
382    {
383        return summary;
384    }
385
386    // Transparent-wrapper deref: `<ManuallyDrop<T> as Deref>::deref` /
387    // `deref_mut` (and `MaybeDangling::as_ref`/`as_mut`) return a reference to
388    // the inner value at the same address.  The std MIR for these is
389    // unavailable cross-crate, so model them with field-value peeling.
390    if let Some(peel) = transparent_deref_peel(tcx, func) {
391        return CallEffectSummary {
392            effects: vec![CallEffect::ReturnTransparentDeref { arg: 0, peel }],
393            unsupported: false,
394        };
395    }
396
397    // Interprocedural fallback for local callees.
398    if let Some(callee) = callee {
399        if tcx.intrinsic(callee).is_some() || mir_utils::is_drop_in_place(callee) {
400            return CallEffectSummary::unknown();
401        }
402        if let Some(must_write_args) = interprocedural::local_must_write_args(tcx, callee, context) {
403            let effects: Vec<_> = must_write_args
404                .into_iter()
405                .map(|arg| CallEffect::WriteMemory { pointer_arg: arg })
406                .collect();
407            if !effects.is_empty() {
408                return CallEffectSummary {
409                    effects,
410                    unsupported: false,
411                };
412            }
413        }
414        if let Some(effect) =
415            interprocedural::try_pointer_arith_wrapper_effect(tcx, callee)
416        {
417            return CallEffectSummary {
418                effects: vec![effect],
419                unsupported: false,
420            };
421        }
422        if let Some(effect) =
423            interprocedural::try_from_raw_parts_wrapper_effect(tcx, callee)
424        {
425            return CallEffectSummary {
426                effects: vec![effect],
427                unsupported: false,
428            };
429        }
430        if let Some(effect) = interprocedural::try_iter_constructor_effect(tcx, callee) {
431            return CallEffectSummary {
432                effects: vec![effect],
433                unsupported: false,
434            };
435        }
436        if let Some((indices_arg, len_arg)) =
437            interprocedural::detect_index_disjoint_validator(tcx, callee)
438                .or_else(|| interprocedural::named_index_disjoint_validator(&name))
439        {
440            return CallEffectSummary {
441                effects: vec![CallEffect::ChecksIndexBoundsDisjoint {
442                    indices_arg,
443                    len_arg,
444                }],
445                unsupported: false,
446            };
447        }
448        if let Some(return_deps) = interprocedural::local_return_dependencies(tcx, callee) {
449            // If the callee does pointer arithmetic, don't produce ReturnAliasArg
450            // since the offset might have been changed (e.g. wrapping_add(1)).
451            if !interprocedural::callee_contains_pointer_arithmetic(tcx, callee) {
452                // If the callee transitively calls functions that may write
453                // through &mut args, ReturnAliasArg alone is insufficient —
454                // the writes are lost. Mark as unsupported so the VM falls
455                // back to `exec_inline_call`, which inlines the full body.
456                let has_nested_calls = interprocedural::callee_calls_other_local(tcx, callee);
457                return CallEffectSummary {
458                    effects: return_deps
459                        .into_iter()
460                        .map(|arg| CallEffect::ReturnAliasArg { arg })
461                        .collect(),
462                    unsupported: has_nested_calls,
463                };
464            }
465        }
466    }
467
468    CallEffectSummary::unknown()
469}
470
471/// Detect a transparent-wrapper deref whose receiver is `ManuallyDrop<T>` or
472/// `MaybeDangling<T>`, and return how many leading field-0 hops must be peeled
473/// to reach the inner `T`:
474///   * `ManuallyDrop<T> { value: MaybeDangling<T> }` → 2 (`value` → `MaybeDangling.0`)
475///   * `MaybeDangling<P>(P)` → 1.
476fn transparent_deref_peel<'tcx>(tcx: TyCtxt<'tcx>, func: &Operand<'tcx>) -> Option<usize> {
477    let self_ty = crate::helpers::mir_utils::fn_def_first_type_arg(func)?;
478    let TyKind::Adt(adt_def, _) = self_ty.kind() else {
479        return None;
480    };
481    let did = adt_def.did();
482    if tcx.is_lang_item(did, LangItem::ManuallyDrop) {
483        return Some(2);
484    }
485    if is_maybe_dangling(tcx, did) {
486        return Some(1);
487    }
488    None
489}
490
491/// Whether `did` is the `MaybeDangling` lang item.
492///
493/// The `MaybeDangling` lang item was only added to rustc's table after
494/// nightly-2026-02-05 (the `verify-std` toolchain), so gate the lang-item
495/// lookup behind a build-time check and fall back to name matching on
496/// toolchains that lack it.
497fn is_maybe_dangling(tcx: TyCtxt<'_>, did: DefId) -> bool {
498    #[cfg(rapx_has_maybe_dangling_lang_item)]
499    {
500        return tcx.is_lang_item(did, LangItem::MaybeDangling);
501    }
502    #[cfg(not(rapx_has_maybe_dangling_lang_item))]
503    {
504        tcx.def_path_str(did).contains("MaybeDangling")
505    }
506}
507
508// ── Collection element-size helpers ──────────────────────────────
509// Used by [`builtin_models`] and the VM to size `from_raw_parts`/`Vec`
510// results; moved here from `helpers::mir_utils` because they depend on the
511// [`crate::verify::api_classify`] classifiers.
512
513/// Element type of a `Vec<T>`, if `ty` is a `Vec`.
514pub(crate) fn vec_elem_ty<'tcx>(_tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
515    if let TyKind::Adt(adt_def, substs) = ty.kind() {
516        if is_std_vec(adt_def.did()) {
517            return substs.first().and_then(|s| s.as_type());
518        }
519    }
520    None
521}
522
523/// Element type of a `from_raw_parts` result: `&[T]`/`*[T]`/`Vec<T>` yield `T`;
524/// other types (including `String`) return `None`.
525pub(crate) fn from_raw_parts_elem_ty<'tcx>(
526    tcx: TyCtxt<'tcx>,
527    caller: DefId,
528    dest: Option<Local>,
529) -> Option<Ty<'tcx>> {
530    let d = dest?;
531    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
532    match ty.kind() {
533        TyKind::Ref(_, inner, _) => match inner.kind() {
534            TyKind::Slice(e) => Some(*e),
535            _ => None,
536        },
537        TyKind::RawPtr(inner, _) => match inner.kind() {
538            TyKind::Slice(e) => Some(*e),
539            _ => None,
540        },
541        TyKind::Adt(..) => vec_elem_ty(tcx, ty),
542        _ => None,
543    }
544}
545
546/// Element size of a `from_raw_parts` result. Covers `&[T]`/`*[T]` (borrowed
547/// slice) and `Vec<T>` (owned); `String` and unknown layouts fall back to 1
548/// (`String`'s element is `u8`, so 1 is correct).
549pub(crate) fn from_raw_parts_elem_size<'tcx>(
550    tcx: TyCtxt<'tcx>,
551    caller: DefId,
552    dest: Option<Local>,
553) -> u64 {
554    from_raw_parts_elem_ty(tcx, caller, dest)
555        .and_then(|e| mir_utils::type_layout(tcx, caller, e).map(|(_, s)| s))
556        .unwrap_or(1)
557}