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_hir::Safety;
5use rustc_middle::ty::{Binder, FnSig, Ty};
6use rustc_span::Symbol;
7use rustc_target::callconv::FnAbi;
8
9use crate::*;
10
11/// Describes the expected signature of a shim.
12pub struct ShimSig<'tcx, const ARGS: usize> {
13    pub abi: ExternAbi,
14    pub args: [Ty<'tcx>; ARGS],
15    pub ret: Ty<'tcx>,
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 `*const _` and `*mut _` since the pointee type is 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| $crate::shims::sig::ShimSig {
33            abi: std::str::FromStr::from_str($abi).expect("incorrect abi specified"),
34            args: shim_sig_args_sep!(this, [$($args)*]),
35            ret: shim_sig_arg!(this, $($ret)*),
36        }
37    };
38}
39
40/// Helper for `shim_sig!`.
41///
42/// Groups tokens into comma-separated chunks and calls the provided macro on them.
43///
44/// # Examples
45///
46/// ```ignore
47/// shim_sig_args_sep!(this, [*const _, i32, libc::off64_t]);
48/// // expands to:
49/// [shim_sig_arg!(*const _), shim_sig_arg!(i32), shim_sig_arg!(libc::off64_t)];
50/// ```
51#[macro_export]
52macro_rules! shim_sig_args_sep {
53    ($this:ident, [$($tt:tt)*]) => {
54        shim_sig_args_sep!(@ $this [] [] $($tt)*)
55    };
56
57    // All below matchers form a fairly simple iterator over the input.
58    // - Non-comma token - append to collector
59    // - Comma token - call the provided macro on the collector and reset the collector
60    // - End of input - empty collector one last time. emit output as an array
61
62    // Handles `,` token - take collected type and call shim_sig_arg on it.
63    // Append the result to the final output.
64    (@ $this:ident [$($final:tt)*] [$($collected:tt)*] , $($tt:tt)*) => {
65        shim_sig_args_sep!(@ $this [$($final)* shim_sig_arg!($this, $($collected)*), ] [] $($tt)*)
66    };
67    // Handle non-comma token - append to collected type.
68    (@ $this:ident [$($final:tt)*] [$($collected:tt)*] $first:tt $($tt:tt)*) => {
69        shim_sig_args_sep!(@ $this [$($final)*] [$($collected)* $first] $($tt)*)
70    };
71    // No more tokens - emit final output, including final non-comma type.
72    (@ $this:ident [$($final:tt)*] [$($collected:tt)+] ) => {
73        [$($final)* shim_sig_arg!($this, $($collected)*)]
74    };
75    // No more tokens - emit final output.
76    (@ $this:ident [$($final:tt)*] [] ) => {
77        [$($final)*]
78    };
79}
80
81/// Helper for `shim_sig!`.
82///
83/// Converts a type
84#[macro_export]
85macro_rules! shim_sig_arg {
86    ($this:ident, i8) => {
87        $this.tcx.types.i8
88    };
89    ($this:ident, i16) => {
90        $this.tcx.types.i16
91    };
92    ($this:ident, i32) => {
93        $this.tcx.types.i32
94    };
95    ($this:ident, i64) => {
96        $this.tcx.types.i64
97    };
98    ($this:ident, i128) => {
99        $this.tcx.types.i128
100    };
101    ($this:ident, isize) => {
102        $this.tcx.types.isize
103    };
104    ($this:ident, u8) => {
105        $this.tcx.types.u8
106    };
107    ($this:ident, u16) => {
108        $this.tcx.types.u16
109    };
110    ($this:ident, u32) => {
111        $this.tcx.types.u32
112    };
113    ($this:ident, u64) => {
114        $this.tcx.types.u64
115    };
116    ($this:ident, u128) => {
117        $this.tcx.types.u128
118    };
119    ($this:ident, usize) => {
120        $this.tcx.types.usize
121    };
122    ($this:ident, ()) => {
123        $this.tcx.types.unit
124    };
125    ($this:ident, bool) => {
126        $this.tcx.types.bool
127    };
128    ($this:ident, *const _) => {
129        $this.machine.layouts.const_raw_ptr.ty
130    };
131    ($this:ident, *mut _) => {
132        $this.machine.layouts.mut_raw_ptr.ty
133    };
134    ($this:ident, winapi::$ty:ident) => {
135        $this.windows_ty_layout(stringify!($ty)).ty
136    };
137    ($this:ident, $krate:ident :: $($path:ident)::+) => {
138        helpers::path_ty_layout($this, &[stringify!($krate), $(stringify!($path)),*]).ty
139    };
140    ($this:ident, $($other:tt)*) => {
141        compile_error!(concat!("unsupported signature type: ", stringify!($($other)*)))
142    }
143}
144
145/// Helper function to compare two ABIs.
146fn check_shim_abi<'tcx>(
147    this: &MiriInterpCx<'tcx>,
148    callee_abi: &FnAbi<'tcx, Ty<'tcx>>,
149    caller_abi: &FnAbi<'tcx, Ty<'tcx>>,
150) -> InterpResult<'tcx> {
151    if callee_abi.conv != caller_abi.conv {
152        throw_ub_format!(
153            r#"calling a function with calling convention "{callee}" using caller calling convention "{caller}""#,
154            callee = callee_abi.conv,
155            caller = caller_abi.conv,
156        );
157    }
158    if callee_abi.can_unwind && !caller_abi.can_unwind {
159        throw_ub_format!(
160            "ABI mismatch: callee may unwind, but caller-side signature prohibits unwinding",
161        );
162    }
163    if caller_abi.c_variadic && !callee_abi.c_variadic {
164        throw_ub_format!(
165            "ABI mismatch: calling a non-variadic function with a variadic caller-side signature"
166        );
167    }
168    if !caller_abi.c_variadic && callee_abi.c_variadic {
169        throw_ub_format!(
170            "ABI mismatch: calling a variadic function with a non-variadic caller-side signature"
171        );
172    }
173
174    if callee_abi.fixed_count != caller_abi.fixed_count {
175        throw_ub_format!(
176            "ABI mismatch: expected {} arguments, found {} arguments ",
177            callee_abi.fixed_count,
178            caller_abi.fixed_count
179        );
180    }
181
182    if !this.check_argument_compat(&caller_abi.ret, &callee_abi.ret)? {
183        throw_ub!(AbiMismatchReturn {
184            caller_ty: caller_abi.ret.layout.ty,
185            callee_ty: callee_abi.ret.layout.ty
186        });
187    }
188
189    for (idx, (caller_arg, callee_arg)) in
190        caller_abi.args.iter().zip(callee_abi.args.iter()).enumerate()
191    {
192        if !this.check_argument_compat(caller_arg, callee_arg)? {
193            throw_ub!(AbiMismatchArgument {
194                arg_idx: idx,
195                caller_ty: caller_abi.args[idx].layout.ty,
196                callee_ty: callee_abi.args[idx].layout.ty
197            });
198        }
199    }
200
201    interp_ok(())
202}
203
204fn check_shim_symbol_clash<'tcx>(
205    this: &mut MiriInterpCx<'tcx>,
206    link_name: Symbol,
207) -> InterpResult<'tcx, ()> {
208    if let Some((body, instance)) = this.lookup_exported_symbol(link_name)? {
209        // If compiler-builtins is providing the symbol, then don't treat it as a clash.
210        // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
211        // performance. Note that this means we won't catch any undefined behavior in
212        // compiler-builtins when running other crates, but Miri can still be run on
213        // compiler-builtins itself (or any crate that uses it as a normal dependency)
214        if this.tcx.is_compiler_builtins(instance.def_id().krate) {
215            return interp_ok(());
216        }
217
218        throw_machine_stop!(TerminationInfo::SymbolShimClashing {
219            link_name,
220            span: body.span.data(),
221        })
222    }
223    interp_ok(())
224}
225
226impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
227pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
228    fn check_shim_sig_lenient<'a, const N: usize>(
229        &mut self,
230        abi: &FnAbi<'tcx, Ty<'tcx>>,
231        exp_abi: CanonAbi,
232        link_name: Symbol,
233        args: &'a [OpTy<'tcx>],
234    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
235        let this = self.eval_context_mut();
236        check_shim_symbol_clash(this, link_name)?;
237
238        if abi.conv != exp_abi {
239            throw_ub_format!(
240                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
241                abi.conv
242            );
243        }
244        if abi.c_variadic {
245            throw_ub_format!(
246                "calling a non-variadic function with a variadic caller-side signature"
247            );
248        }
249
250        if let Ok(ops) = args.try_into() {
251            return interp_ok(ops);
252        }
253        throw_ub_format!(
254            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
255            args.len(),
256            N
257        )
258    }
259
260    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
261    /// then returns the list of arguments.
262    fn check_shim_sig<'a, const N: usize>(
263        &mut self,
264        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
265        link_name: Symbol,
266        caller_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
267        caller_args: &'a [OpTy<'tcx>],
268    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
269        let this = self.eval_context_mut();
270        let shim_sig = shim_sig(this);
271
272        // Compute full callee ABI.
273        let mut inputs_and_output = Vec::with_capacity(N.strict_add(1));
274        inputs_and_output.extend(&shim_sig.args);
275        inputs_and_output.push(shim_sig.ret);
276        let fn_sig_binder = Binder::dummy(FnSig {
277            inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output),
278            c_variadic: false,
279            // This does not matter for the ABI.
280            safety: Safety::Safe,
281            abi: shim_sig.abi,
282        });
283        let callee_fn_abi = this.fn_abi_of_fn_ptr(fn_sig_binder, Default::default())?;
284
285        // Check everything.
286        check_shim_abi(this, callee_fn_abi, caller_fn_abi)?;
287        check_shim_symbol_clash(this, link_name)?;
288
289        // Return arguments.
290        if let Ok(ops) = caller_args.try_into() {
291            return interp_ok(ops);
292        }
293        unreachable!()
294    }
295
296    /// Check shim for variadic function.
297    /// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
298    fn check_shim_sig_variadic_lenient<'a, const N: usize>(
299        &mut self,
300        abi: &FnAbi<'tcx, Ty<'tcx>>,
301        exp_abi: CanonAbi,
302        link_name: Symbol,
303        args: &'a [OpTy<'tcx>],
304    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])>
305    where
306        &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
307    {
308        let this = self.eval_context_mut();
309        check_shim_symbol_clash(this, link_name)?;
310
311        if abi.conv != exp_abi {
312            throw_ub_format!(
313                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
314                abi.conv
315            );
316        }
317        if !abi.c_variadic {
318            throw_ub_format!(
319                "calling a variadic function with a non-variadic caller-side signature"
320            );
321        }
322        if abi.fixed_count != u32::try_from(N).unwrap() {
323            throw_ub_format!(
324                "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}",
325                link_name.as_str(),
326                abi.fixed_count
327            )
328        }
329        if let Some(args) = args.split_first_chunk() {
330            return interp_ok(args);
331        }
332        panic!("mismatch between signature and `args` slice");
333    }
334}
335
336/// Check that the number of varargs is at least the minimum what we expect.
337/// Fixed args should not be included.
338pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
339    name: &'a str,
340    args: &'a [OpTy<'tcx>],
341) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
342    if let Some((ops, _)) = args.split_first_chunk() {
343        return interp_ok(ops);
344    }
345    throw_ub_format!(
346        "not enough variadic arguments for `{name}`: got {}, expected at least {}",
347        args.len(),
348        N
349    )
350}