diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl index b9117c83ae2f6..acc39fefbe16f 100644 --- a/compiler/rustc_ast_passes/messages.ftl +++ b/compiler/rustc_ast_passes/messages.ftl @@ -82,10 +82,6 @@ ast_passes_c_variadic_no_extern = `...` is not supported for non-extern function ast_passes_c_variadic_not_supported = the `{$target}` target does not support c-variadic functions -ast_passes_const_and_c_variadic = functions cannot be both `const` and C-variadic - .const = `const` because of this - .variadic = C-variadic because of this - ast_passes_const_and_coroutine = functions cannot be both `const` and `{$coroutine_kind}` .const = `const` because of this .coroutine = `{$coroutine_kind}` because of this diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 2457e0a777e44..3da262953ac34 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -698,13 +698,11 @@ impl<'a> AstValidator<'a> { unreachable!("C variable argument list cannot be used in closures") }; - // C-variadics are not yet implemented in const evaluation. - if let Const::Yes(const_span) = sig.header.constness { - self.dcx().emit_err(errors::ConstAndCVariadic { - spans: vec![const_span, variadic_param.span], - const_span, - variadic_span: variadic_param.span, - }); + if let Const::Yes(_) = sig.header.constness + && !self.features.enabled(sym::const_c_variadic) + { + let msg = format!("c-variadic const function definitions are unstable"); + feature_err(&self.sess, sym::const_c_variadic, sig.span, msg).emit(); } if let Some(coroutine_kind) = sig.header.coroutine_kind { diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index 22adaae8c6f2f..738a2f7840998 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -710,17 +710,6 @@ pub(crate) struct ConstAndCoroutine { pub coroutine_kind: &'static str, } -#[derive(Diagnostic)] -#[diag(ast_passes_const_and_c_variadic)] -pub(crate) struct ConstAndCVariadic { - #[primary_span] - pub spans: Vec, - #[label(ast_passes_const)] - pub const_span: Span, - #[label(ast_passes_variadic)] - pub variadic_span: Span, -} - #[derive(Diagnostic)] #[diag(ast_passes_coroutine_and_c_variadic)] pub(crate) struct CoroutineAndCVariadic { diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 4aa0a0b2a96fc..845eba53f01dc 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -35,6 +35,16 @@ const_eval_bad_pointer_op_attempting = {const_eval_bad_pointer_op}: {$operation const_eval_bounds_check_failed = indexing out of bounds: the len is {$len} but the index is {$index} + +const_eval_c_variadic_call = + calling const c-variadic functions is unstable in {const_eval_const_context}s + +const_eval_c_variadic_fixed_count_mismatch = + calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee} + +const_eval_c_variadic_mismatch = + calling a function where the caller and callee disagree on whether the function is C-variadic + const_eval_call_nonzero_intrinsic = `{$name}` called on 0 @@ -91,6 +101,8 @@ const_eval_deref_function_pointer = accessing {$allocation} which contains a function const_eval_deref_typeid_pointer = accessing {$allocation} which contains a `TypeId` +const_eval_deref_va_list_pointer = + accessing {$allocation} which contains a variable argument list const_eval_deref_vtable_pointer = accessing {$allocation} which contains a vtable const_eval_division_by_zero = @@ -195,6 +207,9 @@ const_eval_invalid_uninit_bytes = const_eval_invalid_uninit_bytes_unknown = using uninitialized data, but this operation requires initialized memory +const_eval_invalid_va_list_pointer = + using {$pointer} as variable argument list pointer but it does not point to a variable argument list + const_eval_invalid_vtable_pointer = using {$pointer} as vtable pointer but it does not point to a vtable @@ -430,6 +445,8 @@ const_eval_unterminated_c_string = const_eval_unwind_past_top = unwinding past the topmost frame of the stack +const_eval_va_arg_out_of_bounds = more C-variadic arguments read than were passed + ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## (We'd love to sort this differently to make that more clear but tidy won't let us...) const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 95dbf42d4d441..39d0d75a270ac 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -815,6 +815,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }); } + if self.tcx.fn_sig(callee).skip_binder().c_variadic() { + self.check_op(ops::FnCallCVariadic) + } + // At this point, we are calling a function, `callee`, whose `DefId` is known... // `begin_panic` and `panic_display` functions accept generic diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index a9366bf29fbef..283474740543d 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -75,6 +75,27 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect { } } +/// A c-variadic function call. +#[derive(Debug)] +pub(crate) struct FnCallCVariadic; +impl<'tcx> NonConstOp<'tcx> for FnCallCVariadic { + fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { + Status::Unstable { + gate: sym::const_c_variadic, + gate_already_checked: false, + safe_to_expose_on_stable: false, + is_function_call: true, + } + } + + fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { + ccx.tcx.sess.create_feature_err( + errors::NonConstCVariadicCall { span, kind: ccx.const_kind() }, + sym::const_c_variadic, + ) + } +} + /// A call to a function that is in a trait, or has trait bounds that make it conditionally-const. #[derive(Debug)] pub(crate) struct ConditionallyConstCall<'tcx> { diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 50f5448ec20ad..c19b6cff97df6 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -381,6 +381,14 @@ pub struct NonConstClosure { pub non_or_conditionally: &'static str, } +#[derive(Diagnostic)] +#[diag(const_eval_c_variadic_call, code = E0015)] +pub struct NonConstCVariadicCall { + #[primary_span] + pub span: Span, + pub kind: ConstContext, +} + #[derive(Subdiagnostic)] pub enum NonConstClosureNote { #[note(const_eval_closure_fndef_not_const)] @@ -492,11 +500,13 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { WriteToReadOnly(_) => const_eval_write_to_read_only, DerefFunctionPointer(_) => const_eval_deref_function_pointer, DerefVTablePointer(_) => const_eval_deref_vtable_pointer, + DerefVaListPointer(_) => const_eval_deref_va_list_pointer, DerefTypeIdPointer(_) => const_eval_deref_typeid_pointer, InvalidBool(_) => const_eval_invalid_bool, InvalidChar(_) => const_eval_invalid_char, InvalidTag(_) => const_eval_invalid_tag, InvalidFunctionPointer(_) => const_eval_invalid_function_pointer, + InvalidVaListPointer(_) => const_eval_invalid_va_list_pointer, InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer, InvalidVTableTrait { .. } => const_eval_invalid_vtable_trait, InvalidStr(_) => const_eval_invalid_str, @@ -509,8 +519,12 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { InvalidNichedEnumVariantWritten { .. } => { const_eval_invalid_niched_enum_variant_written } + VaArgOutOfBounds => const_eval_va_arg_out_of_bounds, + AbiMismatchArgument { .. } => const_eval_incompatible_arg_types, AbiMismatchReturn { .. } => const_eval_incompatible_return_types, + CVariadicMismatch { .. } => const_eval_c_variadic_mismatch, + CVariadicFixedCountMismatch { .. } => const_eval_c_variadic_fixed_count_mismatch, } } @@ -535,6 +549,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { | InvalidMeta(InvalidMetaKind::TooBig) | InvalidUninitBytes(None) | DeadLocal + | VaArgOutOfBounds | UninhabitedEnumVariantWritten(_) | UninhabitedEnumVariantRead(_) => {} @@ -555,7 +570,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("len", len); diag.arg("index", index); } - UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => { + UnterminatedCString(ptr) + | InvalidFunctionPointer(ptr) + | InvalidVTablePointer(ptr) + | InvalidVaListPointer(ptr) => { diag.arg("pointer", ptr); } InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => { @@ -609,6 +627,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) + | DerefVaListPointer(alloc) | DerefTypeIdPointer(alloc) => { diag.arg("allocation", alloc); } @@ -645,6 +664,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("caller_ty", caller_ty); diag.arg("callee_ty", callee_ty); } + CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => { + diag.arg("caller_is_c_variadic", caller_is_c_variadic); + diag.arg("callee_is_c_variadic", callee_is_c_variadic); + } + CVariadicFixedCountMismatch { caller, callee } => { + diag.arg("caller", caller); + diag.arg("callee", callee); + } } } } diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 9c8ca44c5e8f0..38b9d26bf79c2 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use either::{Left, Right}; use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; use rustc_data_structures::assert_matches; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef}; @@ -17,7 +18,7 @@ use tracing::{info, instrument, trace}; use super::{ CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok, - throw_ub, throw_ub_custom, throw_unsup_format, + throw_ub, throw_ub_custom, }; use crate::interpret::EnteredTraceSpan; use crate::{enter_trace_span, fluent_generated as fluent}; @@ -349,13 +350,27 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx> { let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); - // Compute callee information. - // FIXME: for variadic support, do we have to somehow determine callee's extra_args? - let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?; + // The check for the DefKind is so that we don't request the fn_sig of a closure. + // Otherwise, we hit: + // + // DefId(1:180 ~ std[269c]::rt::lang_start_internal::{closure#0}) does not have a "fn_sig" + let (fixed_count, callee_c_variadic_args) = + if matches!(self.tcx.def_kind(instance.def_id()), DefKind::Fn) + && let callee_fn_sig = self.tcx.fn_sig(instance.def_id()).skip_binder() + && callee_fn_sig.c_variadic() + { + // A mismatch in caller and callee fixed_count will error below. + let fixed_count = callee_fn_sig.inputs().skip_binder().len(); + let extra_tys = caller_fn_abi.args[caller_fn_abi.fixed_count as usize..] + .iter() + .map(|arg_abi| arg_abi.layout.ty); + + (fixed_count, self.tcx.mk_type_list_from_iter(extra_tys)) + } else { + (caller_fn_abi.fixed_count.try_into().unwrap(), ty::List::empty()) + }; - if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic { - throw_unsup_format!("calling a c-variadic function is not supported"); - } + let callee_fn_abi = self.fn_abi_of_instance(instance, callee_c_variadic_args)?; if caller_fn_abi.conv != callee_fn_abi.conv { throw_ub_custom!( @@ -365,6 +380,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) } + if caller_fn_abi.c_variadic != callee_fn_abi.c_variadic { + throw_ub!(CVariadicMismatch { + caller_is_c_variadic: caller_fn_abi.c_variadic, + callee_is_c_variadic: callee_fn_abi.c_variadic, + }); + } + + if caller_fn_abi.c_variadic && caller_fn_abi.fixed_count != callee_fn_abi.fixed_count { + throw_ub!(CVariadicFixedCountMismatch { + caller: caller_fn_abi.fixed_count, + callee: callee_fn_abi.fixed_count, + }); + } + // Check that all target features required by the callee (i.e., from // the attribute `#[target_feature(enable = ...)]`) are enabled at // compile time. @@ -436,8 +465,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // this is a single iterator (that handles `spread_arg`), then // `pass_argument` would be the loop body. It takes care to // not advance `caller_iter` for ignored arguments. - let mut callee_args_abis = callee_fn_abi.args.iter().enumerate(); - for local in body.args_iter() { + let mut callee_args_abis = if caller_fn_abi.c_variadic { + // Only the fixed arguments are passed normally. C-variadic functions cannot be + // `extern "Rust"` and `#[track_caller]` can only be applied to `extern "Rust"`, to + // the extra caller location argument is not relevant here. + assert!(!instance.def.requires_caller_location(*self.tcx)); + callee_fn_abi.args[..fixed_count].iter().enumerate() + } else { + // NOTE: this handles the extra caller location argument that is passed when + // `#[track_caller]` is used. The `fixed_count` does not account for this argument. + // This attribute is only allowed on `extern "Rust"` functions, so the c-variadic + // case does not need to handle the extra argument. + callee_fn_abi.args.iter().enumerate() + }; + + let mut it = body.args_iter().peekable(); + while let Some(local) = it.next() { // Construct the destination place for this argument. At this point all // locals are still dead, so we cannot construct a `PlaceTy`. let dest = mir::Place::from(local); @@ -445,7 +488,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // type, but the result gets cached so this avoids calling the instantiation // query *again* the next time this local is accessed. let ty = self.layout_of_local(self.frame(), local, None)?.ty; - if Some(local) == body.spread_arg { + if caller_fn_abi.c_variadic && it.peek().is_none() { + // This is the last callee-side argument of a variadic function. + // This argument is a VaList holding the remaining caller-side arguments. + self.storage_live(local)?; + + let place = self.eval_place(dest)?; + let mplace = self.force_allocation(&place)?; + + // Consume the remaining arguments by putting them into the variable argument + // list. + let varargs = self.allocate_varargs(&mut caller_args)?; + // When the frame is dropped, these variable arguments are deallocated. + self.frame_mut().va_list = varargs.clone(); + let key = self.va_list_ptr(varargs.into()); + + // Zero the VaList, so it is fully initialized. + self.write_bytes_ptr( + mplace.ptr(), + (0..mplace.layout.size.bytes()).map(|_| 0u8), + )?; + + // Store the "key" pointer in the right field. + let key_mplace = self.va_list_key_field(&mplace)?; + self.write_pointer(key, &key_mplace)?; + } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. self.storage_live(local)?; // Must be a tuple diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index e526f6120689a..af67c6ddc1570 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -22,8 +22,8 @@ use super::memory::MemoryKind; use super::util::ensure_monomorphic_enough; use super::{ AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, - PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval, - throw_ub_custom, throw_ub_format, + PointerArithmetic, Projectable, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, + throw_inval, throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::fluent_generated as fluent; use crate::interpret::Writeable; @@ -734,6 +734,57 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.float_muladd_intrinsic::(args, dest, MulAddType::Nondeterministic)? } + sym::va_copy => { + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; + + let varargs = self.get_ptr_va_list(key)?; + let copy_key = self.va_list_ptr(varargs.clone()); + + let copy_key_mplace = self.va_list_key_field(dest)?; + self.write_pointer(copy_key, ©_key_mplace)?; + } + + sym::va_end => { + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; + + self.deallocate_va_list(key)?; + } + + sym::va_arg => { + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; + + // Invalidate the old list and get its content. We'll recreate the + // new list (one element shorter) below. + let mut varargs = self.deallocate_va_list(key)?; + + let Some(arg_mplace) = varargs.pop_front() else { + throw_ub!(VaArgOutOfBounds); + }; + + // NOTE: In C some type conversions are allowed (e.g. casting between signed and + // unsigned integers). For now we require c-variadic arguments to be read with the + // exact type they were passed as. + if arg_mplace.layout.ty != dest.layout.ty { + throw_unsup_format!( + "va_arg type mismatch: requested `{}`, but next argument is `{}`", + dest.layout.ty, + arg_mplace.layout.ty + ); + } + // Copy the argument. + self.copy_op(&arg_mplace, dest)?; + + // Update the VaList pointer. + let new_key = self.va_list_ptr(varargs); + self.write_pointer(new_key, &key_mplace)?; + } + // Unsupported intrinsic: skip the return_to_block below. _ => return interp_ok(false), } @@ -1214,4 +1265,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(Some(ImmTy::from_scalar(val, cast_to))) } } + + /// Get the MPlace of the key from the place storing the VaList. + pub(super) fn va_list_key_field>( + &self, + va_list: &P, + ) -> InterpResult<'tcx, P> { + // The struct wrapped by VaList. + let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; + + // Find the first pointer field in this struct. The exact index is target-specific. + let ty::Adt(adt, substs) = va_list_inner.layout().ty.kind() else { + bug!("invalid VaListImpl layout"); + }; + + for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { + if field.ty(*self.tcx, substs).is_raw_ptr() { + return self.project_field(&va_list_inner, FieldIdx::from_usize(i)); + } + } + + bug!("no VaListImpl field is a pointer"); + } } diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index a6c8b28cce9f4..67d9c95cad38a 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -22,8 +22,8 @@ use tracing::{debug, instrument, trace}; use super::{ AllocBytes, AllocId, AllocInit, AllocMap, AllocRange, Allocation, CheckAlignMsg, - CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, - Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, + CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, MPlaceTy, Machine, + MayLeak, Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, interp_ok, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; use crate::const_eval::ConstEvalErrKind; @@ -67,6 +67,8 @@ pub enum AllocKind { LiveData, /// A function allocation (that fn ptrs point to). Function, + /// A variable argument list allocation (used by c-variadic functions). + VaList, /// A vtable allocation. VTable, /// A TypeId allocation. @@ -126,6 +128,9 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { /// Map for "extra" function pointers. extra_fn_ptr_map: FxIndexMap, + /// Map storing variable argument lists. + va_list_map: FxIndexMap>>, + /// To be able to compare pointers with null, and to check alignment for accesses /// to ZSTs (where pointers may dangle), we keep track of the size even for allocations /// that do not exist any more. @@ -161,6 +166,7 @@ impl<'tcx, M: Machine<'tcx>> Memory<'tcx, M> { Memory { alloc_map: M::MemoryMap::default(), extra_fn_ptr_map: FxIndexMap::default(), + va_list_map: FxIndexMap::default(), dead_alloc_map: FxIndexMap::default(), validation_in_progress: Cell::new(false), } @@ -199,9 +205,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return M::extern_static_pointer(self, def_id); } None => { + let is_fn_ptr = self.memory.extra_fn_ptr_map.contains_key(&alloc_id); + let is_va_list = self.memory.va_list_map.contains_key(&alloc_id); assert!( - self.memory.extra_fn_ptr_map.contains_key(&alloc_id), - "{alloc_id:?} is neither global nor a function pointer" + is_fn_ptr || is_va_list, + "{alloc_id:?} is neither global, va_list nor a function pointer" ); } _ => {} @@ -229,6 +237,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.global_root_pointer(Pointer::from(id)).unwrap() } + /// Insert a new variable argument list in the global map of variable argument lists. + pub fn va_list_ptr( + &mut self, + varargs: VecDeque>, + ) -> Pointer { + let id = self.tcx.reserve_alloc_id(); + let old = self.memory.va_list_map.insert(id, varargs); + assert!(old.is_none()); + // Variable argument lists are global allocations, so make sure we get the right root + // pointer. We know this is not an `extern static` so this cannot fail. + self.global_root_pointer(Pointer::from(id)).unwrap() + } + pub fn allocate_ptr( &mut self, size: Size, @@ -912,6 +933,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn is_alloc_live(&self, id: AllocId) -> bool { self.memory.alloc_map.contains_key_ref(&id) || self.memory.extra_fn_ptr_map.contains_key(&id) + || self.memory.va_list_map.contains_key(&id) // We check `tcx` last as that has to acquire a lock in `many-seeds` mode. // This also matches the order in `get_alloc_info`. || self.tcx.try_get_global_alloc(id).is_some() @@ -951,6 +973,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return AllocInfo::new(Size::ZERO, align, AllocKind::Function, Mutability::Not); } + // # Variable argument lists + if self.memory.va_list_map.contains_key(&id) { + return AllocInfo::new(Size::ZERO, Align::ONE, AllocKind::VaList, Mutability::Not); + } + // # Global allocations if let Some(global_alloc) = self.tcx.try_get_global_alloc(id) { // NOTE: `static` alignment from attributes has already been applied to the allocation. @@ -1025,6 +1052,43 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { .into() } + pub fn get_ptr_va_list( + &self, + ptr: Pointer>, + ) -> InterpResult<'tcx, &VecDeque>> { + trace!("get_ptr_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.get(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + interp_ok(va_list) + } + + /// Removes this VaList from the global map of variable argument lists. This does not deallocate + /// the VaList elements, that happens when the Frame is popped. + pub fn deallocate_va_list( + &mut self, + ptr: Pointer>, + ) -> InterpResult<'tcx, VecDeque>> { + trace!("deallocate_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + self.memory.dead_alloc_map.insert(alloc_id, (Size::ZERO, Align::ONE)); + interp_ok(va_list) + } + /// Get the dynamic type of the given vtable pointer. /// If `expected_trait` is `Some`, it must be a vtable for the given trait. pub fn get_ptr_vtable_ty( diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 1c1c59da9d886..60410a2d9a449 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -12,13 +12,14 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, mir}; use rustc_mir_dataflow::impls::always_storage_live_locals; use rustc_span::Span; +use rustc_target::callconv::ArgAbi; use tracing::field::Empty; use tracing::{info_span, instrument, trace}; use super::{ - AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, Machine, MemPlace, MemPlaceMeta, - MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout, - interp_ok, throw_ub, throw_unsup, + AllocId, CtfeProvenance, FnArg, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, + MemPlaceMeta, MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, + from_known_layout, interp_ok, throw_ub, throw_unsup, }; use crate::{enter_trace_span, errors}; @@ -91,6 +92,10 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> { /// Do *not* access this directly; always go through the machine hook! pub locals: IndexVec>, + /// The complete variable argument list of this frame. Its elements must be dropped when the + /// frame is popped. + pub(super) va_list: Vec>, + /// The span of the `tracing` crate is stored here. /// When the guard is dropped, the span is exited. This gives us /// a full stack trace on all tracing statements. @@ -259,6 +264,7 @@ impl<'tcx, Prov: Provenance> Frame<'tcx, Prov> { return_cont: self.return_cont, return_place: self.return_place, locals: self.locals, + va_list: self.va_list, loc: self.loc, extra, tracing_span: self.tracing_span, @@ -377,6 +383,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return_cont, return_place: return_place.clone(), locals, + va_list: vec![], instance, tracing_span: SpanGuard::new(), extra: (), @@ -449,11 +456,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; let return_action = if cleanup { - // We need to take the locals out, since we need to mutate while iterating. for local in &frame.locals { self.deallocate_local(local.value)?; } + // Deallocate any c-variadic arguments. + self.deallocate_varargs(&frame.va_list)?; + // Call the machine hook, which determines the next steps. let return_action = M::after_stack_pop(self, frame, unwinding)?; assert_ne!(return_action, ReturnAction::NoCleanup); @@ -626,6 +635,48 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } +impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { + pub(crate) fn allocate_varargs( + &mut self, + caller_args: &mut I, + ) -> InterpResult<'tcx, Vec>> + where + I: Iterator, &'a ArgAbi<'tcx, Ty<'tcx>>)>, + { + // Consume the remaining arguments and store them in fresh allocations. + let mut varargs = Vec::new(); + for (fn_arg, abi) in caller_args { + // FIXME: do we have to worry about in-place argument passing? + let op = self.copy_fn_arg(fn_arg); + let mplace = self.allocate(abi.layout, MemoryKind::Stack)?; + self.copy_op(&op, &mplace)?; + + varargs.push(mplace); + } + + interp_ok(varargs) + } + + fn deallocate_varargs( + &mut self, + varargs: &[MPlaceTy<'tcx, M::Provenance>], + ) -> InterpResult<'tcx> { + for vararg in varargs { + let ptr = vararg.ptr(); + + trace!( + "deallocating vararg {:?}: {:?}", + vararg, + // Locals always have a `alloc_id` (they are never the result of a int2ptr). + self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap()) + ); + self.deallocate_ptr(ptr, None, MemoryKind::Stack)?; + } + + interp_ok(()) + } +} + impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { pub(super) fn print( &self, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index c99af9658cdef..35831219e0a7c 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -416,6 +416,8 @@ declare_features! ( (unstable, const_async_blocks, "1.53.0", Some(85368)), /// Allows `const { ... }` as a shorthand for `const _: () = const { ... };` for module items. (unstable, const_block_items, "CURRENT_RUSTC_VERSION", Some(149226)), + /// Allows defining and calling c-variadic functions in const contexts. + (unstable, const_c_variadic, "CURRENT_RUSTC_VERSION", Some(151787)), /// Allows `const || {}` closures in const contexts. (incomplete, const_closures, "1.68.0", Some(106003)), /// Allows using `[const] Destruct` bounds and calling drop impls in const contexts. diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 66c928f518aa3..035ffd362a6bb 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -392,6 +392,8 @@ pub enum UndefinedBehaviorInfo<'tcx> { DerefFunctionPointer(AllocId), /// Trying to access the data behind a vtable pointer. DerefVTablePointer(AllocId), + /// Trying to access the data behind a va_list pointer. + DerefVaListPointer(AllocId), /// Trying to access the actual type id. DerefTypeIdPointer(AllocId), /// Using a non-boolean `u8` as bool. @@ -402,6 +404,8 @@ pub enum UndefinedBehaviorInfo<'tcx> { InvalidTag(Scalar), /// Using a pointer-not-to-a-function as function pointer. InvalidFunctionPointer(Pointer), + /// Using a pointer-not-to-a-va-list as variable argument list pointer. + InvalidVaListPointer(Pointer), /// Using a pointer-not-to-a-vtable as vtable pointer. InvalidVTablePointer(Pointer), /// Using a vtable for the wrong trait. @@ -434,6 +438,12 @@ pub enum UndefinedBehaviorInfo<'tcx> { }, /// ABI-incompatible return types. AbiMismatchReturn { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> }, + /// `va_arg` was called on an exhausted `VaList`. + VaArgOutOfBounds, + /// The caller and callee disagree on whether they are c-variadic or not. + CVariadicMismatch { caller_is_c_variadic: bool, callee_is_c_variadic: bool }, + /// The caller and callee disagree on the number of fixed (i.e. non-c-variadic) arguments. + CVariadicFixedCountMismatch { caller: u32, callee: u32 }, } #[derive(Debug, Clone, Copy)] diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 9762e0f21da9f..e4ce39730ee55 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -407,7 +407,7 @@ impl<'tcx> GlobalAlloc<'tcx> { // No data to be accessed here. But vtables are pointer-aligned. (Size::ZERO, tcx.data_layout.pointer_align().abi) } - // Fake allocation, there's nothing to access here + // Fake allocation, there's nothing to access here. GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE), } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b80d624bc4966..2bb2d82c1401d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -734,6 +734,7 @@ symbols! { const_allocate, const_async_blocks, const_block_items, + const_c_variadic, const_closures, const_compare_raw_pointers, const_constructor, diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index d0f155316a109..4ed93f54d43c3 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -200,12 +200,13 @@ impl fmt::Debug for VaList<'_> { impl VaList<'_> { // Helper used in the implementation of the `va_copy` intrinsic. - pub(crate) fn duplicate(&self) -> Self { - Self { inner: self.inner.clone(), _marker: self._marker } + pub(crate) const fn duplicate(&self) -> Self { + Self { inner: self.inner, _marker: self._marker } } } -impl Clone for VaList<'_> { +#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] +impl<'f> const Clone for VaList<'f> { #[inline] fn clone(&self) -> Self { // We only implement Clone and not Copy because some future target might not be able to @@ -216,7 +217,8 @@ impl Clone for VaList<'_> { } } -impl<'f> Drop for VaList<'f> { +#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] +impl<'f> const Drop for VaList<'f> { fn drop(&mut self) { // SAFETY: this variable argument list is being dropped, so won't be read from again. unsafe { va_end(self) } @@ -291,7 +293,8 @@ impl<'f> VaList<'f> { /// /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html #[inline] - pub unsafe fn arg(&mut self) -> T { + #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] + pub const unsafe fn arg(&mut self) -> T { // SAFETY: the caller must uphold the safety contract for `va_arg`. unsafe { va_arg(self) } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 051dda731881f..daa5f65626a23 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3473,7 +3473,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; +pub const unsafe fn va_arg(ap: &mut VaList<'_>) -> T; /// Duplicates a variable argument list. The returned list is initially at the same position as /// the one in `src`, but can be advanced independently. @@ -3485,7 +3485,7 @@ pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; /// when a variable argument list is used incorrectly. #[rustc_intrinsic] #[rustc_nounwind] -pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { +pub const fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { src.duplicate() } @@ -3504,6 +3504,6 @@ pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_end(ap: &mut VaList<'_>) { +pub const unsafe fn va_end(ap: &mut VaList<'_>) { /* deliberately does nothing */ } diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index fed51ed86433c..32897eb89a83c 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -184,7 +184,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } #[cfg(not(all(unix, feature = "native-lib")))] AllocKind::Function => dummy_alloc(params), - AllocKind::VTable => dummy_alloc(params), + AllocKind::VTable | AllocKind::VaList => dummy_alloc(params), AllocKind::TypeId | AllocKind::Dead => unreachable!(), }; // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index a21898c506ab9..c9f26d5fefaad 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -651,7 +651,7 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> { dcx.log_protector(); } }, - AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => { + AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead | AllocKind::VaList => { // No stacked borrows on these allocations. } } @@ -1010,7 +1010,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}"); alloc_extra.borrow_tracker_sb().borrow_mut().exposed_tags.insert(tag); } - AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => { + AllocKind::Function + | AllocKind::VTable + | AllocKind::TypeId + | AllocKind::Dead + | AllocKind::VaList => { // No stacked borrows on these allocations. } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 173145788ee39..d5d57115ebcbc 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -576,7 +576,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let protected = protected_tags.contains_key(&tag); alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected); } - AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => { + AllocKind::Function + | AllocKind::VTable + | AllocKind::TypeId + | AllocKind::Dead + | AllocKind::VaList => { // No tree borrows on these allocations. } } diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs new file mode 100644 index 0000000000000..c01860cd1df90 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(...)>(f); + + f(1); + //~^ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr new file mode 100644 index 0000000000000..9350b99861964 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a C-variadic function with 0 fixed arguments, but the function expects 1 + --> tests/fail/c-variadic-mismatch-count.rs:LL:CC + | +LL | f(1); + | ^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.rs b/src/tools/miri/tests/fail/c-variadic-mismatch.rs new file mode 100644 index 0000000000000..bf44fb00bceec --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(_: i32, _: i64)>(f); + + f(1i32, 1i64); + //~^ ERROR: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr new file mode 100644 index 0000000000000..a68b96f738d22 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + --> tests/fail/c-variadic-mismatch.rs:LL:CC + | +LL | f(1i32, 1i64); + | ^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/c-variadic.rs b/src/tools/miri/tests/fail/c-variadic.rs new file mode 100644 index 0000000000000..38141c23d08de --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.rs @@ -0,0 +1,15 @@ +#![feature(c_variadic)] + +//@error-in-other-file: Undefined Behavior: more C-variadic arguments read than were passed + +fn read_too_many() { + unsafe extern "C" fn variadic(mut ap: ...) { + ap.arg::(); + } + + unsafe { variadic() }; +} + +fn main() { + read_too_many(); +} diff --git a/src/tools/miri/tests/fail/c-variadic.stderr b/src/tools/miri/tests/fail/c-variadic.stderr new file mode 100644 index 0000000000000..31695f1cab49b --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.stderr @@ -0,0 +1,22 @@ +error: Undefined Behavior: more C-variadic arguments read than were passed + --> RUSTLIB/core/src/ffi/va_list.rs:LL:CC + | +LL | unsafe { va_arg(self) } + | ^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: stack backtrace: + 0: std::ffi::VaList::<'_>::arg + at RUSTLIB/core/src/ffi/va_list.rs:LL:CC + 1: read_too_many::variadic + at tests/fail/c-variadic.rs:LL:CC + 2: read_too_many + at tests/fail/c-variadic.rs:LL:CC + 3: main + at tests/fail/c-variadic.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/c-variadic.rs b/src/tools/miri/tests/pass/c-variadic.rs new file mode 100644 index 0000000000000..8b353885699e6 --- /dev/null +++ b/src/tools/miri/tests/pass/c-variadic.rs @@ -0,0 +1,115 @@ +#![feature(c_variadic)] + +use std::ffi::{CStr, VaList, c_char, c_double, c_int, c_long}; + +fn ignores_arguments() { + unsafe extern "C" fn variadic(_: ...) {} + + unsafe { variadic() }; + unsafe { variadic(1, 2, 3) }; +} + +fn echo() { + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + ap.arg() + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_val() { + unsafe fn helper(mut ap: VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(ap: ...) -> i32 { + helper(ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_ref() { + unsafe fn helper(ap: &mut VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + helper(&mut ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +#[allow(improper_ctypes_definitions)] +fn nested() { + unsafe fn helper(mut ap1: VaList, mut ap2: VaList) -> (i32, i32) { + (ap1.arg(), ap2.arg()) + } + + unsafe extern "C" fn variadic2(ap1: VaList, ap2: ...) -> (i32, i32) { + helper(ap1, ap2) + } + + unsafe extern "C" fn variadic1(ap1: ...) -> (i32, i32) { + variadic2(ap1, 2, 2) + } + + assert_eq!(unsafe { variadic1(1) }, (1, 2)); + + let (a, b) = unsafe { variadic1(1, 1) }; + + assert_eq!(a, 1); + assert_eq!(b, 2); +} + +fn various_types() { + unsafe extern "C" fn check_list_2(mut ap: ...) { + macro_rules! continue_if { + ($cond:expr) => { + if !($cond) { + panic!(); + } + }; + } + + unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { + match CStr::from_ptr(ptr).to_str() { + Ok(cstr) => cstr == val, + Err(_) => panic!(), + } + } + + continue_if!(ap.arg::().floor() == 3.14f64.floor()); + continue_if!(ap.arg::() == 12); + continue_if!(ap.arg::() == 'a' as c_int); + continue_if!(ap.arg::().floor() == 6.18f64.floor()); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello")); + continue_if!(ap.arg::() == 42); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "World")); + } + + unsafe { + check_list_2( + 3.14 as c_double, + 12 as c_long, + b'a' as c_int, + 6.28 as c_double, + c"Hello".as_ptr(), + 42 as c_int, + c"World".as_ptr(), + ); + } +} + +fn main() { + ignores_arguments(); + echo(); + forward_by_val(); + forward_by_ref(); + nested(); + various_types(); +} diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs new file mode 100644 index 0000000000000..b5a400a8bf255 --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -0,0 +1,147 @@ +//@ build-fail + +#![feature(c_variadic)] +#![feature(const_c_variadic)] +#![feature(const_trait_impl)] +#![feature(const_destruct)] +#![feature(const_clone)] + +use std::ffi::VaList; +use std::mem::MaybeUninit; + +const unsafe extern "C" fn read_n(mut ap: ...) { + let mut i = N; + while i > 0 { + i -= 1; + let _ = ap.arg::(); + } +} + +unsafe fn read_too_many() { + // None passed, none read. + const { read_n::<0>() } + + // One passed, none read. Ignoring arguments is fine. + const { read_n::<0>(1) } + + // None passed, one read. + const { read_n::<1>() } + //~^ ERROR more C-variadic arguments read than were passed + + // One passed, two read. + const { read_n::<2>(1) } + //~^ ERROR more C-variadic arguments read than were passed +} + +const unsafe extern "C" fn read_as(mut ap: ...) -> T { + ap.arg::() +} + +unsafe fn read_cast() { + const { read_as::(1i32) }; + const { read_as::(1u32) }; + + const { read_as::(1i32, 2u64, 3.0f64) }; + const { read_as::(1u32, 2u64, 3.0f64) }; + + const { read_as::(1i64) }; + const { read_as::(1u64) }; + + const { read_as::(1i32) }; + //~^ ERROR va_arg type mismatch: requested `u32`, but next argument is `i32` + + const { read_as::(1u32) }; + //~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u32` + + const { read_as::(1u64) }; + //~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u64` + + const { read_as::(1i32) }; + //~^ ERROR va_arg type mismatch: requested `f64`, but next argument is `i32` + + const { read_as::<*const u8>(1i32) }; + //~^ ERROR va_arg type mismatch: requested `*const u8`, but next argument is `i32` +} + +fn use_after_free() { + const unsafe extern "C" fn helper(ap: ...) -> [u8; size_of::()] { + unsafe { std::mem::transmute(ap) } + } + + const { + unsafe { + let ap = helper(1, 2, 3); + let mut ap = std::mem::transmute::<_, VaList>(ap); + ap.arg::(); + //~^ ERROR memory access failed: ALLOC0 has been freed, so this pointer is dangling [E0080] + } + }; +} + +fn manual_copy_drop() { + const unsafe extern "C" fn helper(ap: ...) { + // A copy created using Clone is valid, and can be used to read arguments. + let mut copy = ap.clone(); + assert!(copy.arg::() == 1i32); + + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; + + // Using the copy is actually fine. + let _ = copy.arg::(); + drop(copy); + + // But then using the original is UB. + drop(ap); + } + + const { unsafe { helper(1, 2, 3) } }; + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] +} + +fn manual_copy_forget() { + const unsafe extern "C" fn helper(ap: ...) { + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; + + // Using the copy is actually fine. + let _ = copy.arg::(); + std::mem::forget(copy); + + // The read (via `copy`) deallocated the original allocation. + drop(ap); + } + + const { unsafe { helper(1, 2, 3) } }; + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] +} + +fn manual_copy_read() { + const unsafe extern "C" fn helper(mut ap: ...) { + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; + + // Reading from `ap` after reading from `copy` is UB. + let _ = copy.arg::(); + let _ = ap.arg::(); + } + + const { unsafe { helper(1, 2, 3) } }; + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] +} + +fn drop_of_invalid() { + const { + let mut invalid: MaybeUninit = MaybeUninit::zeroed(); + let ap = unsafe { invalid.assume_init() }; + } + //~^ ERROR pointer not dereferenceable: pointer must point to some allocation, but got null pointer [E0080] +} + +fn main() { + unsafe { + read_too_many(); + read_cast(); + manual_copy_read(); + manual_copy_drop(); + manual_copy_forget(); + drop_of_invalid(); + } +} diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr new file mode 100644 index 0000000000000..14da5500cb1b6 --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -0,0 +1,355 @@ +error[E0080]: more C-variadic arguments read than were passed + --> $DIR/c-variadic-fail.rs:28:13 + | +LL | const { read_n::<1>() } + | ^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#2}` failed inside this call + | +note: inside `read_n::<1>` + --> $DIR/c-variadic-fail.rs:16:17 + | +LL | let _ = ap.arg::(); + | ^^^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:28:5 + | +LL | const { read_n::<1>() } + | ^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:28:5 + | +LL | const { read_n::<1>() } + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: more C-variadic arguments read than were passed + --> $DIR/c-variadic-fail.rs:32:13 + | +LL | const { read_n::<2>(1) } + | ^^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#3}` failed inside this call + | +note: inside `read_n::<2>` + --> $DIR/c-variadic-fail.rs:16:17 + | +LL | let _ = ap.arg::(); + | ^^^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:32:5 + | +LL | const { read_n::<2>(1) } + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:32:5 + | +LL | const { read_n::<2>(1) } + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: va_arg type mismatch: requested `u32`, but next argument is `i32` + --> $DIR/c-variadic-fail.rs:50:13 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#6}` failed inside this call + | +note: inside `read_as::` + --> $DIR/c-variadic-fail.rs:37:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:50:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:50:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u32` + --> $DIR/c-variadic-fail.rs:53:13 + | +LL | const { read_as::(1u32) }; + | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#7}` failed inside this call + | +note: inside `read_as::` + --> $DIR/c-variadic-fail.rs:37:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:53:5 + | +LL | const { read_as::(1u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:53:5 + | +LL | const { read_as::(1u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u64` + --> $DIR/c-variadic-fail.rs:56:13 + | +LL | const { read_as::(1u64) }; + | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#8}` failed inside this call + | +note: inside `read_as::` + --> $DIR/c-variadic-fail.rs:37:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:56:5 + | +LL | const { read_as::(1u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:56:5 + | +LL | const { read_as::(1u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: va_arg type mismatch: requested `f64`, but next argument is `i32` + --> $DIR/c-variadic-fail.rs:59:13 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#9}` failed inside this call + | +note: inside `read_as::` + --> $DIR/c-variadic-fail.rs:37:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:59:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:59:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: va_arg type mismatch: requested `*const u8`, but next argument is `i32` + --> $DIR/c-variadic-fail.rs:62:13 + | +LL | const { read_as::<*const u8>(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#10}` failed inside this call + | +note: inside `read_as::<*const u8>` + --> $DIR/c-variadic-fail.rs:37:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::<*const u8>` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:62:5 + | +LL | const { read_as::<*const u8>(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:62:5 + | +LL | const { read_as::<*const u8>(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling + --> $DIR/c-variadic-fail.rs:75:13 + | +LL | ap.arg::(); + | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call + | +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:71:5 + | +LL | / const { +LL | | unsafe { +LL | | let ap = helper(1, 2, 3); +LL | | let mut ap = std::mem::transmute::<_, VaList>(ap); +... | +LL | | }; + | |_____^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:71:5 + | +LL | / const { +LL | | unsafe { +LL | | let ap = helper(1, 2, 3); +LL | | let mut ap = std::mem::transmute::<_, VaList>(ap); +... | +LL | | }; + | |_____^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:97:22 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call + | +note: inside `manual_copy_drop::helper` + --> $DIR/c-variadic-fail.rs:94:9 + | +LL | drop(ap); + | ^^^^^^^^ +note: inside `std::mem::drop::>` + --> $SRC_DIR/core/src/mem/mod.rs:LL:COL +note: inside `drop_in_place::> - shim(Some(VaList<'_>))` + --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside ` as Drop>::drop` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:97:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:97:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:113:22 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_forget::{constant#0}` failed inside this call + | +note: inside `manual_copy_forget::helper` + --> $DIR/c-variadic-fail.rs:110:9 + | +LL | drop(ap); + | ^^^^^^^^ +note: inside `std::mem::drop::>` + --> $SRC_DIR/core/src/mem/mod.rs:LL:COL +note: inside `drop_in_place::> - shim(Some(VaList<'_>))` + --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside ` as Drop>::drop` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:113:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:113:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:126:22 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_read::{constant#0}` failed inside this call + | +note: inside `manual_copy_read::helper` + --> $DIR/c-variadic-fail.rs:123:17 + | +LL | let _ = ap.arg::(); + | ^^^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:126:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:126:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer + --> $DIR/c-variadic-fail.rs:134:5 + | +LL | } + | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call + | +note: inside `drop_in_place::> - shim(Some(VaList<'_>))` + --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside ` as Drop>::drop` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:131:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:131:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 12 previous errors + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/const-eval/c-variadic.rs b/tests/ui/consts/const-eval/c-variadic.rs new file mode 100644 index 0000000000000..2f8d043fb5db6 --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic.rs @@ -0,0 +1,155 @@ +//@ edition: 2021 +//@ run-pass +//@ ignore-backends: gcc + +#![feature(c_variadic)] +#![feature(const_c_variadic)] +#![feature(const_destruct)] +#![feature(const_cmp)] +#![feature(const_trait_impl)] + +use std::ffi::*; + +fn ignores_arguments() { + const unsafe extern "C" fn variadic(_: ...) {} + + const { + unsafe { variadic() }; + unsafe { variadic(1, 2, 3) }; + } +} + +fn echo() { + const unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + ap.arg() + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); + + const { + assert!(unsafe { variadic(1) } == 1); + assert!(unsafe { variadic(3, 2, 1) } == 3); + } +} + +fn forward_by_val() { + const unsafe fn helper(mut ap: VaList) -> i32 { + ap.arg() + } + + const unsafe extern "C" fn variadic(ap: ...) -> i32 { + helper(ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); + + const { + assert!(unsafe { variadic(1) } == 1); + assert!(unsafe { variadic(3, 2, 1) } == 3); + } +} + +fn forward_by_ref() { + const unsafe fn helper(ap: &mut VaList) -> i32 { + ap.arg() + } + + const unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + helper(&mut ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); + + const { + assert!(unsafe { variadic(1) } == 1); + assert!(unsafe { variadic(3, 2, 1) } == 3); + } +} + +#[allow(improper_ctypes_definitions)] +fn nested() { + const unsafe fn helper(mut ap1: VaList, mut ap2: VaList) -> (i32, i32) { + (ap1.arg(), ap2.arg()) + } + + const unsafe extern "C" fn variadic2(ap1: VaList, ap2: ...) -> (i32, i32) { + helper(ap1, ap2) + } + + const unsafe extern "C" fn variadic1(ap1: ...) -> (i32, i32) { + variadic2(ap1, 2, 2) + } + + assert_eq!(unsafe { variadic1(1) }, (1, 2)); + + const { + let (a, b) = unsafe { variadic1(1, 1) }; + + assert!(a != 2); + assert!(a == 1); + assert!(b != 1); + assert!(b == 2); + } +} + +fn various_types() { + const unsafe extern "C" fn check_list_2(mut ap: ...) { + macro_rules! continue_if { + ($cond:expr) => { + if !($cond) { + panic!(); + } + }; + } + + const unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { + match CStr::from_ptr(ptr).to_str() { + Ok(cstr) => cstr == val, + Err(_) => panic!(), + } + } + + continue_if!(ap.arg::().floor() == 3.14f64.floor()); + continue_if!(ap.arg::() == 12); + continue_if!(ap.arg::() == 'a' as c_int); + continue_if!(ap.arg::().floor() == 6.18f64.floor()); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello")); + continue_if!(ap.arg::() == 42); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "World")); + } + + unsafe { + check_list_2( + 3.14 as c_double, + 12 as c_long, + b'a' as c_int, + 6.28 as c_double, + c"Hello".as_ptr(), + 42 as c_int, + c"World".as_ptr(), + ); + const { + check_list_2( + 3.14 as c_double, + 12 as c_long, + b'a' as c_int, + 6.28 as c_double, + c"Hello".as_ptr(), + 42 as c_int, + c"World".as_ptr(), + ) + }; + } +} + +fn main() { + ignores_arguments(); + echo(); + forward_by_val(); + forward_by_ref(); + nested(); + various_types(); +} diff --git a/tests/ui/feature-gates/feature-gate-const-c-variadic.rs b/tests/ui/feature-gates/feature-gate-const-c-variadic.rs new file mode 100644 index 0000000000000..4e8b3c54b1fd1 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-c-variadic.rs @@ -0,0 +1,11 @@ +#![feature(c_variadic)] + +fn main() { + const unsafe extern "C" fn foo(ap: ...) { + //~^ ERROR c-variadic const function definitions are unstable + core::mem::forget(ap); + } + + const { unsafe { foo() } } + //~^ ERROR calling const c-variadic functions is unstable in constants +} diff --git a/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr b/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr new file mode 100644 index 0000000000000..8fd85be08fca4 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr @@ -0,0 +1,24 @@ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/feature-gate-const-c-variadic.rs:4:5 + | +LL | const unsafe extern "C" fn foo(ap: ...) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0015]: calling const c-variadic functions is unstable in constants + --> $DIR/feature-gate-const-c-variadic.rs:9:22 + | +LL | const { unsafe { foo() } } + | ^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0015, E0658. +For more information about an error, try `rustc --explain E0015`. diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 6f61425a8bd6c..4e038875d78f8 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -31,18 +31,18 @@ extern "C" fn f3_3(_: ..., x: isize) {} //~^ ERROR `...` must be the last argument of a C-variadic function const unsafe extern "C" fn f4_1(x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~^ ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_2(x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR functions with a C variable argument list must be unsafe +//~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR functions with a C variable argument list must be unsafe +//~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR `...` must be the last argument of a C-variadic function +//~| ERROR c-variadic const function definitions are unstable extern "C" { fn e_f2(..., x: isize); @@ -64,8 +64,8 @@ impl X { //~| ERROR `...` must be the last argument of a C-variadic function const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions - //~| ERROR functions cannot be both `const` and C-variadic //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time + //~| ERROR c-variadic const function definitions are unstable } trait T { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 318015737fa1b..ea9f9baa58ba2 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -80,17 +80,25 @@ error: `...` must be the last argument of a C-variadic function LL | extern "C" fn f3_3(_: ..., x: isize) {} | ^^^^^^ -error: functions cannot be both `const` and C-variadic +error[E0658]: c-variadic const function definitions are unstable --> $DIR/variadic-ffi-semantic-restrictions.rs:33:1 | LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: functions cannot be both `const` and C-variadic +error[E0658]: c-variadic const function definitions are unstable --> $DIR/variadic-ffi-semantic-restrictions.rs:37:1 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: functions with a C variable argument list must be unsafe --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 @@ -109,11 +117,15 @@ error: `...` must be the last argument of a C-variadic function LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ -error: functions cannot be both `const` and C-variadic +error[E0658]: c-variadic const function definitions are unstable --> $DIR/variadic-ffi-semantic-restrictions.rs:42:1 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: functions with a C variable argument list must be unsafe --> $DIR/variadic-ffi-semantic-restrictions.rs:42:44 @@ -176,13 +188,15 @@ LL | fn i_f4(_: ..., x: isize, _: ...) {} | = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list -error: functions cannot be both `const` and C-variadic +error[E0658]: c-variadic const function definitions are unstable --> $DIR/variadic-ffi-semantic-restrictions.rs:65:5 | LL | const fn i_f5(x: isize, _: ...) {} - | ^^^^^ ^^^^^^ C-variadic because of this - | | - | `const` because of this + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: `...` is not supported for non-extern functions --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 @@ -243,6 +257,10 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 @@ -251,6 +269,10 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 @@ -259,7 +281,12 @@ LL | const fn i_f5(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 33 previous errors -For more information about this error, try `rustc --explain E0493`. +Some errors have detailed explanations: E0493, E0658. +For more information about an error, try `rustc --explain E0493`.