diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 0b641ba64b7a..1b68c6535da5 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -407,6 +407,18 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { source_info.span, ) } + AssertKind::InvalidEnumConstruction(source) => { + let source = codegen_operand(fx, source).load_scalar(fx); + let location = fx.get_caller_location(source_info).load_scalar(fx); + + codegen_panic_inner( + fx, + rustc_hir::LangItem::PanicInvalidEnumConstruction, + &[source, location], + *unwind, + source_info.span, + ) + } _ => { let location = fx.get_caller_location(source_info).load_scalar(fx); diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 3df97429e093..1d5fbfc0896d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -776,6 +776,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // `#[track_caller]` adds an implicit argument. (LangItem::PanicNullPointerDereference, vec![location]) } + AssertKind::InvalidEnumConstruction(source) => { + let source = self.codegen_operand(bx, source).immediate(); + // It's `fn panic_invalid_enum_construction(source: u128)`, + // `#[track_caller]` adds an implicit argument. + (LangItem::PanicInvalidEnumConstruction, vec![source, location]) + } _ => { // It's `pub fn panic_...()` and `#[track_caller]` adds an implicit argument. (msg.panic_function(), vec![location]) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 2ec3f8432c77..317b1229a90c 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -508,6 +508,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { found: eval_to_int(found)?, }, NullPointerDereference => NullPointerDereference, + InvalidEnumConstruction(source) => InvalidEnumConstruction(eval_to_int(source)?), }; Err(ConstEvalErrKind::AssertFailure(err)).into() } diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index ab2de279ed8d..f489b05fbbd7 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -21,13 +21,14 @@ fn alloc_caller_location<'tcx>( assert!(!filename.as_str().as_bytes().contains(&0)); let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail; - let file_wide_ptr = { + let filename = { let filename = if loc_details.file { filename.as_str() } else { "" }; let filename_with_nul = filename.to_owned() + "\0"; // This can fail if rustc runs out of memory right here. Trying to emit an error would be // pointless, since that would require allocating more memory than these short strings. let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap(); - Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx) + let file_len = u64::try_from(filename.len()).unwrap(); + Immediate::new_slice(file_ptr.into(), file_len, ecx) }; let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) }; @@ -41,11 +42,8 @@ fn alloc_caller_location<'tcx>( let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); // Initialize fields. - ecx.write_immediate( - file_wide_ptr, - &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap(), - ) - .expect("writing to memory we just allocated cannot fail"); + ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap()) + .expect("writing to memory we just allocated cannot fail"); ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap()) .expect("writing to memory we just allocated cannot fail"); ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap()) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 3a08e5ae3365..10dd5ff9aa76 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -312,6 +312,7 @@ language_item_table! { PanicAsyncGenFnResumedPanic, sym::panic_const_async_gen_fn_resumed_panic, panic_const_async_gen_fn_resumed_panic, Target::Fn, GenericRequirement::None; PanicGenFnNonePanic, sym::panic_const_gen_fn_none_panic, panic_const_gen_fn_none_panic, Target::Fn, GenericRequirement::None; PanicNullPointerDereference, sym::panic_null_pointer_dereference, panic_null_pointer_dereference, Target::Fn, GenericRequirement::None; + PanicInvalidEnumConstruction, sym::panic_invalid_enum_construction, panic_invalid_enum_construction, Target::Fn, GenericRequirement::None; PanicCoroutineResumedDrop, sym::panic_const_coroutine_resumed_drop, panic_const_coroutine_resumed_drop, Target::Fn, GenericRequirement::None; PanicAsyncFnResumedDrop, sym::panic_const_async_fn_resumed_drop, panic_const_async_fn_resumed_drop, Target::Fn, GenericRequirement::None; PanicAsyncGenFnResumedDrop, sym::panic_const_async_gen_fn_resumed_drop, panic_const_async_gen_fn_resumed_drop, Target::Fn, GenericRequirement::None; diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs index 2b00b7dd27aa..78a4d47ca334 100644 --- a/compiler/rustc_macros/src/symbols.rs +++ b/compiler/rustc_macros/src/symbols.rs @@ -190,17 +190,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { let mut symbols_stream = quote! {}; let mut prefill_stream = quote! {}; let mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10); - let mut prev_key: Option<(Span, String)> = None; - - let mut check_order = |span: Span, s: &str, errors: &mut Errors| { - if let Some((prev_span, ref prev_str)) = prev_key { - if s < prev_str { - errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`")); - errors.error(prev_span, format!("location of previous symbol `{prev_str}`")); - } - } - prev_key = Some((span, s.to_string())); - }; // Generate the listed keywords. for keyword in input.keywords.iter() { @@ -219,7 +208,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { // Generate the listed symbols. for symbol in input.symbols.iter() { let name = &symbol.name; - check_order(symbol.name.span(), &name.to_string(), &mut errors); let value = match &symbol.value { Value::SameAsName => name.to_string(), diff --git a/compiler/rustc_macros/src/symbols/tests.rs b/compiler/rustc_macros/src/symbols/tests.rs index 9c53453df5b5..f0a7a2106be4 100644 --- a/compiler/rustc_macros/src/symbols/tests.rs +++ b/compiler/rustc_macros/src/symbols/tests.rs @@ -84,18 +84,3 @@ fn check_dup_symbol_and_keyword() { }; test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]); } - -#[test] -fn check_symbol_order() { - let input = quote! { - Keywords {} - Symbols { - zebra, - aardvark, - } - }; - test_symbols_macro( - input, - &["Symbol `aardvark` must precede `zebra`", "location of previous symbol `zebra`"], - ); -} diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 0073deb18da2..69aa4383f135 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -17,6 +17,9 @@ middle_assert_gen_resume_after_drop = `gen` fn or block cannot be further iterat middle_assert_gen_resume_after_panic = `gen` fn or block cannot be further iterated on after it panicked +middle_assert_invalid_enum_construction = + trying to construct an enum from an invalid value `{$source}` + middle_assert_misaligned_ptr_deref = misaligned pointer dereference: address must be a multiple of {$required} but is {$found} diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 56f19d7929d4..c50a30cf7f38 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1075,6 +1075,7 @@ pub enum AssertKind { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: O, found: O }, NullPointerDereference, + InvalidEnumConstruction(O), } #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 0834fa8844c0..4034a3a06e94 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -208,6 +208,7 @@ impl AssertKind { LangItem::PanicGenFnNonePanic } NullPointerDereference => LangItem::PanicNullPointerDereference, + InvalidEnumConstruction(_) => LangItem::PanicInvalidEnumConstruction, ResumedAfterDrop(CoroutineKind::Coroutine(_)) => LangItem::PanicCoroutineResumedDrop, ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { LangItem::PanicAsyncFnResumedDrop @@ -284,6 +285,9 @@ impl AssertKind { ) } NullPointerDereference => write!(f, "\"null pointer dereference occurred\""), + InvalidEnumConstruction(source) => { + write!(f, "\"trying to construct an enum from an invalid value {{}}\", {source:?}") + } ResumedAfterReturn(CoroutineKind::Coroutine(_)) => { write!(f, "\"coroutine resumed after completion\"") } @@ -367,6 +371,7 @@ impl AssertKind { middle_assert_coroutine_resume_after_panic } NullPointerDereference => middle_assert_null_ptr_deref, + InvalidEnumConstruction(_) => middle_assert_invalid_enum_construction, ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { middle_assert_async_resume_after_drop } @@ -420,6 +425,9 @@ impl AssertKind { add!("required", format!("{required:#?}")); add!("found", format!("{found:#?}")); } + InvalidEnumConstruction(source) => { + add!("source", format!("{source:#?}")); + } } } } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 1777756174bf..929ebe1aee18 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -642,7 +642,7 @@ macro_rules! make_mir_visitor { self.visit_operand(l, location); self.visit_operand(r, location); } - OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => { + OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) | InvalidEnumConstruction(op) => { self.visit_operand(op, location); } ResumedAfterReturn(_) | ResumedAfterPanic(_) | NullPointerDereference | ResumedAfterDrop(_) => { diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index ddedea321123..8a3d26e1b031 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1283,15 +1283,15 @@ rustc_queries! { return_result_from_ensure_ok } - /// Check whether the function has any recursion that could cause the inliner to trigger - /// a cycle. - query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool { + /// Return the set of (transitive) callees that may result in a recursive call to `key`. + query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet { fatal_cycle + arena_cache desc { |tcx| - "computing if `{}` (transitively) calls `{}`", - key.0, - tcx.def_path_str(key.1), + "computing (transitive) callees of `{}` that may recurse", + tcx.def_path_str(key), } + cache_on_disk_if { true } } /// Obtain all the calls into other local functions diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs new file mode 100644 index 000000000000..e06e0c6122e8 --- /dev/null +++ b/compiler/rustc_mir_transform/src/check_enums.rs @@ -0,0 +1,501 @@ +use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange}; +use rustc_hir::LangItem; +use rustc_index::IndexVec; +use rustc_middle::bug; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::*; +use rustc_middle::ty::layout::PrimitiveExt; +use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv}; +use rustc_session::Session; +use tracing::debug; + +/// This pass inserts checks for a valid enum discriminant where they are most +/// likely to find UB, because checking everywhere like Miri would generate too +/// much MIR. +pub(super) struct CheckEnums; + +impl<'tcx> crate::MirPass<'tcx> for CheckEnums { + fn is_enabled(&self, sess: &Session) -> bool { + sess.ub_checks() + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + // This pass emits new panics. If for whatever reason we do not have a panic + // implementation, running this pass may cause otherwise-valid code to not compile. + if tcx.lang_items().get(LangItem::PanicImpl).is_none() { + return; + } + + let typing_env = body.typing_env(tcx); + let basic_blocks = body.basic_blocks.as_mut(); + let local_decls = &mut body.local_decls; + + // This operation inserts new blocks. Each insertion changes the Location for all + // statements/blocks after. Iterating or visiting the MIR in order would require updating + // our current location after every insertion. By iterating backwards, we dodge this issue: + // The only Locations that an insertion changes have already been handled. + for block in basic_blocks.indices().rev() { + for statement_index in (0..basic_blocks[block].statements.len()).rev() { + let location = Location { block, statement_index }; + let statement = &basic_blocks[block].statements[statement_index]; + let source_info = statement.source_info; + + let mut finder = EnumFinder::new(tcx, local_decls, typing_env); + finder.visit_statement(statement, location); + + for check in finder.into_found_enums() { + debug!("Inserting enum check"); + let new_block = split_block(basic_blocks, location); + + match check { + EnumCheckType::Direct { source_op, discr, op_size, valid_discrs } => { + insert_direct_enum_check( + tcx, + local_decls, + basic_blocks, + block, + source_op, + discr, + op_size, + valid_discrs, + source_info, + new_block, + ) + } + EnumCheckType::Uninhabited => insert_uninhabited_enum_check( + tcx, + local_decls, + &mut basic_blocks[block], + source_info, + new_block, + ), + EnumCheckType::WithNiche { + source_op, + discr, + op_size, + offset, + valid_range, + } => insert_niche_check( + tcx, + local_decls, + &mut basic_blocks[block], + source_op, + valid_range, + discr, + op_size, + offset, + source_info, + new_block, + ), + } + } + } + } + } + + fn is_required(&self) -> bool { + true + } +} + +/// Represent the different kind of enum checks we can insert. +enum EnumCheckType<'tcx> { + /// We know we try to create an uninhabited enum from an inhabited variant. + Uninhabited, + /// We know the enum does no niche optimizations and can thus easily compute + /// the valid discriminants. + Direct { + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + valid_discrs: Vec, + }, + /// We try to construct an enum that has a niche. + WithNiche { + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Size, + valid_range: WrappingRange, + }, +} + +struct TyAndSize<'tcx> { + pub ty: Ty<'tcx>, + pub size: Size, +} + +/// A [Visitor] that finds the construction of enums and evaluates which checks +/// we should apply. +struct EnumFinder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + local_decls: &'a mut LocalDecls<'tcx>, + typing_env: TypingEnv<'tcx>, + enums: Vec>, +} + +impl<'a, 'tcx> EnumFinder<'a, 'tcx> { + fn new( + tcx: TyCtxt<'tcx>, + local_decls: &'a mut LocalDecls<'tcx>, + typing_env: TypingEnv<'tcx>, + ) -> Self { + EnumFinder { tcx, local_decls, typing_env, enums: Vec::new() } + } + + /// Returns the found enum creations and which checks should be inserted. + fn into_found_enums(self) -> Vec> { + self.enums + } +} + +impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue { + let ty::Adt(adt_def, _) = ty.kind() else { + return; + }; + if !adt_def.is_enum() { + return; + } + + let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else { + return; + }; + let Ok(op_layout) = self + .tcx + .layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx))) + else { + return; + }; + + match enum_layout.variants { + Variants::Empty if op_layout.is_uninhabited() => return, + // An empty enum that tries to be constructed from an inhabited value, this + // is never correct. + Variants::Empty => { + // The enum layout is uninhabited but we construct it from sth inhabited. + // This is always UB. + self.enums.push(EnumCheckType::Uninhabited); + } + // Construction of Single value enums is always fine. + Variants::Single { .. } => {} + // Construction of an enum with multiple variants but no niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Direct, + tag: Scalar::Initialized { value, .. }, + .. + } => { + let valid_discrs = + adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect(); + + let discr = + TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::Direct { + source_op: op.to_copy(), + discr, + op_size: op_layout.size, + valid_discrs, + }); + } + // Construction of an enum with multiple variants and niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + tag: Scalar::Initialized { value, valid_range, .. }, + tag_field, + .. + } => { + let discr = + TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::WithNiche { + source_op: op.to_copy(), + discr, + op_size: op_layout.size, + offset: enum_layout.fields.offset(tag_field.as_usize()), + valid_range, + }); + } + _ => return, + } + + self.super_rvalue(rvalue, location); + } + } +} + +fn split_block( + basic_blocks: &mut IndexVec>, + location: Location, +) -> BasicBlock { + let block_data = &mut basic_blocks[location.block]; + + // Drain every statement after this one and move the current terminator to a new basic block. + let new_block = BasicBlockData { + statements: block_data.statements.split_off(location.statement_index), + terminator: block_data.terminator.take(), + is_cleanup: block_data.is_cleanup, + }; + + basic_blocks.push(new_block) +} + +/// Inserts the cast of an operand (any type) to a u128 value that holds the discriminant value. +fn insert_discr_cast_to_u128<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Option, + source_info: SourceInfo, +) -> Place<'tcx> { + let get_ty_for_size = |tcx: TyCtxt<'tcx>, size: Size| -> Ty<'tcx> { + match size.bytes() { + 1 => tcx.types.u8, + 2 => tcx.types.u16, + 4 => tcx.types.u32, + 8 => tcx.types.u64, + 16 => tcx.types.u128, + invalid => bug!("Found discriminant with invalid size, has {} bytes", invalid), + } + }; + + let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() { + // The discriminant is less wide than the operand, cast the operand into + // [MaybeUninit; N] and then index into it. + let mu = Ty::new_maybe_uninit(tcx, tcx.types.u8); + let array_len = op_size.bytes(); + let mu_array_ty = Ty::new_array(tcx, mu, array_len); + let mu_array = + local_decls.push(LocalDecl::with_source_info(mu_array_ty, source_info)).into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_array_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((mu_array, rvalue))), + }); + + // Index into the array of MaybeUninit to get something that is actually + // as wide as the discriminant. + let offset = offset.unwrap_or(Size::ZERO); + let smaller_mu_array = mu_array.project_deeper( + &[ProjectionElem::Subslice { + from: offset.bytes(), + to: offset.bytes() + discr.size.bytes(), + from_end: false, + }], + tcx, + ); + + (CastKind::Transmute, Operand::Copy(smaller_mu_array)) + } else { + let operand_int_ty = get_ty_for_size(tcx, op_size); + + let op_as_int = + local_decls.push(LocalDecl::with_source_info(operand_int_ty, source_info)).into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, operand_int_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((op_as_int, rvalue))), + }); + + (CastKind::IntToInt, Operand::Copy(op_as_int)) + }; + + // Cast the resulting value to the actual discriminant integer type. + let rvalue = Rvalue::Cast(cast_kind, discr_ty_bits, discr.ty); + let discr_in_discr_ty = + local_decls.push(LocalDecl::with_source_info(discr.ty, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((discr_in_discr_ty, rvalue))), + }); + + // Cast the discriminant to a u128 (base for comparisions of enum discriminants). + let const_u128 = Ty::new_uint(tcx, ty::UintTy::U128); + let rvalue = Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_in_discr_ty), const_u128); + let discr = local_decls.push(LocalDecl::with_source_info(const_u128, source_info)).into(); + block_data + .statements + .push(Statement { source_info, kind: StatementKind::Assign(Box::new((discr, rvalue))) }); + + discr +} + +fn insert_direct_enum_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + basic_blocks: &mut IndexVec>, + current_block: BasicBlock, + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + discriminants: Vec, + source_info: SourceInfo, + new_block: BasicBlock, +) { + // Insert a new target block that is branched to in case of an invalid discriminant. + let invalid_discr_block_data = BasicBlockData::new(None, false); + let invalid_discr_block = basic_blocks.push(invalid_discr_block_data); + let block_data = &mut basic_blocks[current_block]; + let discr = insert_discr_cast_to_u128( + tcx, + local_decls, + block_data, + source_op, + discr, + op_size, + None, + source_info, + ); + + // Branch based on the discriminant value. + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::SwitchInt { + discr: Operand::Copy(discr), + targets: SwitchTargets::new( + discriminants.into_iter().map(|discr| (discr, new_block)), + invalid_discr_block, + ), + }, + }); + + // Abort in case of an invalid enum discriminant. + basic_blocks[invalid_discr_block].terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool), + })), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} + +fn insert_uninhabited_enum_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_info: SourceInfo, + new_block: BasicBlock, +) { + let is_ok: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + is_ok, + Rvalue::Use(Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool), + }))), + ))), + }); + + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Copy(is_ok), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new( + ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128), + }, + )))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} + +fn insert_niche_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_op: Operand<'tcx>, + valid_range: WrappingRange, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Size, + source_info: SourceInfo, + new_block: BasicBlock, +) { + let discr = insert_discr_cast_to_u128( + tcx, + local_decls, + block_data, + source_op, + discr, + op_size, + Some(offset), + source_info, + ); + + // Compare the discriminant agains the valid_range. + let start_const = Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_u128(valid_range.start), tcx.types.u128), + })); + let end_start_diff_const = Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val( + ConstValue::from_u128(u128::wrapping_sub(valid_range.end, valid_range.start)), + tcx.types.u128, + ), + })); + + let discr_diff: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + discr_diff, + Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(discr), start_const))), + ))), + }); + + let is_ok: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + is_ok, + Rvalue::BinaryOp( + // This is a `WrappingRange`, so make sure to get the wrapping right. + BinOp::Le, + Box::new((Operand::Copy(discr_diff), end_start_diff_const)), + ), + ))), + }); + + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Copy(is_ok), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index f48dba9663a5..c27087fea11f 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -770,14 +770,15 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>( return Ok(()); } - if callee_def_id.is_local() + if let Some(callee_def_id) = callee_def_id.as_local() && !inliner .tcx() .is_lang_item(inliner.tcx().parent(caller_def_id), rustc_hir::LangItem::FnOnce) { // If we know for sure that the function we're calling will itself try to // call us, then we avoid inlining that function. - if inliner.tcx().mir_callgraph_reachable((callee, caller_def_id.expect_local())) { + if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id) + { debug!("query cycle avoidance"); return Err("caller might be reachable from callee"); } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index a944960ce4ab..08f3ce5fd67e 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -1,5 +1,6 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::mir::TerminatorKind; use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt}; @@ -7,137 +8,143 @@ use rustc_session::Limit; use rustc_span::sym; use tracing::{instrument, trace}; -// FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking -// this query ridiculously often. -#[instrument(level = "debug", skip(tcx, root, target))] -pub(crate) fn mir_callgraph_reachable<'tcx>( - tcx: TyCtxt<'tcx>, - (root, target): (ty::Instance<'tcx>, LocalDefId), -) -> bool { - trace!(%root, target = %tcx.def_path_str(target)); - assert_ne!( - root.def_id().expect_local(), - target, - "you should not call `mir_callgraph_reachable` on immediate self recursion" - ); - assert!( - matches!(root.def, InstanceKind::Item(_)), - "you should not call `mir_callgraph_reachable` on shims" - ); - assert!( - !tcx.is_constructor(root.def_id()), - "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" - ); - #[instrument( - level = "debug", - skip(tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit) - )] - fn process<'tcx>( - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - caller: ty::Instance<'tcx>, - target: LocalDefId, - stack: &mut Vec>, - seen: &mut FxHashSet>, - recursion_limiter: &mut FxHashMap, - recursion_limit: Limit, - ) -> bool { - trace!(%caller); - for &(callee, args) in tcx.mir_inliner_callees(caller.def) { - let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions( - tcx, - typing_env, - ty::EarlyBinder::bind(args), - ) else { - trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping"); - continue; - }; - let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else { - trace!(?callee, "cannot resolve, skipping"); - continue; - }; - - // Found a path. - if callee.def_id() == target.to_def_id() { - return true; - } - - if tcx.is_constructor(callee.def_id()) { - trace!("constructors always have MIR"); - // Constructor functions cannot cause a query cycle. - continue; - } - - match callee.def { - InstanceKind::Item(_) => { - // If there is no MIR available (either because it was not in metadata or - // because it has no MIR because it's an extern function), then the inliner - // won't cause cycles on this. - if !tcx.is_mir_available(callee.def_id()) { - trace!(?callee, "no mir available, skipping"); - continue; - } - } - // These have no own callable MIR. - InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => continue, - // These have MIR and if that MIR is inlined, instantiated and then inlining is run - // again, a function item can end up getting inlined. Thus we'll be able to cause - // a cycle that way - InstanceKind::VTableShim(_) - | InstanceKind::ReifyShim(..) - | InstanceKind::FnPtrShim(..) - | InstanceKind::ClosureOnceShim { .. } - | InstanceKind::ConstructCoroutineInClosureShim { .. } - | InstanceKind::ThreadLocalShim { .. } - | InstanceKind::CloneShim(..) => {} - - // This shim does not call any other functions, thus there can be no recursion. - InstanceKind::FnPtrAddrShim(..) => { - continue; - } - InstanceKind::DropGlue(..) - | InstanceKind::FutureDropPollShim(..) - | InstanceKind::AsyncDropGlue(..) - | InstanceKind::AsyncDropGlueCtorShim(..) => { - // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to - // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this - // needs some more analysis. - if callee.has_param() { - continue; - } - } - } - - if seen.insert(callee) { - let recursion = recursion_limiter.entry(callee.def_id()).or_default(); - trace!(?callee, recursion = *recursion); - if recursion_limit.value_within_limit(*recursion) { - *recursion += 1; - stack.push(callee); - let found_recursion = ensure_sufficient_stack(|| { - process( - tcx, - typing_env, - callee, - target, - stack, - seen, - recursion_limiter, - recursion_limit, - ) - }); - if found_recursion { - return true; - } - stack.pop(); - } else { - // Pessimistically assume that there could be recursion. - return true; - } +#[instrument(level = "debug", skip(tcx), ret)] +fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool { + match callee.def { + // If there is no MIR available (either because it was not in metadata or + // because it has no MIR because it's an extern function), then the inliner + // won't cause cycles on this. + InstanceKind::Item(_) => { + if !tcx.is_mir_available(callee.def_id()) { + return false; + } + } + + // These have no own callable MIR. + InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => return false, + + // These have MIR and if that MIR is inlined, instantiated and then inlining is run + // again, a function item can end up getting inlined. Thus we'll be able to cause + // a cycle that way + InstanceKind::VTableShim(_) + | InstanceKind::ReifyShim(..) + | InstanceKind::FnPtrShim(..) + | InstanceKind::ClosureOnceShim { .. } + | InstanceKind::ConstructCoroutineInClosureShim { .. } + | InstanceKind::ThreadLocalShim { .. } + | InstanceKind::CloneShim(..) => {} + + // This shim does not call any other functions, thus there can be no recursion. + InstanceKind::FnPtrAddrShim(..) => return false, + + // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to + // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this + // needs some more analysis. + InstanceKind::DropGlue(..) + | InstanceKind::FutureDropPollShim(..) + | InstanceKind::AsyncDropGlue(..) + | InstanceKind::AsyncDropGlueCtorShim(..) => { + if callee.has_param() { + return false; } } - false } + + crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed) + || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id()) +} + +#[instrument( + level = "debug", + skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit), + ret +)] +fn process<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + caller: ty::Instance<'tcx>, + target: LocalDefId, + seen: &mut FxHashSet>, + involved: &mut FxHashSet, + recursion_limiter: &mut FxHashMap, + recursion_limit: Limit, +) -> bool { + trace!(%caller); + let mut cycle_found = false; + + for &(callee, args) in tcx.mir_inliner_callees(caller.def) { + let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions( + tcx, + typing_env, + ty::EarlyBinder::bind(args), + ) else { + trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping"); + continue; + }; + let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else { + trace!(?callee, "cannot resolve, skipping"); + continue; + }; + + // Found a path. + if callee.def_id() == target.to_def_id() { + cycle_found = true; + } + + if tcx.is_constructor(callee.def_id()) { + trace!("constructors always have MIR"); + // Constructor functions cannot cause a query cycle. + continue; + } + + if !should_recurse(tcx, callee) { + continue; + } + + if seen.insert(callee) { + let recursion = recursion_limiter.entry(callee.def_id()).or_default(); + trace!(?callee, recursion = *recursion); + let found_recursion = if recursion_limit.value_within_limit(*recursion) { + *recursion += 1; + ensure_sufficient_stack(|| { + process( + tcx, + typing_env, + callee, + target, + seen, + involved, + recursion_limiter, + recursion_limit, + ) + }) + } else { + // Pessimistically assume that there could be recursion. + true + }; + if found_recursion { + if let Some(callee) = callee.def_id().as_local() { + // Calling `optimized_mir` of a non-local definition cannot cycle. + involved.insert(callee); + } + cycle_found = true; + } + } + } + + cycle_found +} + +#[instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn mir_callgraph_cyclic<'tcx>( + tcx: TyCtxt<'tcx>, + root: LocalDefId, +) -> UnordSet { + assert!( + !tcx.is_constructor(root.to_def_id()), + "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" + ); + // FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error. // In code like that pointed out in #128887, the type complexity we ask the solver to deal with // grows as we recurse into the call graph. If we use the same recursion limit here and in the @@ -146,16 +153,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>( // the default recursion limits are quite generous for us. If we need to recurse 64 times // into the call graph, we're probably not going to find any useful MIR inlining. let recursion_limit = tcx.recursion_limit() / 2; + let mut involved = FxHashSet::default(); + let typing_env = ty::TypingEnv::post_analysis(tcx, root); + let Ok(Some(root_instance)) = ty::Instance::try_resolve( + tcx, + typing_env, + root.to_def_id(), + ty::GenericArgs::identity_for_item(tcx, root.to_def_id()), + ) else { + trace!("cannot resolve, skipping"); + return involved.into(); + }; + if !should_recurse(tcx, root_instance) { + trace!("cannot walk, skipping"); + return involved.into(); + } process( tcx, - ty::TypingEnv::post_analysis(tcx, target), + typing_env, + root_instance, root, - target, - &mut Vec::new(), &mut FxHashSet::default(), + &mut involved, &mut FxHashMap::default(), recursion_limit, - ) + ); + involved.into() } pub(crate) fn mir_inliner_callees<'tcx>( diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 572ad585c8c8..c4415294264a 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -117,6 +117,7 @@ declare_passes! { mod check_inline : CheckForceInline; mod check_call_recursion : CheckCallRecursion, CheckDropRecursion; mod check_alignment : CheckAlignment; + mod check_enums : CheckEnums; mod check_const_item_mutation : CheckConstItemMutation; mod check_null : CheckNull; mod check_packed_ref : CheckPackedRef; @@ -215,7 +216,7 @@ pub fn provide(providers: &mut Providers) { optimized_mir, is_mir_available, is_ctfe_mir_available: is_mir_available, - mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable, + mir_callgraph_cyclic: inline::cycle::mir_callgraph_cyclic, mir_inliner_callees: inline::cycle::mir_inliner_callees, promoted_mir, deduced_param_attrs: deduce_param_attrs::deduced_param_attrs, @@ -666,6 +667,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' // Add some UB checks before any UB gets optimized away. &check_alignment::CheckAlignment, &check_null::CheckNull, + &check_enums::CheckEnums, // Before inlining: trim down MIR with passes to reduce inlining work. // Has to be done before inlining, otherwise actual call will be almost always inlined. diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 38da5c390efe..798f4da9b3bb 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -834,6 +834,9 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { mir::AssertKind::NullPointerDereference => { push_mono_lang_item(self, LangItem::PanicNullPointerDereference); } + mir::AssertKind::InvalidEnumConstruction(_) => { + push_mono_lang_item(self, LangItem::PanicInvalidEnumConstruction); + } _ => { push_mono_lang_item(self, msg.panic_function()); } diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index 42b3e59b73ab..85e71ed2c255 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -506,6 +506,9 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> { AssertKind::NullPointerDereference => { stable_mir::mir::AssertMessage::NullPointerDereference } + AssertKind::InvalidEnumConstruction(source) => { + stable_mir::mir::AssertMessage::InvalidEnumConstruction(source.stable(tables)) + } } } } diff --git a/compiler/rustc_smir/src/stable_mir/mir/body.rs b/compiler/rustc_smir/src/stable_mir/mir/body.rs index 660cd7db0800..e4b7659ce7f9 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/body.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/body.rs @@ -270,6 +270,7 @@ pub enum AssertMessage { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: Operand, found: Operand }, NullPointerDereference, + InvalidEnumConstruction(Operand), } impl AssertMessage { @@ -342,6 +343,9 @@ impl AssertMessage { Ok("misaligned pointer dereference") } AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"), + AssertMessage::InvalidEnumConstruction(_) => { + Ok("trying to construct an enum from an invalid value") + } } } } diff --git a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs index ba20651f993d..b068a9a1081f 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs @@ -313,6 +313,10 @@ fn pretty_assert_message(writer: &mut W, msg: &AssertMessage) -> io::R AssertMessage::NullPointerDereference => { write!(writer, "\"null pointer dereference occurred\"") } + AssertMessage::InvalidEnumConstruction(op) => { + let pretty_op = pretty_operand(op); + write!(writer, "\"trying to construct an enum from an invalid value {{}}\",{pretty_op}") + } AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) | AssertMessage::ResumedAfterDrop(_) => { diff --git a/compiler/rustc_smir/src/stable_mir/mir/visit.rs b/compiler/rustc_smir/src/stable_mir/mir/visit.rs index e21dc11eea9c..b7dd433eb093 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/visit.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/visit.rs @@ -367,7 +367,8 @@ macro_rules! make_mir_visitor { } AssertMessage::OverflowNeg(op) | AssertMessage::DivisionByZero(op) - | AssertMessage::RemainderByZero(op) => { + | AssertMessage::RemainderByZero(op) + | AssertMessage::InvalidEnumConstruction(op) => { self.visit_operand(op, location); } AssertMessage::ResumedAfterReturn(_) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4be7c7ba5109..a51f4e3dc07f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -150,14 +150,10 @@ symbols! { // As well as the symbols listed, there are symbols for the strings // "0", "1", ..., "9", which are accessible via `sym::integer`. // - // The proc macro will abort if symbols are not in alphabetical order (as - // defined by `impl Ord for str`) or if any symbols are duplicated. Vim - // users can sort the list by selecting it and executing the command - // `:'<,'>!LC_ALL=C sort`. - // // There is currently no checking that all symbols are used; that would be // nice to have. Symbols { + // tidy-alphabetical-start Abi, AcqRel, Acquire, @@ -175,18 +171,18 @@ symbols! { AsyncGenPending, AsyncGenReady, AtomicBool, - AtomicI128, + AtomicI8, AtomicI16, AtomicI32, AtomicI64, - AtomicI8, + AtomicI128, AtomicIsize, AtomicPtr, - AtomicU128, + AtomicU8, AtomicU16, AtomicU32, AtomicU64, - AtomicU8, + AtomicU128, AtomicUsize, BTreeEntry, BTreeMap, @@ -607,10 +603,10 @@ symbols! { catch_unwind, cause, cdylib, - ceilf128, ceilf16, ceilf32, ceilf64, + ceilf128, cfg, cfg_accessible, cfg_attr, @@ -747,10 +743,10 @@ symbols! { copy, copy_closures, copy_nonoverlapping, - copysignf128, copysignf16, copysignf32, copysignf64, + copysignf128, core, core_panic, core_panic_2015_macro, @@ -763,10 +759,10 @@ symbols! { coroutine_state, coroutine_yield, coroutines, - cosf128, cosf16, cosf32, cosf64, + cosf128, count, coverage, coverage_attribute, @@ -874,8 +870,8 @@ symbols! { dotdot_in_tuple_patterns, dotdoteq_in_patterns, dreg, - dreg_low16, dreg_low8, + dreg_low16, drop, drop_in_place, drop_types_in_const, @@ -928,16 +924,16 @@ symbols! { exhaustive_integer_patterns, exhaustive_patterns, existential_type, - exp2f128, exp2f16, exp2f32, exp2f64, + exp2f128, expect, expected, - expf128, expf16, expf32, expf64, + expf128, explicit_extern_abis, explicit_generic_args_with_impl_trait, explicit_tail_calls, @@ -958,9 +954,6 @@ symbols! { external, external_doc, f, - f128, - f128_epsilon, - f128_nan, f16, f16_epsilon, f16_nan, @@ -999,10 +992,13 @@ symbols! { f64_legacy_const_neg_infinity, f64_legacy_const_radix, f64_nan, - fabsf128, + f128, + f128_epsilon, + f128_nan, fabsf16, fabsf32, fabsf64, + fabsf128, fadd_algebraic, fadd_fast, fake_variadic, @@ -1024,22 +1020,22 @@ symbols! { flags, float, float_to_int_unchecked, - floorf128, floorf16, floorf32, floorf64, - fmaf128, + floorf128, fmaf16, fmaf32, fmaf64, + fmaf128, fmt, fmt_debug, fmul_algebraic, fmul_fast, - fmuladdf128, fmuladdf16, fmuladdf32, fmuladdf64, + fmuladdf128, fn_align, fn_body, fn_delegation, @@ -1140,13 +1136,12 @@ symbols! { html_root_url, hwaddress, i, - i128, - i128_legacy_const_max, - i128_legacy_const_min, - i128_legacy_fn_max_value, - i128_legacy_fn_min_value, - i128_legacy_mod, - i128_type, + i8, + i8_legacy_const_max, + i8_legacy_const_min, + i8_legacy_fn_max_value, + i8_legacy_fn_min_value, + i8_legacy_mod, i16, i16_legacy_const_max, i16_legacy_const_min, @@ -1165,12 +1160,13 @@ symbols! { i64_legacy_fn_max_value, i64_legacy_fn_min_value, i64_legacy_mod, - i8, - i8_legacy_const_max, - i8_legacy_const_min, - i8_legacy_fn_max_value, - i8_legacy_fn_min_value, - i8_legacy_mod, + i128, + i128_legacy_const_max, + i128_legacy_const_min, + i128_legacy_fn_max_value, + i128_legacy_fn_min_value, + i128_legacy_mod, + i128_type, ident, if_let, if_let_guard, @@ -1292,19 +1288,19 @@ symbols! { loaded_from_disk, local, local_inner_macros, - log10f128, - log10f16, - log10f32, - log10f64, - log2f128, log2f16, log2f32, log2f64, + log2f128, + log10f16, + log10f32, + log10f64, + log10f128, log_syntax, - logf128, logf16, logf32, logf64, + logf128, loongarch_target_feature, loop_break_value, loop_match, @@ -1334,14 +1330,14 @@ symbols! { match_beginning_vert, match_default_bindings, matches_macro, - maximumf128, maximumf16, maximumf32, maximumf64, - maxnumf128, + maximumf128, maxnumf16, maxnumf32, maxnumf64, + maxnumf128, may_dangle, may_unwind, maybe_uninit, @@ -1372,14 +1368,14 @@ symbols! { min_generic_const_args, min_specialization, min_type_alias_impl_trait, - minimumf128, minimumf16, minimumf32, minimumf64, - minnumf128, + minimumf128, minnumf16, minnumf32, minnumf64, + minnumf128, mips_target_feature, mir_assume, mir_basic_block, @@ -1586,6 +1582,7 @@ symbols! { panic_implementation, panic_in_cleanup, panic_info, + panic_invalid_enum_construction, panic_location, panic_misaligned_pointer_dereference, panic_nounwind, @@ -1633,14 +1630,14 @@ symbols! { post_dash_lto: "post-lto", postfix_match, powerpc_target_feature, - powf128, powf16, powf32, powf64, - powif128, + powf128, powif16, powif32, powif64, + powif128, pre_dash_lto: "pre-lto", precise_capturing, precise_capturing_in_traits, @@ -1785,14 +1782,14 @@ symbols! { ropi_rwpi: "ropi-rwpi", rotate_left, rotate_right, - round_ties_even_f128, round_ties_even_f16, round_ties_even_f32, round_ties_even_f64, - roundf128, + round_ties_even_f128, roundf16, roundf32, roundf64, + roundf128, rt, rtm_target_feature, rust, @@ -1972,8 +1969,8 @@ symbols! { simd_fexp2, simd_ffi, simd_flog, - simd_flog10, simd_flog2, + simd_flog10, simd_floor, simd_fma, simd_fmax, @@ -2021,10 +2018,10 @@ symbols! { simd_with_exposed_provenance, simd_xor, since, - sinf128, sinf16, sinf32, sinf64, + sinf128, size, size_of, size_of_val, @@ -2046,10 +2043,10 @@ symbols! { specialization, speed, spotlight, - sqrtf128, sqrtf16, sqrtf32, sqrtf64, + sqrtf128, sreg, sreg_low16, sse, @@ -2127,10 +2124,10 @@ symbols! { target_has_atomic, target_has_atomic_equal_alignment, target_has_atomic_load_store, - target_has_reliable_f128, - target_has_reliable_f128_math, target_has_reliable_f16, target_has_reliable_f16_math, + target_has_reliable_f128, + target_has_reliable_f128_math, target_os, target_pointer_width, target_thread_local, @@ -2173,10 +2170,10 @@ symbols! { transparent_enums, transparent_unions, trivial_bounds, - truncf128, truncf16, truncf32, truncf64, + truncf128, try_blocks, try_capture, try_from, @@ -2205,12 +2202,12 @@ symbols! { type_name, type_privacy_lints, typed_swap_nonoverlapping, - u128, - u128_legacy_const_max, - u128_legacy_const_min, - u128_legacy_fn_max_value, - u128_legacy_fn_min_value, - u128_legacy_mod, + u8, + u8_legacy_const_max, + u8_legacy_const_min, + u8_legacy_fn_max_value, + u8_legacy_fn_min_value, + u8_legacy_mod, u16, u16_legacy_const_max, u16_legacy_const_min, @@ -2229,12 +2226,12 @@ symbols! { u64_legacy_fn_max_value, u64_legacy_fn_min_value, u64_legacy_mod, - u8, - u8_legacy_const_max, - u8_legacy_const_min, - u8_legacy_fn_max_value, - u8_legacy_fn_min_value, - u8_legacy_mod, + u128, + u128_legacy_const_max, + u128_legacy_const_min, + u128_legacy_fn_max_value, + u128_legacy_fn_min_value, + u128_legacy_mod, ub_checks, unaligned_volatile_load, unaligned_volatile_store, @@ -2387,6 +2384,7 @@ symbols! { zfh, zfhmin, zmm_reg, + // tidy-alphabetical-end } } diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 927a2ce84ea7..a7b915c48455 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -622,6 +622,17 @@ impl> ty::EarlyBinder { where A: SliceLike, { + // Nothing to fold, so let's avoid visiting things and possibly re-hashing/equating + // them when interning. Perf testing found this to be a modest improvement. + // See: + if args.is_empty() { + assert!( + !self.value.has_param(), + "{:?} has parameters, but no args were provided in instantiate", + self.value, + ); + return self.value; + } let mut folder = ArgFolder { cx, args: args.as_slice(), binders_passed: 0 }; self.value.fold_with(&mut folder) } diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs index aa9e5fce1d4c..d50ce02bda74 100644 --- a/library/alloc/src/collections/btree/set.rs +++ b/library/alloc/src/collections/btree/set.rs @@ -1517,9 +1517,7 @@ impl From<[T; N]> for BTreeSet { // use stable sort to preserve the insertion order. arr.sort(); - let iter = IntoIterator::into_iter(arr).map(|k| (k, SetValZST::default())); - let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global); - BTreeSet { map } + BTreeSet::from_sorted_iter(IntoIterator::into_iter(arr), Global) } } diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs index f1eedede8aab..972270208852 100644 --- a/library/core/src/panic/location.rs +++ b/library/core/src/panic/location.rs @@ -1,5 +1,7 @@ use crate::ffi::CStr; use crate::fmt; +use crate::marker::PhantomData; +use crate::ptr::NonNull; /// A struct containing information about the location of a panic. /// @@ -33,14 +35,13 @@ use crate::fmt; #[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] #[stable(feature = "panic_hooks", since = "1.10.0")] pub struct Location<'a> { - // Note: this filename will have exactly one nul byte at its end, but otherwise - // it must never contain interior nul bytes. This is relied on for the conversion - // to `CStr` below. - // - // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`. - file_bytes_with_nul: &'a [u8], + // A raw pointer is used rather than a reference because the pointer is valid for one more byte + // than the length stored in this pointer; the additional byte is the NUL-terminator used by + // `Location::file_with_nul`. + filename: NonNull, line: u32, col: u32, + _filename: PhantomData<&'a str>, } #[stable(feature = "panic_hooks", since = "1.10.0")] @@ -143,10 +144,8 @@ impl<'a> Location<'a> { #[stable(feature = "panic_hooks", since = "1.10.0")] #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")] pub const fn file(&self) -> &str { - let str_len = self.file_bytes_with_nul.len() - 1; - // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be - // valid UTF8. - unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) } + // SAFETY: The filename is valid. + unsafe { self.filename.as_ref() } } /// Returns the name of the source file as a nul-terminated `CStr`. @@ -157,9 +156,17 @@ impl<'a> Location<'a> { #[unstable(feature = "file_with_nul", issue = "141727")] #[inline] pub const fn file_with_nul(&self) -> &CStr { - // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no - // interior nul bytes. - unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) } + let filename = self.filename.as_ptr(); + + // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't + // overflow. + let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) }; + + // SAFETY: The filename is valid for `filename_len+1` bytes. + let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) }; + + // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes. + unsafe { CStr::from_bytes_with_nul_unchecked(slice) } } /// Returns the line number from which the panic originated. @@ -220,3 +227,8 @@ impl fmt::Display for Location<'_> { write!(formatter, "{}:{}:{}", self.file(), self.line, self.col) } } + +#[stable(feature = "panic_hooks", since = "1.10.0")] +unsafe impl Send for Location<'_> {} +#[stable(feature = "panic_hooks", since = "1.10.0")] +unsafe impl Sync for Location<'_> {} diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index d87f4814f021..812bc5e61457 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -314,6 +314,22 @@ fn panic_null_pointer_dereference() -> ! { ) } +#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold, optimize(size))] +#[cfg_attr(feature = "panic_immediate_abort", inline)] +#[track_caller] +#[lang = "panic_invalid_enum_construction"] // needed by codegen for panic on invalid enum construction. +#[rustc_nounwind] // `CheckEnums` MIR pass requires this function to never unwind +fn panic_invalid_enum_construction(source: u128) -> ! { + if cfg!(feature = "panic_immediate_abort") { + super::intrinsics::abort() + } + + panic_nounwind_fmt( + format_args!("trying to construct an enum from an invalid value {source:#x}"), + /* force_no_backtrace */ false, + ) +} + /// Panics because we cannot unwind out of a function. /// /// This is a separate function to avoid the codesize impact of each crate containing the string to diff --git a/library/std/src/os/illumos/mod.rs b/library/std/src/os/illumos/mod.rs index e61926f89356..5fbe352e7cfb 100644 --- a/library/std/src/os/illumos/mod.rs +++ b/library/std/src/os/illumos/mod.rs @@ -3,4 +3,5 @@ #![stable(feature = "raw_ext", since = "1.1.0")] pub mod fs; +pub mod net; pub mod raw; diff --git a/library/std/src/os/illumos/net.rs b/library/std/src/os/illumos/net.rs new file mode 100644 index 000000000000..5ef4e1ec89e3 --- /dev/null +++ b/library/std/src/os/illumos/net.rs @@ -0,0 +1,50 @@ +//! illumos-specific networking functionality. + +#![unstable(feature = "unix_socket_exclbind", issue = "123481")] + +use crate::io; +use crate::os::unix::net; +use crate::sealed::Sealed; +use crate::sys_common::AsInner; + +/// illumos-specific functionality for `AF_UNIX` sockets [`UnixDatagram`] +/// and [`UnixStream`]. +/// +/// [`UnixDatagram`]: net::UnixDatagram +/// [`UnixStream`]: net::UnixStream +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +pub trait UnixSocketExt: Sealed { + /// Enables exclusive binding on the socket. + /// + /// If true and if the socket had been set with `SO_REUSEADDR`, + /// it neutralises its effect. + /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html) + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn so_exclbind(&self, excl: bool) -> io::Result<()>; + + /// Get the bind exclusivity bind state of the socket. + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn exclbind(&self) -> io::Result; +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixDatagram { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixStream { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} diff --git a/library/std/src/os/solaris/mod.rs b/library/std/src/os/solaris/mod.rs index e4cfd53291a6..b4e836208491 100644 --- a/library/std/src/os/solaris/mod.rs +++ b/library/std/src/os/solaris/mod.rs @@ -3,4 +3,5 @@ #![stable(feature = "raw_ext", since = "1.1.0")] pub mod fs; +pub mod net; pub mod raw; diff --git a/library/std/src/os/solaris/net.rs b/library/std/src/os/solaris/net.rs new file mode 100644 index 000000000000..ca841f15b0e7 --- /dev/null +++ b/library/std/src/os/solaris/net.rs @@ -0,0 +1,50 @@ +//! solaris-specific networking functionality. + +#![unstable(feature = "unix_socket_exclbind", issue = "123481")] + +use crate::io; +use crate::os::unix::net; +use crate::sealed::Sealed; +use crate::sys_common::AsInner; + +/// solaris-specific functionality for `AF_UNIX` sockets [`UnixDatagram`] +/// and [`UnixStream`]. +/// +/// [`UnixDatagram`]: net::UnixDatagram +/// [`UnixStream`]: net::UnixStream +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +pub trait UnixSocketExt: Sealed { + /// Enables exclusive binding on the socket. + /// + /// If true and if the socket had been set with `SO_REUSEADDR`, + /// it neutralises its effect. + /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html) + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn so_exclbind(&self, excl: bool) -> io::Result<()>; + + /// Get the bind exclusivity bind state of the socket. + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn exclbind(&self) -> io::Result; +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixDatagram { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixStream { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index b2a4961c3c53..cc111f3521bc 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -522,6 +522,21 @@ impl Socket { Ok(name) } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub fn set_exclbind(&self, excl: bool) -> io::Result<()> { + // not yet on libc crate + const SO_EXCLBIND: i32 = 0x1015; + setsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND, excl) + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub fn exclbind(&self) -> io::Result { + // not yet on libc crate + const SO_EXCLBIND: i32 = 0x1015; + let raw: c_int = getsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND)?; + Ok(raw != 0) + } + #[cfg(any(target_os = "android", target_os = "linux",))] pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int) diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 1fe80c13b81c..bbd03e2b0c4e 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -968,8 +968,8 @@ impl Process { } pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be signaling + // If we've already waited on this process then the pid can be recycled and + // used for another process, and we probably shouldn't be sending signals to // random processes, so return Ok because the process has exited already. if self.status.is_some() { return Ok(()); diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index 51ae8c56bdb9..2275cbb946a9 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -151,8 +151,8 @@ impl Process { } pub fn send_signal(&self, signal: i32) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be killing + // If we've already waited on this process then the pid can be recycled and + // used for another process, and we probably shouldn't be sending signals to // random processes, so return Ok because the process has exited already. if self.status.is_some() { Ok(()) diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh index 064ac5b0a5e4..924bdbc76150 100755 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh @@ -10,4 +10,7 @@ python3 ../x.py build --set rust.debug=true opt-dist build-manifest bootstrap # Use GCC for building GCC, as it seems to behave badly when built with Clang -CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc +# Only build GCC on full builds, not try builds +if [ "${DIST_TRY_BUILD:-0}" == "0" ]; then + CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc +fi diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index e16acc9622fc..e33bdc0db32f 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1703,6 +1703,7 @@ fn render_enum_fields( if v.is_stripped() { continue; } + write!(w, "{}", render_attributes_in_pre(v, TAB, cx))?; w.write_str(TAB)?; match v.kind { clean::VariantItem(ref var) => match var.kind { diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 4c53ea42793c..0520eff0fa27 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -14,6 +14,7 @@ use crate::versions::{PkgType, Versions}; static HOSTS: &[&str] = &[ "aarch64-apple-darwin", + "aarch64-pc-windows-gnullvm", "aarch64-pc-windows-msvc", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", @@ -44,6 +45,7 @@ static HOSTS: &[&str] = &[ "x86_64-apple-darwin", "x86_64-pc-solaris", "x86_64-pc-windows-gnu", + "x86_64-pc-windows-gnullvm", "x86_64-pc-windows-msvc", "x86_64-unknown-freebsd", "x86_64-unknown-illumos", @@ -470,7 +472,7 @@ impl Builder { } // so is rust-mingw if it's available for the target PkgType::RustMingw => { - if host.contains("pc-windows-gnu") { + if host.ends_with("pc-windows-gnu") { components.push(host_component(pkg)); } } diff --git a/src/tools/cargo b/src/tools/cargo index 409fed7dc155..930b4f62cfcd 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 409fed7dc1553d49cb9a8c0637d12d65571346ce +Subproject commit 930b4f62cfcd1f0eabdb30a56d91bf6844b739bf diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 4452442cd4f7..e609fb9b6f9e 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -595,7 +595,7 @@ Definite bugs found: * [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084)) * [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281) * [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo) -* [Mockall reading unintialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite) +* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite) * [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 850478b516c9..edbc004d058e 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -173,7 +173,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[ "-Zmir-emit-retag", "-Zmir-preserve-ub", "-Zmir-opt-level=0", - "-Zmir-enable-passes=-CheckAlignment,-CheckNull", + "-Zmir-enable-passes=-CheckAlignment,-CheckNull,-CheckEnums", // Deduplicating diagnostics means we miss events when tracking what happens during an // execution. Let's not do that. "-Zdeduplicate-diagnostics=no", diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs index c63e926376d4..9646cdb20659 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs @@ -6,7 +6,7 @@ fn direct_raw(x: *const (i32, i32)) -> *const i32 { // Ensure that if a raw pointer is created via an intermediate // reference, we catch that. (Just in case someone decides to -// desugar this differenly or so.) +// desugar this differently or so.) fn via_ref(x: *const (i32, i32)) -> *const i32 { unsafe { &(*x).0 as *const i32 } //~ERROR: dangling pointer } diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index 64ce5cc37752..0dc6e56b9d5f 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -122,7 +122,12 @@ impl Bootstrap { let metrics_path = env.build_root().join("build").join("metrics.json"); let args = dist_args.iter().map(|arg| arg.as_str()).collect::>(); let cmd = cmd(&args).env("RUST_BACKTRACE", "full"); - let cmd = add_shared_x_flags(env, cmd); + let mut cmd = add_shared_x_flags(env, cmd); + if env.is_fast_try_build() { + // We set build.extended=false for fast try builds, but we still need Cargo + cmd = cmd.arg("cargo"); + } + Self { cmd, metrics_path } } @@ -189,5 +194,18 @@ impl Bootstrap { } fn add_shared_x_flags(env: &Environment, cmd: CmdBuilder) -> CmdBuilder { - if env.is_fast_try_build() { cmd.arg("--set").arg("rust.deny-warnings=false") } else { cmd } + if env.is_fast_try_build() { + // Skip things that cannot be skipped through `x ... --skip` + cmd.arg("--set") + .arg("rust.llvm-bitcode-linker=false") + // Skip wasm-component-ld. This also skips cargo, which we need to re-enable for dist + .arg("--set") + .arg("build.extended=false") + .arg("--set") + .arg("rust.codegen-backends=['llvm']") + .arg("--set") + .arg("rust.deny-warnings=false") + } else { + cmd + } } diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs index d2827ec01ca7..9c8a6637a3b1 100644 --- a/src/tools/opt-dist/src/main.rs +++ b/src/tools/opt-dist/src/main.rs @@ -407,13 +407,18 @@ fn main() -> anyhow::Result<()> { for target in [ "rust-docs", "rustc-docs", + "rustc-dev", + "rust-dev", "rust-docs-json", "rust-analyzer", "rustc-src", + "extended", "clippy", "miri", "rustfmt", "gcc", + "generate-copyright", + "bootstrap", ] { build_args.extend(["--skip".to_string(), target.to_string()]); } diff --git a/src/tools/test-float-parse/src/ui.rs b/src/tools/test-float-parse/src/ui.rs index 73473eef0bfd..1a9ba0dc1d99 100644 --- a/src/tools/test-float-parse/src/ui.rs +++ b/src/tools/test-float-parse/src/ui.rs @@ -118,7 +118,7 @@ pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> match result { Ok(FinishedAll) => (), Err(EarlyExit::Timeout) => { - println!(" exited early; exceded {:?} timeout", cfg.timeout) + println!(" exited early; exceeded {:?} timeout", cfg.timeout) } Err(EarlyExit::MaxFailures) => { println!(" exited early; exceeded {:?} max failures", cfg.max_failures) diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.rs b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs new file mode 100644 index 000000000000..ea0234a49f64 --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs @@ -0,0 +1,17 @@ +// regression test for https://github.com/rust-lang/rust/issues/142599 + +#![crate_name = "foo"] + +//@ snapshot type-code 'foo/enum.Type.html' '//pre[@class="rust item-decl"]/code' +pub enum Type { + #[non_exhaustive] + // attribute that should not be shown + #[warn(unsafe_code)] + Variant, +} + +// we would love to use the `following-sibling::` axis +// (along with an `h2[@id="aliased-type"]` query), +// but unfortunately python doesn't implement that. +//@ snapshot type-alias-code 'foo/type.TypeAlias.html' '//pre[@class="rust item-decl"][2]/code' +pub type TypeAlias = Type; diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html new file mode 100644 index 000000000000..04eea709079d --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html @@ -0,0 +1,4 @@ +pub enum TypeAlias { + #[non_exhaustive] + Variant, +} \ No newline at end of file diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html new file mode 100644 index 000000000000..6c8851ea5df4 --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html @@ -0,0 +1,4 @@ +pub enum Type { + #[non_exhaustive] + Variant, +} \ No newline at end of file diff --git a/tests/ui/mir/enum/convert_non_enum_break.rs b/tests/ui/mir/enum/convert_non_enum_break.rs new file mode 100644 index 000000000000..de062c39907a --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_break.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x10000 + +#[allow(dead_code)] +#[repr(u32)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 1 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_niche_break.rs b/tests/ui/mir/enum/convert_non_enum_niche_break.rs new file mode 100644 index 000000000000..9ff4849c5b1f --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_niche_break.rs @@ -0,0 +1,27 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x5 + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 5, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_niche_ok.rs b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs new file mode 100644 index 000000000000..24027da54589 --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs @@ -0,0 +1,29 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 0, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 1, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 2, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 3, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_ok.rs b/tests/ui/mir/enum/convert_non_enum_ok.rs new file mode 100644 index 000000000000..37fc64342ca9 --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_ok.rs @@ -0,0 +1,20 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u32)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 0 }) }; + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 1, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/niche_option_tuple_break.rs b/tests/ui/mir/enum/niche_option_tuple_break.rs new file mode 100644 index 000000000000..43eef3a4cc5f --- /dev/null +++ b/tests/ui/mir/enum/niche_option_tuple_break.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x3 + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: usize, + b: usize, +} + +fn main() { + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 3, b: 3 }) }; +} diff --git a/tests/ui/mir/enum/niche_option_tuple_ok.rs b/tests/ui/mir/enum/niche_option_tuple_ok.rs new file mode 100644 index 000000000000..71c885c7edbf --- /dev/null +++ b/tests/ui/mir/enum/niche_option_tuple_ok.rs @@ -0,0 +1,21 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: usize, + b: usize, +} + +fn main() { + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 0, b: 0 }) }; + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 1, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/numbered_variants_break.rs b/tests/ui/mir/enum/numbered_variants_break.rs new file mode 100644 index 000000000000..e3e71dc8aec4 --- /dev/null +++ b/tests/ui/mir/enum/numbered_variants_break.rs @@ -0,0 +1,13 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x3 + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/numbered_variants_ok.rs b/tests/ui/mir/enum/numbered_variants_ok.rs new file mode 100644 index 000000000000..995a2f6511b1 --- /dev/null +++ b/tests/ui/mir/enum/numbered_variants_ok.rs @@ -0,0 +1,13 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(0) }; + let _val: Foo = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/option_with_bigger_niche_break.rs b/tests/ui/mir/enum/option_with_bigger_niche_break.rs new file mode 100644 index 000000000000..c66614b845b5 --- /dev/null +++ b/tests/ui/mir/enum/option_with_bigger_niche_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x0 + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Option = unsafe { std::mem::transmute::>(0) }; +} diff --git a/tests/ui/mir/enum/option_with_bigger_niche_ok.rs b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs new file mode 100644 index 000000000000..1d44ffd28fce --- /dev/null +++ b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Option = unsafe { std::mem::transmute::>(2) }; + let _val: Option = unsafe { std::mem::transmute::>(3) }; +} diff --git a/tests/ui/mir/enum/plain_no_data_break.rs b/tests/ui/mir/enum/plain_no_data_break.rs new file mode 100644 index 000000000000..db68e752479d --- /dev/null +++ b/tests/ui/mir/enum/plain_no_data_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/plain_no_data_ok.rs b/tests/ui/mir/enum/plain_no_data_ok.rs new file mode 100644 index 000000000000..bbdc18f96dc4 --- /dev/null +++ b/tests/ui/mir/enum/plain_no_data_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(2) }; + let _val: Foo = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/single_ok.rs b/tests/ui/mir/enum/single_ok.rs new file mode 100644 index 000000000000..06b5a237c687 --- /dev/null +++ b/tests/ui/mir/enum/single_ok.rs @@ -0,0 +1,11 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::<(), Single>(()) }; +} diff --git a/tests/ui/mir/enum/single_with_repr_break.rs b/tests/ui/mir/enum/single_with_repr_break.rs new file mode 100644 index 000000000000..5a4ec85a9b55 --- /dev/null +++ b/tests/ui/mir/enum/single_with_repr_break.rs @@ -0,0 +1,13 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/single_with_repr_ok.rs b/tests/ui/mir/enum/single_with_repr_ok.rs new file mode 100644 index 000000000000..b0ed2bad6608 --- /dev/null +++ b/tests/ui/mir/enum/single_with_repr_ok.rs @@ -0,0 +1,12 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::(0) }; +} diff --git a/tests/ui/mir/enum/with_niche_int_break.rs b/tests/ui/mir/enum/with_niche_int_break.rs new file mode 100644 index 000000000000..0ec60a33564a --- /dev/null +++ b/tests/ui/mir/enum/with_niche_int_break.rs @@ -0,0 +1,21 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x4 + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::(4) }; +} diff --git a/tests/ui/mir/enum/with_niche_int_ok.rs b/tests/ui/mir/enum/with_niche_int_ok.rs new file mode 100644 index 000000000000..9a3ff3a73beb --- /dev/null +++ b/tests/ui/mir/enum/with_niche_int_ok.rs @@ -0,0 +1,23 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::(0) }; + let _val: Nested = unsafe { std::mem::transmute::(1) }; + let _val: Nested = unsafe { std::mem::transmute::(2) }; + let _val: Nested = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/with_niche_ptr_ok.rs b/tests/ui/mir/enum/with_niche_ptr_ok.rs new file mode 100644 index 000000000000..969d955f7a4b --- /dev/null +++ b/tests/ui/mir/enum/with_niche_ptr_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +fn main() { + let _val = unsafe { + std::mem::transmute::<*const usize, Option>(std::ptr::null()) + }; + let _val = unsafe { + std::mem::transmute::<*const usize, Option>(usize::MAX as *const _) + }; + let _val = unsafe { std::mem::transmute::>(0) }; + let _val = unsafe { std::mem::transmute::>(1) }; + let _val = unsafe { std::mem::transmute::>(usize::MAX) }; +} diff --git a/tests/ui/mir/enum/wrap_break.rs b/tests/ui/mir/enum/wrap_break.rs new file mode 100644 index 000000000000..4491394ca5a3 --- /dev/null +++ b/tests/ui/mir/enum/wrap_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x0 +#![feature(never_type)] +#![allow(invalid_value)] + +#[allow(dead_code)] +enum Wrap { + A(!), +} + +fn main() { + let _val: Wrap = unsafe { std::mem::transmute::<(), Wrap>(()) }; +} diff --git a/tests/ui/mir/enum/wrap_ok.rs b/tests/ui/mir/enum/wrap_ok.rs new file mode 100644 index 000000000000..2881675c9ce6 --- /dev/null +++ b/tests/ui/mir/enum/wrap_ok.rs @@ -0,0 +1,12 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Wrap { + A(u32), +} + +fn main() { + let _val: Wrap = unsafe { std::mem::transmute::(2) }; + let _val: Wrap = unsafe { std::mem::transmute::(u32::MAX) }; +}