Skip to main content

rapx/verify/call_summary/
fn_simulator.rs

1//! Function simulation: API behaviour modelling when MIR is unavailable.
2//!
3//! Each recognised standard-library API is described by a single table
4//! row: a **name matcher**, **argument dependency**, and **effect
5//! builder**.  The public entry points [`lookup_dependency`] and
6//! [`lookup_effect`] scan the table linearly (first match wins) and
7//! convert the matched row into the concrete summaries consumed by the
8//! backward/forward visitors.
9//!
10//! Two layers, both visible in one place:
11//! 1. **Matcher functions** — cheap name-pattern checks (hot-path `is_*`
12//!    helpers for classification queries).
13//! 2. **Effect functions** — produce the `Vec<CallEffect>` for a single
14//!    API.
15
16use rustc_hir::def_id::DefId;
17use rustc_middle::mir::Operand;
18use rustc_middle::ty::{GenericArgKind, Ty, TyCtxt, TyKind};
19
20use super::{CallDependencySummary, CallEffect, CallEffectSummary};
21use crate::helpers::api_classify;
22use crate::helpers::mir_utils::{
23    type_layout, destination_stride, pointee_alignment,
24    nonnull_inner_ty, slice_element_size, vec_element_size,
25};
26
27// ── Context for effect builders ────────────────────────────────────────
28
29pub struct EffCtx<'a, 'tcx> {
30    pub tcx: TyCtxt<'tcx>,
31    pub caller: DefId,
32    pub callee: Option<DefId>,
33    pub name: &'a str,
34    pub func: &'a Operand<'tcx>,
35    pub dest: Option<rustc_middle::mir::Local>,
36}
37
38// ── Registry table ─────────────────────────────────────────────────────
39
40struct Entry {
41    matches: fn(&str) -> bool,
42    dep_on: &'static [usize],
43    dep_on_all: bool,
44    writes: &'static [usize],
45    effects: fn(&EffCtx<'_, '_>) -> Vec<CallEffect>,
46}
47
48macro_rules! none { () => { &[] } }
49macro_rules! dep0  { () => { &[0usize] } }
50macro_rules! dep01 { () => { &[0usize, 1] } }
51
52macro_rules! E {
53    ($m:expr, $d:expr, $all:expr, $w:expr, $e:ident) => {
54        Entry { matches: $m, dep_on: $d, dep_on_all: $all, writes: $w, effects: $e }
55    };
56}
57
58const ALL: &[usize] = &[];
59
60static REGISTRY: &[Entry] = &[
61    // ── Drop / forget ──────────────────────────────────────────────
62    E!(mem_forget,           dep0!(),  false,  none!(),  eff_forget),
63
64    // ── Pass-through / no-effect calls ──────────────────────────────
65    E!(transmute,             dep0!(),  false,  none!(),  eff_none),
66    E!(api_classify::is_maybe_uninit_uninit,none!(), false, none!(), eff_none),
67    E!(api_classify::is_maybe_uninit_assume_init,dep0!(), false, none!(), eff_none),
68    // Non-zero-preserving integer operations must be matched *before* the
69    // generic `is_numeric_arith` pass-through below. Each is modelled with a
70    // precise expression over its operands (ite / arithmetic) so the solver
71    // can discharge a downstream `!= 0` obligation *conditionally* — only
72    // when the operands are actually non-zero — rather than asserting the
73    // result is unconditionally non-zero.
74    E!(int_max,               ALL,      true,   none!(),  eff_return_max),
75    E!(int_clamp,             ALL,      true,   none!(),  eff_return_clamp),
76    E!(int_abs,               ALL,      true,   none!(),  eff_return_abs),
77    E!(int_neg,               ALL,      true,   none!(),  eff_return_neg),
78    E!(int_add,               ALL,      true,   none!(),  eff_return_add),
79    E!(int_mul,               ALL,      true,   none!(),  eff_return_mul),
80    E!(int_checked_add,       ALL,      true,   none!(),  eff_return_option_some_add),
81    E!(int_checked_mul,       ALL,      true,   none!(),  eff_return_option_some_mul),
82    E!(overflowing_nz,        ALL,      true,   none!(),  eff_overflowing_nz),
83    E!(api_classify::is_numeric_arith, ALL,      true,   none!(),  eff_none),
84    E!(saturating_sub,        ALL,      true,   none!(),  eff_return_sub),
85    E!(api_classify::is_offset_from_unsigned, dep01!(), false, none!(), eff_offset_from_unsigned),
86    E!(api_classify::is_option_unwrap, dep0!(),  false,  none!(),  eff_alias_arg0),
87    E!(from_trait_call,       dep0!(),  false,  none!(),  eff_from_trait),
88
89    // ── Pointer extraction / cast ───────────────────────────────────
90    E!(nonnull_from,          dep0!(),  false,  none!(),  eff_alias_ptr),
91    E!(nonnull_new_unchecked, dep0!(),  false,  none!(),  eff_none),
92    E!(nonnull_new,           dep0!(),  false,  none!(),  eff_alias_ptr),
93    E!(nonnull_as_ref,        dep0!(),  false,  none!(),  eff_alias_ptr),
94    E!(nonnull_as_mut,        dep0!(),  false,  none!(),  eff_alias_ptr),
95    E!(api_classify::is_as_ptr, dep0!(), false,  none!(),  eff_alias_ptr),
96    E!(api_classify::is_as_ptr_range, dep0!(), false, none!(), eff_alias_arg0),
97    E!(api_classify::is_as_mut_ptr_range, dep0!(), false, none!(), eff_alias_arg0),
98
99    // ── Pointer arithmetic ──────────────────────────────────────────
100    E!(|n| api_classify::is_pointer_add(n) && !api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_add),
101    E!(|n| api_classify::is_pointer_sub(n) && !api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_sub),
102    E!(|n| api_classify::is_pointer_add(n) && api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_add),
103    E!(|n| api_classify::is_pointer_sub(n) && api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_sub),
104
105    // ── Memory read / write ─────────────────────────────────────────
106    E!(ptr_read,              dep0!(),  false,  none!(),  eff_read_mem),
107    E!(api_classify::is_ptr_write, none!(), false,  dep0!(),  eff_write_mem),
108    E!(api_classify::is_maybe_uninit_write, none!(), false, dep0!(), eff_write_mem),
109
110    // ── Slice / collection queries ──────────────────────────────────
111    E!(api_classify::is_len,  dep0!(),  false,  none!(),  eff_len),
112    E!(is_empty,              dep0!(),  false,  none!(),  eff_is_empty),
113    E!(cmp_min,               ALL,      true,   none!(),  eff_cmp_min),
114    E!(bit_preserving_nz,     ALL,      true,   none!(),  eff_return_nonzero_iff),
115    E!(checked_pow_nz,        ALL,      true,   none!(),  eff_return_option_some_nonzero_iff),
116
117    // ── SliceIndex::get_unchecked / get_unchecked_mut ───────────────
118    E!(is_slice_get_unchecked, dep0!(), false,  none!(),  eff_alias_ptr),
119
120    // ── Ownership reconstruction ────────────────────────────────────
121    E!(api_classify::is_ownership_reconstruction, dep0!(), false, none!(), eff_ownership_recon),
122
123    // ── Slice helpers ───────────────────────────────────────────────
124    E!(slice_index,           dep01!(), false,  none!(),  eff_alias_arg0),
125    E!(align_to_local,        dep0!(),  false,  none!(),  eff_align_to),
126    E!(into_iter_local,       dep0!(),  false,  none!(),  eff_return_iter),
127    E!(iter_position,         dep0!(),  false,  none!(),  eff_option_scan_index),
128    E!(is_strlen,             dep0!(),  false,  none!(),  eff_scan_length),
129    E!(split_at,              dep01!(), false,  none!(),  eff_split_at),
130    E!(api_classify::is_from_raw_parts, dep01!(), false, none!(), eff_from_raw_parts),
131    E!(api_classify::is_align_offset, dep01!(), false, none!(), eff_align_offset),
132
133    // ── Vec / collection constructors ────────────────────────────────
134    E!(api_classify::is_vec_alloc_constructor, dep01!(), false, none!(), eff_new_allocation),
135    E!(api_classify::is_vec_from_box,          dep0!(),  false, none!(), eff_vec_from_box),
136    E!(api_classify::is_vec_with_capacity,     dep0!(),  false, none!(), eff_new_allocation_from_cap),
137    E!(api_classify::is_into_boxed_slice,      dep0!(),  false, none!(), eff_box_from_vec),
138
139    // ── Allocator::allocate / allocate_zeroed / grow / shrink ────────
140    E!(allocator_allocate,    dep01!(), false,  none!(),  eff_allocator_allocate),
141
142    // ── Layout accessors ────────────────────────────────────────────
143    E!(layout_align,          none!(),  false,  none!(),  eff_layout_align),
144
145    // ── Layout constants ────────────────────────────────────────────
146    E!(api_classify::is_layout_constant, none!(), false,  none!(),  eff_layout_const),
147
148    // ── CStr / CString helpers ──────────────────────────────────────
149    E!(api_classify::is_cstr_from_ptr, dep0!(), false,  none!(),  eff_alias_arg0),
150    E!(api_classify::is_cstr_from_bytes_with_nul_unchecked, dep0!(), false, none!(), eff_alias_arg0),
151    E!(api_classify::is_vec_push, none!(), false,  dep0!(),  eff_write_mem),
152];
153
154pub fn lookup_dependency(
155    callee: Option<DefId>,
156    name: &str,
157    arg_count: usize,
158) -> Option<CallDependencySummary> {
159    for e in REGISTRY {
160        if (e.matches)(name) {
161            let args = if e.dep_on_all { (0..arg_count).collect() } else { e.dep_on.to_vec() };
162            return Some(CallDependencySummary {
163                callee,
164                name: name.to_string(),
165                return_depends_on_args: args,
166                may_write_args: e.writes.to_vec(),
167                unsupported: false,
168            });
169        }
170    }
171    None
172}
173
174pub fn lookup_effect<'tcx>(
175    tcx: TyCtxt<'tcx>,
176    caller: DefId,
177    callee: Option<DefId>,
178    name: &str,
179    func: &Operand<'tcx>,
180    destination: rustc_middle::mir::Local,
181) -> Option<CallEffectSummary> {
182    let dest = Some(destination);
183    for e in REGISTRY {
184        if (e.matches)(name) {
185            let ctx = EffCtx { tcx, caller, callee, name, func, dest };
186            return Some(CallEffectSummary {
187                callee,
188                name: name.to_string(),
189                destination: dest,
190                effects: (e.effects)(&ctx),
191                unsupported: false,
192            });
193        }
194    }
195    None
196}
197
198// ── Effect builders — one small function per API semantic ──────────────
199
200fn eff_none(_: &EffCtx<'_, '_>) -> Vec<CallEffect> { Vec::new() }
201
202fn eff_alias_ptr(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
203    let mut eff = vec![
204        CallEffect::ReturnPointerFromArg { arg: 0 },
205        CallEffect::ReturnNonZero,
206    ];
207    if let Some((a, n)) = pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
208        eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
209    }
210    eff
211}
212
213fn eff_alias_nonnull(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
214    let mut eff = vec![
215        CallEffect::ReturnPointerFromArg { arg: 0 },
216        CallEffect::ReturnNonZero,
217    ];
218    if let Some((a, n)) = nonnull_pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
219        eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
220    }
221    eff
222}
223
224fn eff_from_trait(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
225    if is_nonnull_dest(ctx.tcx, ctx.caller, ctx.dest) {
226        eff_alias_nonnull(ctx)
227    } else {
228        Vec::new()
229    }
230}
231
232fn eff_alias_arg0(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
233    vec![CallEffect::ReturnAliasArg { arg: 0 }]
234}
235
236fn eff_ptr_add(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
237    let stride = if api_classify::is_byte_ptr_arith(ctx.name) {
238        Some(1)
239    } else {
240        destination_stride(ctx.tcx, ctx.caller, ctx.dest)
241    };
242    vec![CallEffect::ReturnPointerAdd { base_arg: 0, offset_arg: 1, stride }]
243}
244
245fn eff_ptr_sub(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
246    let stride = if api_classify::is_byte_ptr_arith(ctx.name) {
247        Some(1)
248    } else {
249        destination_stride(ctx.tcx, ctx.caller, ctx.dest)
250    };
251    vec![CallEffect::ReturnPointerSub { base_arg: 0, offset_arg: 1, stride }]
252}
253
254fn eff_offset_from_unsigned(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
255    vec![CallEffect::ReturnOffsetFromUnsigned { self_arg: 0, origin_arg: 1 }]
256}
257
258fn eff_read_mem(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
259    vec![CallEffect::ReadMemory { arg: 0 }]
260}
261
262fn eff_write_mem(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
263    vec![CallEffect::WriteMemory { pointer_arg: 0 }]
264}
265
266fn eff_len(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
267    vec![CallEffect::ReturnLengthOfArg { arg: 0 }]
268}
269
270fn eff_is_empty(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
271    vec![CallEffect::ReturnIsEmptyOfArg { arg: 0 }]
272}
273
274fn eff_cmp_min(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
275    vec![CallEffect::ReturnMin { lhs_arg: 0, rhs_arg: 1 }]
276}
277
278fn eff_return_nonzero_iff(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
279    vec![CallEffect::ReturnNonZeroIff { arg: 0 }]
280}
281
282fn eff_return_option_some_nonzero_iff(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
283    vec![CallEffect::ReturnOptionSomeNonZeroIff { arg: 0 }]
284}
285
286fn eff_return_max(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
287    vec![CallEffect::ReturnMax { lhs_arg: 0, rhs_arg: 1 }]
288}
289
290fn eff_return_clamp(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
291    vec![CallEffect::ReturnClamp { value_arg: 0, min_arg: 1, max_arg: 2 }]
292}
293
294fn eff_return_abs(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
295    vec![CallEffect::ReturnAbs { arg: 0 }]
296}
297
298fn eff_return_neg(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
299    vec![CallEffect::ReturnNeg { arg: 0 }]
300}
301
302fn eff_return_add(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
303    vec![CallEffect::ReturnAdd { lhs_arg: 0, rhs_arg: 1 }]
304}
305
306fn eff_return_sub(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
307    vec![CallEffect::ReturnSub { lhs_arg: 0, rhs_arg: 1 }]
308}
309
310fn eff_return_mul(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
311    vec![CallEffect::ReturnMul { lhs_arg: 0, rhs_arg: 1 }]
312}
313
314fn eff_return_option_some_add(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
315    vec![CallEffect::ReturnOptionSomeAdd { lhs_arg: 0, rhs_arg: 1 }]
316}
317
318fn eff_return_option_some_mul(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
319    vec![CallEffect::ReturnOptionSomeMul { lhs_arg: 0, rhs_arg: 1 }]
320}
321
322fn eff_overflowing_nz(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
323    vec![CallEffect::ReturnTupleFieldNonZero { field: 0 }]
324}
325
326fn eff_ownership_recon(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
327    vec![
328        CallEffect::ReturnAliasArg { arg: 0 },
329        CallEffect::ReturnNonZero,
330        CallEffect::OwnsInitMemory { arg: 0 },
331    ]
332}
333
334fn eff_align_to(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
335    vec![CallEffect::ReturnAlignTo { receiver_arg: 0 }]
336}
337
338fn eff_return_iter(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
339    vec![CallEffect::ReturnIter { receiver_arg: 0 }]
340}
341
342fn eff_option_scan_index(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
343    vec![CallEffect::ReturnOptionSomeScanIndex { self_arg: 0 }]
344}
345
346fn eff_scan_length(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
347    vec![CallEffect::ReturnScanLength { ptr_arg: 0 }]
348}
349
350fn eff_align_offset(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
351    vec![CallEffect::ReturnAlignOffset { ptr_arg: 0, align_arg: 1 }]
352}
353
354fn eff_split_at(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
355    vec![
356        CallEffect::ReturnAliasArg { arg: 0 },
357        CallEffect::ReturnTupleFieldLength { field: 0, from_arg: 1 },
358    ]
359}
360
361fn eff_from_raw_parts(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
362    let elem = slice_element_size(ctx.tcx, ctx.caller, ctx.dest);
363    let mut eff = vec![
364        // ReturnAliasArg keeps the legacy PointsTo chain intact so the
365        // legacy SMT Align checker can trace through as_ptr() → reference
366        // provenance.  Without it, place_is_reference_aligned cannot
367        // prove alignment for the pointer argument.
368        CallEffect::ReturnAliasArg { arg: 0 },
369        // ReturnFreshAllocation provides the allocation-tracking hint
370        // used by the VM backend's memory model.
371        CallEffect::ReturnFreshAllocation {
372            pointer_arg: 0,
373            size_arg: 1,
374            elem_size: elem,
375        },
376        CallEffect::ReturnNonZero,
377    ];
378    if let Some((a, n)) = pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
379        eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
380    }
381    eff
382}
383
384fn eff_new_allocation(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
385    let elem = vec_element_size(ctx.tcx, ctx.caller, ctx.dest);
386    vec![
387        CallEffect::ReturnNewAllocation {
388            size_arg: 1,
389            elem_size: elem,
390        },
391    ]
392}
393
394fn eff_new_allocation_from_cap(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
395    let elem = vec_element_size(ctx.tcx, ctx.caller, ctx.dest);
396    vec![
397        CallEffect::ReturnNewAllocation {
398            size_arg: 0,
399            elem_size: elem,
400        },
401    ]
402}
403
404fn eff_vec_from_box(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
405    vec![
406        CallEffect::ReturnNewAllocationFromBox { box_arg: 0 },
407    ]
408}
409
410fn eff_allocator_allocate(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
411    vec![CallEffect::ReturnAllocBuffer]
412}
413
414fn eff_layout_align(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
415    vec![CallEffect::ReturnPowerOfTwo]
416}
417
418fn eff_forget(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
419    vec![
420        CallEffect::CleanSliceDataLinks { arg: 0 },
421    ]
422}
423
424fn eff_layout_const(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
425    layout_constant_effect(ctx.tcx, ctx.caller, ctx.func, ctx.name)
426        .into_iter()
427        .collect()
428}
429
430// ── Matcher functions (one per API pattern) ────────────────────────────
431
432fn mem_forget(n: &str) -> bool             { n.ends_with("mem::forget") }
433fn transmute(n: &str) -> bool               { n.contains("::transmute") || n.contains("intrinsics::transmute") }
434fn slice_index(n: &str) -> bool             { n.ends_with("::Index::index") || n.ends_with("::IndexMut::index_mut") }
435fn align_to_local(n: &str) -> bool           {
436    n.ends_with("align_to_ext") || n.ends_with("align_to_mut_ext")
437}
438fn into_iter_local(n: &str) -> bool          {
439    (n.contains("into_iter") && (n.contains("IntoIterator") || n.contains("slice::into_iter")))
440        || n.contains("slice::<impl [T]>::iter")
441}
442fn iter_position(n: &str) -> bool            { n.contains("Iterator::position") || n.contains("Iterator::find") }
443fn is_strlen(n: &str) -> bool                { n == "strlen" || n.ends_with("::strlen") }
444fn from_trait_call(n: &str) -> bool         { n == "std::convert::From::from" || n == "core::convert::From::from" }
445fn nonnull_from(n: &str) -> bool            { n.ends_with("::from") && api_classify::is_nonnull_api(n) }
446fn nonnull_new_unchecked(n: &str) -> bool   { n.ends_with("::new_unchecked") && api_classify::is_nonnull_api(n) }
447fn nonnull_new(n: &str) -> bool             { n.ends_with("::new") && api_classify::is_nonnull_api(n) && !n.ends_with("::new_unchecked") }
448fn nonnull_as_ref(n: &str) -> bool          { n.ends_with("::as_ref") && api_classify::is_nonnull_api(n) }
449fn nonnull_as_mut(n: &str) -> bool          { n.ends_with("::as_mut") && api_classify::is_nonnull_api(n) }
450fn ptr_read(n: &str) -> bool                { n.ends_with("::read") && n.contains("::ptr::") }
451fn is_empty(n: &str) -> bool                { n.ends_with("::is_empty") }
452fn cmp_min(n: &str) -> bool                 { (n.contains("::cmp::min") || n.contains("::Ord::min") || n.starts_with("core::cmp::min")) && !n.contains("min_by") }
453
454/// Bit-preserving integer operations: rotations, byte/bit reversals,
455/// endianness conversions, popcount, integer square root and saturating power
456/// all map `0` to `0` and non-zero to non-zero, so the result is non-zero
457/// *iff* the operand is (`ReturnNonZeroIff`).
458fn bit_preserving_nz(n: &str) -> bool {
459    n.contains("::rotate_left")
460        || n.contains("::rotate_right")
461        || n.contains("::swap_bytes")
462        || n.contains("::reverse_bits")
463        || n.contains("::from_be")
464        || n.contains("::from_le")
465        || n.contains("::to_be")
466        || n.contains("::to_le")
467        || n.contains("::count_ones")
468        || n.contains("::isqrt")
469        || n.contains("::saturating_pow")
470}
471
472/// `checked_pow` returns `Option<T>` whose `Some` payload is non-zero iff the
473/// base is non-zero (`ReturnOptionSomeNonZeroIff`).
474fn checked_pow_nz(n: &str) -> bool {
475    n.ends_with("::checked_pow")
476}
477
478/// Comparison / absolute-value / negation / saturating & unchecked arithmetic
479/// operations. Each is modelled with a precise expression over its operands
480/// (see the `eff_return_*` builders) so non-zero-ness is discharged
481/// *conditionally* — only when the operands are actually non-zero.
482fn int_max(n: &str) -> bool { n.ends_with("::max") }
483fn int_clamp(n: &str) -> bool { n.ends_with("::clamp") }
484fn int_abs(n: &str) -> bool {
485    n.ends_with("::abs")
486        || n.ends_with("::saturating_abs")
487        || n.ends_with("::wrapping_abs")
488        || n.ends_with("::unsigned_abs")
489}
490fn int_neg(n: &str) -> bool {
491    n.ends_with("::neg")
492        || n.ends_with("::wrapping_neg")
493        || n.ends_with("::saturating_neg")
494}
495fn int_add(n: &str) -> bool {
496    n.ends_with("::saturating_add") || n.ends_with("::unchecked_add")
497}
498fn int_mul(n: &str) -> bool {
499    n.ends_with("::saturating_mul") || n.ends_with("::unchecked_mul")
500}
501fn int_checked_add(n: &str) -> bool { n.ends_with("::checked_add") }
502fn int_checked_mul(n: &str) -> bool { n.ends_with("::checked_mul") }
503
504/// `overflowing_abs` / `overflowing_neg` return `(result, overflow)` where the
505/// `result` field (0) is non-zero whenever the operand is non-zero.  Model the
506/// field 0 as non-zero (`ReturnTupleFieldNonZero { field: 0 }`).
507fn overflowing_nz(n: &str) -> bool {
508    n.ends_with("::overflowing_abs") || n.ends_with("::overflowing_neg")
509}
510fn allocator_allocate(n: &str) -> bool      {
511    n.ends_with("::Allocator::allocate")
512        || n.ends_with("::Allocator::allocate_zeroed")
513        || n.ends_with("::Allocator::grow")
514        || n.ends_with("::Allocator::shrink")
515}
516fn layout_align(n: &str) -> bool            { n.ends_with("Layout::align") && !n.ends_with("Layout::alignment") }
517fn saturating_sub(n: &str) -> bool          { n.contains("::saturating_sub") }
518fn split_at(n: &str) -> bool                { n.contains("::split_at") }
519fn is_slice_get_unchecked(n: &str) -> bool   { 
520    (n.contains("::get_unchecked") || n.contains("::get_unchecked_mut"))
521        && (n.contains("::SliceIndex")
522            || n.contains("::<impl [T]>::get_unchecked")
523            || n.contains("::impl [T]>::get_unchecked")
524            || n.contains("::mut_ptr::get_unchecked")
525            || n.contains("::const_ptr::get_unchecked"))
526}
527
528// ── Layout helpers (used by effect builders) ─────────────────────────
529
530fn nonnull_pointee_alignment<'tcx>(
531    tcx: TyCtxt<'tcx>, caller: DefId, dest: Option<rustc_middle::mir::Local>,
532) -> Option<(u64, String)> {
533    let d = dest?;
534    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
535    let pointee = nonnull_inner_ty(tcx, ty)?;
536    type_layout(tcx, caller, pointee).map(|(a, _)| (a, format!("{pointee:?}")))
537}
538
539fn is_nonnull_dest(tcx: TyCtxt<'_>, caller: DefId, dest: Option<rustc_middle::mir::Local>) -> bool {
540    let Some(d) = dest else { return false };
541    nonnull_inner_ty(tcx, tcx.optimized_mir(caller).local_decls[d].ty).is_some()
542}
543
544fn layout_call_ty<'tcx>(func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
545    let Operand::Constant(c) = func else { return None };
546    let TyKind::FnDef(_, args) = c.const_.ty().kind() else { return None };
547    args.iter().find_map(|a| {
548        #[cfg(rapx_ge_99)] let a = a.skip_binder();
549        match a.kind() { GenericArgKind::Type(t) => Some(t), _ => None }
550    })
551}
552
553fn layout_constant_effect<'tcx>(
554    tcx: TyCtxt<'tcx>, caller: DefId, func: &Operand<'tcx>, name: &str,
555) -> Option<CallEffect> {
556    let ty = layout_call_ty(func)?;
557    let (align, size) = type_layout(tcx, caller, ty)?;
558    if name.contains("align_of") {
559        Some(CallEffect::ReturnConst { value: align, label: format!("align_of::<{ty:?}>()") })
560    } else if name.contains("size_of") {
561        Some(CallEffect::ReturnConst { value: size, label: format!("size_of::<{ty:?}>()") })
562    } else {
563        None
564    }
565}
566
567fn eff_box_from_vec(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
568    vec![CallEffect::ReturnBoxFromVec { arg: 0 }]
569}