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