diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 5f71fb97d768c..792de77e9d4c1 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3063,6 +3063,7 @@ pub struct FieldDef { pub id: NodeId, pub span: Span, pub vis: Visibility, + pub safety: Safety, pub ident: Option, pub ty: P, diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 61f2f91635df3..198e1bca77443 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1115,10 +1115,11 @@ fn walk_poly_trait_ref(vis: &mut T, p: &mut PolyTraitRef) { } pub fn walk_field_def(visitor: &mut T, fd: &mut FieldDef) { - let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _ } = fd; + let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _, safety } = fd; visitor.visit_id(id); visit_attrs(visitor, attrs); visitor.visit_vis(vis); + visit_safety(visitor, safety); visit_opt(ident, |ident| visitor.visit_ident(ident)); visitor.visit_ty(ty); visitor.visit_span(span); diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 0302c9fa7f8ed..20ac9fa02bb53 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -961,7 +961,7 @@ pub fn walk_struct_def<'a, V: Visitor<'a>>( } pub fn walk_field_def<'a, V: Visitor<'a>>(visitor: &mut V, field: &'a FieldDef) -> V::Result { - let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _ } = field; + let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _, safety: _ } = field; walk_list!(visitor, visit_attribute, attrs); try_visit!(visitor.visit_vis(vis)); visit_opt!(visitor, visit_ident, ident); diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index d724560750114..48b42fa2e9762 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -724,6 +724,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }, vis_span: self.lower_span(f.vis.span), ty, + safety: self.lower_safety(f.safety, hir::Safety::Safe), } } diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index be6f2c152a4d2..8a392e4407b2c 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -557,6 +557,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(global_registration, "global registration is experimental"); gate_all!(return_type_notation, "return type notation is experimental"); gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); + gate_all!(unsafe_fields, "`unsafe` fields are experimental"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index bae16a18bcbfd..f044d964f1358 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -2,7 +2,7 @@ use rustc_ast::mut_visit::*; use rustc_ast::ptr::P; use rustc_ast::token::Delimiter; use rustc_ast::visit::AssocCtxt; -use rustc_ast::{self as ast}; +use rustc_ast::{self as ast, Safety}; use rustc_data_structures::fx::FxHashMap; use rustc_span::DUMMY_SP; use rustc_span::symbol::Ident; @@ -173,6 +173,7 @@ pub(crate) fn placeholder( ty: ty(), vis, is_placeholder: true, + safety: Safety::Default, }]), AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant { attrs: Default::default(), diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index b376b0d652fac..cf0f2a7e48c93 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -629,6 +629,8 @@ declare_features! ( /// Allows creation of instances of a struct by moving fields that have /// not changed from prior instances of the same struct (RFC #2528) (unstable, type_changing_struct_update, "1.58.0", Some(86555)), + /// Allows declaring fields `unsafe`. + (incomplete, unsafe_fields, "CURRENT_RUSTC_VERSION", Some(132922)), /// Allows const generic parameters to be defined with types that /// are not `Sized`, e.g. `fn foo() {`. (incomplete, unsized_const_params, "1.82.0", Some(95174)), diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 554097bf11515..16e53a27128b8 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3177,6 +3177,7 @@ pub struct FieldDef<'hir> { pub hir_id: HirId, pub def_id: LocalDefId, pub ty: &'hir Ty<'hir>, + pub safety: Safety, } impl FieldDef<'_> { diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 64a30e633cf9d..f5ccf8c9dffe2 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -253,6 +253,13 @@ hir_analysis_invalid_union_field = hir_analysis_invalid_union_field_sugg = wrap the field type in `ManuallyDrop<...>` +hir_analysis_invalid_unsafe_field = + field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be unsafe + .note = unsafe fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` + +hir_analysis_invalid_unsafe_field_sugg = + wrap the field type in `ManuallyDrop<...>` + hir_analysis_late_bound_const_in_apit = `impl Trait` can only mention const parameters from an fn or impl .label = const parameter declared here diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index cf3f3003bf514..c66572a937726 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -6,7 +6,7 @@ use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::MultiSpan; use rustc_errors::codes::*; use rustc_hir::def::{CtorKind, DefKind}; -use rustc_hir::{Node, intravisit}; +use rustc_hir::{Node, Safety, intravisit}; use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt}; use rustc_infer::traits::{Obligation, ObligationCauseCode}; use rustc_lint_defs::builtin::{ @@ -70,6 +70,7 @@ fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) { check_transparent(tcx, def); check_packed(tcx, span, def); + check_unsafe_fields(tcx, def_id); } fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) { @@ -81,38 +82,45 @@ fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) { check_packed(tcx, span, def); } +fn allowed_union_or_unsafe_field<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + span: Span, +) -> bool { + // We don't just accept all !needs_drop fields, due to semver concerns. + let allowed = match ty.kind() { + ty::Ref(..) => true, // references never drop (even mutable refs, which are non-Copy and hence fail the later check) + ty::Tuple(tys) => { + // allow tuples of allowed types + tys.iter().all(|ty| allowed_union_or_unsafe_field(tcx, ty, typing_env, span)) + } + ty::Array(elem, _len) => { + // Like `Copy`, we do *not* special-case length 0. + allowed_union_or_unsafe_field(tcx, *elem, typing_env, span) + } + _ => { + // Fallback case: allow `ManuallyDrop` and things that are `Copy`, + // also no need to report an error if the type is unresolved. + ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop()) + || ty.is_copy_modulo_regions(tcx, typing_env) + || ty.references_error() + } + }; + if allowed && ty.needs_drop(tcx, typing_env) { + // This should never happen. But we can get here e.g. in case of name resolution errors. + tcx.dcx() + .span_delayed_bug(span, "we should never accept maybe-dropping union or unsafe fields"); + } + allowed +} + /// Check that the fields of the `union` do not need dropping. fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool { let item_type = tcx.type_of(item_def_id).instantiate_identity(); if let ty::Adt(def, args) = item_type.kind() { assert!(def.is_union()); - fn allowed_union_field<'tcx>( - ty: Ty<'tcx>, - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - ) -> bool { - // We don't just accept all !needs_drop fields, due to semver concerns. - match ty.kind() { - ty::Ref(..) => true, // references never drop (even mutable refs, which are non-Copy and hence fail the later check) - ty::Tuple(tys) => { - // allow tuples of allowed types - tys.iter().all(|ty| allowed_union_field(ty, tcx, typing_env)) - } - ty::Array(elem, _len) => { - // Like `Copy`, we do *not* special-case length 0. - allowed_union_field(*elem, tcx, typing_env) - } - _ => { - // Fallback case: allow `ManuallyDrop` and things that are `Copy`, - // also no need to report an error if the type is unresolved. - ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop()) - || ty.is_copy_modulo_regions(tcx, typing_env) - || ty.references_error() - } - } - } - let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id); for field in &def.non_enum_variant().fields { let Ok(field_ty) = tcx.try_normalize_erasing_regions(typing_env, field.ty(tcx, args)) @@ -121,7 +129,7 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b continue; }; - if !allowed_union_field(field_ty, tcx, typing_env) { + if !allowed_union_or_unsafe_field(tcx, field_ty, typing_env, span) { let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) { // We are currently checking the type this field came from, so it must be local. Some(Node::Field(field)) => (field.span, field.ty.span), @@ -136,10 +144,6 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b note: (), }); return false; - } else if field_ty.needs_drop(tcx, typing_env) { - // This should never happen. But we can get here e.g. in case of name resolution errors. - tcx.dcx() - .span_delayed_bug(span, "we should never accept maybe-dropping union fields"); } } } else { @@ -148,6 +152,41 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b true } +/// Check that the unsafe fields do not need dropping. +fn check_unsafe_fields(tcx: TyCtxt<'_>, item_def_id: LocalDefId) { + let span = tcx.def_span(item_def_id); + let item_type = tcx.type_of(item_def_id).instantiate_identity(); + let ty::Adt(def, args) = item_type.kind() else { + span_bug!(span, "structs/enums must be ty::Adt, but got {:?}", item_type.kind()); + }; + let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id); + for field in def.all_fields() { + if field.safety != Safety::Unsafe { + continue; + } + let Ok(field_ty) = tcx.try_normalize_erasing_regions(typing_env, field.ty(tcx, args)) + else { + tcx.dcx().span_delayed_bug(span, "could not normalize field type"); + continue; + }; + + if !allowed_union_or_unsafe_field(tcx, field_ty, typing_env, span) { + let hir::Node::Field(field) = tcx.hir_node_by_def_id(field.did.expect_local()) else { + unreachable!("field has to correspond to hir field") + }; + let ty_span = field.ty.span; + tcx.dcx().emit_err(errors::InvalidUnsafeField { + field_span: field.span, + sugg: errors::InvalidUnsafeFieldSuggestion { + lo: ty_span.shrink_to_lo(), + hi: ty_span.shrink_to_hi(), + }, + note: (), + }); + } + } +} + /// Check that a `static` is inhabited. fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) { // Make sure statics are inhabited. @@ -1464,6 +1503,7 @@ fn check_enum(tcx: TyCtxt<'_>, def_id: LocalDefId) { detect_discriminant_duplicate(tcx, def); check_transparent(tcx, def); + check_unsafe_fields(tcx, def_id); } /// Part of enum check. Given the discriminants of an enum, errors if two or more discriminants are equal diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 73b73afb0a54b..93be3e06e5d9f 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1040,6 +1040,7 @@ fn lower_variant( did: f.def_id.to_def_id(), name: f.ident.name, vis: tcx.visibility(f.def_id), + safety: f.safety, }) .collect(); let recovered = match def { diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 07d3273b09c77..f5ca3c49475be 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -734,6 +734,17 @@ pub(crate) struct InvalidUnionField { pub note: (), } +#[derive(Diagnostic)] +#[diag(hir_analysis_invalid_unsafe_field, code = E0740)] +pub(crate) struct InvalidUnsafeField { + #[primary_span] + pub field_span: Span, + #[subdiagnostic] + pub sugg: InvalidUnsafeFieldSuggestion, + #[note] + pub note: (), +} + #[derive(Diagnostic)] #[diag(hir_analysis_return_type_notation_on_non_rpitit)] pub(crate) struct ReturnTypeNotationOnNonRpitit<'tcx> { @@ -755,6 +766,18 @@ pub(crate) struct InvalidUnionFieldSuggestion { pub hi: Span, } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + hir_analysis_invalid_unsafe_field_sugg, + applicability = "machine-applicable" +)] +pub(crate) struct InvalidUnsafeFieldSuggestion { + #[suggestion_part(code = "std::mem::ManuallyDrop<")] + pub lo: Span, + #[suggestion_part(code = ">")] + pub hi: Span, +} + #[derive(Diagnostic)] #[diag(hir_analysis_return_type_notation_equality_bound)] pub(crate) struct ReturnTypeNotationEqualityBound { diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 87357b74c411e..56beff5aa6425 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -15,6 +15,7 @@ use rustc_data_structures::sync::{Lock, Lrc, OnceLock}; use rustc_data_structures::unhash::UnhashMap; use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind}; use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro}; +use rustc_hir::Safety; use rustc_hir::def::Res; use rustc_hir::def_id::{CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::definitions::{DefPath, DefPathData}; @@ -1101,6 +1102,7 @@ impl<'a> CrateMetadataRef<'a> { did, name: self.item_name(did.index), vis: self.get_visibility(did.index), + safety: self.get_safety(did.index), }) .collect(), adt_kind, @@ -1162,6 +1164,10 @@ impl<'a> CrateMetadataRef<'a> { .map_id(|index| self.local_def_id(index)) } + fn get_safety(self, id: DefIndex) -> Safety { + self.root.tables.safety.get(self, id).unwrap_or_else(|| self.missing("safety", id)) + } + fn get_trait_item_def_id(self, id: DefIndex) -> Option { self.root.tables.trait_item_def_id.get(self, id).map(|d| d.decode_from_cdata(self)) } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8378e7c6e9b99..dd149af52052e 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1599,6 +1599,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { f.did.index })); + for field in &variant.fields { + self.tables.safety.set_some(field.did.index, field.safety); + } + if let Some((CtorKind::Fn, ctor_def_id)) = variant.ctor { let fn_sig = tcx.fn_sig(ctor_def_id); // FIXME only encode signature for ctor_def_id diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 4a8f8521b4ff7..a486ad42482a6 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -411,6 +411,7 @@ define_tables! { associated_item_or_field_def_ids: Table>, def_kind: Table, visibility: Table>>, + safety: Table, def_span: Table>, def_ident_span: Table>, lookup_stability: Table>, diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index 63abd2be9a57a..9438350ca0972 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -198,6 +198,13 @@ fixed_size_enum! { } } +fixed_size_enum! { + hir::Safety { + ( Unsafe ) + ( Safe ) + } +} + fixed_size_enum! { ty::Asyncness { ( Yes ) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 2bc055453a40c..a6c875ec61861 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -34,9 +34,9 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::tagged_ptr::CopyTaggedPtr; use rustc_errors::{Diag, ErrorGuaranteed, StashKey}; -use rustc_hir::LangItem; use rustc_hir::def::{CtorKind, CtorOf, DefKind, DocLinkResMap, LifetimeRes, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap}; +use rustc_hir::{LangItem, Safety}; use rustc_index::IndexVec; use rustc_macros::{ Decodable, Encodable, HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable, @@ -1365,6 +1365,11 @@ impl VariantDef { pub fn tail(&self) -> &FieldDef { self.tail_opt().expect("expected unsized ADT to have a tail field") } + + /// Returns whether this variant has unsafe fields. + pub fn has_unsafe_fields(&self) -> bool { + self.fields.iter().any(|x| x.safety == Safety::Unsafe) + } } impl PartialEq for VariantDef { @@ -1447,6 +1452,7 @@ pub struct FieldDef { pub did: DefId, pub name: Symbol, pub vis: Visibility, + pub safety: hir::Safety, } impl PartialEq for FieldDef { @@ -1459,15 +1465,16 @@ impl PartialEq for FieldDef { // of `FieldDef` changes, a compile-error will be produced, reminding // us to revisit this assumption. - let Self { did: lhs_did, name: _, vis: _ } = &self; + let Self { did: lhs_did, name: _, vis: _, safety: _ } = &self; - let Self { did: rhs_did, name: _, vis: _ } = other; + let Self { did: rhs_did, name: _, vis: _, safety: _ } = other; let res = lhs_did == rhs_did; // Double check that implicit assumption detailed above. if cfg!(debug_assertions) && res { - let deep = self.name == other.name && self.vis == other.vis; + let deep = + self.name == other.name && self.vis == other.vis && self.safety == other.safety; assert!(deep, "FieldDef for the same def-id has differing data"); } @@ -1487,7 +1494,7 @@ impl Hash for FieldDef { // of `FieldDef` changes, a compile-error will be produced, reminding // us to revisit this assumption. - let Self { did, name: _, vis: _ } = &self; + let Self { did, name: _, vis: _, safety: _ } = &self; did.hash(s) } diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs index 7c280bc8b49e9..f7322217aa33f 100644 --- a/compiler/rustc_middle/src/ty/parameterized.rs +++ b/compiler/rustc_middle/src/ty/parameterized.rs @@ -86,6 +86,7 @@ trivially_parameterized_over_tcx! { rustc_attr::Stability, rustc_hir::Constness, rustc_hir::Defaultness, + rustc_hir::Safety, rustc_hir::CoroutineKind, rustc_hir::IsAsync, rustc_hir::LangItem, diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index 5cb5990ae59b4..f2e2f31bd2bac 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -125,6 +125,16 @@ mir_build_initializing_type_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior .label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_build_initializing_type_with_unsafe_field_requires_unsafe = + initializing type with an unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = initialization of struct with unsafe field + +mir_build_initializing_type_with_unsafe_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + initializing type with an unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = initialization of struct with unsafe field + mir_build_inline_assembly_requires_unsafe = use of inline assembly is unsafe and requires unsafe block .note = inline assembly is entirely unchecked and can cause undefined behavior @@ -347,6 +357,16 @@ mir_build_unreachable_pattern = unreachable pattern .unreachable_pattern_let_binding = there is a binding of the same name; if you meant to pattern match against the value of that binding, that is a feature of constants that is not available for `let` bindings .suggestion = remove the match arm +mir_build_unsafe_field_requires_unsafe = + use of unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = use of unsafe field + +mir_build_unsafe_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + use of unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = use of unsafe field + mir_build_unsafe_fn_safe_body = an unsafe function restricts its caller, but its body is safe by default mir_build_unsafe_not_inherited = items do not inherit unsafety from separate enclosing items @@ -395,6 +415,11 @@ mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_requires_unsafe = .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior .label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_unsafe_field_requires_unsafe = + initializing type with an unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = initialization of struct with unsafe field + mir_build_unsafe_op_in_unsafe_fn_inline_assembly_requires_unsafe = use of inline assembly is unsafe and requires unsafe block .note = inline assembly is entirely unchecked and can cause undefined behavior @@ -415,6 +440,11 @@ mir_build_unsafe_op_in_unsafe_fn_union_field_requires_unsafe = .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior .label = access to union field +mir_build_unsafe_op_in_unsafe_fn_unsafe_field_requires_unsafe = + use of unsafe field is unsafe and requires unsafe block + .note = unsafe fields may carry library invariants + .label = use of unsafe field + mir_build_unsized_pattern = cannot use unsized non-slice type `{$non_sm_ty}` in constant patterns mir_build_unused_unsafe = unnecessary `unsafe` block diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index b497e54d480d2..f37b3f977fa25 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -4,7 +4,7 @@ use std::ops::Bound; use rustc_errors::DiagArgValue; use rustc_hir::def::DefKind; -use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability}; +use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability, Safety}; use rustc_middle::middle::codegen_fn_attrs::TargetFeature; use rustc_middle::mir::BorrowKind; use rustc_middle::span_bug; @@ -339,8 +339,13 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { }; match &pat.kind { - PatKind::Leaf { .. } => { + PatKind::Leaf { subpatterns, .. } => { if let ty::Adt(adt_def, ..) = pat.ty.kind() { + for pat in subpatterns { + if adt_def.non_enum_variant().fields[pat.field].safety == Safety::Unsafe { + self.requires_unsafe(pat.pattern.span, UseOfUnsafeField); + } + } if adt_def.is_union() { let old_in_union_destructure = std::mem::replace(&mut self.in_union_destructure, true); @@ -359,6 +364,15 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { visit::walk_pat(self, pat); } } + PatKind::Variant { adt_def, args: _, variant_index, subpatterns } => { + for pat in subpatterns { + let field = &pat.field; + if adt_def.variant(*variant_index).fields[*field].safety == Safety::Unsafe { + self.requires_unsafe(pat.pattern.span, UseOfUnsafeField); + } + } + visit::walk_pat(self, pat); + } PatKind::Binding { mode: BindingMode(ByRef::Yes(rm), _), ty, .. } => { if self.inside_adt { let ty::Ref(_, ty, _) = ty.kind() else { @@ -579,15 +593,20 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { } ExprKind::Adt(box AdtExpr { adt_def, - variant_index: _, + variant_index, args: _, user_ty: _, fields: _, base: _, - }) => match self.tcx.layout_scalar_valid_range(adt_def.did()) { - (Bound::Unbounded, Bound::Unbounded) => {} - _ => self.requires_unsafe(expr.span, InitializingTypeWith), - }, + }) => { + if adt_def.variant(variant_index).has_unsafe_fields() { + self.requires_unsafe(expr.span, InitializingTypeWithUnsafeField) + } + match self.tcx.layout_scalar_valid_range(adt_def.did()) { + (Bound::Unbounded, Bound::Unbounded) => {} + _ => self.requires_unsafe(expr.span, InitializingTypeWith), + } + } ExprKind::Closure(box ClosureExpr { closure_id, args: _, @@ -601,23 +620,24 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { let def_id = did.expect_local(); self.visit_inner_body(def_id); } - ExprKind::Field { lhs, .. } => { + ExprKind::Field { lhs, variant_index, name } => { let lhs = &self.thir[lhs]; - if let ty::Adt(adt_def, _) = lhs.ty.kind() - && adt_def.is_union() - { - if let Some(assigned_ty) = self.assignment_info { - if assigned_ty.needs_drop(self.tcx, self.typing_env) { - // This would be unsafe, but should be outright impossible since we - // reject such unions. - assert!( - self.tcx.dcx().has_errors().is_some(), - "union fields that need dropping should be impossible: \ - {assigned_ty}" - ); + if let ty::Adt(adt_def, _) = lhs.ty.kind() { + if adt_def.variant(variant_index).fields[name].safety == Safety::Unsafe { + self.requires_unsafe(expr.span, UseOfUnsafeField); + } else if adt_def.is_union() { + if let Some(assigned_ty) = self.assignment_info { + if assigned_ty.needs_drop(self.tcx, self.typing_env) { + // This would be unsafe, but should be outright impossible since we + // reject such unions. + assert!( + self.tcx.dcx().has_errors().is_some(), + "union fields that need dropping should be impossible: {assigned_ty}" + ); + } + } else { + self.requires_unsafe(expr.span, AccessToUnionField); } - } else { - self.requires_unsafe(expr.span, AccessToUnionField); } } } @@ -689,8 +709,10 @@ enum UnsafeOpKind { CallToUnsafeFunction(Option), UseOfInlineAssembly, InitializingTypeWith, + InitializingTypeWithUnsafeField, UseOfMutableStatic, UseOfExternStatic, + UseOfUnsafeField, DerefOfRawPointer, AccessToUnionField, MutationOfLayoutConstrainedField, @@ -770,6 +792,15 @@ impl UnsafeOpKind { unsafe_not_inherited_note, }, ), + InitializingTypeWithUnsafeField => tcx.emit_node_span_lint( + UNSAFE_OP_IN_UNSAFE_FN, + hir_id, + span, + UnsafeOpInUnsafeFnInitializingTypeWithUnsafeFieldRequiresUnsafe { + span, + unsafe_not_inherited_note, + }, + ), UseOfMutableStatic => tcx.emit_node_span_lint( UNSAFE_OP_IN_UNSAFE_FN, hir_id, @@ -788,6 +819,15 @@ impl UnsafeOpKind { unsafe_not_inherited_note, }, ), + UseOfUnsafeField => tcx.emit_node_span_lint( + UNSAFE_OP_IN_UNSAFE_FN, + hir_id, + span, + UnsafeOpInUnsafeFnUseOfUnsafeFieldRequiresUnsafe { + span, + unsafe_not_inherited_note, + }, + ), DerefOfRawPointer => tcx.emit_node_span_lint( UNSAFE_OP_IN_UNSAFE_FN, hir_id, @@ -927,6 +967,20 @@ impl UnsafeOpKind { unsafe_not_inherited_note, }); } + InitializingTypeWithUnsafeField if unsafe_op_in_unsafe_fn_allowed => { + dcx.emit_err( + InitializingTypeWithUnsafeFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }, + ); + } + InitializingTypeWithUnsafeField => { + dcx.emit_err(InitializingTypeWithUnsafeFieldRequiresUnsafe { + span, + unsafe_not_inherited_note, + }); + } UseOfMutableStatic if unsafe_op_in_unsafe_fn_allowed => { dcx.emit_err(UseOfMutableStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span, @@ -945,6 +999,15 @@ impl UnsafeOpKind { UseOfExternStatic => { dcx.emit_err(UseOfExternStaticRequiresUnsafe { span, unsafe_not_inherited_note }); } + UseOfUnsafeField if unsafe_op_in_unsafe_fn_allowed => { + dcx.emit_err(UseOfUnsafeFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); + } + UseOfUnsafeField => { + dcx.emit_err(UseOfUnsafeFieldRequiresUnsafe { span, unsafe_not_inherited_note }); + } DerefOfRawPointer if unsafe_op_in_unsafe_fn_allowed => { dcx.emit_err(DerefOfRawPointerRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span, diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index df3a2344c5eba..2e8e0e527190c 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -86,6 +86,16 @@ pub(crate) struct UnsafeOpInUnsafeFnInitializingTypeWithRequiresUnsafe { pub(crate) unsafe_not_inherited_note: Option, } +#[derive(LintDiagnostic)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_unsafe_field_requires_unsafe, code = E0133)] +#[note] +pub(crate) struct UnsafeOpInUnsafeFnInitializingTypeWithUnsafeFieldRequiresUnsafe { + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + #[derive(LintDiagnostic)] #[diag(mir_build_unsafe_op_in_unsafe_fn_mutable_static_requires_unsafe, code = E0133)] #[note] @@ -106,6 +116,16 @@ pub(crate) struct UnsafeOpInUnsafeFnUseOfExternStaticRequiresUnsafe { pub(crate) unsafe_not_inherited_note: Option, } +#[derive(LintDiagnostic)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_unsafe_field_requires_unsafe, code = E0133)] +#[note] +pub(crate) struct UnsafeOpInUnsafeFnUseOfUnsafeFieldRequiresUnsafe { + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + #[derive(LintDiagnostic)] #[diag(mir_build_unsafe_op_in_unsafe_fn_deref_raw_pointer_requires_unsafe, code = E0133)] #[note] @@ -250,6 +270,17 @@ pub(crate) struct InitializingTypeWithRequiresUnsafe { pub(crate) unsafe_not_inherited_note: Option, } +#[derive(Diagnostic)] +#[diag(mir_build_initializing_type_with_unsafe_field_requires_unsafe, code = E0133)] +#[note] +pub(crate) struct InitializingTypeWithUnsafeFieldRequiresUnsafe { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + #[derive(Diagnostic)] #[diag( mir_build_initializing_type_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed, @@ -264,6 +295,20 @@ pub(crate) struct InitializingTypeWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed { pub(crate) unsafe_not_inherited_note: Option, } +#[derive(Diagnostic)] +#[diag( + mir_build_initializing_type_with_unsafe_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed, + code = E0133 +)] +#[note] +pub(crate) struct InitializingTypeWithUnsafeFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + #[derive(Diagnostic)] #[diag(mir_build_mutable_static_requires_unsafe, code = E0133)] #[note] @@ -308,6 +353,28 @@ pub(crate) struct UseOfExternStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { pub(crate) unsafe_not_inherited_note: Option, } +#[derive(Diagnostic)] +#[diag(mir_build_unsafe_field_requires_unsafe, code = E0133)] +#[note] +pub(crate) struct UseOfUnsafeFieldRequiresUnsafe { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + +#[derive(Diagnostic)] +#[diag(mir_build_unsafe_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed, code = E0133)] +#[note] +pub(crate) struct UseOfUnsafeFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) unsafe_not_inherited_note: Option, +} + #[derive(Diagnostic)] #[diag(mir_build_deref_raw_pointer_requires_unsafe, code = E0133)] #[note] diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index fddbf5896ad83..26e81b7676bb1 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1791,6 +1791,17 @@ impl<'a> Parser<'a> { Ok((fields, recovered)) } + fn parse_unsafe_field(&mut self) -> Safety { + // not using parse_safety as that also accepts `safe`. + if self.eat_keyword(kw::Unsafe) { + let span = self.prev_token.span; + self.psess.gated_spans.gate(sym::unsafe_fields, span); + Safety::Unsafe(span) + } else { + Safety::Default + } + } + pub(super) fn parse_tuple_struct_body(&mut self) -> PResult<'a, ThinVec> { // This is the case where we find `struct Foo(T) where T: Copy;` // Unit like structs are handled in parse_item_struct function @@ -1814,6 +1825,8 @@ impl<'a> Parser<'a> { return Err(err); } }; + // Unsafe fields are not supported in tuple structs, as doing so would result in a + // parsing ambiguity for `struct X(unsafe fn())`. let ty = match p.parse_ty() { Ok(ty) => ty, Err(err) => { @@ -1828,6 +1841,7 @@ impl<'a> Parser<'a> { FieldDef { span: lo.to(ty.span), vis, + safety: Safety::Default, ident: None, id: DUMMY_NODE_ID, ty, @@ -1850,7 +1864,8 @@ impl<'a> Parser<'a> { self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; let vis = this.parse_visibility(FollowedByType::No)?; - this.parse_single_struct_field(adt_ty, lo, vis, attrs) + let safety = this.parse_unsafe_field(); + this.parse_single_struct_field(adt_ty, lo, vis, safety, attrs) .map(|field| (field, Trailing::No, UsePreAttrPos::No)) }) } @@ -1861,10 +1876,11 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, + safety: Safety, attrs: AttrVec, ) -> PResult<'a, FieldDef> { let mut seen_comma: bool = false; - let a_var = self.parse_name_and_ty(adt_ty, lo, vis, attrs)?; + let a_var = self.parse_name_and_ty(adt_ty, lo, vis, safety, attrs)?; if self.token == token::Comma { seen_comma = true; } @@ -1992,6 +2008,7 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, + safety: Safety, attrs: AttrVec, ) -> PResult<'a, FieldDef> { let name = self.parse_field_ident(adt_ty, lo)?; @@ -2017,6 +2034,7 @@ impl<'a> Parser<'a> { span: lo.to(self.prev_token.span), ident: Some(name), vis, + safety, id: DUMMY_NODE_ID, ty, attrs, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c7c29abc3cbbe..3d0ec2afa2b73 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2087,6 +2087,7 @@ symbols! { unsafe_cell, unsafe_cell_raw_get, unsafe_extern_blocks, + unsafe_fields, unsafe_no_drop_flag, unsafe_pin_internals, unsize, diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 68c481bda6e18..18932587f1fb0 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -1916,15 +1916,15 @@ pub(crate) fn rewrite_struct_field_prefix( field: &ast::FieldDef, ) -> RewriteResult { let vis = format_visibility(context, &field.vis); + let safety = format_safety(field.safety); let type_annotation_spacing = type_annotation_spacing(context.config); Ok(match field.ident { Some(name) => format!( - "{}{}{}:", - vis, + "{vis}{safety}{}{}:", rewrite_ident(context, name), type_annotation_spacing.0 ), - None => vis.to_string(), + None => format!("{vis}{safety}"), }) } diff --git a/src/tools/rustfmt/tests/source/unsafe-field.rs b/src/tools/rustfmt/tests/source/unsafe-field.rs new file mode 100644 index 0000000000000..35cf40a22d367 --- /dev/null +++ b/src/tools/rustfmt/tests/source/unsafe-field.rs @@ -0,0 +1,16 @@ +struct Foo { + unsafe + field: (), +} + +enum Bar { + Variant { + unsafe + field: (), + }, +} + +union Baz { + unsafe + field: (), +} diff --git a/src/tools/rustfmt/tests/target/unsafe-field.rs b/src/tools/rustfmt/tests/target/unsafe-field.rs new file mode 100644 index 0000000000000..fc7fd3750c60e --- /dev/null +++ b/src/tools/rustfmt/tests/target/unsafe-field.rs @@ -0,0 +1,11 @@ +struct Foo { + unsafe field: (), +} + +enum Bar { + Variant { unsafe field: () }, +} + +union Baz { + unsafe field: (), +} diff --git a/tests/ui/auxiliary/unsafe-fields-crate-dep.rs b/tests/ui/auxiliary/unsafe-fields-crate-dep.rs new file mode 100644 index 0000000000000..95b928402a9f8 --- /dev/null +++ b/tests/ui/auxiliary/unsafe-fields-crate-dep.rs @@ -0,0 +1,7 @@ +#![allow(incomplete_features)] +#![feature(unsafe_fields)] + +pub struct WithUnsafeField { + pub unsafe unsafe_field: u32, + pub safe_field: u32, +} diff --git a/tests/ui/feature-gates/feature-gate-unsafe_fields.rs b/tests/ui/feature-gates/feature-gate-unsafe_fields.rs new file mode 100644 index 0000000000000..8f9b411df4691 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-unsafe_fields.rs @@ -0,0 +1,25 @@ +//@ compile-flags: --crate-type=lib +//@ revisions: with_gate without_gate +//@ [with_gate] check-pass + +#![cfg_attr(with_gate, feature(unsafe_fields))] //[with_gate]~ WARNING + +#[cfg(any())] +struct Foo { + unsafe field: (), //[without_gate]~ ERROR +} + +// This should not parse as an unsafe field definition. +struct FooTuple(unsafe fn()); + +#[cfg(any())] +enum Bar { + Variant { unsafe field: () }, //[without_gate]~ ERROR + // This should not parse as an unsafe field definition. + VariantTuple(unsafe fn()), +} + +#[cfg(any())] +union Baz { + unsafe field: (), //[without_gate]~ ERROR +} diff --git a/tests/ui/feature-gates/feature-gate-unsafe_fields.with_gate.stderr b/tests/ui/feature-gates/feature-gate-unsafe_fields.with_gate.stderr new file mode 100644 index 0000000000000..a7deeb057d86a --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-unsafe_fields.with_gate.stderr @@ -0,0 +1,11 @@ +warning: the feature `unsafe_fields` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/feature-gate-unsafe_fields.rs:5:32 + | +LL | #![cfg_attr(with_gate, feature(unsafe_fields))] + | ^^^^^^^^^^^^^ + | + = note: see issue #132922 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/feature-gates/feature-gate-unsafe_fields.without_gate.stderr b/tests/ui/feature-gates/feature-gate-unsafe_fields.without_gate.stderr new file mode 100644 index 0000000000000..ad0f5fd59ac3d --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-unsafe_fields.without_gate.stderr @@ -0,0 +1,33 @@ +error[E0658]: `unsafe` fields are experimental + --> $DIR/feature-gate-unsafe_fields.rs:9:5 + | +LL | unsafe field: (), + | ^^^^^^ + | + = note: see issue #132922 for more information + = help: add `#![feature(unsafe_fields)]` 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[E0658]: `unsafe` fields are experimental + --> $DIR/feature-gate-unsafe_fields.rs:17:15 + | +LL | Variant { unsafe field: () }, + | ^^^^^^ + | + = note: see issue #132922 for more information + = help: add `#![feature(unsafe_fields)]` 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[E0658]: `unsafe` fields are experimental + --> $DIR/feature-gate-unsafe_fields.rs:24:5 + | +LL | unsafe field: (), + | ^^^^^^ + | + = note: see issue #132922 for more information + = help: add `#![feature(unsafe_fields)]` 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 3 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/stats/input-stats.stderr b/tests/ui/stats/input-stats.stderr index 2cc2c8019fa1b..f2dd904375981 100644 --- a/tests/ui/stats/input-stats.stderr +++ b/tests/ui/stats/input-stats.stderr @@ -15,17 +15,17 @@ ast-stats-1 ForeignItem 88 ( 1.3%) 1 88 ast-stats-1 - Fn 88 ( 1.3%) 1 ast-stats-1 Arm 96 ( 1.4%) 2 48 ast-stats-1 FnDecl 120 ( 1.8%) 5 24 -ast-stats-1 FieldDef 160 ( 2.4%) 2 80 ast-stats-1 Param 160 ( 2.4%) 4 40 ast-stats-1 Stmt 160 ( 2.4%) 5 32 ast-stats-1 - Let 32 ( 0.5%) 1 ast-stats-1 - MacCall 32 ( 0.5%) 1 ast-stats-1 - Expr 96 ( 1.4%) 3 +ast-stats-1 FieldDef 176 ( 2.6%) 2 88 ast-stats-1 Block 192 ( 2.9%) 6 32 ast-stats-1 Variant 208 ( 3.1%) 2 104 ast-stats-1 AssocItem 352 ( 5.3%) 4 88 -ast-stats-1 - Type 176 ( 2.7%) 2 -ast-stats-1 - Fn 176 ( 2.7%) 2 +ast-stats-1 - Type 176 ( 2.6%) 2 +ast-stats-1 - Fn 176 ( 2.6%) 2 ast-stats-1 GenericBound 352 ( 5.3%) 4 88 ast-stats-1 - Trait 352 ( 5.3%) 4 ast-stats-1 GenericParam 480 ( 7.2%) 5 96 @@ -38,7 +38,7 @@ ast-stats-1 - Path 72 ( 1.1%) 1 ast-stats-1 - Match 72 ( 1.1%) 1 ast-stats-1 - Struct 72 ( 1.1%) 1 ast-stats-1 - Lit 144 ( 2.2%) 2 -ast-stats-1 - Block 216 ( 3.3%) 3 +ast-stats-1 - Block 216 ( 3.2%) 3 ast-stats-1 PathSegment 744 (11.2%) 31 24 ast-stats-1 Ty 896 (13.5%) 14 64 ast-stats-1 - Ref 64 ( 1.0%) 1 @@ -53,7 +53,7 @@ ast-stats-1 - Enum 136 ( 2.0%) 1 ast-stats-1 - Fn 272 ( 4.1%) 2 ast-stats-1 - Use 408 ( 6.1%) 3 ast-stats-1 ---------------------------------------------------------------- -ast-stats-1 Total 6_640 116 +ast-stats-1 Total 6_656 116 ast-stats-1 ast-stats-2 POST EXPANSION AST STATS ast-stats-2 Name Accumulated Size Count Item Size @@ -73,14 +73,14 @@ ast-stats-2 InlineAsm 120 ( 1.6%) 1 120 ast-stats-2 Attribute 128 ( 1.8%) 4 32 ast-stats-2 - DocComment 32 ( 0.4%) 1 ast-stats-2 - Normal 96 ( 1.3%) 3 -ast-stats-2 FieldDef 160 ( 2.2%) 2 80 ast-stats-2 Param 160 ( 2.2%) 4 40 ast-stats-2 Stmt 160 ( 2.2%) 5 32 ast-stats-2 - Let 32 ( 0.4%) 1 ast-stats-2 - Semi 32 ( 0.4%) 1 ast-stats-2 - Expr 96 ( 1.3%) 3 +ast-stats-2 FieldDef 176 ( 2.4%) 2 88 ast-stats-2 Block 192 ( 2.6%) 6 32 -ast-stats-2 Variant 208 ( 2.9%) 2 104 +ast-stats-2 Variant 208 ( 2.8%) 2 104 ast-stats-2 AssocItem 352 ( 4.8%) 4 88 ast-stats-2 - Type 176 ( 2.4%) 2 ast-stats-2 - Fn 176 ( 2.4%) 2 @@ -98,7 +98,7 @@ ast-stats-2 - Struct 72 ( 1.0%) 1 ast-stats-2 - InlineAsm 72 ( 1.0%) 1 ast-stats-2 - Lit 144 ( 2.0%) 2 ast-stats-2 - Block 216 ( 3.0%) 3 -ast-stats-2 PathSegment 864 (11.9%) 36 24 +ast-stats-2 PathSegment 864 (11.8%) 36 24 ast-stats-2 Ty 896 (12.3%) 14 64 ast-stats-2 - Ref 64 ( 0.9%) 1 ast-stats-2 - Ptr 64 ( 0.9%) 1 @@ -111,9 +111,9 @@ ast-stats-2 - Impl 136 ( 1.9%) 1 ast-stats-2 - ExternCrate 136 ( 1.9%) 1 ast-stats-2 - ForeignMod 136 ( 1.9%) 1 ast-stats-2 - Fn 272 ( 3.7%) 2 -ast-stats-2 - Use 544 ( 7.5%) 4 +ast-stats-2 - Use 544 ( 7.4%) 4 ast-stats-2 ---------------------------------------------------------------- -ast-stats-2 Total 7_288 127 +ast-stats-2 Total 7_304 127 ast-stats-2 hir-stats HIR STATS hir-stats Name Accumulated Size Count Item Size @@ -132,11 +132,11 @@ hir-stats Body 72 ( 0.8%) 3 24 hir-stats ImplItemRef 72 ( 0.8%) 2 36 hir-stats InlineAsm 72 ( 0.8%) 1 72 hir-stats Arm 80 ( 0.9%) 2 40 -hir-stats FieldDef 96 ( 1.1%) 2 48 hir-stats Stmt 96 ( 1.1%) 3 32 hir-stats - Let 32 ( 0.4%) 1 hir-stats - Semi 32 ( 0.4%) 1 hir-stats - Expr 32 ( 0.4%) 1 +hir-stats FieldDef 112 ( 1.2%) 2 56 hir-stats FnDecl 120 ( 1.3%) 3 40 hir-stats Attribute 128 ( 1.4%) 4 32 hir-stats GenericArgs 144 ( 1.6%) 3 48 @@ -162,17 +162,17 @@ hir-stats - Match 64 ( 0.7%) 1 hir-stats - Struct 64 ( 0.7%) 1 hir-stats - InlineAsm 64 ( 0.7%) 1 hir-stats - Lit 128 ( 1.4%) 2 -hir-stats - Block 384 ( 4.3%) 6 +hir-stats - Block 384 ( 4.2%) 6 hir-stats Item 968 (10.7%) 11 88 hir-stats - Enum 88 ( 1.0%) 1 hir-stats - Trait 88 ( 1.0%) 1 hir-stats - Impl 88 ( 1.0%) 1 hir-stats - ExternCrate 88 ( 1.0%) 1 hir-stats - ForeignMod 88 ( 1.0%) 1 -hir-stats - Fn 176 ( 2.0%) 2 +hir-stats - Fn 176 ( 1.9%) 2 hir-stats - Use 352 ( 3.9%) 4 hir-stats Path 1_240 (13.7%) 31 40 -hir-stats PathSegment 1_920 (21.3%) 40 48 +hir-stats PathSegment 1_920 (21.2%) 40 48 hir-stats ---------------------------------------------------------------- -hir-stats Total 9_024 180 +hir-stats Total 9_040 180 hir-stats diff --git a/tests/ui/thir-print/thir-tree-match.stdout b/tests/ui/thir-print/thir-tree-match.stdout index a9d6985928aad..d56f15fb221c8 100644 --- a/tests/ui/thir-print/thir-tree-match.stdout +++ b/tests/ui/thir-print/thir-tree-match.stdout @@ -92,7 +92,7 @@ body: adt_def: AdtDef { did: DefId(0:10 ~ thir_tree_match[fcf8]::Foo) - variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])) }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] + variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])), safety: Safe }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] flags: IS_ENUM repr: ReprOptions { int: None, align: None, pack: None, flags: , field_shuffle_seed: 3477539199540094892 } args: [] @@ -154,7 +154,7 @@ body: adt_def: AdtDef { did: DefId(0:10 ~ thir_tree_match[fcf8]::Foo) - variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])) }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] + variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])), safety: Safe }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] flags: IS_ENUM repr: ReprOptions { int: None, align: None, pack: None, flags: , field_shuffle_seed: 3477539199540094892 } args: [] @@ -206,7 +206,7 @@ body: adt_def: AdtDef { did: DefId(0:10 ~ thir_tree_match[fcf8]::Foo) - variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])) }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] + variants: [VariantDef { def_id: DefId(0:11 ~ thir_tree_match[fcf8]::Foo::FooOne), ctor: Some((Fn, DefId(0:12 ~ thir_tree_match[fcf8]::Foo::FooOne::{constructor#0}))), name: "FooOne", discr: Relative(0), fields: [FieldDef { did: DefId(0:13 ~ thir_tree_match[fcf8]::Foo::FooOne::0), name: "0", vis: Restricted(DefId(0:0 ~ thir_tree_match[fcf8])), safety: Safe }], tainted: None, flags: }, VariantDef { def_id: DefId(0:14 ~ thir_tree_match[fcf8]::Foo::FooTwo), ctor: Some((Const, DefId(0:15 ~ thir_tree_match[fcf8]::Foo::FooTwo::{constructor#0}))), name: "FooTwo", discr: Relative(1), fields: [], tainted: None, flags: }] flags: IS_ENUM repr: ReprOptions { int: None, align: None, pack: None, flags: , field_shuffle_seed: 3477539199540094892 } args: [] diff --git a/tests/ui/unsafe-fields-crate.rs b/tests/ui/unsafe-fields-crate.rs new file mode 100644 index 0000000000000..cfb9ad6b544fd --- /dev/null +++ b/tests/ui/unsafe-fields-crate.rs @@ -0,0 +1,62 @@ +//@ compile-flags: --crate-type=lib +//@ aux-build: unsafe-fields-crate-dep.rs + +extern crate unsafe_fields_crate_dep; + +use unsafe_fields_crate_dep::WithUnsafeField; + +fn new_without_unsafe() -> WithUnsafeField { + WithUnsafeField { + //~^ ERROR + unsafe_field: 0, + safe_field: 0, + } +} + +fn operate_on_safe_field(s: &mut WithUnsafeField) { + s.safe_field = 2; + &s.safe_field; + s.safe_field; +} + +fn set_unsafe_field(s: &mut WithUnsafeField) { + unsafe { + s.unsafe_field = 2; + } +} + +fn read_unsafe_field(s: &WithUnsafeField) -> u32 { + unsafe { s.unsafe_field } +} + +fn ref_unsafe_field(s: &WithUnsafeField) -> &u32 { + unsafe { &s.unsafe_field } +} + +fn destructure(s: &WithUnsafeField) { + unsafe { + let WithUnsafeField { safe_field, unsafe_field } = s; + } +} + +fn set_unsafe_field_without_unsafe(s: &mut WithUnsafeField) { + s.unsafe_field = 2; + //~^ ERROR +} + +fn read_unsafe_field_without_unsafe(s: &WithUnsafeField) -> u32 { + s.unsafe_field + //~^ ERROR +} + +fn ref_unsafe_field_without_unsafe(s: &WithUnsafeField) -> &u32 { + &s.unsafe_field + //~^ ERROR +} + +fn destructure_without_unsafe(s: &WithUnsafeField) { + let WithUnsafeField { safe_field, unsafe_field } = s; + //~^ ERROR + + let WithUnsafeField { safe_field, .. } = s; +} diff --git a/tests/ui/unsafe-fields-crate.stderr b/tests/ui/unsafe-fields-crate.stderr new file mode 100644 index 0000000000000..778c26e0a4398 --- /dev/null +++ b/tests/ui/unsafe-fields-crate.stderr @@ -0,0 +1,47 @@ +error[E0133]: initializing type with an unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields-crate.rs:9:5 + | +LL | / WithUnsafeField { +LL | | +LL | | unsafe_field: 0, +LL | | safe_field: 0, +LL | | } + | |_____^ initialization of struct with unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields-crate.rs:43:5 + | +LL | s.unsafe_field = 2; + | ^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields-crate.rs:48:5 + | +LL | s.unsafe_field + | ^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields-crate.rs:53:6 + | +LL | &s.unsafe_field + | ^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields-crate.rs:58:39 + | +LL | let WithUnsafeField { safe_field, unsafe_field } = s; + | ^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0133`. diff --git a/tests/ui/unsafe-fields-parse.rs b/tests/ui/unsafe-fields-parse.rs new file mode 100644 index 0000000000000..67277731293a7 --- /dev/null +++ b/tests/ui/unsafe-fields-parse.rs @@ -0,0 +1,10 @@ +//@ compile-flags: --crate-type=lib +#![allow(incomplete_features)] +#![feature(unsafe_fields)] + +// Parse errors even *with* unsafe_fields, which would make the compiler early-exit otherwise. +enum A { + TupleLike(unsafe u32), //~ ERROR +} + +struct B(unsafe u32); //~ ERROR diff --git a/tests/ui/unsafe-fields-parse.stderr b/tests/ui/unsafe-fields-parse.stderr new file mode 100644 index 0000000000000..5a45ab03ffe05 --- /dev/null +++ b/tests/ui/unsafe-fields-parse.stderr @@ -0,0 +1,18 @@ +error: expected type, found keyword `unsafe` + --> $DIR/unsafe-fields-parse.rs:7:15 + | +LL | enum A { + | - while parsing this enum +LL | TupleLike(unsafe u32), + | ^^^^^^ expected type + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + +error: expected type, found keyword `unsafe` + --> $DIR/unsafe-fields-parse.rs:10:10 + | +LL | struct B(unsafe u32); + | ^^^^^^ expected type + +error: aborting due to 2 previous errors + diff --git a/tests/ui/unsafe-fields.rs b/tests/ui/unsafe-fields.rs new file mode 100644 index 0000000000000..637471582d7e4 --- /dev/null +++ b/tests/ui/unsafe-fields.rs @@ -0,0 +1,109 @@ +//@ compile-flags: --crate-type=lib +#![allow(incomplete_features)] +#![feature(unsafe_fields)] + +struct WithUnsafeField { + unsafe unsafe_field: u32, + safe_field: u32, +} + +enum A { + WithUnsafeField { unsafe unsafe_field: u32, safe_field: u32 }, +} + +fn f(a: A) { + let A::WithUnsafeField { unsafe_field, safe_field } = a; + //~^ ERROR +} + +struct WithInvalidUnsafeField { + unsafe unsafe_noncopy_field: Vec, //~ ERROR +} + +struct WithManuallyDropUnsafeField { + unsafe unsafe_noncopy_field: std::mem::ManuallyDrop>, +} + +union WithUnsafeFieldUnion { + unsafe unsafe_field: u32, + safe_field: u32, +} + +impl WithUnsafeField { + fn new() -> WithUnsafeField { + unsafe { + WithUnsafeField { + unsafe_field: 0, + safe_field: 0, + } + } + } + + fn new_without_unsafe() -> WithUnsafeField { + WithUnsafeField { //~ ERROR + unsafe_field: 0, + safe_field: 0, + } + } + + fn operate_on_safe_field(&mut self) { + self.safe_field = 2; + &self.safe_field; + self.safe_field; + } + + fn set_unsafe_field(&mut self) { + unsafe { + self.unsafe_field = 2; + } + } + + fn read_unsafe_field(&self) -> u32 { + unsafe { + self.unsafe_field + } + } + + fn ref_unsafe_field(&self) -> &u32 { + unsafe { + &self.unsafe_field + } + } + + fn destructure(&self) { + unsafe { + let Self { safe_field, unsafe_field } = self; + } + } + + fn set_unsafe_field_without_unsafe(&mut self) { + self.unsafe_field = 2; + //~^ ERROR + } + + fn read_unsafe_field_without_unsafe(&self) -> u32 { + self.unsafe_field + //~^ ERROR + } + + fn ref_unsafe_field_without_unsafe(&self) -> &u32 { + &self.unsafe_field + //~^ ERROR + } + + fn destructure_without_unsafe(&self) { + let Self { safe_field, unsafe_field } = self; + //~^ ERROR + + let WithUnsafeField { safe_field, .. } = self; + } + + fn offset_of(&self) -> usize { + std::mem::offset_of!(WithUnsafeField, unsafe_field) + } + + fn raw_const(&self) -> *const u32 { + &raw const self.unsafe_field + //~^ ERROR + } +} diff --git a/tests/ui/unsafe-fields.stderr b/tests/ui/unsafe-fields.stderr new file mode 100644 index 0000000000000..a1c5d2b44cdf1 --- /dev/null +++ b/tests/ui/unsafe-fields.stderr @@ -0,0 +1,75 @@ +error[E0740]: field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be unsafe + --> $DIR/unsafe-fields.rs:20:5 + | +LL | unsafe unsafe_noncopy_field: Vec, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsafe fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` +help: wrap the field type in `ManuallyDrop<...>` + | +LL | unsafe unsafe_noncopy_field: std::mem::ManuallyDrop>, + | +++++++++++++++++++++++ + + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:15:30 + | +LL | let A::WithUnsafeField { unsafe_field, safe_field } = a; + | ^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: initializing type with an unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:43:9 + | +LL | / WithUnsafeField { +LL | | unsafe_field: 0, +LL | | safe_field: 0, +LL | | } + | |_________^ initialization of struct with unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:80:9 + | +LL | self.unsafe_field = 2; + | ^^^^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:85:9 + | +LL | self.unsafe_field + | ^^^^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:90:10 + | +LL | &self.unsafe_field + | ^^^^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:95:32 + | +LL | let Self { safe_field, unsafe_field } = self; + | ^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error[E0133]: use of unsafe field is unsafe and requires unsafe block + --> $DIR/unsafe-fields.rs:106:20 + | +LL | &raw const self.unsafe_field + | ^^^^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants + +error: aborting due to 8 previous errors + +Some errors have detailed explanations: E0133, E0740. +For more information about an error, try `rustc --explain E0133`.