Skip to content

Commit c0e6c76

Browse files
committed
interpret: when passing an argument fails, point at that argument
1 parent 13e2aba commit c0e6c76

52 files changed

Lines changed: 366 additions & 281 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
8888
&ret.clone().into(),
8989
ReturnContinuation::Stop { cleanup: false },
9090
)?;
91-
ecx.storage_live_for_always_live_locals()?;
91+
ecx.push_stack_frame_done()?;
9292

9393
// The main interpreter loop.
9494
while ecx.step()? {

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 139 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -404,166 +404,158 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
404404

405405
// Push the "raw" frame -- this leaves locals uninitialized.
406406
self.push_stack_frame_raw(instance, body, destination, cont)?;
407+
let preamble_span = self.frame().loc.unwrap_right(); // the span used for preamble errors
408+
409+
trace!(
410+
"caller ABI: {:#?}, args: {:#?}",
411+
caller_fn_abi,
412+
args.iter()
413+
.map(|arg| (
414+
arg.layout().ty,
415+
match arg {
416+
FnArg::Copy(op) => format!("copy({op:?})"),
417+
FnArg::InPlace(mplace) => format!("in-place({mplace:?})"),
418+
}
419+
))
420+
.collect::<Vec<_>>()
421+
);
422+
trace!(
423+
"spread_arg: {:?}, locals: {:#?}",
424+
body.spread_arg,
425+
body.args_iter()
426+
.map(|local| (local, self.layout_of_local(self.frame(), local, None).unwrap().ty,))
427+
.collect::<Vec<_>>()
428+
);
407429

408-
// If an error is raised here, pop the frame again to get an accurate backtrace.
409-
// To this end, we wrap it all in a `try` block.
410-
let res: InterpResult<'tcx> = try {
411-
trace!(
412-
"caller ABI: {:#?}, args: {:#?}",
413-
caller_fn_abi,
414-
args.iter()
415-
.map(|arg| (
416-
arg.layout().ty,
417-
match arg {
418-
FnArg::Copy(op) => format!("copy({op:?})"),
419-
FnArg::InPlace(mplace) => format!("in-place({mplace:?})"),
420-
}
421-
))
422-
.collect::<Vec<_>>()
423-
);
424-
trace!(
425-
"spread_arg: {:?}, locals: {:#?}",
426-
body.spread_arg,
427-
body.args_iter()
428-
.map(|local| (
429-
local,
430-
self.layout_of_local(self.frame(), local, None).unwrap().ty,
431-
))
432-
.collect::<Vec<_>>()
433-
);
434-
435-
// In principle, we have two iterators: Where the arguments come from, and where
436-
// they go to.
437-
438-
// The "where they come from" part is easy, we expect the caller to do any special handling
439-
// that might be required here (e.g. for untupling).
440-
// If `with_caller_location` is set we pretend there is an extra argument (that
441-
// we will not pass; our `caller_location` intrinsic implementation walks the stack instead).
442-
assert_eq!(
443-
args.len() + if with_caller_location { 1 } else { 0 },
444-
caller_fn_abi.args.len(),
445-
"mismatch between caller ABI and caller arguments",
446-
);
447-
let mut caller_args = args
448-
.iter()
449-
.zip(caller_fn_abi.args.iter())
450-
.filter(|arg_and_abi| !arg_and_abi.1.is_ignore());
451-
452-
// Now we have to spread them out across the callee's locals,
453-
// taking into account the `spread_arg`. If we could write
454-
// this is a single iterator (that handles `spread_arg`), then
455-
// `pass_argument` would be the loop body. It takes care to
456-
// not advance `caller_iter` for ignored arguments.
457-
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
458-
// Determine whether there is a special VaList argument. This is always the
459-
// last argument, and since arguments start at index 1 that's `arg_count`.
460-
let va_list_arg =
461-
callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count));
462-
for local in body.args_iter() {
463-
// Construct the destination place for this argument. At this point all
464-
// locals are still dead, so we cannot construct a `PlaceTy`.
465-
let dest = mir::Place::from(local);
466-
// `layout_of_local` does more than just the instantiation we need to get the
467-
// type, but the result gets cached so this avoids calling the instantiation
468-
// query *again* the next time this local is accessed.
469-
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
470-
if Some(local) == va_list_arg {
471-
// This is the last callee-side argument of a variadic function.
472-
// This argument is a VaList holding the remaining caller-side arguments.
473-
self.storage_live(local)?;
474-
475-
let place = self.eval_place(dest)?;
476-
let mplace = self.force_allocation(&place)?;
477-
478-
// Consume the remaining arguments by putting them into the variable argument
479-
// list.
480-
let varargs = self.allocate_varargs(
481-
&mut caller_args,
482-
// "Ignored" arguments aren't actually passed, so the callee should also
483-
// ignore them. (`pass_argument` does this for regular arguments.)
484-
(&mut callee_args_abis).filter(|(_, abi)| !abi.is_ignore()),
485-
)?;
486-
// When the frame is dropped, these variable arguments are deallocated.
487-
self.frame_mut().va_list = varargs.clone();
488-
let key = self.va_list_ptr(varargs.into());
489-
490-
// Zero the VaList, so it is fully initialized.
491-
self.write_bytes_ptr(
492-
mplace.ptr(),
493-
(0..mplace.layout.size.bytes()).map(|_| 0u8),
494-
)?;
430+
// In principle, we have two iterators: Where the arguments come from, and where
431+
// they go to.
495432

496-
// Store the "key" pointer in the right field.
497-
let key_mplace = self.va_list_key_field(&mplace)?;
498-
self.write_pointer(key, &key_mplace)?;
499-
} else if Some(local) == body.spread_arg {
500-
// Make the local live once, then fill in the value field by field.
501-
self.storage_live(local)?;
502-
// Must be a tuple
503-
let ty::Tuple(fields) = ty.kind() else {
504-
span_bug!(self.cur_span(), "non-tuple type for `spread_arg`: {ty}")
505-
};
506-
for (i, field_ty) in fields.iter().enumerate() {
507-
let dest = dest.project_deeper(
508-
&[mir::ProjectionElem::Field(FieldIdx::from_usize(i), field_ty)],
509-
*self.tcx,
510-
);
511-
let (idx, callee_abi) = callee_args_abis.next().unwrap();
512-
self.pass_argument(
513-
&mut caller_args,
514-
callee_abi,
515-
idx,
516-
&dest,
517-
field_ty,
518-
/* already_live */ true,
519-
)?;
520-
}
521-
} else {
522-
// Normal argument. Cannot mark it as live yet, it might be unsized!
433+
// The "where they come from" part is easy, we expect the caller to do any special handling
434+
// that might be required here (e.g. for untupling).
435+
// If `with_caller_location` is set we pretend there is an extra argument (that
436+
// we will not pass; our `caller_location` intrinsic implementation walks the stack instead).
437+
assert_eq!(
438+
args.len() + if with_caller_location { 1 } else { 0 },
439+
caller_fn_abi.args.len(),
440+
"mismatch between caller ABI and caller arguments",
441+
);
442+
let mut caller_args = args
443+
.iter()
444+
.zip(caller_fn_abi.args.iter())
445+
.filter(|arg_and_abi| !arg_and_abi.1.is_ignore());
446+
447+
// Now we have to spread them out across the callee's locals,
448+
// taking into account the `spread_arg`. If we could write
449+
// this is a single iterator (that handles `spread_arg`), then
450+
// `pass_argument` would be the loop body. It takes care to
451+
// not advance `caller_iter` for ignored arguments.
452+
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
453+
// Determine whether there is a special VaList argument. This is always the
454+
// last argument, and since arguments start at index 1 that's `arg_count`.
455+
let va_list_arg = callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count));
456+
for local in body.args_iter() {
457+
// Update the span that we show in case of an error to point to this argument.
458+
self.frame_mut().loc = Right(body.local_decls[local].source_info.span);
459+
// Construct the destination place for this argument. At this point all
460+
// locals are still dead, so we cannot construct a `PlaceTy`.
461+
let dest = mir::Place::from(local);
462+
// `layout_of_local` does more than just the instantiation we need to get the
463+
// type, but the result gets cached so this avoids calling the instantiation
464+
// query *again* the next time this local is accessed.
465+
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
466+
if Some(local) == va_list_arg {
467+
// This is the last callee-side argument of a variadic function.
468+
// This argument is a VaList holding the remaining caller-side arguments.
469+
self.storage_live(local)?;
470+
471+
let place = self.eval_place(dest)?;
472+
let mplace = self.force_allocation(&place)?;
473+
474+
// Consume the remaining arguments by putting them into the variable argument
475+
// list.
476+
let varargs = self.allocate_varargs(
477+
&mut caller_args,
478+
// "Ignored" arguments aren't actually passed, so the callee should also
479+
// ignore them. (`pass_argument` does this for regular arguments.)
480+
(&mut callee_args_abis).filter(|(_, abi)| !abi.is_ignore()),
481+
)?;
482+
// When the frame is dropped, these variable arguments are deallocated.
483+
self.frame_mut().va_list = varargs.clone();
484+
let key = self.va_list_ptr(varargs.into());
485+
486+
// Zero the VaList, so it is fully initialized.
487+
self.write_bytes_ptr(mplace.ptr(), (0..mplace.layout.size.bytes()).map(|_| 0u8))?;
488+
489+
// Store the "key" pointer in the right field.
490+
let key_mplace = self.va_list_key_field(&mplace)?;
491+
self.write_pointer(key, &key_mplace)?;
492+
} else if Some(local) == body.spread_arg {
493+
// Make the local live once, then fill in the value field by field.
494+
self.storage_live(local)?;
495+
// Must be a tuple
496+
let ty::Tuple(fields) = ty.kind() else {
497+
span_bug!(self.cur_span(), "non-tuple type for `spread_arg`: {ty}")
498+
};
499+
for (i, field_ty) in fields.iter().enumerate() {
500+
let dest = dest.project_deeper(
501+
&[mir::ProjectionElem::Field(FieldIdx::from_usize(i), field_ty)],
502+
*self.tcx,
503+
);
523504
let (idx, callee_abi) = callee_args_abis.next().unwrap();
524505
self.pass_argument(
525506
&mut caller_args,
526507
callee_abi,
527508
idx,
528509
&dest,
529-
ty,
530-
/* already_live */ false,
510+
field_ty,
511+
/* already_live */ true,
531512
)?;
532513
}
514+
} else {
515+
// Normal argument. Cannot mark it as live yet, it might be unsized!
516+
let (idx, callee_abi) = callee_args_abis.next().unwrap();
517+
self.pass_argument(
518+
&mut caller_args,
519+
callee_abi,
520+
idx,
521+
&dest,
522+
ty,
523+
/* already_live */ false,
524+
)?;
533525
}
534-
// If the callee needs a caller location, pretend we consume one more argument from the ABI.
535-
if instance.def.requires_caller_location(*self.tcx) {
536-
callee_args_abis.next().unwrap();
537-
}
538-
// Now we should have no more caller args or callee arg ABIs.
539-
assert!(
540-
callee_args_abis.next().is_none(),
541-
"mismatch between callee ABI and callee body arguments"
542-
);
543-
if caller_args.next().is_some() {
544-
throw_ub_format!("calling a function with more arguments than it expected");
545-
}
546-
// Don't forget to check the return type!
547-
if !self.check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret)? {
548-
throw_ub!(AbiMismatchReturn {
549-
caller_ty: caller_fn_abi.ret.layout.ty,
550-
callee_ty: callee_fn_abi.ret.layout.ty
551-
});
552-
}
526+
}
553527

554-
// Protect return place for in-place return value passing.
555-
// We only need to protect anything if this is actually an in-memory place.
556-
if let Some(mplace) = destination_mplace {
557-
M::protect_in_place_function_argument(self, &mplace)?;
558-
}
528+
// Don't forget to check the return type!
529+
self.frame_mut().loc = Right(body.local_decls[mir::RETURN_PLACE].source_info.span);
530+
if !self.check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret)? {
531+
throw_ub!(AbiMismatchReturn {
532+
caller_ty: caller_fn_abi.ret.layout.ty,
533+
callee_ty: callee_fn_abi.ret.layout.ty
534+
});
535+
}
536+
// Protect return place for in-place return value passing.
537+
// We only need to protect anything if this is actually an in-memory place.
538+
if let Some(mplace) = destination_mplace {
539+
M::protect_in_place_function_argument(self, &mplace)?;
540+
}
559541

560-
// Don't forget to mark "initially live" locals as live.
561-
self.storage_live_for_always_live_locals()?;
562-
};
563-
res.inspect_err_kind(|_| {
564-
// Don't show the incomplete stack frame in the error stacktrace.
565-
self.stack_mut().pop();
566-
})
542+
// For the final checks, use same span as preamble since it is unclear what else to do.
543+
self.frame_mut().loc = Right(preamble_span);
544+
// If the callee needs a caller location, pretend we consume one more argument from the ABI.
545+
if instance.def.requires_caller_location(*self.tcx) {
546+
callee_args_abis.next().unwrap();
547+
}
548+
// Now we should have no more caller args or callee arg ABIs.
549+
assert!(
550+
callee_args_abis.next().is_none(),
551+
"mismatch between callee ABI and callee body arguments"
552+
);
553+
if caller_args.next().is_some() {
554+
throw_ub_format!("calling a function with more arguments than it expected");
555+
}
556+
557+
// Done!
558+
self.push_stack_frame_done()
567559
}
568560

569561
/// Initiate a call to this function -- pushing the stack frame and initializing the arguments.

compiler/rustc_const_eval/src/interpret/stack.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
381381
let locals = IndexVec::from_elem(dead_local, &body.local_decls);
382382
let pre_frame = Frame {
383383
body,
384-
loc: Right(body.span), // Span used for errors caused during preamble.
384+
loc: Right(self.tcx.def_span(body.source.def_id())), // Span used for errors caused during preamble.
385385
return_cont,
386386
return_place: return_place.clone(),
387387
locals,
@@ -408,7 +408,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
408408

409409
// Finish things up.
410410
M::after_stack_push(self)?;
411-
self.frame_mut().loc = Left(mir::Location::START);
412411
// `tracing_separate_thread` is used to instruct the tracing_chrome [tracing::Layer] in Miri
413412
// to put the "frame" span on a separate trace thread/line than other spans, to make the
414413
// visualization in <https://ui.perfetto.dev> easier to interpret. It is set to a value of
@@ -466,9 +465,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
466465
}
467466
}
468467

469-
/// In the current stack frame, mark all locals as live that are not arguments and don't have
470-
/// `Storage*` annotations (this includes the return place).
471-
pub(crate) fn storage_live_for_always_live_locals(&mut self) -> InterpResult<'tcx> {
468+
/// Call this after `push_stack_frame_raw` and when all the other setup that needs to be done
469+
/// is completed.
470+
pub(crate) fn push_stack_frame_done(&mut self) -> InterpResult<'tcx> {
471+
// Mark all locals as live that are not arguments and don't have `Storage*` annotations
472+
// (this includes the return place, but not the arguments).
472473
self.storage_live(mir::RETURN_PLACE)?;
473474

474475
let body = self.body();
@@ -478,6 +479,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
478479
self.storage_live(local)?;
479480
}
480481
}
482+
483+
// Get ready to execute the first instruction in the stack frame.
484+
self.frame_mut().loc = Left(mir::Location::START);
485+
481486
interp_ok(())
482487
}
483488

compiler/rustc_const_eval/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#![feature(never_type)]
66
#![feature(slice_ptr_get)]
77
#![feature(trait_alias)]
8-
#![feature(try_blocks)]
98
#![feature(unqualified_local_imports)]
109
#![feature(yeet_expr)]
1110
#![warn(unqualified_local_imports)]

src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use std::{mem, ptr};
66

77
extern "C" fn thread_start() -> *mut libc::c_void {
8+
//~^ERROR: calling a function with more arguments than it expected
89
panic!()
910
}
1011

@@ -16,7 +17,6 @@ fn main() {
1617
mem::transmute(thread_start);
1718
assert_eq!(
1819
libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()),
19-
//~^ERROR: calling a function with more arguments than it expected
2020
0
2121
);
2222
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);

0 commit comments

Comments
 (0)