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}