Skip to main content

miri/shims/
sig.rs

1//! Everything related to checking the signature of shim invocations.
2
3use rustc_abi::ExternAbi;
4use rustc_middle::ty::{Binder, FnSig, FnSigKind, Ty};
5use rustc_span::Symbol;
6use rustc_target::callconv::FnAbi;
7
8use crate::*;
9
10/// Describes the expected signature of a shim.
11pub struct ShimSig<'tcx, const ARGS: usize> {
12    pub abi: ExternAbi,
13    pub args: [Ty<'tcx>; ARGS],
14    pub ret: Ty<'tcx>,
15    pub c_variadic: bool,
16}
17
18/// Construct a `ShimSig` with convenient syntax:
19/// ```rust,ignore
20/// shim_sig!(extern "C" fn (*const T, i32) -> usize)
21/// ```
22///
23/// The following types are supported:
24/// - primitive integer and floating-point types
25/// - `()`
26/// - (thin) raw pointers, written `*_` since the mutability and pointee type are irrelevant
27/// - `$crate::$mod::...::$ty` for a type from the given crate (most commonly that is `libc`)
28/// - `winapi::$ty` for a type from `std::sys::pal::windows::c`
29#[macro_export]
30macro_rules! shim_sig {
31    (extern $abi:literal fn($($args:tt)*) -> $($ret:tt)*) => {
32        |ecx| {
33            let (args, c_variadic) = shim_sig_args_sep!(ecx, [$($args)*]);
34            $crate::shims::sig::ShimSig {
35                abi: std::str::FromStr::from_str($abi).expect("incorrect abi specified"),
36                args,
37                ret: shim_sig_arg!(ecx, $($ret)*),
38                c_variadic,
39            }
40        }
41    };
42}
43
44/// Computes a list of types for varargs, using the same syntax as `shim_sig!`.
45#[macro_export]
46macro_rules! shim_varargs {
47    ($($args:tt)*) => {
48        |ecx| {
49            let (args, c_variadic) = shim_sig_args_sep!(ecx, [$($args)*]);
50            assert!(!c_variadic); // don't accept `...` here
51            args
52        }
53    };
54}
55
56/// Helper for `shim_sig!`.
57///
58/// Groups tokens into comma-separated chunks and calls the provided macro on them.
59/// Returns a list of types and a boolean indicating whether there was a trailing `...`.
60///
61/// # Examples
62///
63/// ```ignore
64/// shim_sig_args_sep!(ecx, [*_, i32, libc::off64_t]);
65/// // expands to:
66/// [shim_sig_arg!(*_), shim_sig_arg!(i32), shim_sig_arg!(libc::off64_t)];
67/// ```
68#[macro_export]
69macro_rules! shim_sig_args_sep {
70    ($ecx:ident, [$($tt:tt)*]) => {
71        shim_sig_args_sep!(@ $ecx [] [] $($tt)*)
72    };
73
74    // All below matchers form a fairly simple iterator over the input.
75    // - Non-comma token - append to collector
76    // - Comma token - call the provided macro on the collector and reset the collector
77    // - End of input - empty collector one last time. emit output as an array
78
79    // Handles `,` token - take collected type and call shim_sig_arg on it.
80    // Append the result to the final output.
81    (@ $ecx:ident [$($final:tt)*] [$($collected:tt)*] , $($tt:tt)*) => {
82        shim_sig_args_sep!(@ $ecx [$($final)* shim_sig_arg!($ecx, $($collected)*), ] [] $($tt)*)
83    };
84    // Handle non-comma token - append to collected type.
85    (@ $ecx:ident [$($final:tt)*] [$($collected:tt)*] $first:tt $($tt:tt)*) => {
86        shim_sig_args_sep!(@ $ecx [$($final)*] [$($collected)* $first] $($tt)*)
87    };
88    // No more tokens, trailing `...` - emit final output, indicate ecx is variadic.
89    (@ $ecx:ident [$($final:tt)*] [...] ) => {
90        ([$($final)*], true)
91    };
92    // No more tokens - emit final output, including final non-comma type.
93    (@ $ecx:ident [$($final:tt)*] [$($collected:tt)+] ) => {
94        ([$($final)* shim_sig_arg!($ecx, $($collected)*)], false)
95    };
96    // No more tokens, empty collector - emit final output.
97    (@ $ecx:ident [$($final:tt)*] [] ) => {
98        ([$($final)*], false)
99    };
100}
101
102/// Helper for `shim_sig!`.
103///
104/// Converts a type
105#[macro_export]
106macro_rules! shim_sig_arg {
107    ($ecx:ident, i8) => {
108        $ecx.tcx.types.i8
109    };
110    ($ecx:ident, i16) => {
111        $ecx.tcx.types.i16
112    };
113    ($ecx:ident, i32) => {
114        $ecx.tcx.types.i32
115    };
116    ($ecx:ident, i64) => {
117        $ecx.tcx.types.i64
118    };
119    ($ecx:ident, i128) => {
120        $ecx.tcx.types.i128
121    };
122    ($ecx:ident, isize) => {
123        $ecx.tcx.types.isize
124    };
125    ($ecx:ident, u8) => {
126        $ecx.tcx.types.u8
127    };
128    ($ecx:ident, u16) => {
129        $ecx.tcx.types.u16
130    };
131    ($ecx:ident, u32) => {
132        $ecx.tcx.types.u32
133    };
134    ($ecx:ident, u64) => {
135        $ecx.tcx.types.u64
136    };
137    ($ecx:ident, u128) => {
138        $ecx.tcx.types.u128
139    };
140    ($ecx:ident, usize) => {
141        $ecx.tcx.types.usize
142    };
143    ($ecx:ident, f32) => {
144        $ecx.tcx.types.f32
145    };
146    ($ecx:ident, f64) => {
147        $ecx.tcx.types.f64
148    };
149    ($ecx:ident, ()) => {
150        $ecx.tcx.types.unit
151    };
152    ($ecx:ident, !) => {
153        $ecx.tcx.types.never
154    };
155    ($ecx:ident, bool) => {
156        $ecx.tcx.types.bool
157    };
158    ($ecx:ident, *_) => {
159        // Mutability does not matter for ABI.
160        $ecx.machine.layouts.void_ptr_mut.ty
161    };
162    ($ecx:ident, *$($ty:tt)*) => {
163        // Pointee types matter for varargs so we support explicitly giving them.
164        // Mutability does not matter for ABI.
165        rustc_middle::ty::Ty::new_ptr(
166            *$ecx.tcx,
167            shim_sig_arg!($ecx, $($ty)*),
168            rustc_middle::mir::Mutability::Mut,
169        )
170    };
171    ($ecx:ident, fn(..) -> _) => {
172        // All function pointer types are the same on the ABI level so we don't care about arg/ret.
173        $ecx.machine.layouts.fn_ptr.ty
174    };
175    ($ecx:ident, &[$($ty:tt)*]) => {
176        rustc_middle::ty::Ty::new_ref(
177            *$ecx.tcx,
178            $ecx.tcx.lifetimes.re_erased,
179            rustc_middle::ty::Ty::new_slice(*$ecx.tcx, shim_sig_arg!($ecx, $($ty)*)),
180            rustc_middle::mir::Mutability::Not,
181        )
182    };
183    ($ecx:ident, winapi::$ty:ident) => {
184        $ecx.windows_ty_layout(stringify!($ty)).ty
185    };
186    ($ecx:ident, $krate:ident :: $($path:ident)::+) => {
187        helpers::path_ty_layout($ecx, &[stringify!($krate), $(stringify!($path)),*]).ty
188    };
189    ($ecx:ident, $($other:tt)*) => {
190        compile_error!(concat!("unsupported signature type: ", stringify!($($other)*)))
191    }
192}
193
194impl<'tcx, const ARGS: usize> ShimSig<'tcx, ARGS> {
195    fn as_abi(&self, ecx: &MiriInterpCx<'tcx>) -> &FnAbi<'tcx, Ty<'tcx>> {
196        let mut inputs_and_output = Vec::with_capacity(ARGS.strict_add(1));
197        inputs_and_output.extend(&self.args);
198        inputs_and_output.push(self.ret);
199        let fn_sig_binder = Binder::dummy(FnSig {
200            inputs_and_output: ecx.machine.tcx.mk_type_list(&inputs_and_output),
201            fn_sig_kind: FnSigKind::default().set_c_variadic(self.c_variadic).set_abi(self.abi),
202        });
203        ecx.fn_abi_of_fn_ptr(fn_sig_binder, Default::default()).unwrap()
204    }
205}
206
207/// Helper function to compare two ABIs.
208fn check_shim_abi<'tcx>(
209    ecx: &MiriInterpCx<'tcx>,
210    link_name: Symbol,
211    callee_abi: &FnAbi<'tcx, Ty<'tcx>>,
212    caller_abi: &FnAbi<'tcx, Ty<'tcx>>,
213) -> InterpResult<'tcx> {
214    if callee_abi.conv != caller_abi.conv {
215        throw_ub_format!(
216            r#"ABI mismatch: `{link_name}` has calling convention "{callee}", but the caller is using calling convention "{caller}""#,
217            callee = callee_abi.conv,
218            caller = caller_abi.conv,
219        );
220    }
221    // No need to check unwinding: if the caller signature forbids unwinding, that's already
222    // reflected in the unwind destination so if an unwind occurs it will be reported as UB.
223
224    if caller_abi.c_variadic && !callee_abi.c_variadic {
225        throw_ub_format!(
226            "ABI mismatch: `{link_name}` is a non-variadic function, but the caller is using a c-variadic signature"
227        );
228    }
229    if !caller_abi.c_variadic && callee_abi.c_variadic {
230        throw_ub_format!(
231            "ABI mismatch: `{link_name}` is a c-variadic function, but the caller is using a non-variadic signature"
232        );
233    }
234
235    if callee_abi.fixed_count != caller_abi.fixed_count {
236        throw_ub_format!(
237            "ABI mismatch: calling `{link_name}` which takes {} {}argument{}, but {} argument{} given",
238            callee_abi.fixed_count,
239            if callee_abi.c_variadic { "fixed (non-variadic) " } else { "" },
240            if callee_abi.fixed_count == 1 { "" } else { "s" },
241            caller_abi.fixed_count,
242            if caller_abi.fixed_count == 1 { " was" } else { "s were" },
243        );
244    }
245
246    if !ecx.check_argument_compat(&caller_abi.ret, &callee_abi.ret)? {
247        throw_ub!(AbiMismatchReturn {
248            caller_ty: caller_abi.ret.layout.ty,
249            callee_ty: callee_abi.ret.layout.ty
250        });
251    }
252
253    for (idx, (caller_arg, callee_arg)) in
254        caller_abi.args.iter().zip(callee_abi.args.iter()).enumerate()
255    {
256        if !ecx.check_argument_compat(caller_arg, callee_arg)? {
257            throw_ub!(AbiMismatchArgument {
258                arg_idx: idx,
259                caller_ty: caller_abi.args[idx].layout.ty,
260                callee_ty: callee_abi.args[idx].layout.ty
261            });
262        }
263    }
264
265    interp_ok(())
266}
267
268/// Represents a tail of variadic arguments that have not yet been checked.
269// Deliberately not `Copy` so that we don't consume the same vararg multiple times accidentally.
270pub struct Varargs<'tcx, 'a> {
271    args: &'a [OpTy<'tcx>],
272    /// Number of arguments (variadic and fixed) that have already been taken, for error messages.
273    already_gone: usize,
274}
275
276impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
277pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
278    /// Ensure the given symbol is not exported by the program.
279    fn check_shim_symbol_clash(&self, link_name: Symbol) -> InterpResult<'tcx, ()> {
280        let this = self.eval_context_ref();
281        if let Some(instance) = this.lookup_exported_symbol(link_name)? {
282            // If compiler-builtins is providing the symbol, then don't treat it as a clash.
283            // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
284            // performance. Note that this means we won't catch any undefined behavior in
285            // compiler-builtins when running other crates, but Miri can still be run on
286            // compiler-builtins itself (or any crate that uses it as a normal dependency)
287            if this.tcx.is_compiler_builtins(instance.def_id().krate) {
288                return interp_ok(());
289            }
290
291            throw_machine_stop!(TerminationInfo::SymbolShimClashing {
292                link_name,
293                span: this.tcx.def_span(instance.def_id()),
294            })
295        }
296        interp_ok(())
297    }
298
299    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
300    /// then returns the list of arguments, transmuted to the expected (shim) types.
301    fn check_shim_sig<const N: usize>(
302        &self,
303        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
304        // We take these as a tuple so that this takes less space on the caller side.
305        (link_name, caller_fn_abi, caller_args): (Symbol, &FnAbi<'tcx, Ty<'tcx>>, &[OpTy<'tcx>]),
306    ) -> InterpResult<'tcx, [OpTy<'tcx>; N]> {
307        let this = self.eval_context_ref();
308
309        // Compute callee ABI.
310        let shim_sig = shim_sig(this);
311        assert!(!shim_sig.c_variadic);
312        let callee_fn_abi = shim_sig.as_abi(this);
313
314        // Check everything.
315        check_shim_abi(this, link_name, callee_fn_abi, caller_fn_abi)?;
316        this.check_shim_symbol_clash(link_name)?;
317
318        // Return arguments.
319        let transmuted = std::array::try_from_fn(|i| {
320            caller_args[i].transmute(callee_fn_abi.args[i].layout, this)
321        })?;
322        interp_ok(transmuted)
323    }
324
325    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
326    /// then returns the list of fixed and variadic arguments in separate lists.
327    /// The fixed arguments are transmuted to the expected (shim) types.
328    fn check_shim_sig_variadic<'a, const N: usize>(
329        &self,
330        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
331        // We take these as a tuple so that this takes less space on the caller side.
332        (link_name, caller_fn_abi, caller_args): (Symbol, &FnAbi<'tcx, Ty<'tcx>>, &'a [OpTy<'tcx>]),
333    ) -> InterpResult<'tcx, ([OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
334        let this = self.eval_context_ref();
335
336        // Compute callee ABI.
337        let shim_sig = shim_sig(this);
338        assert!(shim_sig.c_variadic);
339        let callee_fn_abi = shim_sig.as_abi(this);
340
341        // Check everything.
342        check_shim_abi(this, link_name, callee_fn_abi, caller_fn_abi)?;
343        this.check_shim_symbol_clash(link_name)?;
344
345        // Return transmuted arguments.
346        if let Some((fixed, var)) = caller_args.split_first_chunk::<N>() {
347            let transmuted = std::array::try_from_fn(|i| {
348                fixed[i].transmute(callee_fn_abi.args[i].layout, this)
349            })?;
350            return interp_ok((transmuted, Varargs { args: var, already_gone: N }));
351        }
352        unreachable!()
353    }
354
355    /// Fetches `N` arguments from `varargs`, checking their types.
356    /// Also returns the remaining varargs.
357    /// The returned arguments are transmuted to the expected types.
358    fn check_varargs<'a, const N: usize>(
359        &self,
360        tys: fn(&MiriInterpCx<'tcx>) -> [Ty<'tcx>; N],
361        varargs: Varargs<'tcx, 'a>,
362        fn_name: &str,
363    ) -> InterpResult<'tcx, ([OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
364        let this = self.eval_context_ref();
365        let tys = tys(this);
366
367        let Some((now, tail)) = varargs.args.split_first_chunk::<N>() else {
368            throw_ub_format!(
369                "not enough arguments for `{fn_name}`: got {}, expected at least {}",
370                varargs.already_gone.strict_add(varargs.args.len()),
371                varargs.already_gone.strict_add(N),
372            )
373        };
374
375        for (n, (caller_gave, callee_expected)) in now.iter().zip(tys).enumerate() {
376            // Check ABI compatibility.
377            let compatible =
378                this.validate_c_variadic_compatible_ty(caller_gave.layout.ty, callee_expected)?;
379            match compatible {
380                VarArgCompatible::Compatible => {}
381                VarArgCompatible::Incompatible => {
382                    throw_ub_format!(
383                        "incorrect c-variadic argument type for `{fn_name}`: \
384                        expected argument #{n} to have type `{callee_expected}` but got incompatible type `{caller_ty}`",
385                        n = varargs.already_gone.strict_add(n).strict_add(1),
386                        caller_ty = caller_gave.layout.ty,
387                    );
388                }
389                VarArgCompatible::CastIntTo { source_is_signed } => {
390                    // Check that the value can be represented in the target type.
391                    let size = caller_gave.layout.size;
392                    let scalar = this.read_scalar(caller_gave)?;
393                    if scalar.to_int(size)? < 0 {
394                        throw_ub_format!(
395                            "incorrect c-variadic argument type for `{fn_name}`: \
396                            argument #{n} has value `{value}_{caller_ty}` which cannot be represented in expected type `{callee_expected}`",
397                            n = varargs.already_gone.strict_add(n).strict_add(1),
398                            caller_ty = caller_gave.layout.ty,
399                            value = if source_is_signed {
400                                scalar.to_int(size)?.to_string()
401                            } else {
402                                scalar.to_uint(size)?.to_string()
403                            }
404                        )
405                    }
406                }
407            }
408        }
409
410        let transmuted = std::array::try_from_fn(|i| {
411            let layout = this.layout_of(tys[i])?;
412            now[i].transmute(layout, this)
413        })?;
414
415        interp_ok((
416            transmuted,
417            Varargs { args: tail, already_gone: varargs.already_gone.strict_add(N) },
418        ))
419    }
420
421    /// Check that the given function has the expected amount of arguments, and then
422    /// return the list of arguments.
423    ///
424    /// This may only be used for `extern "llvm-intrinsic"` LLVM intrinsics.
425    fn check_shim_sig_llvm_intrinsic<'a, const N: usize>(
426        &mut self,
427        link_name: Symbol,
428        args: &'a [OpTy<'tcx>],
429    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
430        assert!(link_name.as_str().starts_with("llvm."));
431
432        self.check_shim_symbol_clash(link_name)?;
433
434        if let Ok(ops) = args.try_into() {
435            return interp_ok(ops);
436        }
437        throw_ub_format!(
438            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
439            args.len(),
440            N
441        )
442    }
443}