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        // Mutability does not matter for ABI.
154        $this.machine.layouts.mut_raw_ptr.ty
155    };
156    ($this:ident, fn(..) -> _) => {
157        // We currently treat fn ptrs as ABI-compatible with data ptrs so we can just use a raw ptr.
158        $this.machine.layouts.const_raw_ptr.ty
159    };
160    ($this:ident, &[$($ty:tt)*]) => {
161        rustc_middle::ty::Ty::new_ref(
162            *$this.tcx,
163            $this.tcx.lifetimes.re_erased,
164            rustc_middle::ty::Ty::new_slice(*$this.tcx, shim_sig_arg!($this, $($ty)*)),
165            rustc_middle::mir::Mutability::Not,
166        )
167    };
168    ($this:ident, winapi::$ty:ident) => {
169        $this.windows_ty_layout(stringify!($ty)).ty
170    };
171    ($this:ident, $krate:ident :: $($path:ident)::+) => {
172        helpers::path_ty_layout($this, &[stringify!($krate), $(stringify!($path)),*]).ty
173    };
174    ($this:ident, $($other:tt)*) => {
175        compile_error!(concat!("unsupported signature type: ", stringify!($($other)*)))
176    }
177}
178
179impl<'tcx, const ARGS: usize> ShimSig<'tcx, ARGS> {
180    fn as_abi(&self, ecx: &MiriInterpCx<'tcx>) -> &FnAbi<'tcx, Ty<'tcx>> {
181        let mut inputs_and_output = Vec::with_capacity(ARGS.strict_add(1));
182        inputs_and_output.extend(&self.args);
183        inputs_and_output.push(self.ret);
184        let fn_sig_binder = Binder::dummy(FnSig {
185            inputs_and_output: ecx.machine.tcx.mk_type_list(&inputs_and_output),
186            fn_sig_kind: FnSigKind::default().set_c_variadic(self.c_variadic).set_abi(self.abi),
187        });
188        ecx.fn_abi_of_fn_ptr(fn_sig_binder, Default::default()).unwrap()
189    }
190}
191
192/// Helper function to compare two ABIs.
193fn check_shim_abi<'tcx>(
194    this: &MiriInterpCx<'tcx>,
195    link_name: Symbol,
196    callee_abi: &FnAbi<'tcx, Ty<'tcx>>,
197    caller_abi: &FnAbi<'tcx, Ty<'tcx>>,
198) -> InterpResult<'tcx> {
199    if callee_abi.conv != caller_abi.conv {
200        throw_ub_format!(
201            r#"ABI mismatch: `{link_name}` has calling convention "{callee}", but the caller is using calling convention "{caller}""#,
202            callee = callee_abi.conv,
203            caller = caller_abi.conv,
204        );
205    }
206    // No need to check unwinding: if the caller signature forbids unwinding, that's already
207    // reflected in the unwind destination so if an unwind occurs it will be reported as UB.
208
209    if caller_abi.c_variadic && !callee_abi.c_variadic {
210        throw_ub_format!(
211            "ABI mismatch: `{link_name}` is a non-variadic function, but the caller is using a variadic signature"
212        );
213    }
214    if !caller_abi.c_variadic && callee_abi.c_variadic {
215        throw_ub_format!(
216            "ABI mismatch: `{link_name}` is a variadic function, but the caller is using a non-variadic signature"
217        );
218    }
219
220    if callee_abi.fixed_count != caller_abi.fixed_count {
221        throw_ub_format!(
222            "ABI mismatch: calling `{link_name}` which takes {} {}argument{}, but {} argument{} given",
223            callee_abi.fixed_count,
224            if callee_abi.c_variadic { "fixed (non-variadic) " } else { "" },
225            if callee_abi.fixed_count == 1 { "" } else { "s" },
226            caller_abi.fixed_count,
227            if caller_abi.fixed_count == 1 { " was" } else { "s were" },
228        );
229    }
230
231    if !this.check_argument_compat(&caller_abi.ret, &callee_abi.ret)? {
232        throw_ub!(AbiMismatchReturn {
233            caller_ty: caller_abi.ret.layout.ty,
234            callee_ty: callee_abi.ret.layout.ty
235        });
236    }
237
238    for (idx, (caller_arg, callee_arg)) in
239        caller_abi.args.iter().zip(callee_abi.args.iter()).enumerate()
240    {
241        if !this.check_argument_compat(caller_arg, callee_arg)? {
242            throw_ub!(AbiMismatchArgument {
243                arg_idx: idx,
244                caller_ty: caller_abi.args[idx].layout.ty,
245                callee_ty: callee_abi.args[idx].layout.ty
246            });
247        }
248    }
249
250    interp_ok(())
251}
252
253/// Represents a tail of variadic arguments that have not yet been checked.
254// Deliberately not `Copy` so that we don't consume the same vararg multiple times accidentally.
255pub struct Varargs<'tcx, 'a> {
256    args: &'a [OpTy<'tcx>],
257    /// Number of variadic arguments that have already been taken, for error messages.
258    already_gone: usize,
259}
260
261impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
262pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
263    /// Ensure the given symbol is not exported by the program.
264    fn check_shim_symbol_clash(&self, link_name: Symbol) -> InterpResult<'tcx, ()> {
265        let this = self.eval_context_ref();
266        if let Some(instance) = this.lookup_exported_symbol(link_name)? {
267            // If compiler-builtins is providing the symbol, then don't treat it as a clash.
268            // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
269            // performance. Note that this means we won't catch any undefined behavior in
270            // compiler-builtins when running other crates, but Miri can still be run on
271            // compiler-builtins itself (or any crate that uses it as a normal dependency)
272            if this.tcx.is_compiler_builtins(instance.def_id().krate) {
273                return interp_ok(());
274            }
275
276            throw_machine_stop!(TerminationInfo::SymbolShimClashing {
277                link_name,
278                span: this.tcx.def_span(instance.def_id()).data(),
279            })
280        }
281        interp_ok(())
282    }
283
284    /// 'Lenient' signature check. Deprecated; use `check_shim_sig` instead.
285    fn check_shim_sig_deprecated<'a, const N: usize>(
286        &mut self,
287        abi: &FnAbi<'tcx, Ty<'tcx>>,
288        exp_abi: CanonAbi,
289        link_name: Symbol,
290        args: &'a [OpTy<'tcx>],
291    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
292        self.check_shim_symbol_clash(link_name)?;
293
294        if abi.conv != exp_abi {
295            throw_ub_format!(
296                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
297                abi.conv
298            );
299        }
300        if abi.c_variadic {
301            throw_ub_format!(
302                "calling a non-variadic function with a variadic caller-side signature"
303            );
304        }
305
306        if let Ok(ops) = args.try_into() {
307            return interp_ok(ops);
308        }
309        throw_ub_format!(
310            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
311            args.len(),
312            N
313        )
314    }
315
316    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
317    /// then returns the list of arguments.
318    fn check_shim_sig<'a, const N: usize>(
319        &self,
320        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
321        // We take these as a tuple so that this takes less space on the caller side.
322        (link_name, caller_fn_abi, caller_args): (Symbol, &FnAbi<'tcx, Ty<'tcx>>, &'a [OpTy<'tcx>]),
323    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
324        let this = self.eval_context_ref();
325
326        // Compute callee ABI.
327        let shim_sig = shim_sig(this);
328        assert!(!shim_sig.c_variadic);
329        let callee_fn_abi = shim_sig.as_abi(this);
330
331        // Check everything.
332        check_shim_abi(this, link_name, callee_fn_abi, caller_fn_abi)?;
333        this.check_shim_symbol_clash(link_name)?;
334
335        // Return arguments.
336        if let Ok(ops) = caller_args.try_into() {
337            return interp_ok(ops);
338        }
339        unreachable!()
340    }
341
342    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
343    /// then returns the list of fixed and variadic arguments in separate lists.
344    fn check_shim_sig_variadic<'a, const N: usize>(
345        &self,
346        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
347        // We take these as a tuple so that this takes less space on the caller side.
348        (link_name, caller_fn_abi, caller_args): (Symbol, &FnAbi<'tcx, Ty<'tcx>>, &'a [OpTy<'tcx>]),
349    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
350        let this = self.eval_context_ref();
351
352        // Compute callee ABI.
353        let shim_sig = shim_sig(this);
354        assert!(shim_sig.c_variadic);
355        let callee_fn_abi = shim_sig.as_abi(this);
356
357        // Check everything.
358        check_shim_abi(this, link_name, callee_fn_abi, caller_fn_abi)?;
359        this.check_shim_symbol_clash(link_name)?;
360
361        // Return arguments.
362        if let Some((fixed, var)) = caller_args.split_first_chunk() {
363            return interp_ok((fixed, Varargs { args: var, already_gone: 0 }));
364        }
365        unreachable!()
366    }
367
368    /// Fetches `N` arguments from `varargs`, checking their types.
369    /// Also returns the remaining varargs.
370    fn check_varargs<'a, const N: usize>(
371        &self,
372        tys: fn(&MiriInterpCx<'tcx>) -> [Ty<'tcx>; N],
373        varargs: Varargs<'tcx, 'a>,
374        fn_name: &str,
375    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], Varargs<'tcx, 'a>)> {
376        let this = self.eval_context_ref();
377        let tys = tys(this);
378
379        let Some((now, tail)) = varargs.args.split_first_chunk::<N>() else {
380            throw_ub_format!(
381                "not enough variadic arguments for `{fn_name}`: got {}, expected at least {}",
382                varargs.already_gone.strict_add(varargs.args.len()),
383                varargs.already_gone.strict_add(N),
384            )
385        };
386
387        for (n, (caller_gave, callee_expected)) in now.iter().zip(tys).enumerate() {
388            // Check ABI compatibility. This is less strict than `next_arg` but we're also
389            // not limited to just a few simple types.
390            let callee_expected = this.layout_of(callee_expected)?;
391
392            // FIXME: check compatibility once <https://github.com/rust-lang/rust/pull/161615>
393            // landed.
394            let _unused = (n, caller_gave, callee_expected);
395        }
396
397        interp_ok((now, Varargs { args: tail, already_gone: varargs.already_gone.strict_add(N) }))
398    }
399
400    /// Check that the given function has the expected amount of arguments, and then
401    /// return the list of arguments.
402    ///
403    /// This may only be used for `extern "llvm-intrinsic"` LLVM intrinsics.
404    fn check_shim_sig_llvm_intrinsic<'a, const N: usize>(
405        &mut self,
406        link_name: Symbol,
407        args: &'a [OpTy<'tcx>],
408    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
409        assert!(link_name.as_str().starts_with("llvm."));
410
411        self.check_shim_symbol_clash(link_name)?;
412
413        if let Ok(ops) = args.try_into() {
414            return interp_ok(ops);
415        }
416        throw_ub_format!(
417            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
418            args.len(),
419            N
420        )
421    }
422}