Skip to main content

miri/shims/
sig.rs

1//! Everything related to checking the signature of shim invocations.
2
3use rustc_abi::{CanonAbi, 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 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        |this| {
33            let (args, c_variadic) = shim_sig_args_sep!(this, [$($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!(this, $($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        |this| {
49            let (args, c_variadic) = shim_sig_args_sep!(this, [$($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!(this, [*_, 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    ($this:ident, [$($tt:tt)*]) => {
71        shim_sig_args_sep!(@ $this [] [] $($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    (@ $this:ident [$($final:tt)*] [$($collected:tt)*] , $($tt:tt)*) => {
82        shim_sig_args_sep!(@ $this [$($final)* shim_sig_arg!($this, $($collected)*), ] [] $($tt)*)
83    };
84    // Handle non-comma token - append to collected type.
85    (@ $this:ident [$($final:tt)*] [$($collected:tt)*] $first:tt $($tt:tt)*) => {
86        shim_sig_args_sep!(@ $this [$($final)*] [$($collected)* $first] $($tt)*)
87    };
88    // No more tokens, trailing `...` - emit final output, indicate this is variadic.
89    (@ $this:ident [$($final:tt)*] [...] ) => {
90        ([$($final)*], true)
91    };
92    // No more tokens - emit final output, including final non-comma type.
93    (@ $this:ident [$($final:tt)*] [$($collected:tt)+] ) => {
94        ([$($final)* shim_sig_arg!($this, $($collected)*)], false)
95    };
96    // No more tokens, empty collector - emit final output.
97    (@ $this: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    ($this:ident, i8) => {
108        $this.tcx.types.i8
109    };
110    ($this:ident, i16) => {
111        $this.tcx.types.i16
112    };
113    ($this:ident, i32) => {
114        $this.tcx.types.i32
115    };
116    ($this:ident, i64) => {
117        $this.tcx.types.i64
118    };
119    ($this:ident, i128) => {
120        $this.tcx.types.i128
121    };
122    ($this:ident, isize) => {
123        $this.tcx.types.isize
124    };
125    ($this:ident, u8) => {
126        $this.tcx.types.u8
127    };
128    ($this:ident, u16) => {
129        $this.tcx.types.u16
130    };
131    ($this:ident, u32) => {
132        $this.tcx.types.u32
133    };
134    ($this:ident, u64) => {
135        $this.tcx.types.u64
136    };
137    ($this:ident, u128) => {
138        $this.tcx.types.u128
139    };
140    ($this:ident, usize) => {
141        $this.tcx.types.usize
142    };
143    ($this:ident, ()) => {
144        $this.tcx.types.unit
145    };
146    ($this:ident, !) => {
147        $this.tcx.types.never
148    };
149    ($this:ident, bool) => {
150        $this.tcx.types.bool
151    };
152    ($this:ident, *_) => {
153        // Pointee types usually don't matter so we allow it to be omitted.
154        // Mutability does not matter for ABI.
155        $this.machine.layouts.mut_raw_ptr.ty
156    };
157    ($this:ident, *$($ty:tt)*) => {
158        // Pointee types matter for varargs so we support explicitly giving them.
159        // Mutability does not matter for ABI.
160        rustc_middle::ty::Ty::new_ptr(
161            *$this.tcx,
162            shim_sig_arg!($this, $($ty)*),
163            rustc_middle::mir::Mutability::Mut,
164        )
165    };
166    ($this:ident, fn(..) -> _) => {
167        // We currently treat fn ptrs as ABI-compatible with data ptrs so we can just use a raw ptr.
168        $this.machine.layouts.const_raw_ptr.ty
169    };
170    ($this:ident, &[$($ty:tt)*]) => {
171        rustc_middle::ty::Ty::new_ref(
172            *$this.tcx,
173            $this.tcx.lifetimes.re_erased,
174            rustc_middle::ty::Ty::new_slice(*$this.tcx, shim_sig_arg!($this, $($ty)*)),
175            rustc_middle::mir::Mutability::Not,
176        )
177    };
178    ($this:ident, winapi::$ty:ident) => {
179        $this.windows_ty_layout(stringify!($ty)).ty
180    };
181    ($this:ident, $krate:ident :: $($path:ident)::+) => {
182        helpers::path_ty_layout($this, &[stringify!($krate), $(stringify!($path)),*]).ty
183    };
184    ($this:ident, $($other:tt)*) => {
185        compile_error!(concat!("unsupported signature type: ", stringify!($($other)*)))
186    }
187}
188
189impl<'tcx, const ARGS: usize> ShimSig<'tcx, ARGS> {
190    fn as_abi(&self, ecx: &MiriInterpCx<'tcx>) -> &FnAbi<'tcx, Ty<'tcx>> {
191        let mut inputs_and_output = Vec::with_capacity(ARGS.strict_add(1));
192        inputs_and_output.extend(&self.args);
193        inputs_and_output.push(self.ret);
194        let fn_sig_binder = Binder::dummy(FnSig {
195            inputs_and_output: ecx.machine.tcx.mk_type_list(&inputs_and_output),
196            fn_sig_kind: FnSigKind::default().set_c_variadic(self.c_variadic).set_abi(self.abi),
197        });
198        ecx.fn_abi_of_fn_ptr(fn_sig_binder, Default::default()).unwrap()
199    }
200}
201
202/// Helper function to compare two ABIs.
203fn check_shim_abi<'tcx>(
204    this: &MiriInterpCx<'tcx>,
205    link_name: Symbol,
206    callee_abi: &FnAbi<'tcx, Ty<'tcx>>,
207    caller_abi: &FnAbi<'tcx, Ty<'tcx>>,
208) -> InterpResult<'tcx> {
209    if callee_abi.conv != caller_abi.conv {
210        throw_ub_format!(
211            r#"ABI mismatch: `{link_name}` has calling convention "{callee}", but the caller is using calling convention "{caller}""#,
212            callee = callee_abi.conv,
213            caller = caller_abi.conv,
214        );
215    }
216    // No need to check unwinding: if the caller signature forbids unwinding, that's already
217    // reflected in the unwind destination so if an unwind occurs it will be reported as UB.
218
219    if caller_abi.c_variadic && !callee_abi.c_variadic {
220        throw_ub_format!(
221            "ABI mismatch: `{link_name}` is a non-variadic function, but the caller is using a c-variadic signature"
222        );
223    }
224    if !caller_abi.c_variadic && callee_abi.c_variadic {
225        throw_ub_format!(
226            "ABI mismatch: `{link_name}` is a c-variadic function, but the caller is using a non-variadic signature"
227        );
228    }
229
230    if callee_abi.fixed_count != caller_abi.fixed_count {
231        throw_ub_format!(
232            "ABI mismatch: calling `{link_name}` which takes {} {}argument{}, but {} argument{} given",
233            callee_abi.fixed_count,
234            if callee_abi.c_variadic { "fixed (non-variadic) " } else { "" },
235            if callee_abi.fixed_count == 1 { "" } else { "s" },
236            caller_abi.fixed_count,
237            if caller_abi.fixed_count == 1 { " was" } else { "s were" },
238        );
239    }
240
241    if !this.check_argument_compat(&caller_abi.ret, &callee_abi.ret)? {
242        throw_ub!(AbiMismatchReturn {
243            caller_ty: caller_abi.ret.layout.ty,
244            callee_ty: callee_abi.ret.layout.ty
245        });
246    }
247
248    for (idx, (caller_arg, callee_arg)) in
249        caller_abi.args.iter().zip(callee_abi.args.iter()).enumerate()
250    {
251        if !this.check_argument_compat(caller_arg, callee_arg)? {
252            throw_ub!(AbiMismatchArgument {
253                arg_idx: idx,
254                caller_ty: caller_abi.args[idx].layout.ty,
255                callee_ty: callee_abi.args[idx].layout.ty
256            });
257        }
258    }
259
260    interp_ok(())
261}
262
263/// Represents a tail of variadic arguments that have not yet been checked.
264// Deliberately not `Copy` so that we don't consume the same vararg multiple times accidentally.
265pub struct Varargs<'tcx, 'a> {
266    args: &'a [OpTy<'tcx>],
267    /// Number of arguments (variadic and fixed) that have already been taken, for error messages.
268    already_gone: usize,
269}
270
271impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
272pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
273    /// Ensure the given symbol is not exported by the program.
274    fn check_shim_symbol_clash(&self, link_name: Symbol) -> InterpResult<'tcx, ()> {
275        let this = self.eval_context_ref();
276        if let Some(instance) = this.lookup_exported_symbol(link_name)? {
277            // If compiler-builtins is providing the symbol, then don't treat it as a clash.
278            // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
279            // performance. Note that this means we won't catch any undefined behavior in
280            // compiler-builtins when running other crates, but Miri can still be run on
281            // compiler-builtins itself (or any crate that uses it as a normal dependency)
282            if this.tcx.is_compiler_builtins(instance.def_id().krate) {
283                return interp_ok(());
284            }
285
286            throw_machine_stop!(TerminationInfo::SymbolShimClashing {
287                link_name,
288                span: this.tcx.def_span(instance.def_id()).data(),
289            })
290        }
291        interp_ok(())
292    }
293
294    /// 'Lenient' signature check. Deprecated; use `check_shim_sig` instead.
295    fn check_shim_sig_deprecated<'a, const N: usize>(
296        &mut self,
297        abi: &FnAbi<'tcx, Ty<'tcx>>,
298        exp_abi: CanonAbi,
299        link_name: Symbol,
300        args: &'a [OpTy<'tcx>],
301    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
302        self.check_shim_symbol_clash(link_name)?;
303
304        if abi.conv != exp_abi {
305            throw_ub_format!(
306                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
307                abi.conv
308            );
309        }
310        if abi.c_variadic {
311            throw_ub_format!(
312                "calling a non-variadic function with a c-variadic caller-side signature"
313            );
314        }
315
316        if let Ok(ops) = args.try_into() {
317            return interp_ok(ops);
318        }
319        throw_ub_format!(
320            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
321            args.len(),
322            N
323        )
324    }
325
326    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
327    /// then returns the list of arguments.
328    fn check_shim_sig<'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, &'a [OpTy<'tcx>; N]> {
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 arguments.
346        if let Ok(ops) = caller_args.try_into() {
347            return interp_ok(ops);
348        }
349        unreachable!()
350    }
351
352    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
353    /// then returns the list of fixed and variadic arguments in separate lists.
354    fn check_shim_sig_variadic<'a, const N: usize>(
355        &self,
356        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
357        // We take these as a tuple so that this takes less space on the caller side.
358        (link_name, caller_fn_abi, caller_args): (Symbol, &FnAbi<'tcx, Ty<'tcx>>, &'a [OpTy<'tcx>]),
359    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
360        let this = self.eval_context_ref();
361
362        // Compute callee ABI.
363        let shim_sig = shim_sig(this);
364        assert!(shim_sig.c_variadic);
365        let callee_fn_abi = shim_sig.as_abi(this);
366
367        // Check everything.
368        check_shim_abi(this, link_name, callee_fn_abi, caller_fn_abi)?;
369        this.check_shim_symbol_clash(link_name)?;
370
371        // Return arguments.
372        if let Some((fixed, var)) = caller_args.split_first_chunk() {
373            return interp_ok((fixed, Varargs { args: var, already_gone: N }));
374        }
375        unreachable!()
376    }
377
378    /// Fetches `N` arguments from `varargs`, checking their types.
379    /// Also returns the remaining varargs.
380    fn check_varargs<'a, const N: usize>(
381        &self,
382        tys: fn(&MiriInterpCx<'tcx>) -> [Ty<'tcx>; N],
383        varargs: Varargs<'tcx, 'a>,
384        fn_name: &str,
385    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
386        let this = self.eval_context_ref();
387        let tys = tys(this);
388
389        let Some((now, tail)) = varargs.args.split_first_chunk::<N>() else {
390            throw_ub_format!(
391                "not enough arguments for `{fn_name}`: got {}, expected at least {}",
392                varargs.already_gone.strict_add(varargs.args.len()),
393                varargs.already_gone.strict_add(N),
394            )
395        };
396
397        for (n, (caller_gave, callee_expected)) in now.iter().zip(tys).enumerate() {
398            // Check ABI compatibility.
399            let compatible =
400                this.validate_c_variadic_compatible_ty(caller_gave.layout.ty, callee_expected)?;
401            match compatible {
402                VarArgCompatible::Compatible => {}
403                VarArgCompatible::Incompatible => {
404                    throw_ub_format!(
405                        "incorrect c-variadic argument type for `{fn_name}`: \
406                        expected argument #{n} to have type `{callee_expected}` but got incompatible type `{caller_ty}`",
407                        n = varargs.already_gone.strict_add(n).strict_add(1),
408                        caller_ty = caller_gave.layout.ty,
409                    );
410                }
411                VarArgCompatible::CastIntTo { source_is_signed } => {
412                    // Check that the value can be represented in the target type.
413                    let size = caller_gave.layout.size;
414                    let scalar = this.read_scalar(caller_gave)?;
415                    if scalar.to_int(size)? < 0 {
416                        throw_ub_format!(
417                            "incorrect c-variadic argument type for `{fn_name}`: \
418                            argument #{n} has value `{value}_{caller_ty}` which cannot be represented in expected type `{callee_expected}`",
419                            n = varargs.already_gone.strict_add(n).strict_add(1),
420                            caller_ty = caller_gave.layout.ty,
421                            value = if source_is_signed {
422                                scalar.to_int(size)?.to_string()
423                            } else {
424                                scalar.to_uint(size)?.to_string()
425                            }
426                        )
427                    }
428                }
429            }
430        }
431
432        interp_ok((now, Varargs { args: tail, already_gone: varargs.already_gone.strict_add(N) }))
433    }
434
435    /// Check that the given function has the expected amount of arguments, and then
436    /// return the list of arguments.
437    ///
438    /// This may only be used for `extern "llvm-intrinsic"` LLVM intrinsics.
439    fn check_shim_sig_llvm_intrinsic<'a, const N: usize>(
440        &mut self,
441        link_name: Symbol,
442        args: &'a [OpTy<'tcx>],
443    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
444        assert!(link_name.as_str().starts_with("llvm."));
445
446        self.check_shim_symbol_clash(link_name)?;
447
448        if let Ok(ops) = args.try_into() {
449            return interp_ok(ops);
450        }
451        throw_ub_format!(
452            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
453            args.len(),
454            N
455        )
456    }
457}