diff --git a/Cargo.lock b/Cargo.lock index 02ca28a287bbb..9b6f75d8cb01f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4892,6 +4892,7 @@ dependencies = [ name = "rustc_type_ir_macros" version = "0.0.0" dependencies = [ + "indexmap", "proc-macro2", "quote", "syn", diff --git a/compiler/rustc_borrowck/src/diagnostics/find_use.rs b/compiler/rustc_borrowck/src/diagnostics/find_use.rs index 96f48840468e5..ab4a710ba70e5 100644 --- a/compiler/rustc_borrowck/src/diagnostics/find_use.rs +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -35,7 +35,7 @@ impl<'a, 'tcx> UseFinder<'a, 'tcx> { queue.push_back(self.start_point); while let Some(p) = queue.pop_front() { - if !self.regioncx.region_contains(self.region_vid, p) { + if !self.regioncx.region_contains_point(self.region_vid, p) { continue; } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 6bdb3ac14ee98..5e56ae80ff5da 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -30,7 +30,7 @@ use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; -use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues}; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -345,7 +345,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { outlives_constraints, scc_annotations, type_tests, - liveness_constraints, + mut liveness_constraints, universe_causes, placeholder_indices, } = lowered_constraints; @@ -364,106 +364,54 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut scc_values = RegionValues::new(location_map, universal_regions.len(), placeholder_indices); - for region in liveness_constraints.regions() { + // Initializes the region variables with their initial live points. + for (region, definition) in definitions.iter_enumerated() { let scc = constraint_sccs.scc(region); - scc_values.merge_liveness(scc, region, &liveness_constraints); - } - let mut result = Self { - definitions, - liveness_constraints, - constraints: outlives_constraints, - constraint_graph, - constraint_sccs, - scc_annotations, - universe_causes, - scc_values, - type_tests, - universal_region_relations, - }; - - result.init_free_and_bound_regions(); - - result - } - - /// Initializes the region variables for each universally - /// quantified region (lifetime parameter). The first N variables - /// always correspond to the regions appearing in the function - /// signature (both named and anonymous) and where-clauses. This - /// function iterates over those regions and initializes them with - /// minimum values. - /// - /// For example: - /// ```ignore (illustrative) - /// fn foo<'a, 'b>( /* ... */ ) where 'a: 'b { /* ... */ } - /// ``` - /// would initialize two variables like so: - /// ```ignore (illustrative) - /// R0 = { CFG, R0 } // 'a - /// R1 = { CFG, R0, R1 } // 'b - /// ``` - /// Here, R0 represents `'a`, and it contains (a) the entire CFG - /// and (b) any universally quantified regions that it outlives, - /// which in this case is just itself. R1 (`'b`) in contrast also - /// outlives `'a` and hence contains R0 and R1. - /// - /// This bit of logic also handles invalid universe relations - /// for higher-kinded types. - /// - /// We Walk each SCC `A` and `B` such that `A: B` - /// and ensure that universe(A) can see universe(B). - /// - /// This serves to enforce the 'empty/placeholder' hierarchy - /// (described in more detail on `RegionKind`): - /// - /// ```ignore (illustrative) - /// static -----+ - /// | | - /// empty(U0) placeholder(U1) - /// | / - /// empty(U1) - /// ``` - /// - /// In particular, imagine we have variables R0 in U0 and R1 - /// created in U1, and constraints like this; - /// - /// ```ignore (illustrative) - /// R1: !1 // R1 outlives the placeholder in U1 - /// R1: R0 // R1 outlives R0 - /// ``` - /// - /// Here, we wish for R1 to be `'static`, because it - /// cannot outlive `placeholder(U1)` and `empty(U0)` any other way. - /// - /// Thanks to this loop, what happens is that the `R1: R0` - /// constraint has lowered the universe of `R1` to `U0`, which in turn - /// means that the `R1: !1` constraint here will cause - /// `R1` to become `'static`. - fn init_free_and_bound_regions(&mut self) { - for variable in self.definitions.indices() { - let scc = self.constraint_sccs.scc(variable); - - match self.definitions[variable].origin { + // For each universally quantified region (lifetime parameter). The + // first N variables always correspond to the regions appearing in the + // function signature (both named and anonymous) and in where-clauses. + match definition.origin { + // For each free, universally quantified region X: NllRegionVariableOrigin::FreeRegion => { - // For each free, universally quantified region X: - // Add all nodes in the CFG to liveness constraints - self.liveness_constraints.add_all_points(variable); - self.scc_values.add_all_points(scc); + liveness_constraints.add_all_points(region); // Add `end(X)` into the set for X. - self.scc_values.add_element(scc, variable); + scc_values.add_free_region(scc, region); } NllRegionVariableOrigin::Placeholder(placeholder) => { - self.scc_values.add_element(scc, placeholder); + scc_values.add_placeholder(scc, placeholder); } NllRegionVariableOrigin::Existential { .. } => { // For existential, regions, nothing to do. } } + + // Initially copy the liveness constraints of any region that + // has them, setting `scc_values[scc(region)] |= liveness_constraints[region]`. + // + // These values will later be propagated during [`Self::propagate_constraints()`]. + // The values include any live-at-all-points constraints added above + // for free regions. + if let Some(liveness) = liveness_constraints.point_liveness(region) { + scc_values.merge_liveness(scc, liveness) + } + } + + Self { + definitions, + liveness_constraints, + constraints: outlives_constraints, + constraint_graph, + constraint_sccs, + scc_annotations, + universe_causes, + scc_values, + type_tests, + universal_region_relations, } } @@ -495,9 +443,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// Returns `true` if the region `r` contains the point `p`. /// /// Panics if called before `solve()` executes, - pub(crate) fn region_contains(&self, r: RegionVid, p: impl ToElementIndex<'tcx>) -> bool { + pub(crate) fn region_contains_point(&self, r: RegionVid, p: Location) -> bool { let scc = self.constraint_sccs.scc(r); - self.scc_values.contains(scc, p) + self.scc_values.contains_point(scc, p) } /// Returns the lowest statement index in `start..=end` which is not contained by `r`. @@ -608,7 +556,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { // To propagate constraints, we walk the DAG induced by the // SCC. For each SCC `A`, we visit its successors and compute // their values, then we union all those values to get our - // own. + // own. This one-shot approach works because iteration is in + // dependency order. I.e. a chain A: B: C will visit C, B, A. for scc_a in self.constraint_sccs.all_sccs() { // Walk each SCC `B` such that `A: B`... for &scc_b in self.constraint_sccs.successors(scc_a) { @@ -1643,10 +1592,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self.definitions[r] } - /// Check if the SCC of `r` contains `upper`. + /// Check if the SCC of `r` contains `upper`, a free region. pub(crate) fn upper_bound_in_region_scc(&self, r: RegionVid, upper: RegionVid) -> bool { let r_scc = self.constraint_sccs.scc(r); - self.scc_values.contains(r_scc, upper) + self.scc_values.contains_free_region(r_scc, upper) } pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs index ada8908e220ac..ae5a213c5c37d 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs @@ -78,11 +78,11 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> { let placeholder_indices = Default::default(); let mut scc_values = RegionValues::new(location_map, universal_regions.len(), placeholder_indices); - for variable in definitions.indices() { + for (variable, definition) in definitions.iter_enumerated() { let scc = constraint_sccs.scc(variable); - match definitions[variable].origin { + match definition.origin { NllRegionVariableOrigin::FreeRegion => { - scc_values.add_element(scc, variable); + scc_values.add_free_region(scc, variable); } _ => {} } diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 626b7e1084192..27ee3f6176ee0 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -95,9 +95,10 @@ impl LivenessValues { } } - /// Iterate through each region that has a value in this set. - pub(crate) fn regions(&self) -> impl Iterator { - self.points().rows() + /// Get the liveness status of a region `r`, if any. + /// Panics if liveness data is not tracked for any region. + pub(crate) fn point_liveness(&self, region: RegionVid) -> Option<&IntervalSet> { + self.points().row(region) } /// Iterate through each region that has a value in this set. @@ -166,13 +167,12 @@ impl LivenessValues { /// [`point`][rustc_mir_dataflow::points::PointIndex]. #[inline] pub(crate) fn is_live_at_point(&self, region: RegionVid, point: PointIndex) -> bool { - self.points().row(region).is_some_and(|r| r.contains(point)) + self.point_liveness(region).is_some_and(|r| r.contains(point)) } /// Returns an iterator of all the points where `region` is live. fn live_points(&self, region: RegionVid) -> impl Iterator { - self.points() - .row(region) + self.point_liveness(region) .into_iter() .flat_map(|set| set.iter()) .take_while(|&p| self.location_map.point_in_range(p)) @@ -296,18 +296,6 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { } } - /// Adds the given element to the value for the given region. Returns whether - /// the element is newly added (i.e., was not already present). - pub(crate) fn add_element(&mut self, r: N, elem: impl ToElementIndex<'tcx>) -> bool { - debug!("add(r={:?}, elem={:?})", r, elem); - elem.add_to_row(self, r) - } - - /// Adds all the control-flow points to the values for `r`. - pub(crate) fn add_all_points(&mut self, r: N) { - self.points.insert_all_into_row(r); - } - /// Adds all elements in `r_from` to `r_to` (because e.g., `r_to: /// r_from`). pub(crate) fn add_region(&mut self, r_to: N, r_from: N) -> bool { @@ -316,11 +304,6 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { | self.placeholders.union_rows(r_from, r_to) } - /// Returns `true` if the region `r` contains the given element. - pub(crate) fn contains(&self, r: N, elem: impl ToElementIndex<'tcx>) -> bool { - elem.contained_in_row(self, r) - } - /// Returns the lowest statement index in `start..=end` which is not contained by `r`. pub(crate) fn first_non_contained_inclusive( &self, @@ -337,13 +320,9 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { Some(first_unset.index() - block.index()) } - /// `self[to] |= values[from]`, essentially: that is, take all the - /// elements for the region `from` from `values` and add them to - /// the region `to` in `self`. - pub(crate) fn merge_liveness(&mut self, to: N, from: RegionVid, values: &LivenessValues) { - if let Some(set) = values.points().row(from) { - self.points.union_row(to, set); - } + /// Merge a row of liveness into our points. + pub(crate) fn merge_liveness(&mut self, to: N, liveness: &IntervalSet) { + self.points.union_row(to, liveness); } /// Returns `true` if `sup_region` contains all the CFG points that @@ -405,47 +384,26 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { pub(crate) fn region_value_str(&self, r: N) -> String { pretty_print_region_elements(self.elements_contained_in(r)) } -} - -pub(crate) trait ToElementIndex<'tcx>: Debug + Copy { - fn add_to_row(self, values: &mut RegionValues<'tcx, N>, row: N) -> bool; - - fn contained_in_row(self, values: &RegionValues<'tcx, N>, row: N) -> bool; -} - -impl ToElementIndex<'_> for Location { - fn add_to_row(self, values: &mut RegionValues<'_, N>, row: N) -> bool { - let index = values.location_map.point_from_location(self); - values.points.insert(row, index) - } - fn contained_in_row(self, values: &RegionValues<'_, N>, row: N) -> bool { - let index = values.location_map.point_from_location(self); - values.points.contains(row, index) - } -} - -impl ToElementIndex<'_> for RegionVid { - fn add_to_row(self, values: &mut RegionValues<'_, N>, row: N) -> bool { - values.free_regions.insert(row, self) + /// Add a the free region with rvid `region` to SCC `scc` + pub(crate) fn add_free_region(&mut self, scc: N, region: RegionVid) { + self.free_regions.insert(scc, region); } - fn contained_in_row(self, values: &RegionValues<'_, N>, row: N) -> bool { - values.free_regions.contains(row, self) + pub(crate) fn add_placeholder(&mut self, scc: N, placeholder: ty::PlaceholderRegion<'tcx>) { + let index = self.placeholder_indices.lookup_index(placeholder); + self.placeholders.insert(scc, index); } -} -impl<'tcx> ToElementIndex<'tcx> for ty::PlaceholderRegion<'tcx> { - fn add_to_row(self, values: &mut RegionValues<'tcx, N>, row: N) -> bool { - let placeholder: ty::PlaceholderRegion<'tcx> = self.into(); - let index = values.placeholder_indices.lookup_index(placeholder); - values.placeholders.insert(row, index) + /// Determine if `scc` contains the CFG point `p`. + pub(crate) fn contains_point(&self, scc: N, p: Location) -> bool { + let index = self.location_map.point_from_location(p); + self.points.contains(scc, index) } - fn contained_in_row(self, values: &RegionValues<'tcx, N>, row: N) -> bool { - let placeholder: ty::PlaceholderRegion<'tcx> = self.into(); - let index = values.placeholder_indices.lookup_index(placeholder); - values.placeholders.contains(row, index) + /// Determine if `scc` contains the free region `free_region`. + pub(crate) fn contains_free_region(&self, scc: N, free_region: RegionVid) -> bool { + self.free_regions.contains(scc, free_region) } } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 85a7f9cab73a4..2b4d82f61d0e5 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -201,6 +201,14 @@ pub(crate) unsafe fn create_module<'ll>( if sess.target.arch == Arch::PowerPC64 { // LLVM 22 updated the ABI alignment for double on AIX: https://github.com/llvm/llvm-project/pull/144673 target_data_layout = target_data_layout.replace("-f64:32:64", ""); + + // LLVM 22 fixed the data layout calculation for targets that default to ELFv1 + // when the ABI is set to ELFv2. With LLVM 21, the ELFv1 datalayout must be used, + // which will overalign function entries. + // https://github.com/llvm/llvm-project/pull/149725 + if sess.target.llvm_target == "powerpc64-unknown-linux-gnu" { + target_data_layout = target_data_layout.replace("-Fn32", "-Fi64"); + } } if sess.target.arch == Arch::AmdGpu { // LLVM 22 specified ELF mangling in the amdgpu data layout: diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 0fd47cb15f286..26d98ec13cc2b 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -152,7 +152,20 @@ fn build_pointer_or_reference_di_node<'ll, 'tcx>( cx.size_and_align_of(Ty::new_mut_ptr(cx.tcx, pointee_type)) ); - let pointee_type_di_node = type_di_node(cx, pointee_type); + let pointee_type_di_node = match pointee_type.kind() { + // `&[T]` will look like `{ data_ptr: *const T, length: usize }` + ty::Slice(element_type) => type_di_node(cx, *element_type), + // `&str` will look like `{ data_ptr: *const u8, length: usize }` + ty::Str => type_di_node(cx, cx.tcx.types.u8), + + // `&dyn K` will look like `{ pointer: _, vtable: _}` + // any Adt `Foo` containing an unsized type (eg `&[_]` or `&dyn _`) + // will look like `{ data_ptr: *const Foo, length: usize }` + // and thin pointers `&Foo` will just look like `*const Foo`. + // + // in all those cases, we just use the pointee_type + _ => type_di_node(cx, pointee_type), + }; return_if_di_node_created_in_meantime!(cx, unique_type_id); @@ -389,26 +402,11 @@ fn build_dyn_type_di_node<'ll, 'tcx>( } /// Create debuginfo for `[T]` and `str`. These are unsized. -/// -/// NOTE: We currently emit just emit the debuginfo for the element type here -/// (i.e. `T` for slices and `u8` for `str`), so that we end up with -/// `*const T` for the `data_ptr` field of the corresponding wide-pointer -/// debuginfo of `&[T]`. -/// -/// It would be preferable and more accurate if we emitted a DIArray of T -/// without an upper bound instead. That is, LLVM already supports emitting -/// debuginfo of arrays of unknown size. But GDB currently seems to end up -/// in an infinite loop when confronted with such a type. -/// -/// As a side effect of the current encoding every instance of a type like -/// `struct Foo { unsized_field: [u8] }` will look like -/// `struct Foo { unsized_field: u8 }` in debuginfo. If the length of the -/// slice is zero, then accessing `unsized_field` in the debugger would -/// result in an out-of-bounds access. fn build_slice_type_di_node<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, slice_type: Ty<'tcx>, unique_type_id: UniqueTypeId<'tcx>, + span: Span, ) -> DINodeCreationResult<'ll> { let element_type = match slice_type.kind() { ty::Slice(element_type) => *element_type, @@ -423,7 +421,20 @@ fn build_slice_type_di_node<'ll, 'tcx>( let element_type_di_node = type_di_node(cx, element_type); return_if_di_node_created_in_meantime!(cx, unique_type_id); - DINodeCreationResult { di_node: element_type_di_node, already_stored_in_typemap: false } + let (size, align) = cx.spanned_size_and_align_of(slice_type, span); + let subrange = unsafe { llvm::LLVMDIBuilderGetOrCreateSubrange(DIB(cx), 0, -1) }; + let subscripts = &[subrange]; + let di_node = unsafe { + llvm::LLVMDIBuilderCreateArrayType( + DIB(cx), + size.bits(), + align.bits() as u32, + element_type_di_node, + subscripts.as_ptr(), + subscripts.len() as c_uint, + ) + }; + DINodeCreationResult { di_node, already_stored_in_typemap: false } } /// Get the debuginfo node for the given type. @@ -454,7 +465,7 @@ pub(crate) fn spanned_type_di_node<'ll, 'tcx>( } ty::Tuple(elements) if elements.is_empty() => build_basic_type_di_node(cx, t), ty::Array(..) => build_fixed_size_array_di_node(cx, unique_type_id, t, span), - ty::Slice(_) | ty::Str => build_slice_type_di_node(cx, t, unique_type_id), + ty::Slice(_) | ty::Str => build_slice_type_di_node(cx, t, unique_type_id, span), ty::Dynamic(..) => build_dyn_type_di_node(cx, t, unique_type_id), ty::Foreign(..) => build_foreign_type_di_node(cx, t, unique_type_id), ty::RawPtr(pointee_type, _) | ty::Ref(_, pointee_type, _) => { diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 40ba31d37f2f0..85da687d8af37 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -502,8 +502,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let mplace = ecx.force_allocation(&place)?; // Consume the remaining arguments by putting them into the variable argument - // list. - let varargs = ecx.allocate_varargs(&mut caller_args, &mut callee_args_abis)?; + // list. We disable retagging to avoid creating protected tags. Protection should + // only use callee-side information, and the varargs have no static callee-side type. + let varargs = M::with_retag_mode(ecx, RetagMode::None, |ecx| { + ecx.allocate_varargs(&mut caller_args, &mut callee_args_abis) + })?; + // When the frame is dropped, these variable arguments are deallocated. ecx.frame_mut().va_list = varargs.clone(); let key = ecx.va_list_ptr(varargs.into()); diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 249a65e228245..224337e68cfbf 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -925,7 +925,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { } interp_ok(true) } - ty::RawPtr(..) => { + ty::RawPtr(pointee, ..) => { let ptr = self.read_immediate(value, ExpectedKind::RawPtr)?; if self.reset_provenance_and_padding { self.reset_pointer_provenance(value, &ptr)?; @@ -933,8 +933,12 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { self.add_data_range_place(value); } - let place = self.ecx.imm_ptr_to_mplace(&ptr)?; - if place.layout.is_unsized() { + if !pointee.is_sized(*self.ecx.tcx, self.ecx.typing_env) { + // Raw pointers to unsized types need to have their metadata checked. + // We avoid creating this place for sized types to match codegen: those types + // might actually be invalid (i.e., too big)! + let place = self.ecx.imm_ptr_to_mplace(&ptr)?; + assert!(place.layout.is_unsized()); self.check_wide_ptr_meta(place.meta(), place.layout)?; } interp_ok(true) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 0553276b1053b..f3bf57ab4cd34 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -903,16 +903,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Type check the pattern. Override if necessary to avoid knock-on errors. self.check_pat_top(decl.pat, decl_ty, ty_span, origin_expr, Some(decl.origin)); - if decl.ty.is_none() - && decl.init.is_none() - && !matches!(decl.pat.kind, hir::PatKind::Binding(.., None) | hir::PatKind::Wild) - { - self.register_wf_obligation( - decl_ty.into(), - decl.pat.span, - ObligationCauseCode::WellFormed(None), - ); - } let pat_ty = self.node_ty(decl.pat.hir_id); self.overwrite_local_ty_if_err(decl.hir_id, decl.pat, pat_ty); diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index b5ae3d06b3b33..ae716e4dc6870 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -544,21 +544,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { debug!("scrutinee ty {expected:?} is a pinned reference, inserting pin deref"); - // if the inner_ty is an ADT, make sure that it can be structurally pinned - // (i.e., it is `#[pin_v2]`). - if let Some(adt) = inner_ty.ty_adt_def() - && !adt.is_pin_project() - && !adt.is_pin() - { - let def_span: Option = self.tcx.hir_span_if_local(adt.did()); - let sugg_span = def_span.map(|span| span.shrink_to_lo()); - self.dcx().emit_err(crate::errors::ProjectOnNonPinProjectType { - span: pat.span, - def_span, - sugg_span, - }); - } - // Use the old pat info to keep `current_depth` to its old value. let new_pat_info = self.adjust_pat_info(Pinnedness::Pinned, inner_mutability, old_pat_info); @@ -1523,6 +1508,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(ResolvedPat { ty: pat_ty, kind: ResolvedPatKind::Struct { variant } }) } + /// Reject pin-projection through a type that isn't structurally pinnable. + /// + /// Destructuring an ADT underneath a `&pin` reference projects its fields as pinned references. + /// This is only sound if the type opted into structural pinning with `#[pin_v2]`; otherwise it + /// would let safe code form a `Pin<&mut Field>` for a type that should never be pinned, breaking + /// the `Pin` guarantee (see #157634). + /// + /// This covers both explicit (`&pin mut`/`&pin const`) and implicit (match-ergonomics) + /// projection. `max_pinnedness` is only set for `&pin mut`, so the implicit shared (`&pin + /// const`) case is instead recognized through its pinned binding mode, hence both are checked. + fn check_pin_projection( + &self, + pat: &'tcx Pat<'tcx>, + pat_ty: Ty<'tcx>, + pat_info: PatInfo<'tcx>, + ) { + let through_pin = pat_info.max_pinnedness == PinnednessCap::Pinned + || matches!(pat_info.binding_mode, ByRef::Yes(Pinnedness::Pinned, _)); + if through_pin + && let Some(adt) = pat_ty.ty_adt_def() + && !adt.is_pin_project() + && !adt.is_pin() + { + let def_span: Option = self.tcx.hir_span_if_local(adt.did()); + let sugg_span = def_span.map(|span| span.shrink_to_lo()); + self.dcx().emit_err(crate::errors::ProjectOnNonPinProjectType { + span: pat.span, + def_span, + sugg_span, + }); + } + } + fn check_pat_struct( &self, pat: &'tcx Pat<'tcx>, @@ -1533,6 +1551,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, pat_info: PatInfo<'tcx>, ) -> Ty<'tcx> { + self.check_pin_projection(pat, pat_ty, pat_info); + // Type-check the path. let had_err = self.demand_eqtype_pat(pat.span, expected, pat_ty, &pat_info.top_info); @@ -1791,6 +1811,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, pat_info: PatInfo<'tcx>, ) -> Ty<'tcx> { + self.check_pin_projection(pat, pat_ty, pat_info); + let tcx = self.tcx; let on_error = |e| { for pat in subpats { diff --git a/compiler/rustc_incremental/src/assert_dep_graph.rs b/compiler/rustc_incremental/src/assert_dep_graph.rs index b3f9a137817a7..8a13cb3e9a96d 100644 --- a/compiler/rustc_incremental/src/assert_dep_graph.rs +++ b/compiler/rustc_incremental/src/assert_dep_graph.rs @@ -52,7 +52,7 @@ use rustc_middle::ty::TyCtxt; use rustc_span::{Span, Symbol, sym}; use tracing::debug; -use crate::errors; +use crate::diagnostics; #[allow(missing_docs)] pub(crate) fn assert_dep_graph(tcx: TyCtxt<'_>) { @@ -128,7 +128,7 @@ impl<'tcx> IfThisChanged<'tcx> { Err(()) => self .tcx .dcx() - .emit_fatal(errors::UnrecognizedDepNode { span, name: n }), + .emit_fatal(diagnostics::UnrecognizedDepNode { span, name: n }), } } }; @@ -139,9 +139,10 @@ impl<'tcx> IfThisChanged<'tcx> { let Ok(dep_node) = DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) else { - self.tcx - .dcx() - .emit_fatal(errors::UnrecognizedDepNode { span: n.span, name: n.name }); + self.tcx.dcx().emit_fatal(diagnostics::UnrecognizedDepNode { + span: n.span, + name: n.name, + }); }; self.then_this_would_need.push((n.span, n.name, hir_id, dep_node)); } @@ -186,7 +187,7 @@ fn check_paths<'tcx>( ) { if if_this_changed.is_empty() { for &(target_span, _, _, _) in then_this_would_need { - tcx.dcx().emit_err(errors::MissingIfThisChanged { span: target_span }); + tcx.dcx().emit_err(diagnostics::MissingIfThisChanged { span: target_span }); } return; } @@ -195,13 +196,13 @@ fn check_paths<'tcx>( let dependents = query.transitive_predecessors(source_dep_node); for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need { if !dependents.contains(&target_dep_node) { - tcx.dcx().emit_err(errors::NoPath { + tcx.dcx().emit_err(diagnostics::NoPath { span: target_span, source: tcx.def_path_str(source_def_id), target: *target_pass, }); } else { - tcx.dcx().emit_err(errors::Ok { span: target_span }); + tcx.dcx().emit_err(diagnostics::Ok { span: target_span }); } } } diff --git a/compiler/rustc_incremental/src/errors.rs b/compiler/rustc_incremental/src/diagnostics.rs similarity index 100% rename from compiler/rustc_incremental/src/errors.rs rename to compiler/rustc_incremental/src/diagnostics.rs diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs index ebaed7bd16346..0f5d9c5389667 100644 --- a/compiler/rustc_incremental/src/lib.rs +++ b/compiler/rustc_incremental/src/lib.rs @@ -6,7 +6,7 @@ // tidy-alphabetical-end mod assert_dep_graph; -mod errors; +mod diagnostics; mod persist; pub use persist::{ diff --git a/compiler/rustc_incremental/src/persist/clean.rs b/compiler/rustc_incremental/src/persist/clean.rs index 800805551af63..a8eb1ff3d5a42 100644 --- a/compiler/rustc_incremental/src/persist/clean.rs +++ b/compiler/rustc_incremental/src/persist/clean.rs @@ -33,7 +33,7 @@ use rustc_middle::ty::TyCtxt; use rustc_span::{Span, Symbol}; use tracing::debug; -use crate::errors; +use crate::diagnostics; // Base and Extra labels to build up the labels @@ -193,7 +193,7 @@ impl<'tcx> CleanVisitor<'tcx> { let loaded_from_disk = self.loaded_from_disk(attr); for e in except.items().into_sorted_stable_ord() { if !auto.remove(e) { - self.tcx.dcx().emit_fatal(errors::AssertionAuto { span: attr.span, name, e }); + self.tcx.dcx().emit_fatal(diagnostics::AssertionAuto { span: attr.span, name, e }); } } Assertion { clean: auto, dirty: except, loaded_from_disk } @@ -267,7 +267,7 @@ impl<'tcx> CleanVisitor<'tcx> { // An implementation, eg `impl Trait for Foo { .. }` HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL), - _ => self.tcx.dcx().emit_fatal(errors::UndefinedCleanDirtyItem { + _ => self.tcx.dcx().emit_fatal(diagnostics::UndefinedCleanDirtyItem { span, kind: format!("{:?}", item.kind), }), @@ -286,7 +286,7 @@ impl<'tcx> CleanVisitor<'tcx> { _ => self .tcx .dcx() - .emit_fatal(errors::UndefinedCleanDirty { span, kind: format!("{node:?}") }), + .emit_fatal(diagnostics::UndefinedCleanDirty { span, kind: format!("{node:?}") }), }; let labels = Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| (*l).to_string()))); @@ -301,13 +301,13 @@ impl<'tcx> CleanVisitor<'tcx> { if out.contains(label_str) { self.tcx .dcx() - .emit_fatal(errors::RepeatedDepNodeLabel { span, label: label_str }); + .emit_fatal(diagnostics::RepeatedDepNodeLabel { span, label: label_str }); } out.insert(label_str.to_string()); } else { self.tcx .dcx() - .emit_fatal(errors::UnrecognizedDepNodeLabel { span, label: label_str }); + .emit_fatal(diagnostics::UnrecognizedDepNodeLabel { span, label: label_str }); } } out @@ -328,7 +328,7 @@ impl<'tcx> CleanVisitor<'tcx> { let dep_node_str = self.dep_node_str(&dep_node); self.tcx .dcx() - .emit_err(errors::NotDirty { span: item_span, dep_node_str: &dep_node_str }); + .emit_err(diagnostics::NotDirty { span: item_span, dep_node_str: &dep_node_str }); } } @@ -339,7 +339,7 @@ impl<'tcx> CleanVisitor<'tcx> { let dep_node_str = self.dep_node_str(&dep_node); self.tcx .dcx() - .emit_err(errors::NotClean { span: item_span, dep_node_str: &dep_node_str }); + .emit_err(diagnostics::NotClean { span: item_span, dep_node_str: &dep_node_str }); } } @@ -369,7 +369,7 @@ impl<'tcx> CleanVisitor<'tcx> { Ok(dep_node) => { if !self.tcx.dep_graph.debug_was_loaded_from_disk(dep_node) { let dep_node_str = self.dep_node_str(&dep_node); - self.tcx.dcx().emit_err(errors::NotLoaded { + self.tcx.dcx().emit_err(diagnostics::NotLoaded { span: item_span, dep_node_str: &dep_node_str, }); @@ -379,7 +379,7 @@ impl<'tcx> CleanVisitor<'tcx> { Err(()) => { let dep_kind = dep_kind_from_label(label); if !self.tcx.dep_graph.debug_dep_kind_was_loaded_from_disk(dep_kind) { - self.tcx.dcx().emit_err(errors::NotLoaded { + self.tcx.dcx().emit_err(diagnostics::NotLoaded { span: item_span, dep_node_str: &label, }); @@ -407,7 +407,7 @@ impl<'tcx> FindAllAttrs<'tcx> { fn report_unchecked_attrs(&self, mut checked_attrs: FxHashSet) { for attr in &self.found_attrs { if !checked_attrs.contains(&attr.span) { - self.tcx.dcx().emit_err(errors::UncheckedClean { span: attr.span }); + self.tcx.dcx().emit_err(diagnostics::UncheckedClean { span: attr.span }); checked_attrs.insert(attr.span); } } diff --git a/compiler/rustc_incremental/src/persist/file_format.rs b/compiler/rustc_incremental/src/persist/file_format.rs index eee40fbedf569..853a5c9ba7ab0 100644 --- a/compiler/rustc_incremental/src/persist/file_format.rs +++ b/compiler/rustc_incremental/src/persist/file_format.rs @@ -20,7 +20,7 @@ use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use rustc_session::Session; use tracing::debug; -use crate::errors; +use crate::diagnostics; /// The first few bytes of files generated by incremental compilation. const FILE_MAGIC: &[u8] = b"RSIC"; @@ -57,12 +57,12 @@ where debug!("save: remove old file"); } Err(err) if err.kind() == io::ErrorKind::NotFound => (), - Err(err) => sess.dcx().emit_fatal(errors::DeleteOld { name, path: path_buf, err }), + Err(err) => sess.dcx().emit_fatal(diagnostics::DeleteOld { name, path: path_buf, err }), } let mut encoder = match FileEncoder::new(&path_buf) { Ok(encoder) => encoder, - Err(err) => sess.dcx().emit_fatal(errors::CreateNew { name, path: path_buf, err }), + Err(err) => sess.dcx().emit_fatal(diagnostics::CreateNew { name, path: path_buf, err }), }; write_file_header(&mut encoder, sess); @@ -76,7 +76,7 @@ where ); debug!("save: data written to disk successfully"); } - Err((path, err)) => sess.dcx().emit_fatal(errors::WriteNew { name, path, err }), + Err((path, err)) => sess.dcx().emit_fatal(diagnostics::WriteNew { name, path, err }), } } diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 9e22935bb646f..80134d5ff2202 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -120,7 +120,7 @@ use rustc_session::{Session, StableCrateId}; use rustc_span::Symbol; use tracing::debug; -use crate::errors; +use crate::diagnostics; #[cfg(test)] mod tests; @@ -233,7 +233,7 @@ pub(crate) fn prepare_session_directory( let crate_dir = match try_canonicalize(&crate_dir) { Ok(v) => v, Err(err) => { - sess.dcx().emit_fatal(errors::CanonicalizePath { path: crate_dir, err }); + sess.dcx().emit_fatal(diagnostics::CanonicalizePath { path: crate_dir, err }); } }; @@ -276,7 +276,7 @@ pub(crate) fn prepare_session_directory( debug!("successfully copied data from: {}", source_directory.display()); if !allows_links { - sess.dcx().emit_warn(errors::HardLinkFailed { path: &session_dir }); + sess.dcx().emit_warn(diagnostics::HardLinkFailed { path: &session_dir }); } sess.init_incr_comp_session(session_dir, directory_lock); @@ -291,7 +291,7 @@ pub(crate) fn prepare_session_directory( // Try to remove the session directory we just allocated. We don't // know if there's any garbage in it from the failed copy action. if let Err(err) = std_fs::remove_dir_all(&session_dir) { - sess.dcx().emit_warn(errors::DeletePartial { path: &session_dir, err }); + sess.dcx().emit_warn(diagnostics::DeletePartial { path: &session_dir, err }); } delete_session_dir_lock_file(sess, &lock_file_path); @@ -325,7 +325,7 @@ pub fn finalize_session_directory(sess: &Session, svh: Option) { ); if let Err(err) = std_fs::remove_dir_all(&*incr_comp_session_dir) { - sess.dcx().emit_warn(errors::DeleteFull { path: &incr_comp_session_dir, err }); + sess.dcx().emit_warn(diagnostics::DeleteFull { path: &incr_comp_session_dir, err }); } let lock_file_path = lock_file_path(&*incr_comp_session_dir); @@ -364,7 +364,7 @@ pub fn finalize_session_directory(sess: &Session, svh: Option) { } Err(e) => { // Warn about the error. However, no need to abort compilation now. - sess.dcx().emit_note(errors::Finalize { path: &incr_comp_session_dir, err: e }); + sess.dcx().emit_note(diagnostics::Finalize { path: &incr_comp_session_dir, err: e }); debug!("finalize_session_directory() - error, marking as invalid"); // Drop the file lock, so we can garage collect @@ -464,7 +464,9 @@ fn create_dir(sess: &Session, path: &Path, dir_tag: &str) { Ok(()) => { debug!("{} directory created successfully", dir_tag); } - Err(err) => sess.dcx().emit_fatal(errors::CreateIncrCompDir { tag: dir_tag, path, err }), + Err(err) => { + sess.dcx().emit_fatal(diagnostics::CreateIncrCompDir { tag: dir_tag, path, err }) + } } } @@ -483,7 +485,7 @@ fn lock_directory(sess: &Session, session_dir: &Path) -> (flock::Lock, PathBuf) Ok(lock) => (lock, lock_file_path), Err(lock_err) => { let is_unsupported_lock = flock::Lock::error_unsupported(&lock_err); - sess.dcx().emit_fatal(errors::CreateLock { + sess.dcx().emit_fatal(diagnostics::CreateLock { lock_err, session_dir, is_unsupported_lock, @@ -495,7 +497,7 @@ fn lock_directory(sess: &Session, session_dir: &Path) -> (flock::Lock, PathBuf) fn delete_session_dir_lock_file(sess: &Session, lock_file_path: &Path) { if let Err(err) = safe_remove_file(lock_file_path) { - sess.dcx().emit_warn(errors::DeleteLock { path: lock_file_path, err }); + sess.dcx().emit_warn(diagnostics::DeleteLock { path: lock_file_path, err }); } } @@ -708,7 +710,7 @@ pub(crate) fn garbage_collect_session_directories(sess: &Session) -> io::Result< if !lock_file_to_session_dir.items().any(|(_, dir)| *dir == directory_name) { let path = crate_directory.join(directory_name); if let Err(err) = std_fs::remove_dir_all(&path) { - sess.dcx().emit_warn(errors::InvalidGcFailed { path: &path, err }); + sess.dcx().emit_warn(diagnostics::InvalidGcFailed { path: &path, err }); } } } @@ -840,7 +842,7 @@ pub(crate) fn garbage_collect_session_directories(sess: &Session) -> io::Result< debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); if let Err(err) = std_fs::remove_dir_all(&path) { - sess.dcx().emit_warn(errors::FinalizedGcFailed { path: &path, err }); + sess.dcx().emit_warn(diagnostics::FinalizedGcFailed { path: &path, err }); } else { delete_session_dir_lock_file(sess, &lock_file_path(&path)); } @@ -858,7 +860,7 @@ fn delete_old(sess: &Session, path: &Path) { debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); if let Err(err) = std_fs::remove_dir_all(path) { - sess.dcx().emit_warn(errors::SessionGcFailed { path, err }); + sess.dcx().emit_warn(diagnostics::SessionGcFailed { path, err }); } else { delete_session_dir_lock_file(sess, &lock_file_path(path)); } diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index 6229c66afa75b..c3b9f433417a6 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -18,7 +18,7 @@ use tracing::{debug, warn}; use super::data::*; use super::fs::*; use super::{file_format, work_product}; -use crate::errors; +use crate::diagnostics; use crate::persist::file_format::{OpenFile, OpenFileError}; #[derive(Debug)] @@ -60,7 +60,7 @@ fn load_dep_graph(sess: &Session) -> LoadResult { { // Decode the list of work_products let Ok(mut work_product_decoder) = MemDecoder::new(&mmap[..], start_pos) else { - sess.dcx().emit_warn(errors::CorruptFile { path: &work_products_path }); + sess.dcx().emit_warn(diagnostics::CorruptFile { path: &work_products_path }); return LoadResult::DataOutOfDate; }; let work_products: Vec = @@ -93,7 +93,7 @@ fn load_dep_graph(sess: &Session) -> LoadResult { Err(OpenFileError::IoError { err }) => LoadResult::IoError { path: path.to_owned(), err }, Ok(OpenFile { mmap, start_pos }) => { let Ok(mut decoder) = MemDecoder::new(&mmap, start_pos) else { - sess.dcx().emit_warn(errors::CorruptFile { path: &path }); + sess.dcx().emit_warn(diagnostics::CorruptFile { path: &path }); return LoadResult::DataOutOfDate; }; let prev_commandline_args_hash = Hash64::decode(&mut decoder); @@ -135,7 +135,7 @@ pub fn load_query_result_cache(sess: &Session) -> Option { match file_format::open_incremental_file(sess, &path) { Ok(OpenFile { mmap, start_pos }) => { let cache = OnDiskCache::new(sess, mmap, start_pos).unwrap_or_else(|()| { - sess.dcx().emit_warn(errors::CorruptFile { path: &path }); + sess.dcx().emit_warn(diagnostics::CorruptFile { path: &path }); OnDiskCache::new_empty() }); Some(cache) @@ -161,12 +161,12 @@ fn maybe_assert_incr_state(sess: &Session, load_result: &LoadResult) { match assertion { IncrementalStateAssertion::Loaded => { if !loaded { - sess.dcx().emit_fatal(errors::AssertLoaded); + sess.dcx().emit_fatal(diagnostics::AssertLoaded); } } IncrementalStateAssertion::NotLoaded => { if loaded { - sess.dcx().emit_fatal(errors::AssertNotLoaded) + sess.dcx().emit_fatal(diagnostics::AssertNotLoaded) } } } @@ -205,12 +205,13 @@ pub fn setup_dep_graph( let (prev_graph, prev_work_products) = match load_result { LoadResult::IoError { path, err } => { - sess.dcx().emit_warn(errors::LoadDepGraph { path, err }); + sess.dcx().emit_warn(diagnostics::LoadDepGraph { path, err }); Default::default() } LoadResult::DataOutOfDate => { if let Err(err) = delete_all_session_dir_contents(sess) { - sess.dcx().emit_err(errors::DeleteIncompatible { path: dep_graph_path(sess), err }); + sess.dcx() + .emit_err(diagnostics::DeleteIncompatible { path: dep_graph_path(sess), err }); } Default::default() } @@ -223,7 +224,7 @@ pub fn setup_dep_graph( let mut encoder = FileEncoder::new(&path_buf).unwrap_or_else(|err| { // We're in incremental mode but couldn't set up streaming output of the dep graph. // Exit immediately instead of continuing in an inconsistent and untested state. - sess.dcx().emit_fatal(errors::CreateDepGraph { path: &path_buf, err }) + sess.dcx().emit_fatal(diagnostics::CreateDepGraph { path: &path_buf, err }) }); file_format::write_file_header(&mut encoder, sess); diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index 8360b352a2643..544ab66766f39 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -13,7 +13,7 @@ use super::data::*; use super::fs::*; use super::{clean, file_format, work_product}; use crate::assert_dep_graph::assert_dep_graph; -use crate::errors; +use crate::diagnostics; /// Saves and writes the [`DepGraph`] to the file system. /// @@ -45,7 +45,7 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) { move || { sess.time("incr_comp_persist_dep_graph", || { if let Err(err) = fs::rename(&staging_dep_graph_path, &dep_graph_path) { - sess.dcx().emit_err(errors::MoveDepGraph { + sess.dcx().emit_err(diagnostics::MoveDepGraph { from: &staging_dep_graph_path, to: &dep_graph_path, err, diff --git a/compiler/rustc_incremental/src/persist/work_product.rs b/compiler/rustc_incremental/src/persist/work_product.rs index 64dae5c0869b9..910860bfafd6e 100644 --- a/compiler/rustc_incremental/src/persist/work_product.rs +++ b/compiler/rustc_incremental/src/persist/work_product.rs @@ -11,7 +11,7 @@ use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_session::Session; use tracing::debug; -use crate::errors; +use crate::diagnostics; use crate::persist::fs::*; /// Copies a CGU work product to the incremental compilation directory, so next compilation can @@ -40,7 +40,7 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( let _ = saved_files.insert(ext.to_string(), file_name); } Err(err) => { - sess.dcx().emit_warn(errors::CopyWorkProductToCache { + sess.dcx().emit_warn(diagnostics::CopyWorkProductToCache { from: path, to: &path_in_incr_dir, err, @@ -60,7 +60,7 @@ pub(crate) fn delete_workproduct_files(sess: &Session, work_product: &WorkProduc for (_, path) in work_product.saved_files.items().into_sorted_stable_ord() { let path = in_incr_comp_dir_sess(sess, path); if let Err(err) = std_fs::remove_file(&path) { - sess.dcx().emit_warn(errors::DeleteWorkProduct { path: &path, err }); + sess.dcx().emit_warn(diagnostics::DeleteWorkProduct { path: &path, err }); } } } diff --git a/compiler/rustc_index/src/interval.rs b/compiler/rustc_index/src/interval.rs index 716dd81d08fd4..b7b1531e50857 100644 --- a/compiler/rustc_index/src/interval.rs +++ b/compiler/rustc_index/src/interval.rs @@ -370,6 +370,10 @@ impl SparseIntervalMatrix { self.rows.get(row) } + pub fn iter_enumerated(&self) -> impl Iterator)> { + self.rows.iter_enumerated() + } + fn ensure_row(&mut self, row: R) -> &mut IntervalSet { self.rows.ensure_contains_elem(row, || IntervalSet::new(self.column_size)) } diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index fc0c4bb0d5f5e..c23bc4adf914c 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -209,7 +209,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ) } - fn enter_forall>, U>( + fn enter_forall_without_assumptions>, U>( &self, value: ty::Binder<'tcx, T>, f: impl FnOnce(T) -> U, @@ -217,6 +217,20 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.enter_forall(value, f) } + fn enter_forall_with_empty_assumptions>, U>( + &self, + value: ty::Binder<'tcx, T>, + f: impl FnOnce(T) -> U, + ) -> U { + self.enter_forall(value, |value| { + let u = self.universe(); + self.placeholder_assumptions_for_next_solver + .borrow_mut() + .insert(u, Some(rustc_type_ir::region_constraint::Assumptions::empty())); + f(value) + }) + } + fn equate_ty_vids_raw(&self, a: ty::TyVid, b: ty::TyVid) { self.inner.borrow_mut().type_variables().equate(a, b); } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 622077caddc40..289967ade56c8 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -25,7 +25,7 @@ use rustc_session::utils::{CanonicalizedPath, NativeLib}; use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, build_session, getopts}; use rustc_span::edition::{DEFAULT_EDITION, Edition}; use rustc_span::source_map::{RealFileLoader, SourceMapInputs}; -use rustc_span::{FileName, SourceFileHashAlgorithm, sym}; +use rustc_span::{FileName, RealFileName, RemapPathScopeComponents, SourceFileHashAlgorithm, sym}; use rustc_target::spec::{ CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TlsModel, @@ -175,6 +175,33 @@ fn test_can_print_warnings() { }); } +// `-Zremap-cwd-prefix` must not be merged into the tracked `remap_path_prefix` option: +// storing the absolute cwd there invalidates the incremental cache across build +// directories (see #132132). It must instead be applied via `file_path_mapping`. +#[test] +fn test_remap_cwd_prefix_not_in_remap_path_prefix() { + sess_and_cfg( + &["--remap-path-prefix=/explicit=mapped", "-Zremap-cwd-prefix=cwd-mapped"], + |sess, _cfg| { + // The tracked option holds only the explicit `--remap-path-prefix` entry. + assert_eq!( + sess.opts.remap_path_prefix, + vec![(PathBuf::from("/explicit"), PathBuf::from("mapped"))], + ); + + // ... but the cwd remapping is still applied via `file_path_mapping`. + let cwd = std::env::current_dir().unwrap(); + let remapped = sess + .opts + .file_path_mapping() + .to_real_filename(&RealFileName::empty(), cwd.join("foo.rs")) + .path(RemapPathScopeComponents::DEBUGINFO) + .to_path_buf(); + assert_eq!(remapped, PathBuf::from("cwd-mapped/foo.rs")); + }, + ); +} + #[test] fn test_output_types_tracking_hash_different_paths() { let mut v1 = Options::default(); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 8c5a8a625ec99..0421edc543151 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -235,7 +235,7 @@ use rustc_session::config::{DebugInfo, EntryFnType}; use rustc_span::{DUMMY_SP, Span, Spanned, dummy_spanned, respan}; use tracing::{debug, instrument, trace}; -use crate::errors::{ +use crate::diagnostics::{ self, EncounteredErrorWhileInstantiating, EncounteredErrorWhileInstantiatingGlobalAsm, NoOptimizedMir, RecursionLimit, }; @@ -1702,7 +1702,7 @@ impl<'v> RootCollector<'_, 'v> { } let Some(start_def_id) = self.tcx.lang_items().start_fn() else { - self.tcx.dcx().emit_fatal(errors::StartNotFound); + self.tcx.dcx().emit_fatal(diagnostics::StartNotFound); }; let main_ret_ty = self.tcx.fn_sig(main_def_id).no_bound_vars().unwrap().output(); diff --git a/compiler/rustc_monomorphize/src/errors.rs b/compiler/rustc_monomorphize/src/diagnostics.rs similarity index 100% rename from compiler/rustc_monomorphize/src/errors.rs rename to compiler/rustc_monomorphize/src/diagnostics.rs diff --git a/compiler/rustc_monomorphize/src/graph_checks/statics.rs b/compiler/rustc_monomorphize/src/graph_checks/statics.rs index 16642b9960126..4a6416843fded 100644 --- a/compiler/rustc_monomorphize/src/graph_checks/statics.rs +++ b/compiler/rustc_monomorphize/src/graph_checks/statics.rs @@ -8,7 +8,7 @@ use rustc_middle::mono::MonoItem; use rustc_middle::ty::TyCtxt; use crate::collector::UsageMap; -use crate::errors; +use crate::diagnostics; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] struct StaticNodeIdx(usize); @@ -105,7 +105,7 @@ pub(super) fn check_static_initializers_are_acyclic<'tcx, 'a, 'b>( let head_def = statics[nodes[0].index()]; let head_span = tcx.def_span(head_def); - tcx.dcx().emit_err(errors::StaticInitializerCyclic { + tcx.dcx().emit_err(diagnostics::StaticInitializerCyclic { span: head_span, labels: nodes.iter().map(|&n| tcx.def_span(statics[n.index()])).collect(), head: &tcx.def_path_str(head_def), diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index ae97bf830d8c0..0b5a3d1f267da 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -13,7 +13,7 @@ use rustc_middle::{bug, traits}; use rustc_span::ErrorGuaranteed; mod collector; -mod errors; +mod diagnostics; mod graph_checks; mod mono_checks; mod partitioning; diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 812979d13b26f..8b79eaae28753 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -8,7 +8,7 @@ use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use rustc_target::callconv::{FnAbi, PassMode}; -use crate::errors; +use crate::diagnostics; /// Are vector registers used? enum UsesVectorRegisters { @@ -71,7 +71,7 @@ fn do_check_simd_vector_abi<'tcx>( Some((_, feature)) => feature, None => { let (span, _hir_id) = loc(); - tcx.dcx().emit_err(errors::AbiErrorUnsupportedVectorType { + tcx.dcx().emit_err(diagnostics::AbiErrorUnsupportedVectorType { span, ty: arg_abi.layout.ty, is_call, @@ -81,7 +81,7 @@ fn do_check_simd_vector_abi<'tcx>( }; if !feature.is_empty() && !have_feature(Symbol::intern(feature)) { let (span, _hir_id) = loc(); - tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType { + tcx.dcx().emit_err(diagnostics::AbiErrorDisabledVectorType { span, required_feature: feature, ty: arg_abi.layout.ty, @@ -98,7 +98,7 @@ fn do_check_simd_vector_abi<'tcx>( }; if !required_feature.is_empty() && !have_feature(Symbol::intern(required_feature)) { let (span, _) = loc(); - tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType { + tcx.dcx().emit_err(diagnostics::AbiErrorDisabledVectorType { span, required_feature, ty: arg_abi.layout.ty, @@ -115,7 +115,7 @@ fn do_check_simd_vector_abi<'tcx>( // The `vectorcall` ABI is special in that it requires SSE2 no matter which types are being passed. if abi.conv == CanonAbi::X86(X86Call::Vectorcall) && !have_feature(sym::sse2) { let (span, _hir_id) = loc(); - tcx.dcx().emit_err(errors::AbiRequiredTargetFeature { + tcx.dcx().emit_err(diagnostics::AbiRequiredTargetFeature { span, required_feature: "sse2", abi: "vectorcall", @@ -142,7 +142,7 @@ fn do_check_unsized_params<'tcx>( for arg_abi in fn_abi.args.iter() { if !arg_abi.layout.layout.is_sized() { let (span, _hir_id) = loc(); - tcx.dcx().emit_err(errors::AbiErrorUnsupportedUnsizedParameter { + tcx.dcx().emit_err(diagnostics::AbiErrorUnsupportedUnsizedParameter { span, ty: arg_abi.layout.ty, is_call, diff --git a/compiler/rustc_monomorphize/src/mono_checks/move_check.rs b/compiler/rustc_monomorphize/src/mono_checks/move_check.rs index af03e2a0d2d66..f0fe4c9056187 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/move_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/move_check.rs @@ -9,7 +9,7 @@ use rustc_session::lint::builtin::LARGE_ASSIGNMENTS; use rustc_span::{Span, Spanned, sym}; use tracing::{debug, trace}; -use crate::errors::LargeAssignmentsLint; +use crate::diagnostics::LargeAssignmentsLint; struct MoveCheckVisitor<'tcx> { tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index 6058cb892dc14..aee7153419883 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -124,7 +124,7 @@ use rustc_target::spec::SymbolVisibility; use tracing::debug; use crate::collector::{self, MonoItemCollectionStrategy, UsageMap}; -use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined}; +use crate::diagnostics::{CouldntDumpMonoStats, SymbolAlreadyDefined}; use crate::graph_checks::target_specific_checks; struct PartitioningCx<'a, 'tcx> { diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index b5bba4680b31d..c053f106155dc 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1331,8 +1331,8 @@ where self.delegate.instantiate_binder_with_infer(value) } - /// `enter_forall`, but takes `&mut self` and passes it back through the - /// callback since it can't be aliased during the call. + /// `enter_forall_with_assumptions`, but takes `&mut self` and passes it back through + /// the callback since it can't be aliased during the call. /// /// The `param_env` is used to *compute* the assumptions of the binder, not *as* the /// assumptions associated with the binder. @@ -1344,7 +1344,7 @@ where param_env: I::ParamEnv, f: impl FnOnce(&mut Self, T) -> U, ) -> U { - self.delegate.enter_forall(value, |value| { + self.delegate.enter_forall_without_assumptions(value, |value| { let u = self.delegate.universe(); let assumptions = if self.cx().assumptions_on_binders() { self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 1360dc23a78f2..fbceb185c190c 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -752,16 +752,19 @@ impl<'a> Parser<'a> { Err(err) } + pub(super) fn is_expected_raw_ref_mut(&self) -> bool { + self.prev_token.is_keyword(kw::Raw) + && self.expected_token_types.contains(TokenType::KwMut) + && self.expected_token_types.contains(TokenType::KwConst) + && self.token.can_begin_expr() + } + /// Adds a label when `&raw EXPR` was written instead of `&raw const EXPR`/`&raw mut EXPR`. /// /// Given that not all parser diagnostics flow through `expected_one_of_not_found`, this /// label may need added to other diagnostics emission paths as needed. pub(super) fn label_expected_raw_ref(&mut self, err: &mut Diag<'_>) { - if self.prev_token.is_keyword(kw::Raw) - && self.expected_token_types.contains(TokenType::KwMut) - && self.expected_token_types.contains(TokenType::KwConst) - && self.token.can_begin_expr() - { + if self.is_expected_raw_ref_mut() { err.span_suggestions( self.prev_token.span.shrink_to_hi(), "`&raw` must be followed by `const` or `mut` to be a raw reference expression", diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 957ed9ab55e0a..aa72068387b30 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1279,15 +1279,38 @@ impl<'a> Parser<'a> { }; let open_paren = self.token.span; - let seq = self - .parse_expr_paren_seq() - .map(|args| self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args))); + let seq = match self.parse_expr_paren_seq() { + Ok(args) => Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args))), + Err(err) if self.is_expected_raw_ref_mut() => { + let guar = err.emit(); + // Preserve the call expression so later passes can still diagnose the callee, + // while treating the malformed `&raw ` argument as an error expression. + let args = self.recover_raw_ref_call_args(guar); + return self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args)); + } + Err(err) => Err(err), + }; match self.maybe_recover_struct_lit_bad_delims(lo, open_paren, seq, snapshot) { Ok(expr) => expr, Err(err) => self.recover_seq_parse_error(exp!(OpenParen), exp!(CloseParen), lo, err), } } + fn recover_raw_ref_call_args(&mut self, guar: ErrorGuaranteed) -> ThinVec> { + let err_span = self.prev_token.span.to(self.token.span); + let mut args = thin_vec![self.mk_expr_err(err_span, guar)]; + while self.token != token::Eof && self.token != token::CloseParen { + if self.eat(exp!(Comma)) && self.token != token::Eof && self.token != token::CloseParen + { + args.push(self.mk_expr_err(self.prev_token.span.shrink_to_hi(), guar)); + } else { + self.parse_token_tree(); + } + } + let _ = self.eat(exp!(CloseParen)); + args + } + /// If we encounter a parser state that looks like the user has written a `struct` literal with /// parentheses instead of braces, recover the parser state and provide suggestions. #[instrument(skip(self, seq, snapshot), level = "trace")] diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index a16ddb34b3c2e..64255d51a7c69 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -917,6 +917,13 @@ impl<'a> Parser<'a> { } // Attempt to keep parsing if it was an omitted separator. + // `&raw ` already has a specific suggestion for missing + // `const`/`mut`, so don't recover `` as the next element in + // a comma-separated list. + if exp.token_type == TokenType::Comma && self.is_expected_raw_ref_mut() + { + return Err(expect_err); + } self.last_unexpected_token_span = None; match f(self) { Ok(t) => { diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 1b3217ed0a030..003b813124eea 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1384,9 +1384,21 @@ pub fn host_tuple() -> &'static str { fn file_path_mapping( remap_path_prefix: Vec<(PathBuf, PathBuf)>, + remap_cwd_prefix: Option<&Path>, remap_path_scope: RemapPathScopeComponents, ) -> FilePathMapping { - FilePathMapping::new(remap_path_prefix.clone(), remap_path_scope) + // Apply `-Zremap-cwd-prefix` here rather than in `parse_remap_path_prefix`, so the + // absolute cwd is never stored in the tracked `remap_path_prefix` option (#132132). + let cwd_remap = if let Some(to) = remap_cwd_prefix + && let Ok(cwd) = std::env::current_dir() + { + Some((cwd, to.to_path_buf())) + } else { + None + }; + // The cwd remapping is appended last: `map_prefix` tries entries in reverse order, so this + // keeps `-Zremap-cwd-prefix` taking precedence over `--remap-path-prefix`, as documented. + FilePathMapping::new(remap_path_prefix.into_iter().chain(cwd_remap).collect(), remap_path_scope) } impl Default for Options { @@ -1398,7 +1410,8 @@ impl Default for Options { // to create a default working directory. let working_dir = { let working_dir = std::env::current_dir().unwrap(); - let file_mapping = file_path_mapping(Vec::new(), RemapPathScopeComponents::empty()); + let file_mapping = + file_path_mapping(Vec::new(), None, RemapPathScopeComponents::empty()); file_mapping.to_real_filename(&RealFileName::empty(), &working_dir) }; @@ -1459,7 +1472,11 @@ impl Options { } pub fn file_path_mapping(&self) -> FilePathMapping { - file_path_mapping(self.remap_path_prefix.clone(), self.remap_path_scope) + file_path_mapping( + self.remap_path_prefix.clone(), + self.unstable_opts.remap_cwd_prefix.as_deref(), + self.remap_path_scope, + ) } /// Returns `true` if there will be an output file generated. @@ -2393,9 +2410,8 @@ pub fn parse_externs( fn parse_remap_path_prefix( early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches, - unstable_opts: &UnstableOptions, ) -> Vec<(PathBuf, PathBuf)> { - let mut mapping: Vec<(PathBuf, PathBuf)> = matches + matches .opt_strs("remap-path-prefix") .into_iter() .map(|remap| match remap.rsplit_once('=') { @@ -2404,15 +2420,7 @@ fn parse_remap_path_prefix( } Some((from, to)) => (PathBuf::from(from), PathBuf::from(to)), }) - .collect(); - match &unstable_opts.remap_cwd_prefix { - Some(to) => match std::env::current_dir() { - Ok(cwd) => mapping.push((cwd, to.clone())), - Err(_) => (), - }, - None => (), - }; - mapping + .collect() } fn parse_logical_env( @@ -2678,7 +2686,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let externs = parse_externs(early_dcx, matches, &unstable_opts); - let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches, &unstable_opts); + let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches); let remap_path_scope = parse_remap_path_scope(early_dcx, matches, &unstable_opts); let pretty = parse_pretty(early_dcx, &unstable_opts); @@ -2746,7 +2754,11 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M early_dcx.early_fatal(format!("Current directory is invalid: {e}")); }); - let file_mapping = file_path_mapping(remap_path_prefix.clone(), remap_path_scope); + let file_mapping = file_path_mapping( + remap_path_prefix.clone(), + unstable_opts.remap_cwd_prefix.as_deref(), + remap_path_scope, + ); file_mapping.to_real_filename(&RealFileName::empty(), &working_dir) }; diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index d21082e9b99ab..87c40fa588c03 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1462,6 +1462,7 @@ supported_targets! { ("powerpc-unknown-linux-muslspe", powerpc_unknown_linux_muslspe), ("powerpc64-ibm-aix", powerpc64_ibm_aix), ("powerpc64-unknown-linux-gnu", powerpc64_unknown_linux_gnu), + ("powerpc64-unknown-linux-gnuelfv2", powerpc64_unknown_linux_gnuelfv2), ("powerpc64-unknown-linux-musl", powerpc64_unknown_linux_musl), ("powerpc64le-unknown-linux-gnu", powerpc64le_unknown_linux_gnu), ("powerpc64le-unknown-linux-musl", powerpc64le_unknown_linux_musl), diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_freebsd.rs index 69a65e6b0f024..8ae72393b7cbf 100644 --- a/compiler/rustc_target/src/spec/targets/aarch64_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_freebsd.rs @@ -7,7 +7,7 @@ pub(crate) fn target() -> Target { llvm_target: "aarch64-unknown-freebsd".into(), metadata: TargetMetadata { description: Some("ARM64 FreeBSD".into()), - tier: Some(3), + tier: Some(2), host_tools: Some(true), std: Some(true), }, diff --git a/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_gnuelfv2.rs b/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_gnuelfv2.rs new file mode 100644 index 0000000000000..c7392a1b90b57 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_gnuelfv2.rs @@ -0,0 +1,30 @@ +use rustc_abi::Endian; + +use crate::spec::{ + Arch, Cc, CfgAbi, LinkerFlavor, Lld, LlvmAbi, StackProbeType, Target, TargetMetadata, + TargetOptions, base, +}; + +pub(crate) fn target() -> Target { + let mut base = base::linux_gnu::opts(); + base.cpu = "ppc64".into(); + base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); + base.max_atomic_width = Some(64); + base.stack_probes = StackProbeType::Inline; + base.cfg_abi = CfgAbi::ElfV2; + base.llvm_abiname = LlvmAbi::ElfV2; + + Target { + llvm_target: "powerpc64-unknown-linux-gnu".into(), + metadata: TargetMetadata { + description: Some("PPC64 Linux (ELFv2 ABI, kernel 3.2, glibc 2.17)".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "E-m:e-Fn32-i64:64-i128:128-n32:64-S128-v256:256:256-v512:512:512".into(), + arch: Arch::PowerPC64, + options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, + } +} diff --git a/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs b/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs index c15b23b9192ce..8f6bbe4f641d9 100644 --- a/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs +++ b/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs @@ -10,7 +10,7 @@ pub(crate) fn target() -> Target { base.llvm_abiname = LlvmAbi::Lp64d; base.max_atomic_width = Some(64); base.stack_probes = StackProbeType::Inline; - base.supported_sanitizers = SanitizerSet::SHADOWCALLSTACK; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::SHADOWCALLSTACK; base.default_sanitizers = SanitizerSet::SHADOWCALLSTACK; base.supports_xray = true; diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 7045b87db9cba..e61e96c70f2e5 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -406,7 +406,16 @@ pub trait InferCtxtLike: Sized { value: ty::Binder, ) -> T; - fn enter_forall, U>( + fn enter_forall_without_assumptions, U>( + &self, + value: ty::Binder, + f: impl FnOnce(T) -> U, + ) -> U; + + /// FIXME(-Zassumptions-on-binders): Any usage of this method is likely wrong + /// and should be replaced in the long term by actually taking assumptions into + /// account. + fn enter_forall_with_empty_assumptions, U>( &self, value: ty::Binder, f: impl FnOnce(T) -> U, diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 4305aa14980e4..4b23bfd0f7765 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -1096,11 +1096,7 @@ fn alias_outlives_candidates_from_assumptions let prev_universe = infcx.universe(); - // FIXME(-Zassumptions-on-binders): Handle the assumptions on this binder - infcx.enter_forall(bound_outlives, |(alias, r)| { - let u = infcx.universe(); - infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); - + infcx.enter_forall_with_empty_assumptions(bound_outlives, |(alias, r)| { for bound_type_outlives in assumptions.type_outlives.iter() { let OutlivesPredicate(alias2, r2) = infcx.instantiate_binder_with_infer(*bound_type_outlives); @@ -1187,14 +1183,14 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation where T: Relate, { - self.infcx.enter_forall(a, |a| { + self.infcx.enter_forall_with_empty_assumptions(a, |a| { let u = self.infcx.universe(); self.infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); let b = self.infcx.instantiate_binder_with_infer(b); self.relate(a, b) })?; - self.infcx.enter_forall(b, |b| { + self.infcx.enter_forall_with_empty_assumptions(b, |b| { let u = self.infcx.universe(); self.infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); let a = self.infcx.instantiate_binder_with_infer(a); diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index a643d22c17643..96e30f06473cf 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -311,13 +311,13 @@ where // // [rd]: https://rustc-dev-guide.rust-lang.org/borrow_check/region_inference/placeholders_and_universes.html ty::Covariant => { - self.infcx.enter_forall(b, |b| { + self.infcx.enter_forall_with_empty_assumptions(b, |b| { let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) })?; } ty::Contravariant => { - self.infcx.enter_forall(a, |a| { + self.infcx.enter_forall_with_empty_assumptions(a, |a| { let b = self.infcx.instantiate_binder_with_infer(b); self.relate(a, b) })?; @@ -334,13 +334,13 @@ where // `exists<..> A == for<..> B` and `exists<..> B == for<..> A`. // Check if `exists<..> A == for<..> B` ty::Invariant => { - self.infcx.enter_forall(b, |b| { + self.infcx.enter_forall_with_empty_assumptions(b, |b| { let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) })?; // Check if `exists<..> B == for<..> A`. - self.infcx.enter_forall(a, |a| { + self.infcx.enter_forall_with_empty_assumptions(a, |a| { let b = self.infcx.instantiate_binder_with_infer(b); self.relate(a, b) })?; diff --git a/compiler/rustc_type_ir_macros/Cargo.toml b/compiler/rustc_type_ir_macros/Cargo.toml index 910eef5e21f41..41562bd4baeb1 100644 --- a/compiler/rustc_type_ir_macros/Cargo.toml +++ b/compiler/rustc_type_ir_macros/Cargo.toml @@ -11,6 +11,7 @@ nightly = [] [dependencies] # tidy-alphabetical-start +indexmap = "2.4.0" proc-macro2 = "1" quote = "1" syn = { version = "2.0.9", features = ["full", "visit-mut"] } diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index 9d576c3deacee..bafd8d72dc437 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -1,3 +1,4 @@ +use indexmap::IndexSet; use quote::{ToTokens, quote}; use syn::visit_mut::VisitMut; use syn::{Attribute, parse_quote}; @@ -17,11 +18,24 @@ decl_derive!( [GenericTypeVisitable] => customizable_type_visitable_derive ); -struct LiftedTy { +struct TransformedTy { ty: syn::Type, - generic_parameter_bounds: Vec, + generic_parameter_bounds: IndexSet, } +enum TypeParameterPath { + Interner, + GenericParameter(syn::Ident), +} + +enum TypeParameterTransform { + Continue, + Stop, +} + +type TypeParameterVisitor = + fn(TypeParameterPath, &mut syn::TypePath, &mut IndexSet) -> TypeParameterTransform; + fn has_ignore_attr(attrs: &[Attribute], name: &'static str, meta: &'static str) -> bool { let mut ignored = false; attrs.iter().for_each(|attr| { @@ -91,6 +105,9 @@ fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::Toke s.add_where_predicate(parse_quote! { I: Interner }); s.add_bounds(synstructure::AddBounds::Fields); + let generic_parameters = + s.ast().generics.type_params().map(|ty| ty.ident.clone()).collect::>(); + let mut generic_parameter_bounds = IndexSet::new(); s.bind_with(|_| synstructure::BindStyle::Move); let body_try_fold = s.each_variant(|vi| { let bindings = vi.bindings(); @@ -101,6 +118,12 @@ fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::Toke if has_ignore_attr(&bind.ast().attrs, "type_foldable", "identity") { bind.to_token_stream() } else { + for param in + type_foldable_generic_parameters(bind.ast().ty.clone(), &generic_parameters) + { + generic_parameter_bounds.insert(param); + } + quote! { ::rustc_type_ir::TypeFoldable::try_fold_with(#bind, __folder)? } @@ -129,6 +152,9 @@ fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::Toke // to generate code for them. s.filter(|bi| !has_ignore_attr(&bi.ast().attrs, "type_foldable", "identity")); s.add_bounds(synstructure::AddBounds::Fields); + for param in generic_parameter_bounds { + s.add_where_predicate(parse_quote! { #param: ::rustc_type_ir::TypeFoldable }); + } s.bound_impl( quote!(::rustc_type_ir::TypeFoldable), quote! { @@ -149,6 +175,19 @@ fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::Toke ) } +fn type_foldable_generic_parameters( + ty: syn::Type, + generic_parameters: &[syn::Ident], +) -> IndexSet { + transform_type_parameters(ty, generic_parameters, |path, _, generic_parameter_bounds| { + if let TypeParameterPath::GenericParameter(param) = path { + generic_parameter_bounds.insert(param); + } + TypeParameterTransform::Continue + }) + .generic_parameter_bounds +} + /// `Lift_Generic` is specialised for structs/enums parameterised by an interner /// `I: Interner`. It derives `Lift` by rewriting interner associated types /// from `I::Assoc` to `J::Assoc`. The required associated type lift bounds are @@ -251,40 +290,72 @@ fn is_type_phantom(ty: &syn::Type) -> bool { get_first_path_segment(ty).is_some_and(|segment| segment.ident == "PhantomData") } -fn lift(mut ty: syn::Type, generic_parameters: &[syn::Ident]) -> LiftedTy { - struct ItoJ<'a> { +fn lift(ty: syn::Type, generic_parameters: &[syn::Ident]) -> TransformedTy { + transform_type_parameters(ty, generic_parameters, |path, ty, generic_parameter_bounds| { + match path { + TypeParameterPath::Interner => { + *ty.path.segments.first_mut().unwrap() = parse_quote! { J }; + TypeParameterTransform::Continue + } + TypeParameterPath::GenericParameter(param) => { + generic_parameter_bounds.insert(param.clone()); + *ty = parse_quote! { <#param as ::rustc_type_ir::lift::Lift>::Lifted }; + TypeParameterTransform::Stop + } + } + }) +} + +fn transform_type_parameters( + mut ty: syn::Type, + generic_parameters: &[syn::Ident], + visit: TypeParameterVisitor, +) -> TransformedTy { + struct TypeParameterTransformer<'a> { generic_parameters: &'a [syn::Ident], - generic_parameter_bounds: Vec, + generic_parameter_bounds: IndexSet, + visit: TypeParameterVisitor, } - impl VisitMut for ItoJ<'_> { + impl VisitMut for TypeParameterTransformer<'_> { fn visit_type_path_mut(&mut self, i: &mut syn::TypePath) { - if i.qself.is_none() { + let path = if i.qself.is_none() { let segments_len = i.path.segments.len(); - if let Some(first) = i.path.segments.first_mut() { - // Turn paths from `I` into `J` + i.path.segments.first().and_then(|first| { if first.ident == "I" { - *first = parse_quote! { J }; + Some(TypeParameterPath::Interner) } else if segments_len == 1 && matches!(first.arguments, syn::PathArguments::None) - && self.generic_parameters.iter().any(|param| first.ident == *param) + && self.generic_parameters.contains(&first.ident) { - let ident = first.ident.clone(); - if !self.generic_parameter_bounds.iter().any(|param| *param == ident) { - self.generic_parameter_bounds.push(ident.clone()); - } - - *i = parse_quote! { <#ident as ::rustc_type_ir::lift::Lift>::Lifted }; - return; + Some(TypeParameterPath::GenericParameter(first.ident.clone())) + } else { + None } + }) + } else { + None + }; + + if let Some(path) = path { + if let TypeParameterTransform::Stop = + (self.visit)(path, i, &mut self.generic_parameter_bounds) + { + return; } } + syn::visit_mut::visit_type_path_mut(self, i); } } - let mut visitor = ItoJ { generic_parameters, generic_parameter_bounds: Vec::new() }; + + let mut visitor = TypeParameterTransformer { + generic_parameters, + generic_parameter_bounds: IndexSet::new(), + visit, + }; visitor.visit_type_mut(&mut ty); - LiftedTy { ty, generic_parameter_bounds: visitor.generic_parameter_bounds } + TransformedTy { ty, generic_parameter_bounds: visitor.generic_parameter_bounds } } #[cfg(not(feature = "nightly"))] diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 93e774226349c..898138ed1f36e 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -293,21 +293,36 @@ pub macro Clone($item:item) { /// A trait for types whose [`Clone`] operation creates another alias to the same /// logical resource or shared state. /// -/// `Share` marks types where cloning creates another handle, reference, or alias -/// to the same logical resource or shared state, rather than an independent owned -/// value. The distinction is semantic, not cost-based: implementing `Share` does -/// not merely mean that cloning is cheap, constant-time, allocation-free, or -/// convenient. +/// `Share` refines the meaning of [`Clone`] for types where cloning a value +/// creates another handle, reference, or alias to the same logical resource or +/// shared state, rather than an independent owned value. The distinction is +/// semantic, not operational: `Share` does not mean merely that cloning is +/// cheap, constant-time, allocation-free, or convenient. /// -/// Calling [`share`](Share::share) is equivalent to calling [`clone`](Clone::clone) -/// for implementors, but communicates that the resulting value aliases the same -/// underlying resource. +/// `Share` is a third way to think about creating another usable value: +/// +/// * [`Copy`] may duplicate a value implicitly. +/// * [`Clone`] explicitly creates another value. +/// * `Share` explicitly creates another value that aliases the same underlying +/// logical resource or shared state. +/// +/// `Share` is not a replacement for either [`Copy`] or [`Clone`], and neither +/// trait implies it. For example, integers are [`Copy`] but not `Share`, because +/// copying an integer creates an independent value. Likewise, not every cheap +/// [`Clone`] implementation is `Share`. /// /// Shared references, `Rc`, `Arc`, `Sender`, and `SyncSender` are /// examples of types that can be shared this way. Types such as `Vec`, -/// `String`, and `Box` are not `Share` even though they implement `Clone`, -/// because cloning them creates another owned value rather than another handle -/// to the same logical resource. +/// `String`, `Box`, owned collections, and similar owned values are not +/// `Share`, even though they implement [`Clone`], because cloning them creates +/// independent owned storage or value ownership. Mutable references (`&mut T`) +/// are neither `Clone` nor `Share`, because you cannot have two active at once. +/// +/// Calling [`share`](Share::share) is equivalent to calling [`clone`](Clone::clone) +/// for implementors, but communicates that the resulting value aliases the same +/// underlying resource. The `share` method is final, so implementors should +/// define the operation through [`Clone::clone`] and implement `Share` only when +/// those cloning semantics are clone-as-alias semantics. /// /// # Examples /// @@ -367,9 +382,11 @@ pub macro Clone($item:item) { pub trait Share: Clone { /// Creates another alias to the same underlying resource or shared state. /// - /// This is equivalent to calling [`Clone::clone`]. + /// This is equivalent to calling [`Clone::clone`]. Use `share` at call + /// sites to make aliasing intent explicit; implementors define this + /// operation through [`Clone::clone`], not by overriding this method. #[unstable(feature = "share_trait", issue = "156756")] - fn share(&self) -> Self { + final fn share(&self) -> Self { Clone::clone(self) } } diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index b2ccb6994d676..192c5eff29e10 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -138,6 +138,7 @@ #![feature(f16)] #![feature(f128)] #![feature(field_projections)] +#![feature(final_associated_functions)] #![feature(freeze_impls)] #![feature(fundamental)] #![feature(funnel_shifts)] diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index e09b1a1d5fb88..ae6cc46a22a84 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -596,6 +596,7 @@ impl [T] { #[inline] #[must_use] #[rustc_const_unstable(feature = "const_index", issue = "143775")] + #[rustc_no_writable] pub const fn get_mut(&mut self, index: I) -> Option<&mut I::Output> where I: [const] SliceIndex, @@ -681,6 +682,7 @@ impl [T] { #[must_use] #[track_caller] #[rustc_const_unstable(feature = "const_index", issue = "143775")] + #[rustc_no_writable] pub const unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output where I: [const] SliceIndex, diff --git a/rust-bors.toml b/rust-bors.toml index 0efbb5e8fc61c..c9fd2e568aca3 100644 --- a/rust-bors.toml +++ b/rust-bors.toml @@ -26,6 +26,15 @@ labels_blocking_approval = [ "S-waiting-on-t-clippy", # PR manually set to blocked "S-blocked", + # Needs a Crater run before being approved + "needs-crater", + # Needs a formal decision to be made + "needs-rfc", + "needs-fcp", + "needs-acp", + "needs-mcp", + # A PR in the Rust reference must be done first + "needs-reference-pr" ] # If CI runs quicker than this duration, consider it to be a failure diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index ca8af279b92bc..3c2348c9bf71c 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -37,6 +37,7 @@ pub struct Finder { /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "powerpc64-unknown-linux-gnuelfv2", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-pre-stabilization/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-pre-stabilization/Dockerfile new file mode 100644 index 0000000000000..8b61470aeb5f1 --- /dev/null +++ b/src/ci/docker/host-x86_64/x86_64-gnu-pre-stabilization/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:22.04 + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + g++ \ + make \ + ninja-build \ + file \ + curl \ + ca-certificates \ + python3 \ + git \ + cmake \ + sudo \ + gdb \ + libssl-dev \ + pkg-config \ + xz-utils \ + mingw-w64 \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +ENV RUST_CONFIGURE_ARGS="--build=x86_64-unknown-linux-gnu" + +COPY scripts/x86_64-gnu-pre-stabilization.sh /scripts/ +ENV SCRIPT="/scripts/x86_64-gnu-pre-stabilization.sh" diff --git a/src/ci/docker/scripts/x86_64-gnu-llvm.sh b/src/ci/docker/scripts/x86_64-gnu-llvm.sh index 79177b96a4afc..21e2146609213 100755 --- a/src/ci/docker/scripts/x86_64-gnu-llvm.sh +++ b/src/ci/docker/scripts/x86_64-gnu-llvm.sh @@ -15,8 +15,3 @@ set -ex # Run the UI test suite in `--pass=check` mode, to ensure it continues to work. ../x.ps1 --stage 2 test tests/ui --pass=check --host='' --target=i686-unknown-linux-gnu - -# Rebuild the stdlib using the new trait solver, to ensure it doesn't regress -# until stabilization. -RUSTFLAGS_NOT_BOOTSTRAP="-Znext-solver=globally" ../x --stage 1 build library \ - --host='' --target=i686-unknown-linux-gnu diff --git a/src/ci/docker/scripts/x86_64-gnu-pre-stabilization.sh b/src/ci/docker/scripts/x86_64-gnu-pre-stabilization.sh new file mode 100755 index 0000000000000..81c1c4356c342 --- /dev/null +++ b/src/ci/docker/scripts/x86_64-gnu-pre-stabilization.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -ex + +# This script tests features intended to be stabilized in 2026. We want to +# ensure they don't regress until then. + +# 1. For the new trait solver, we want to: +# - ensure it can build the standard library +# +# FIXME: we also need to ensure it actually bootstraps. + +RUSTFLAGS_NOT_BOOTSTRAP="-Znext-solver=globally" ../x build library --stage 1 + +# 2. For the polonius alpha, we run the UI tests under the polonius +# compare-mode. +# +# Note that we keep the same rustflags to avoid needing to rebuild any stage 1 +# artifacts from the previous command. It also tests both features at the same +# time. + +RUSTFLAGS_NOT_BOOTSTRAP="-Znext-solver=globally" ../x test tests/ui \ + --compare-mode polonius --stage 1 diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 2bdf83a9c006b..2f92a30065b1f 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -22,10 +22,14 @@ runners: os: ubuntu-24.04-16core-64gb <<: *base-job - - &job-macos + - &job-macos-15 os: macos-15 # macOS 15 Arm64 <<: *base-job + - &job-macos-26 + os: macos-26 # macOS 26 Arm64 + <<: *base-job + - &job-windows os: windows-2025 <<: *base-job @@ -152,6 +156,14 @@ pr: env: CODEGEN_BACKENDS: gcc <<: *job-linux-4c + + # This job tests features we want to stabilize soon, to ensure they don't + # regress. + - name: x86_64-gnu-pre-stabilization + doc_url: https://rustc-dev-guide.rust-lang.org/tests/pre-stabilization-ci-job.html + env: + CODEGEN_BACKENDS: llvm + <<: *job-linux-4c # Jobs that run when you perform a try build (@bors try) # These jobs automatically inherit envs.try, to avoid repeating @@ -476,7 +488,7 @@ auto: DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer DIST_REQUIRE_ALL_TOOLS: 1 CODEGEN_BACKENDS: llvm,cranelift - <<: *job-macos + <<: *job-macos-15 - name: dist-apple-various env: @@ -513,7 +525,7 @@ auto: MACOSX_DEPLOYMENT_TARGET: 10.12 MACOSX_STD_DEPLOYMENT_TARGET: 10.12 DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer - <<: *job-macos + <<: *job-macos-15 - name: dist-aarch64-apple env: @@ -537,7 +549,7 @@ auto: DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer DIST_REQUIRE_ALL_TOOLS: 1 CODEGEN_BACKENDS: llvm,cranelift - <<: *job-macos + <<: *job-macos-15 - name: aarch64-apple env: @@ -553,7 +565,29 @@ auto: # supports the hardware, so only need to test it there. MACOSX_DEPLOYMENT_TARGET: 11.0 MACOSX_STD_DEPLOYMENT_TARGET: 11.0 - <<: *job-macos + <<: *job-macos-15 + + # EXPERIMENT(#157687): we will try to run `aarch64-apple`-equivalent workload + # on `macos-26` runner images in parallel to evaluate the running times, since + # previous attempts have timed out multiple times. Remove/revert this job if + # this hangs or times out, or if it becomes the slowest Merge CI job, and let + # T-infra know. + - name: aarch64-apple-macos-26 + doc_url: https://github.com/rust-lang/rust/issues/157687 + env: + SCRIPT: > + ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin && + ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin src/tools/cargo + RUST_CONFIGURE_ARGS: >- + --enable-sanitizers + --enable-profiler + --set rust.jemalloc + DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer + # Aarch64 tooling only needs to support macOS 11.0 and up as nothing else + # supports the hardware, so only need to test it there. + MACOSX_DEPLOYMENT_TARGET: 11.0 + MACOSX_STD_DEPLOYMENT_TARGET: 11.0 + <<: *job-macos-26 ###################### # Windows Builders # diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index cc10f476780c5..af8060adcc262 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -107,6 +107,7 @@ - [powerpc-unknown-linux-gnuspe](platform-support/powerpc-unknown-linux-gnuspe.md) - [powerpc-unknown-linux-muslspe](platform-support/powerpc-unknown-linux-muslspe.md) - [powerpc64-ibm-aix](platform-support/aix.md) + - [powerpc64-unknown-linux-gnuelfv2](platform-support/powerpc64-unknown-linux-gnuelfv2.md) - [powerpc64-unknown-linux-musl](platform-support/powerpc64-unknown-linux-musl.md) - [powerpc64le-unknown-linux-gnu](platform-support/powerpc64le-unknown-linux-gnu.md) - [powerpc64le-unknown-linux-musl](platform-support/powerpc64le-unknown-linux-musl.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 8bcc618be40b5..0bc90e2a14008 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -89,6 +89,7 @@ so Rustup may install the documentation for a similar tier 1 target instead. target | notes -------|------- [`aarch64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ARM64 MinGW (Windows 10+), LLVM ABI +[`aarch64-unknown-freebsd`](platform-support/freebsd.md) | ARM64 FreeBSD [`aarch64-unknown-linux-musl`](platform-support/aarch64-unknown-linux-musl.md) | ARM64 Linux with musl 1.2.5 [`aarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ARM64 OpenHarmony `arm-unknown-linux-gnueabi` | Armv6 Linux (kernel 3.2+, glibc 2.17) @@ -266,7 +267,6 @@ target | std | host | notes -------|:---:|:----:|------- [`aarch64-kmc-solid_asp3`](platform-support/kmc-solid.md) | ✓ | | ARM64 SOLID with TOPPERS/ASP3 [`aarch64-nintendo-switch-freestanding`](platform-support/aarch64-nintendo-switch-freestanding.md) | * | | ARM64 Nintendo Switch, Horizon -[`aarch64-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | ARM64 FreeBSD [`aarch64-unknown-helenos`](platform-support/helenos.md) | ✓ | | ARM64 HelenOS [`aarch64-unknown-hermit`](platform-support/hermit.md) | ✓ | | ARM64 Hermit [`aarch64-unknown-illumos`](platform-support/illumos.md) | ✓ | ✓ | ARM64 illumos @@ -385,6 +385,7 @@ target | std | host | notes [`powerpc-wrs-vxworks-spe`](platform-support/vxworks.md) | ✓ | | [`powerpc64-ibm-aix`](platform-support/aix.md) | ? | | 64-bit AIX (7.2 and newer) [`powerpc64-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64 FreeBSD (ELFv2) +[`powerpc64-unknown-linux-gnuelfv2`](platform-support/powerpc64-unknown-linux-gnuelfv2.md) | ✓ | ✓ | PPC64 Linux (ELFv2 ABI, kernel 3.2, glibc 2.17) [`powerpc64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/powerpc64 [`powerpc64-wrs-vxworks`](platform-support/vxworks.md) | ✓ | | [`powerpc64le-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64LE FreeBSD diff --git a/src/doc/rustc/src/platform-support/freebsd.md b/src/doc/rustc/src/platform-support/freebsd.md index 9d7218b258ec4..d1f8f009ae4cc 100644 --- a/src/doc/rustc/src/platform-support/freebsd.md +++ b/src/doc/rustc/src/platform-support/freebsd.md @@ -11,8 +11,9 @@ ## Requirements -The `x86_64-unknown-freebsd` target is Tier 2 with host tools. -`i686-unknown-freebsd` is Tier 2 without host tools. Other targets are Tier 3. +The `x86_64-unknown-freebsd` and `aarch64-unknown-freebsd` targets are Tier 2 +with host tools. `i686-unknown-freebsd` is Tier 2 without host tools. +Other targets are Tier 3. See [platform-support.md](../platform-support.md) for the full list. We commit that rustc will run on all currently supported releases of @@ -34,9 +35,10 @@ FreeBSD OS binaries use the ELF file format. ## Building Rust programs -The `x86_64-unknown-freebsd` and `i686-unknown-freebsd` artifacts are -distributed by the rust project and may be installed with rustup. Other -targets are built by the ports system and may be installed with +The `x86_64-unknown-freebsd`, `aarch64-unknown-freebsd` and +`i686-unknown-freebsd` artifacts are distributed by the rust +project and may be installed with rustup. Other targets are +built by the ports system and may be installed with [pkg(7)][pkg] or [ports(7)][ports]. By default the `i686-unknown-freebsd` target uses SSE2 instructions. To build diff --git a/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-gnuelfv2.md b/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-gnuelfv2.md new file mode 100644 index 0000000000000..59ae4507de302 --- /dev/null +++ b/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-gnuelfv2.md @@ -0,0 +1,50 @@ +# powerpc64-unknown-linux-gnuelfv2 + +**Tier: 3** + +Target for 64-bit big endian PowerPC Linux programs using the ELFv2 ABI and +the GNU C library. + +## Target maintainers + +[@Gelbpunkt](https://github.com/Gelbpunkt) + +## Requirements + +Building the target itself requires a 64-bit big endian PowerPC compiler that +uses the ELFv2 ABI and is supported by `cc-rs`. + +## Building the target + +The target can be built by enabling it for a `rustc` build. + +```toml +[build] +target = ["powerpc64-unknown-linux-gnuelfv2"] +``` + +Make sure your C compiler is included in `$PATH`, then add it to the +`bootstrap.toml`: + +```toml +[target.powerpc64-unknown-linux-gnuelfv2] +cc = "powerpc64-linux-gnu-gcc" +cxx = "powerpc64-linux-gnu-g++" +ar = "powerpc64-linux-gnu-ar" +linker = "powerpc64-linux-gnu-gcc" +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will first need to build Rust with the target enabled (see +"Building the target" above). + +## Cross-compilation + +This target can be cross-compiled from any host. + +## Testing + +This target can be tested as normal with `x.py` on a 64-bit big endian PowerPC +host or via QEMU emulation. diff --git a/src/doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md b/src/doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md index 3890a12b7e684..bcf526694ad94 100644 --- a/src/doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md +++ b/src/doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md @@ -16,6 +16,12 @@ directory from build output, while allowing the command line to be universally reproducible, such that the same execution will work on all machines, regardless of build environment. +Unlike passing the equivalent mapping through `--remap-path-prefix`, the current +working directory does not take part in incremental compilation's dependency +tracking. Building the same sources from different directories (for example, a +sandboxed or per-build checkout path) therefore reuses the incremental cache +rather than invalidating it. + ## Example ```sh # This would produce an absolute path to main.rs in build outputs of diff --git a/src/etc/gdb_lookup.py b/src/etc/gdb_lookup.py index c70944790d2b5..ae9696fa2ca92 100644 --- a/src/etc/gdb_lookup.py +++ b/src/etc/gdb_lookup.py @@ -103,6 +103,7 @@ def __call__(self, valobj): printer.add(RustType.StdString, StdStringProvider) printer.add(RustType.StdOsString, StdOsStringProvider) printer.add(RustType.StdStr, StdStrProvider) +printer.add(RustType.StdBoxStr, StdBoxStrProvider) printer.add(RustType.StdSlice, StdSliceProvider) printer.add(RustType.StdVec, StdVecProvider) printer.add(RustType.StdVecDeque, StdVecDequeProvider) diff --git a/src/etc/gdb_providers.py b/src/etc/gdb_providers.py index f361fc38eeaba..a6ef59738c8c7 100644 --- a/src/etc/gdb_providers.py +++ b/src/etc/gdb_providers.py @@ -142,6 +142,20 @@ def display_hint(): return "array" +class StdBoxStrProvider(printer_base): + def __init__(self, valobj): + self._valobj = valobj + self._length = int(valobj["length"]) + self._data_ptr = valobj["data_ptr"] + + def to_string(self): + return self._data_ptr.lazy_string(encoding="utf-8", length=self._length) + + @staticmethod + def display_hint(): + return "string" + + class StdVecProvider(printer_base): def __init__(self, valobj): self._valobj = valobj @@ -209,6 +223,12 @@ def __init__(self, valobj, is_atomic=False): self._is_atomic = is_atomic self._ptr = unwrap_unique_or_non_null(valobj["ptr"]) self._value = self._ptr["data" if is_atomic else "value"] + # FIXME(shua): the debuginfo template type should be 'str' not 'u8' + if self._ptr.type.target().name == "alloc::rc::RcInner": + length = self._valobj["ptr"]["pointer"]["length"] + u8_ptr_ty = gdb.Type.pointer(gdb.lookup_type("u8")) + ptr = self._value.address.reinterpret_cast(u8_ptr_ty) + self._value = ptr.lazy_string(encoding="utf-8", length=length) self._strong = unwrap_scalar_wrappers(self._ptr["strong"]) self._weak = unwrap_scalar_wrappers(self._ptr["weak"]) - 1 diff --git a/src/etc/rust_types.py b/src/etc/rust_types.py index ca462654e44e6..1cc526a25604a 100644 --- a/src/etc/rust_types.py +++ b/src/etc/rust_types.py @@ -37,12 +37,14 @@ class RustType(Enum): StdNonZeroNumber = 29 StdPath = 30 StdPathBuf = 31 + StdBoxStr = 32 STD_STRING_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)String$") STD_STR_REGEX = re.compile(r"^&(mut )?str$") STD_SLICE_REGEX = re.compile(r"^&(mut )?\[.+\]$") STD_OS_STRING_REGEX = re.compile(r"^(std::ffi::([a-z_]+::)+)OsString$") +STD_BOX_STR_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Box$") STD_VEC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Vec<.+>$") STD_VEC_DEQUE_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)VecDeque<.+>$") STD_BTREE_SET_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)BTreeSet<.+>$") @@ -67,6 +69,7 @@ class RustType(Enum): RustType.StdString: STD_STRING_REGEX, RustType.StdOsString: STD_OS_STRING_REGEX, RustType.StdStr: STD_STR_REGEX, + RustType.StdBoxStr: STD_BOX_STR_REGEX, RustType.StdSlice: STD_SLICE_REGEX, RustType.StdVec: STD_VEC_REGEX, RustType.StdVecDeque: STD_VEC_DEQUE_REGEX, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 5558c36f1d43d..9a08e76604df2 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2955,7 +2955,7 @@ fn render_call_locations( fn render_attributes_in_code( w: &mut impl fmt::Write, item: &clean::Item, - prefix: &str, + prefix: impl fmt::Display, cx: &Context<'_>, ) -> fmt::Result { render_attributes_in_code_with_options(w, item, prefix, cx, true, "") @@ -2964,14 +2964,14 @@ fn render_attributes_in_code( pub(super) fn render_attributes_in_code_with_options( w: &mut impl fmt::Write, item: &clean::Item, - prefix: &str, + prefix: impl fmt::Display, cx: &Context<'_>, render_doc_hidden: bool, open_tag: &str, ) -> fmt::Result { w.write_str(open_tag)?; if render_doc_hidden && item.is_doc_hidden() { - render_code_attribute(prefix, "#[doc(hidden)]", w)?; + render_code_attribute(&prefix, "#[doc(hidden)]", w)?; } for attr in &item.attrs.other_attrs { let hir::Attribute::Parsed(kind) = attr else { continue }; @@ -2986,7 +2986,7 @@ pub(super) fn render_attributes_in_code_with_options( AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"), _ => continue, }; - render_code_attribute(prefix, attr.as_ref(), w)?; + render_code_attribute(&prefix, attr.as_ref(), w)?; } if let Some(def_id) = item.def_id() @@ -3008,7 +3008,11 @@ fn render_repr_attribute_in_code( Ok(()) } -fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) -> fmt::Result { +fn render_code_attribute( + prefix: impl fmt::Display, + attr: impl fmt::Display, + w: &mut impl fmt::Write, +) -> fmt::Result { write!(w, "
{prefix}{attr}
") } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 7ade72429cb4d..7c1cc4ba7f9d9 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cmp::Ordering; use std::fmt::{self, Display, Write as _}; use std::iter; @@ -395,25 +396,26 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let (stab_tags, deprecation) = match import.source.did { Some(import_def_id) => { let stab_tags = - print_extra_info_tags(tcx, myitem, item, Some(import_def_id)) - .to_string(); + print_extra_info_tags(tcx, myitem, item, Some(import_def_id)); let deprecation = tcx .lookup_deprecation(import_def_id) .is_some_and(|deprecation| deprecation.is_in_effect()); - (stab_tags, deprecation) + (Some(stab_tags), deprecation) } - None => (String::new(), item.is_deprecated(tcx)), + None => (None, item.is_deprecated(tcx)), }; let visibility_and_hidden = visibility_and_hidden(myitem); let id = match import.kind { - clean::ImportKind::Simple(s) => { - format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) - } - clean::ImportKind::Glob => String::new(), + clean::ImportKind::Simple(s) => Some(format_args!( + " id=\"{}\"", + cx.derive_id(format!("reexport.{s}")) + )), + clean::ImportKind::Glob => None, }; write!( w, "", + id = id.maybe_display(), deprecation_attr = deprecation_class_attr(deprecation) )?; write!( @@ -423,6 +425,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i vis = visibility_print_with_space(myitem, cx), imp = print_import(import, cx), visibility_and_hidden = visibility_and_hidden, + stab_tags = stab_tags.maybe_display(), )?; } _ => { @@ -512,18 +515,14 @@ fn print_extra_info_tags( write!(f, "{}", tag_html("unstable", "", "Experimental"))?; } + debug!(name = ?item.name, cfg = ?item.cfg, parent_cfg = ?parent.cfg, "Portability"); + let cfg = match (&item.cfg, parent.cfg.as_ref()) { - (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), - (cfg, _) => cfg.as_deref().cloned(), + (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg).map(Cow::Owned), + (cfg, _) => cfg.as_deref().map(Cow::Borrowed), }; - debug!( - "Portability name={name:?} {cfg:?} - {parent_cfg:?} = {cfg:?}", - name = item.name, - cfg = item.cfg, - parent_cfg = parent.cfg - ); - if let Some(ref cfg) = cfg { + if let Some(cfg) = cfg { write!( f, "{}", @@ -976,7 +975,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: "Dyn Compatibility", "dyn-compatibility", None, - format!( + format_args!( "

This trait {} \ dyn compatible.

\

In older versions of Rust, dyn compatibility was called \"object safety\".

", @@ -1775,10 +1774,10 @@ fn item_variants( w, "{}", write_section_heading( - &format!("Variants{}", document_non_exhaustive_header(it)), + format_args!("Variants{}", document_non_exhaustive_header(it)), "variants", Some("variants"), - format!("{}
", document_non_exhaustive(it)), + format_args!("{}
", document_non_exhaustive(it)), ), )?; @@ -2105,7 +2104,7 @@ fn item_fields( if let None | Some(CtorKind::Fn) = ctor_kind && fields.peek().is_some() { - let title = format!( + let title = format_args!( "{}{}", if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, document_non_exhaustive_header(it), @@ -2113,12 +2112,7 @@ fn item_fields( write!( w, "{}", - write_section_heading( - &title, - "fields", - Some("fields"), - document_non_exhaustive(it) - ) + write_section_heading(title, "fields", Some("fields"), document_non_exhaustive(it)) )?; for (index, (field, ty)) in fields.enumerate() { let field_name = @@ -2554,7 +2548,7 @@ fn render_struct_fields( } for field in fields { if let clean::StructFieldItem(ref ty) = field.kind { - render_attributes_in_code(w, field, &format!("{tab} "), cx)?; + render_attributes_in_code(w, field, format_args!("{tab} "), cx)?; writeln!( w, "{tab} {vis}{name}: {ty},", diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index c366af8778ed6..ad8c6588e521f 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -413,7 +413,7 @@ impl CratesIndexPart { let layout = &cx.shared.layout; let style_files = &cx.shared.style_files; const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this - let content = format!( + let content = format_args!( "
\

List of all crates

\ \ diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 73a6d9026e9c4..f929aaaa04b14 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: - 'master' schedule: - - cron: '44 4 * * *' # At 4:44 UTC every day. + - cron: '14 4 * * *' # At 4:14 UTC every day. defaults: run: @@ -157,15 +157,22 @@ jobs: # This job is intentionally separate from `test` so that Priroda can be # developed as a separate crate inside the Miri repository for now. - priroda-build: + priroda: name: Priroda runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/workflows/setup + - name: install Miri driver + run: ./miri install - name: build Priroda working-directory: priroda run: cargo build --locked + - name: test Priroda + working-directory: priroda + run: | + sysroot="$(cargo miri setup --print-sysroot)" + MIRI_SYSROOT="$sysroot" cargo test --locked coverage: name: coverage report @@ -180,7 +187,7 @@ jobs: # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # And they should be added below in `cron-fail-notify` as well. conclusion: - needs: [test, style, bootstrap, coverage, priroda-build] + needs: [test, style, bootstrap, coverage, priroda] # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run @@ -264,7 +271,7 @@ jobs: cron-fail-notify: name: cronjob failure notification runs-on: ubuntu-latest - needs: [test, style, bootstrap, coverage, priroda-build] + needs: [test, style, bootstrap, coverage, priroda] if: ${{ github.event_name == 'schedule' && failure() }} steps: # Send a Zulip notification diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index bebacba77daf9..00794371d50c6 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ "cipher", "cpubits", diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 550a2e25140ef..6b8d8ed1f8018 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -1,5 +1,9 @@ # Miri +Corro, the Unsafe Rusturchin, drinking from a juice bottle labeled 'Miri - 100% safe' + Miri is an [Undefined Behavior][reference-ub] detection tool for Rust. It can run binaries and test suites of cargo projects and detect unsafe code that fails to uphold its safety requirements. For instance: diff --git a/src/tools/miri/miri-sticker.png b/src/tools/miri/miri-sticker.png new file mode 100644 index 0000000000000..9bb282af1b00a Binary files /dev/null and b/src/tools/miri/miri-sticker.png differ diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock index 25c97f8919423..2eb63f8a16b3b 100644 --- a/src/tools/miri/priroda/Cargo.lock +++ b/src/tools/miri/priroda/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aes" version = "0.9.0" @@ -13,6 +28,31 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.102" @@ -25,6 +65,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "bincode" version = "1.3.3" @@ -40,12 +95,32 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + [[package]] name = "capstone" version = "0.14.0" @@ -65,6 +140,29 @@ dependencies = [ "cc", ] +[[package]] +name = "cargo-platform" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "cc" version = "1.2.62" @@ -127,6 +225,60 @@ dependencies = [ "inout", ] +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys", +] + [[package]] name = "cpubits" version = "0.1.1" @@ -187,6 +339,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -203,6 +361,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -288,6 +456,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "hashbrown" version = "0.15.5" @@ -324,6 +498,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "2.14.0" @@ -336,6 +516,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "inout" version = "0.2.2" @@ -381,12 +574,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" version = "0.2.186" @@ -481,6 +686,15 @@ dependencies = [ "libc", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.2.0" @@ -537,6 +751,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -549,6 +772,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + [[package]] name = "parking_lot" version = "0.12.5" @@ -605,6 +834,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -614,6 +849,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettydiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" +dependencies = [ + "owo-colors", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -629,6 +873,8 @@ name = "priroda" version = "0.1.0" dependencies = [ "miri", + "regex", + "ui_test", ] [[package]] @@ -724,15 +970,71 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 2.0.18", ] +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "rustix" version = "1.1.4" @@ -763,6 +1065,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -807,6 +1113,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -831,6 +1146,17 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "spanned" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" +dependencies = [ + "anyhow", + "bstr", + "color-eyre", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -861,13 +1187,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -881,24 +1227,112 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "typenum" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +[[package]] +name = "ui_test" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8811281d587a786747c0c49245925016c07767bc996305bdd34d5ce076786a" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "indicatif", + "levenshtein", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "spanned", +] + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "uuid" version = "1.23.1" @@ -910,6 +1344,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1013,6 +1453,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows" version = "0.61.3" diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml index 084773f10e660..88e65653449c8 100644 --- a/src/tools/miri/priroda/Cargo.toml +++ b/src/tools/miri/priroda/Cargo.toml @@ -10,10 +10,19 @@ edition = "2024" [[bin]] name = "priroda" path = "src/main.rs" +test = false # Disable unit tests to avoid errors with --bless doctest = false # and no doc tests +[[test]] +name = "cli" +harness = false + [dependencies] miri = { path = ".." } [package.metadata.rust-analyzer] rustc_private = true + +[dev-dependencies] +ui_test = "0.30.2" +regex = "1.5.5" diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index 246fa93fbc44f..e0dafd2685504 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -1,13 +1,13 @@ # Priroda -Priroda is a step-through debugger for Rust programs running under -Miri. +Priroda is a step-through debugger for Rust programs running under Miri. Current focus: - simple CLI prototype - single-threaded stepping with Miri's interpreter -- commands: empty Enter, `s`, or `step` +- source-location output after stepping +- source-location breakpoint prototype ## Setup @@ -28,10 +28,47 @@ export MIRI_SYSROOT="$(cargo +miri miri setup --print-sysroot)" ## Run -Priroda currently reads `MIRI_SYSROOT` directly. After setup: +Priroda currently reads `MIRI_SYSROOT` directly. After setup, run Priroda +from `miri/priroda/`: ```sh -cargo run -p priroda -- tests/pass/empty_main.rs +cargo run -- ../tests/pass/empty_main.rs ``` -At the prompt, press Enter or type `s` / `step`. +## Test + +Priroda's CLI tests also need `MIRI_SYSROOT`. Run them from `miri/priroda/`: + +```sh +cargo test +``` + +If the CLI tests fail due to mismatched output, you can update the expected output files by running the tests with the `--bless` flag: + +```sh +cargo test -- --bless +``` + +or + +```sh +RUSTC_BLESS=1 cargo test +``` + +## Commands + +| Command | Description | +|---|---| +| Enter, `s`, `step` | Execute one Miri interpreter step. | +| `c`, `continue` | Continue until the program finishes or reaches a breakpoint. | +| `b :`, `break :` | Add a source-location breakpoint. | +| `q`, `quit` | Exit Priroda. | + +EOF also exits Priroda cleanly. + +Example: + +```text +(priroda) break tests/pass/empty_main.rs:3 +(priroda) continue +``` diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 775f2403bd1f0..6ac0ce761dcb0 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -10,16 +10,23 @@ extern crate rustc_interface; extern crate rustc_log; extern crate rustc_middle; extern crate rustc_session; +extern crate rustc_span; +use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; +use std::path::PathBuf; use miri::*; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; use rustc_interface::interface; +use rustc_middle::mir; use rustc_middle::ty::TyCtxt; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; +use rustc_span::Span; +use rustc_span::source_map::SourceMap; + fn find_sysroot() -> String { std::env::var("MIRI_SYSROOT") .expect("set MIRI_SYSROOT to the path from `cargo miri setup --print-sysroot`") @@ -38,13 +45,14 @@ fn main() { args.push(sysroot_flag); args.push(find_sysroot()); } - //TODO: handle the same `-Z` flags that Miri accepts. + // FIXME: handle the same `-Z` flags that Miri accepts. rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new()); } struct PrirodaCompilerCalls; impl PrirodaCompilerCalls { + // FIXME: remove this constructor if PrirodaCompilerCalls remains a unit struct. fn new() -> Self { Self } @@ -56,20 +64,25 @@ impl rustc_driver::Callbacks for PrirodaCompilerCalls { tcx.dcx().abort_if_errors(); if !tcx.crate_types().contains(&CrateType::Executable) { - //TODO: support non-bin crates by listing functions and letting users call them with manually entered arguments. + // FIXME: support non-bin crates by listing functions and letting users call them with manually entered arguments. tcx.dcx().fatal("priroda only makes sense on bin crates"); } let ecx = create_ecx(tcx); let mut session = PrirodaContext::new(ecx); - let result = run_cli_loop(&mut session); + let cli = CLI {}; + let result = cli.run_cli_loop(&mut session); match result.report_err() { Ok(()) => {} Err(err) => if let Some((return_code, _leak_check)) = report_result(&session.ecx, err) { - //TODO: print the evaluated program's exit code and return to the debugger prompt instead of exiting Priroda. + // FIXME: translate Miri termination into a Priroda execution-state enum so + // the CLI loop can distinguish whole-program exit from individual thread + // completion, run Miri-equivalent leak checks, print the exit code, and + // return to the debugger prompt. + println!("program finished with exit code {return_code}"); if return_code != 0 { std::process::exit(return_code); } @@ -82,66 +95,307 @@ impl rustc_driver::Callbacks for PrirodaCompilerCalls { fn create_ecx<'tcx>(tcx: TyCtxt<'tcx>) -> MiriInterpCx<'tcx> { let (entry_id, entry_type) = miri::entry_fn(tcx); + // FIXME: share Miri launcher configuration so interpreted programs receive + // their program name, arguments, environment snapshot, and `MIRI_CWD`. let config = MiriConfig::default(); + // FIXME: report interpreter initialization failures instead of panicking. miri::create_ecx(tcx, entry_id, entry_type, &config, None).unwrap() } -pub struct PrirodaContext<'tcx> { +/// Structured source information for frontends. +struct SourceLocation { + // storing `span` to use it lazily to compute path. + span: Span, + line: usize, +} + +impl SourceLocation { + fn local_path(&self, source_map: &SourceMap) -> Option { + let loc = source_map.lookup_char_pos(self.span.lo()); + loc.file.name.clone().into_local_path().map(normalize_path) + } +} + +/// Source-level breakpoints indexed by normalized path, then line. +type BreakpointTable = HashMap>; + +/// Owns one interpreter session and its debugger state. +/// +/// Frontend rendering should eventually live outside this type. +struct PrirodaContext<'tcx> { ecx: MiriInterpCx<'tcx>, + breakpoints: BreakpointTable, + current_location: Option, + last_location: Option, +} + +/// Controls when execution returns to the frontend. +enum ResumeMode { + /// Stop at the next visible MIR instruction. + MirInstruction, + /// Continue until reaching a breakpoint. + Continue, +} + +/// Describes whether the current MIR instruction should be shown to the user. +enum InstructionVisibility { + NoInstruction, + Hidden, + Visible, +} + +/// Describes why execution stopped and returned control to the frontend. +enum StepResult { + Step, + Breakpoint, +} + +fn normalize_path(path: PathBuf) -> PathBuf { + path.canonicalize().unwrap_or(path) } impl<'tcx> PrirodaContext<'tcx> { fn new(ecx: MiriInterpCx<'tcx>) -> Self { - Self { ecx } + Self { ecx, breakpoints: HashMap::new(), current_location: None, last_location: None } + } + + /// Step to the next visible MIR instruction. + fn step(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::MirInstruction) } - // TODO: return a StepResult enum once we distinguish breakpoint stops, - // program exit, and other debugger states. - pub fn step(&mut self) -> InterpResult<'tcx> { - self.ecx.miri_step() + /// Continue execution until reaching a breakpoint or propagating termination. + fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::Continue) } - pub fn print_location(&self) { + fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { + // FIXME: validate breakpoints here so every frontend gets the same behavior. + // Reject empty paths, missing files, directories, and line 0. Decide whether + // out-of-range lines should be rejected or kept as pending breakpoints. + // Report duplicate registrations separately. + + let path = normalize_path(path); + match self.breakpoints.entry(path.clone()).or_default().insert(line) { + true => BreakpointSetResult::Added(path, line), + false => BreakpointSetResult::Duplicate, + } + } + + /// Advance execution until the selected resume mode reaches a stopping point. + fn resume(&mut self, mode: ResumeMode) -> InterpResult<'tcx, StepResult> { + loop { + self.advance()?; + + // An explicit breakpoint should stop execution even when the current + // MIR instruction would normally be hidden during manual stepping. + if self.is_at_breakpoint() { + return interp_ok(StepResult::Breakpoint); + } + + match mode { + ResumeMode::MirInstruction + if matches!( + self.current_instruction_visibility(), + InstructionVisibility::Visible + ) => + { + return interp_ok(StepResult::Step); + } + ResumeMode::MirInstruction | ResumeMode::Continue => {} + } + } + } + + /// Advance Miri by one interpreter-loop transition. + fn advance(&mut self) -> InterpResult<'tcx> { + // FIXME: use a Miri-owned scheduler-aware debugger step API before + // claiming support for multi-threaded interpreted programs. + + // State inspection should happen only after a successful step. + self.ecx.miri_step()?; + self.last_location = self.current_location.take(); + self.current_location = self.resolve_current_location(); + interp_ok(()) + } + + fn current_instruction_visibility(&self) -> InstructionVisibility { + // If the active thread has no stack frame, there is no MIR instruction to show. + let Some(frame) = self.ecx.active_thread_stack().last() else { + return InstructionVisibility::NoInstruction; + }; + + // `Right(span)` means the frame has source context but no precise MIR program-counter location. + let Either::Left(location) = frame.current_loc() else { + return InstructionVisibility::NoInstruction; + }; + + let basic_block = &frame.body().basic_blocks[location.block]; + + // `statement_index == statements.len()` points at the block terminator. + // Terminators affect control flow, so they are always visible. + let Some(statement) = basic_block.statements.get(location.statement_index) else { + return InstructionVisibility::Visible; + }; + + // Hide bookkeeping-only MIR statements during manual stepping. + match statement.kind { + mir::StatementKind::StorageLive(_) + | mir::StatementKind::StorageDead(_) + | mir::StatementKind::Nop => InstructionVisibility::Hidden, + _ => InstructionVisibility::Visible, + } + } + + fn is_at_breakpoint(&self) -> bool { + // FIXME: avoid repeated stops when one source line maps to multiple MIR statements. + let Some(location) = &self.current_location else { + return false; + }; + + let source_map = self.ecx.tcx.sess.source_map(); + let Some(path) = &location.local_path(source_map) else { + return false; + }; + + let lines = match self.breakpoints.get(path) { + Some(lines) => lines, + None => return false, + }; + lines.contains(&location.line) + } + + fn resolve_current_location(&self) -> Option { + // FIXME: resolve macro-backed lines such as `println!` and `assert_eq!` + // through `span.source_callsite()` before matching breakpoints. let span = self.ecx.machine.current_user_relevant_span(); - let location = self.ecx.tcx.sess.source_map().span_to_diagnostic_string(span); - // TODO: skip noisy std/runtime spans and avoid printing `no-location` - // once the basic command loop is solid. - println!("{location}"); - io::stdout().flush().unwrap(); + if span.is_dummy() { + return None; + } + + let source_map = self.ecx.tcx.sess.source_map(); + let loc = source_map.lookup_char_pos(span.lo()); + + Some(SourceLocation { span: span, line: loc.line }) } - fn run_command(&mut self, command: SessionCommand) -> InterpResult<'tcx> { + + fn run_command(&mut self, command: DebuggerCommand) -> InterpResult<'tcx, CommandResult> { match command { - SessionCommand::Step => self.step(), + DebuggerCommand::Step => self.step().map(CommandResult::ExecutionStopped), + DebuggerCommand::Continue => + self.continue_execution().map(CommandResult::ExecutionStopped), + DebuggerCommand::Breakpoint(path, line) => + interp_ok(CommandResult::BreakpointResult(self.set_breakpoint(path, line))), + DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), } } } -enum SessionCommand { +enum DebuggerCommand { Step, + TerminateSession, + Continue, + Breakpoint(PathBuf, usize), } -fn parse_command(input: &str) -> Option { - match input.trim() { - "" | "s" | "step" => Some(SessionCommand::Step), - _ => None, - } +enum BreakpointSetResult { + Added(PathBuf, usize), + Duplicate, + // FIXME: add pending breakpoint support later if needed. } -fn run_cli_loop<'tcx>(session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { - loop { - print!("(priroda) "); - io::stdout().flush().unwrap(); +enum CommandResult { + ExecutionStopped(StepResult), + BreakpointResult(BreakpointSetResult), + // FIXME: distinguish terminating the debugger session from disconnecting a + // frontend and terminating the interpreted program once multiple frontends exist. + TerminateSession, +} + +struct CLI; + +impl CLI { + pub fn run_cli_loop<'tcx>(&self, session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { + loop { + print!("(priroda) "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + let bytes_read = io::stdin().read_line(&mut input).unwrap(); - let mut input = String::new(); - // TODO: handle EOF explicitly so scripted input can stop the CLI instead - // of being treated like an empty Enter step. - io::stdin().read_line(&mut input).unwrap(); + if bytes_read == 0 { + println!("stdin closed, stopping"); + return interp_ok(()); + } - if let Some(command) = parse_command(&input) { - session.run_command(command)?; - session.print_location(); - } else { - println!("no command"); + if let Some(command) = self.parse_command(&input) { + match session.run_command(command)? { + CommandResult::ExecutionStopped(result) => { + if matches!(result, StepResult::Breakpoint) { + println!("Hit breakpoint"); + } + self.print_location(&session); + } + CommandResult::BreakpointResult(res) => + match res { + BreakpointSetResult::Added(path, line) => + println!("breakpoint added: {}:{}", path.display(), line), + + BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), + }, + CommandResult::TerminateSession => { + println!("quitting"); + return interp_ok(()); + } + } + } else { + println!("no command"); + } + + io::stdout().flush().unwrap(); + } + } + + fn parse_command(&self, input: &str) -> Option { + // TODO: look at the Spanned crate for how to easily produce errors in + // rustc's style while manually parsing text input. + // FIXME: we need to distinguish malformed input from the unknown commands by returning useful + // command error that describes if it malformed or non exist command + let input = input.trim(); + let mut parts = input.splitn(2, char::is_whitespace); + let command = parts.next().unwrap_or(""); + let args = parts.next().unwrap_or("").trim(); + + match command { + "" | "s" | "step" => Some(DebuggerCommand::Step), + "q" | "quit" => Some(DebuggerCommand::TerminateSession), + "c" | "continue" => Some(DebuggerCommand::Continue), + "b" | "break" => self.parse_breakpoint(args), + _ => None, } } + + fn print_location(&self, session: &PrirodaContext) { + let source_map = session.ecx.tcx.sess.source_map(); + match &session.current_location { + Some(location) => + if let Some(path) = location.local_path(source_map) { + println!("{}:{}", path.display(), location.line); + } else { + println!("{}", source_map.span_to_diagnostic_string(location.span)); + }, + None => println!("no-location"), + } + io::stdout().flush().unwrap(); + } + + fn parse_breakpoint(&self, input: &str) -> Option { + // FIXME: return a typed CommandError so malformed breakpoint input is + // distinguishable from an unknown command. Semantic validation belongs + // in PrirodaContext::set_breakpoint so non-CLI frontends cannot bypass it. + let (path, line) = input.rsplit_once(':')?; + let line = line.parse().ok()?; + + Some(DebuggerCommand::Breakpoint(PathBuf::from(path), line)) + } } diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs new file mode 100644 index 0000000000000..f4f9bf5d176ac --- /dev/null +++ b/src/tools/miri/priroda/tests/cli.rs @@ -0,0 +1,58 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +use regex::bytes::Regex; +use ui_test::spanned::Spanned; +use ui_test::status_emitter::StatusEmitter; +use ui_test::{CommandBuilder, Config, default_file_filter, run_tests_generic}; + +fn main() -> Result<(), Box> { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let miri_dir = manifest_dir.parent().unwrap(); + + let rustc_sysroot = Command::new("rustc").arg("--print").arg("sysroot").output()?; + let rustc_sysroot = String::from_utf8(rustc_sysroot.stdout)?.trim().to_owned(); + + let mut program = CommandBuilder::rustc(); + program.program = PathBuf::from(env!("CARGO_BIN_EXE_priroda")); + + // Remove logging env vars that might leak into stderr + program.envs.push(("RUSTC_LOG".into(), None)); + program.envs.push(("RUST_LOG".into(), None)); + + let mut config = Config { + program, + out_dir: PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("priroda_ui"), + ..Config::rustc("tests/ui") + }; + + // Replace the dynamic paths in the actual stdout with the stable placeholders + let manifest_dir_regex = + Regex::new(®ex::escape(&manifest_dir.display().to_string())).unwrap(); + let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); + let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); + + config.comment_defaults.base().normalize_stdout.extend([ + (manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()), + (miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()), + (rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()), + ]); + + // Priroda CLI tests do not currently require annotation comments in the test files + config.comment_defaults.base().exit_status = Spanned::dummy(0).into(); + config.comment_defaults.base().require_annotations = Spanned::dummy(false).into(); + + let mut args = ui_test::Args::test()?; + args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); + config.with_args(&args); + + run_tests_generic( + vec![config], + default_file_filter, + |_, _| {}, + Box::::from(args.format), + )?; + + Ok(()) +} diff --git a/src/tools/miri/priroda/tests/ui/continue_finishes_program.rs b/src/tools/miri/priroda/tests/ui/continue_finishes_program.rs new file mode 100644 index 0000000000000..e187158df4b8b --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_finishes_program.rs @@ -0,0 +1,4 @@ +// Verifies continue can drive the interpreted program to normal completion. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdin b/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdin new file mode 100644 index 0000000000000..44c5d7d65ead7 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdin @@ -0,0 +1 @@ +continue diff --git a/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdout b/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdout new file mode 100644 index 0000000000000..d6c4605d6baf3 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_finishes_program.stdout @@ -0,0 +1 @@ +(priroda) program finished with exit code 0 diff --git a/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.rs b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.rs new file mode 100644 index 0000000000000..756e00b859fce --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.rs @@ -0,0 +1,7 @@ +// Verifies continue stops when execution reaches a registered source-location +// breakpoint. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() { + let _value = 0; +} diff --git a/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdin b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdin new file mode 100644 index 0000000000000..f7ad7e7ed0ea9 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdin @@ -0,0 +1,3 @@ +break tests/ui/continue_hits_breakpoint.rs:6 +continue +quit diff --git a/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdout b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdout new file mode 100644 index 0000000000000..15cae3bed56d5 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/continue_hits_breakpoint.stdout @@ -0,0 +1,4 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/continue_hits_breakpoint.rs:6 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/continue_hits_breakpoint.rs:6 +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.rs b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.rs new file mode 100644 index 0000000000000..dfd33a0de8611 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.rs @@ -0,0 +1,4 @@ +// Verifies breakpoint aliases and duplicate detection before execution starts. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdin b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdin new file mode 100644 index 0000000000000..bf4065c08aa76 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdin @@ -0,0 +1,3 @@ +break nowhere.rs:12 +b nowhere.rs:12 +quit diff --git a/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdout b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdout new file mode 100644 index 0000000000000..c3ba71bcaf6d7 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/duplicate_breakpoint.stdout @@ -0,0 +1,3 @@ +(priroda) breakpoint added: nowhere.rs:12 +(priroda) Duplicate breakpoint +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/empty_main.rs b/src/tools/miri/priroda/tests/ui/empty_main.rs new file mode 100644 index 0000000000000..2e103198f3649 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/empty_main.rs @@ -0,0 +1,5 @@ +// Verifies Priroda can start on the simplest passing Rust program and accept +// a scripted `quit` command. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/empty_main.stdin b/src/tools/miri/priroda/tests/ui/empty_main.stdin new file mode 100644 index 0000000000000..ff604669be5ca --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/empty_main.stdin @@ -0,0 +1 @@ +quit diff --git a/src/tools/miri/priroda/tests/ui/empty_main.stdout b/src/tools/miri/priroda/tests/ui/empty_main.stdout new file mode 100644 index 0000000000000..daad97b3037c2 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/empty_main.stdout @@ -0,0 +1 @@ +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.rs b/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.rs new file mode 100644 index 0000000000000..84ce4c91f7e73 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.rs @@ -0,0 +1,5 @@ +// Verifies EOF exits the debugger loop cleanly without requiring an explicit +// quit command. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.stdin b/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.stdin new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.stdout b/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.stdout new file mode 100644 index 0000000000000..ee4ed45becbee --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/eof_exits_cleanly.stdout @@ -0,0 +1 @@ +(priroda) stdin closed, stopping diff --git a/src/tools/miri/priroda/tests/ui/invalid_commands.rs b/src/tools/miri/priroda/tests/ui/invalid_commands.rs new file mode 100644 index 0000000000000..f869316a199b7 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/invalid_commands.rs @@ -0,0 +1,5 @@ +// Verifies unknown commands and malformed breakpoints are rejected without +// mutating debugger state. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/invalid_commands.stdin b/src/tools/miri/priroda/tests/ui/invalid_commands.stdin new file mode 100644 index 0000000000000..87d47a341b59c --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/invalid_commands.stdin @@ -0,0 +1,4 @@ +wat +break +break nowhere.rs:not-a-line +quit diff --git a/src/tools/miri/priroda/tests/ui/invalid_commands.stdout b/src/tools/miri/priroda/tests/ui/invalid_commands.stdout new file mode 100644 index 0000000000000..c48a6c72fde76 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/invalid_commands.stdout @@ -0,0 +1,4 @@ +(priroda) no command +(priroda) no command +(priroda) no command +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.rs b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.rs new file mode 100644 index 0000000000000..4a1247ab37b4a --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.rs @@ -0,0 +1,7 @@ +// Documents the current repeated-stop behavior when multiple MIR locations map +// to the same source breakpoint line. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() { + let _value = 0; +} diff --git a/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdin b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdin new file mode 100644 index 0000000000000..a179d469cc2ba --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdin @@ -0,0 +1,4 @@ +break tests/ui/repeated_same_line_breakpoint.rs:6 +continue +continue +quit diff --git a/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdout b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdout new file mode 100644 index 0000000000000..27ba93d504ed4 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/repeated_same_line_breakpoint.stdout @@ -0,0 +1,6 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/repeated_same_line_breakpoint.rs:6 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/repeated_same_line_breakpoint.rs:6 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/repeated_same_line_breakpoint.rs:6 +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/source_step_changes_line.rs b/src/tools/miri/priroda/tests/ui/source_step_changes_line.rs new file mode 100644 index 0000000000000..796b193009841 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/source_step_changes_line.rs @@ -0,0 +1,5 @@ +// Verifies source-level stepping advances until the displayed source location +// changes. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdin b/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdin new file mode 100644 index 0000000000000..955048258c669 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdin @@ -0,0 +1,3 @@ +s +step +quit diff --git a/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdout b/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdout new file mode 100644 index 0000000000000..bd2d2487d316b --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/source_step_changes_line.stdout @@ -0,0 +1,3 @@ +(priroda) {RUSTC_SYSROOT}/lib/rustlib/src/rust/library/std/src/rt.rs:206 +(priroda) {RUSTC_SYSROOT}/lib/rustlib/src/rust/library/std/src/rt.rs:206 +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/step_aliases.rs b/src/tools/miri/priroda/tests/ui/step_aliases.rs new file mode 100644 index 0000000000000..694b1be4f49cd --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/step_aliases.rs @@ -0,0 +1,5 @@ +// Verifies every current spelling of MIR-instruction stepping advances +// execution and reports a location. +// This may look trivial, but a bunch of code runs in std before +// `main` is called, so we are ensuring that that all works. +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/step_aliases.stdin b/src/tools/miri/priroda/tests/ui/step_aliases.stdin new file mode 100644 index 0000000000000..923b000e78e7e --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/step_aliases.stdin @@ -0,0 +1,4 @@ +s +step + +quit diff --git a/src/tools/miri/priroda/tests/ui/step_aliases.stdout b/src/tools/miri/priroda/tests/ui/step_aliases.stdout new file mode 100644 index 0000000000000..32233f657b41a --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/step_aliases.stdout @@ -0,0 +1,4 @@ +(priroda) {RUSTC_SYSROOT}/lib/rustlib/src/rust/library/std/src/rt.rs:206 +(priroda) {RUSTC_SYSROOT}/lib/rustlib/src/rust/library/std/src/rt.rs:206 +(priroda) {RUSTC_SYSROOT}/lib/rustlib/src/rust/library/std/src/rt.rs:206 +(priroda) quitting diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index aaeb424e108f0..387bd8edd2196 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -bef8e620f19adbfd1530e916ab8caa296ef9c3ee +029c9e18dd1f4668e1d42bb187c1c263dfe20093 diff --git a/src/tools/miri/src/shims/loongarch.rs b/src/tools/miri/src/shims/loongarch.rs index e5a57fbcaebfb..8642a5ce1a47b 100644 --- a/src/tools/miri/src/shims/loongarch.rs +++ b/src/tools/miri/src/shims/loongarch.rs @@ -1,4 +1,4 @@ -use rustc_abi::CanonAbi; +use rustc_abi::{CanonAbi, Size}; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -55,10 +55,17 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // b/h/w variants and i64 for the d variant, per the LLVM intrinsic // definitions. // https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsLoongArch.td - // If the higher bits are non-zero, `compute_crc32` will panic. We should probably - // raise a proper error instead, but outside stdarch nobody can trigger this anyway. + // LoongArch CRC b/h/w instructions ignore any bits above `bit_size`. + // https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#crc-check-instructions + // Miri's `compute_crc32` requires all higher bits to be zero and may + // panic otherwise, so we explicitly mask them off here to reproduce the + // hardware behavior. let crc = crc.to_u32()?; - let data = if bit_size == 64 { data.to_u64()? } else { u64::from(data.to_u32()?) }; + let data = if bit_size == 64 { + data.to_u64()? + } else { + Size::from_bits(bit_size).truncate(data.to_u32()?.into()).try_into().unwrap() + }; let result = compute_crc32(crc, data, bit_size, polynomial); this.write_scalar(Scalar::from_u32(result), dest)?; diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index cbfbb4e200a0f..5767add2b9450 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -63,19 +63,19 @@ impl<'tcx> Default for TlsData<'tcx> { impl<'tcx> TlsData<'tcx> { /// Generate a new TLS key with the given destructor. - /// `max_size` determines the integer size the key has to fit in. + /// `key_size` determines the integer size the key has to fit in. #[expect(clippy::arithmetic_side_effects)] pub fn create_tls_key( &mut self, dtor: Option<(ty::Instance<'tcx>, Span)>, - max_size: Size, + key_size: Size, ) -> InterpResult<'tcx, TlsKey> { let new_key = self.next_key; self.next_key += 1; self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap(); trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor); - if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) { + if new_key > key_size.unsigned_int_max() { throw_unsup_format!("we ran out of TLS key space"); } interp_ok(new_key) @@ -267,7 +267,13 @@ impl<'tcx> TlsDtorsState<'tcx> { let fls_keys_with_dtors = this.lookup_windows_fls_keys_with_dtors()?; // And move to the next state, that runs them. - break 'new_state WindowsDtors(RunningWindowsDtorState { last_key: None, remaining_keys: fls_keys_with_dtors }, dtors); + break 'new_state WindowsDtors( + RunningWindowsDtorState { + last_key: None, + remaining_keys: fls_keys_with_dtors, + }, + dtors, + ); } _ => { // No TLS dtor support. @@ -453,35 +459,31 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Keys without dtors will not be set to zero. // [PflsCallbackFunction's docs]: https://learn.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pfls_callback_function // [`RtlProcessFlsData`]: https://github.com/wine-mirror/wine/blob/wine-11.0/dlls/ntdll/thread.c#L679 - + // We are done running the previous key's destructor, so set it's value to zero. if let Some(last_key) = state.last_key.take() { if let Some(TlsEntry { data, .. }) = this.machine.tls.keys.get_mut(&last_key) { - data.remove(&active_thread); + data.remove(&active_thread); }; } while let Some(key) = state.remaining_keys.pop_front() { // Fetch dtor for this `key`. // If the key doesn't have a dtor or does not exist any more, move on to the next key. - let (data, dtor) = match this.machine.tls.keys.get(&key) { - Some(TlsEntry { data, dtor: Some(dtor) }) => (data, dtor), - _ => continue, + let Some(TlsEntry { data, dtor: Some(dtor) }) = this.machine.tls.keys.get(&key) else { + continue; }; let (instance, span) = dtor.to_owned(); - + // If the key has no value in this thread, move on to the next key. - let ptr = match data.get(&active_thread) { - Some(data_scalar) => *data_scalar, - None => continue, - }; + let Some(&ptr) = data.get(&active_thread) else { continue }; assert!( ptr.to_target_usize(this).unwrap() != 0, "TLS key's value can't be null (should be absent instead)" ); - + trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread); // We'll clear this key's value next time we are called. @@ -497,7 +499,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Poll::Pending); } - + // We are done scheduling all the keys. interp_ok(Poll::Ready(())) } diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index f7feeb4cfde14..efef55f5cf91b 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -638,6 +638,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "TlsAlloc" => { + // FIXME: This does not have a direct test (#3179). // This just creates a key; Windows does not natively support TLS destructors. // Create key and return it. @@ -651,6 +652,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "TlsGetValue" => { + // FIXME: This does not have a direct test (#3179). let [key] = this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *mut _), link_name, @@ -663,6 +665,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(ptr, dest)?; } "TlsSetValue" => { + // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = this.check_shim_sig( shim_sig!(extern "system" fn(u32, *mut _) -> winapi::BOOL), link_name, @@ -678,6 +681,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "TlsFree" => { + // FIXME: This does not have a direct test (#3179). let [key] = this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), link_name, @@ -693,6 +697,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Fiber-local storage - similar to TLS but supports destructors. "FlsAlloc" => { + // FIXME: This does not have a direct test (#3179). // Create key and return it. let [dtor] = this.check_shim_sig( shim_sig!(extern "system" fn(winapi::PFLS_CALLBACK_FUNCTION) -> u32), @@ -716,6 +721,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "FlsGetValue" => { + // FIXME: This does not have a direct test (#3179). let [key] = this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *mut _), link_name, @@ -728,6 +734,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(ptr, dest)?; } "FlsSetValue" => { + // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = this.check_shim_sig( shim_sig!(extern "system" fn(u32, *mut _) -> winapi::BOOL), link_name, @@ -743,6 +750,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "FlsFree" => { + // FIXME: This does not have a direct test (#3179). let [key] = this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), link_name, @@ -763,6 +771,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "IsThreadAFiber" => { + // FIXME: This does not have a direct test (#3179). let [] = this.check_shim_sig( shim_sig!(extern "system" fn() -> winapi::BOOL), link_name, diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index de0ad7ffa5edd..6c5610ea07705 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -157,29 +157,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("CreateFileW: Template files are not supported"); } - // We need to know if the file is a directory to correctly open directory handles. This is - // racy, but currently the stdlib doesn't appear to offer a better solution. We do later - // verify that our guess was correct so worst-case, Miri ICEs here. - // FIXME: retry in a loop if we get an error indicating we got the wrong file type? - let is_dir = file_name.is_dir(); - - // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle. - if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir { - this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?; - return interp_ok(Handle::Invalid); - } - - let desired_read = desired_access & generic_read != 0; - let desired_write = desired_access & generic_write != 0; - - let mut options = fs::OpenOptions::new(); - if desired_read { + // Parse desired_access + let mut desired_read = false; + if desired_access & generic_read != 0 { + desired_read = true; desired_access &= !generic_read; - options.read(true); } - if desired_write { + let mut desired_write = false; + if desired_access & generic_write != 0 { + desired_write = true; desired_access &= !generic_write; - options.write(true); } if desired_access != 0 { @@ -188,90 +175,150 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - // Per the documentation: - // If the specified file exists and is writable, the function truncates the file, - // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS. - // If the specified file does not exist and is a valid path, a new file is created, - // the function succeeds, and the last-error code is set to zero. - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - // - // This is racy, but there doesn't appear to be an std API that both succeeds if a - // file exists but tells us it isn't new. Either we accept racing one way or another, - // or we use an iffy heuristic like file creation time. This implementation prefers - // to fail in the direction of erroring more often. - if let CreateAlways | OpenAlways = creation_disposition - && file_name.exists() - { - this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; - } + // We start a retry loop to deal with the `is_dir` and `exists_already` race, see below. + // We add a retry counter to avoid infinite loops when things go wrong. + let mut counter = 0u32; + loop { + if counter >= 100 { + panic!( + "CreateFileW seems stuck in an infinite retry loop. \ + If you can reproduce this, please file a bug." + ); + } + counter = counter.strict_add(1); + + // We need to know if the file is a directory to correctly open directory handles. + // The standard library only lets us open something as a file or a directory, so + // we check for that and then retry if we end up with the wrong thing. + let is_dir = file_name.is_dir(); - let handle = if is_dir { - // Open this as a directory. - // FIXME: shouldn't we check `creation_disposition` here? - Dir::open(&file_name).map(|dir| { + // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle. + if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir { + this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?; + return interp_ok(Handle::Invalid); + } + + if is_dir { + // Open this as a directory. + // FIXME: shouldn't we check `creation_disposition` here? We do know that it already + // exists. + let dir = match Dir::open(&file_name) { + Ok(dir) => dir, + Err(e) => { + if e.kind() == io::ErrorKind::NotADirectory { + // This changed from a directory to a file. Retry. + continue; + } + this.set_last_error(e)?; + return interp_ok(Handle::Invalid); + } + }; #[cfg(not(bootstrap))] - assert!( - dir.metadata().unwrap().is_dir(), - "we tried to open a directory and got a file" - ); + if !dir.metadata().unwrap().is_dir() { + // This changed from a directory to a file. Retry. + continue; + } + + // Windows communicates information via the error code on success. + if let CreateAlways | OpenAlways = creation_disposition { + this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; + } + let fd_num = this.machine.fds.insert_new(DirHandle { dir, path: file_name }); - Handle::File(fd_num) - }) - } else { - // Open this as a standard file. We already set the `read`/`write` flags above, - // but we still need to represent the `creation_disposition`. - match creation_disposition { - CreateAlways | OpenAlways => { - options.create(true); - if creation_disposition == CreateAlways { - options.truncate(true); + return interp_ok(Handle::File(fd_num)); + } else { + // Per the documentation: + // If the specified file exists and is writable, the function truncates the file, + // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS. + // If the specified file does not exist and is a valid path, a new file is created, + // the function succeeds, and the last-error code is set to zero. + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + // + // We check whether it exists before trying to open it. This is racy, but there + // doesn't appear to be an std API that both succeeds whether or not a file already + // exists and tells us whether it is new. So instead we will open the file in a way + // that we can verify whether our guess is correct, and retry if it is not. + let exists_already = file_name.exists(); + + // Open this as a standard file. + let mut options = fs::OpenOptions::new(); + options.read(desired_read); + options.write(desired_write); + match creation_disposition { + CreateAlways | OpenAlways => { + // We verify `exists_already`: if we expect it to already exist, we set no + // flag, thus failing if it doesn't exist. If we expect the file to not + // exist, we use `create_new` to fail if it does exist. + if !exists_already { + options.create_new(true); + } + if creation_disposition == CreateAlways { + options.truncate(true); + } } - } - CreateNew => { - options.create_new(true); - // Per `create_new` documentation: - // The file must be opened with write or append access in order to create a new file. - // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new - if !desired_write { - options.append(true); + CreateNew => { + options.create_new(true); + // Per `create_new` documentation: + // The file must be opened with write or append access in order to create a new file. + // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new + if !desired_write { + options.append(true); + } } - } - OpenExisting => { - if !desired_read && !desired_write { - // Windows supports handles with no permissions. These allow things such as - // reading metadata, but not file content. This is used by `Path::metadata`. - // `std` does not support this. To ensure we behave correctly as often as - // possible, we open the file for reading and live with the fact that this - // might incorrectly return `PermissionDenied`. - // FIXME: We could probably use `OpenOptionsExt`? On a Unix host, - // `O_PATH` apparently can open files for metadata use only. - options.read(true); + OpenExisting => { + if !desired_read && !desired_write { + // Windows supports handles with no permissions. These allow things such as + // reading metadata, but not file content. This is used by `Path::metadata`. + // `std` does not support this. To ensure we behave correctly as often as + // possible, we open the file for reading and live with the fact that this + // might incorrectly return `PermissionDenied`. + // FIXME: We could probably use `OpenOptionsExt`? On a Unix host, + // `O_PATH` apparently can open files for metadata use only. + options.read(true); + } + } + TruncateExisting => { + options.truncate(true); } } - TruncateExisting => { - options.truncate(true); + + let file = match options.open(&file_name) { + Ok(file) => file, + Err(e) => { + let kind = e.kind(); + if kind == io::ErrorKind::IsADirectory { + // This changed from a file to a directory. Retry. + continue; + } + if exists_already && kind == io::ErrorKind::NotFound { + // The file disappeared. Retry. + continue; + } + if !exists_already && kind == io::ErrorKind::AlreadyExists { + // The file got created by something else. Retry. + continue; + } + this.set_last_error(e)?; + return interp_ok(Handle::Invalid); + } + }; + if file.metadata().unwrap().is_dir() { + // This changed from a file to a directory. Retry. + continue; } - } - options.open(file_name).map(|file| { - assert!( - !file.metadata().unwrap().is_dir(), - "we tried to open a file and got a directory" - ); + // Windows communicates information via the error code on success. + if let CreateAlways | OpenAlways = creation_disposition + && exists_already + { + this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; + } let fd_num = this.machine.fds.insert_new(FileHandle { file, writable: desired_write, readable: desired_read, }); - Handle::File(fd_num) - }) - }; - - match handle { - Ok(handle) => interp_ok(handle), - Err(e) => { - this.set_last_error(e)?; - interp_ok(Handle::Invalid) + return interp_ok(Handle::File(fd_num)); } } } diff --git a/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.rs b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.rs new file mode 100644 index 0000000000000..553c79e6270f6 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.rs @@ -0,0 +1,16 @@ +//! Ensure that we do not permit mutating the part of an interior mutable static +//! that is outside the `Cell`. + +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +use std::sync::atomic::*; + +static X: (i32, AtomicI32) = (0, AtomicI32::new(1)); + +fn main() { + let ptr = &raw const X; + unsafe { ptr.cast_mut().write((1, AtomicI32::new(0))) }; + //~[stack]^ERROR: that tag only grants SharedReadOnly permission + //~[tree]|ERROR: /write access .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.stack.stderr b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.stack.stderr new file mode 100644 index 0000000000000..2af796bbf9619 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.stack.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + --> tests/fail/both_borrows/mixed_mutability_static.rs:LL:CC + | +LL | unsafe { ptr.cast_mut().write((1, AtomicI32::new(0))) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> tests/fail/both_borrows/mixed_mutability_static.rs:LL:CC + | +LL | let ptr = &raw const X; + | ^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.tree.stderr b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.tree.stderr new file mode 100644 index 0000000000000..5e8cb33edf2da --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mixed_mutability_static.tree.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/mixed_mutability_static.rs:LL:CC + | +LL | unsafe { ptr.cast_mut().write((1, AtomicI32::new(0))) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Cell + --> tests/fail/both_borrows/mixed_mutability_static.rs:LL:CC + | +LL | let ptr = &raw const X; + | ^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs b/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs index a6ffb325191b0..04d6597f64c92 100644 --- a/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs +++ b/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs @@ -2,7 +2,8 @@ use std::mem::transmute; enum Never {} -fn foo(x: Never) { //~ERROR: invalid value of type Never +fn foo(x: Never) { + //~^ERROR: invalid value of type Never let ptr = &raw const x; println!("{ptr:p}"); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs index f9794d76fcd00..05f1b8ae660a4 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -1,10 +1,11 @@ //@only-target: linux android illumos // test_epoll_block_then_unblock and test_epoll_race depend on a deterministic schedule. -//@compile-flags: -Zmiri-deterministic-concurrency //@revisions: edge_triggered level_triggered +//@run-native use std::convert::TryInto; use std::thread; +use std::time::Duration; #[path = "../../utils/libc.rs"] mod libc_utils; @@ -71,8 +72,8 @@ fn test_epoll_block_then_unblock() { check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); let thread1 = thread::spawn(move || { - thread::yield_now(); - // Due to deterministic concurrency, we'll only get here when the other thread blocks. + thread::sleep(Duration::from_millis(10)); + // Since we slept, we should only get here after the other thread blocks. write_all(fds[1], b"abcde").unwrap(); }); @@ -80,7 +81,7 @@ fn test_epoll_block_then_unblock() { // Edge-triggered epoll will block until the write succeeds and the buffer // becomes readable. This is because we already read the writable edge // before so at the time of calling `epoll_wait` there is no active readiness. - check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 100); } else { // Level-triggered epoll won't wait for the write to succeed because // _some_ readiness is already set (in this case the EPOLLOUT). @@ -142,7 +143,7 @@ fn test_epoll_race() { // Write to the eventfd instance. write_all(fd, &1_u64.to_ne_bytes()).unwrap(); }); - thread::yield_now(); + thread::sleep(Duration::from_millis(10)); // epoll_wait for EPOLLIN. check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: fd }], -1); // Read from the static mut variable. @@ -168,7 +169,7 @@ fn wakeup_on_new_interest() { check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }], -1); }); // Ensure the thread is blocked. - std::thread::yield_now(); + thread::sleep(Duration::from_millis(10)); // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLRDHUP (and EPOLLET if we're in the // `edge_triggered` revision). @@ -212,7 +213,7 @@ fn multiple_events_wake_multiple_threads() { Ev { events: e.events.cast_signed(), data: e.u64.try_into().unwrap() } }); // Yield so both threads are waiting now. - thread::yield_now(); + thread::sleep(Duration::from_millis(10)); // Trigger the eventfd. This triggers two events at once! write_all(fd1, &0_u64.to_ne_bytes()).unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index c1d6f913e8a1c..75eb06bc12cfb 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -1,5 +1,6 @@ //@only-target: linux android illumos //@revisions: edge_triggered level_triggered +//@run-native #[path = "../../utils/libc.rs"] mod libc_utils; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs index 6745836924a93..cc8351fdce8ff 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs @@ -1,6 +1,7 @@ //@ignore-target: windows # no libc //@ignore-target: solaris # Does not have flock //@compile-flags: -Zmiri-disable-isolation +//@run-native //@revisions: windows_host unix_host //@[unix_host] ignore-host: windows diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs index fd67afbe83c4b..819717fed8523 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs @@ -1,6 +1,7 @@ //@ignore-target: windows # no libc //@ignore-host: windows # needs unix PermissionExt //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::ffi::{CStr, CString}; use std::mem::MaybeUninit; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs index 52a0d978963e8..4fe66c338a045 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs @@ -2,6 +2,7 @@ //@ignore-host: windows # creating symlinks requires admin permissions on Windows //@ignore-target: windows # File handling is not implemented yet //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::ffi::CString; use std::io::ErrorKind; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index d1dbcd1ef4cd9..a131112ee258d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # no libc //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::ffi::{CStr, CString, OsString}; use std::fs::{self, File, canonicalize, create_dir, remove_dir, remove_file}; @@ -423,17 +424,21 @@ fn test_posix_mkstemp() { drop(file); remove_file(path).unwrap(); - let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"]; - for t in invalid_templates { - let ptr = CString::new(t).unwrap().into_raw(); - let fd = unsafe { libc::mkstemp(ptr) }; - let _ = unsafe { CString::from_raw(ptr) }; - // "On error, -1 is returned, and errno is set to - // indicate the error" - assert_eq!(fd, -1); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); - assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + // Test invalid inputs. We skip this on native macOS since macOS apparently does + // not bother to validate inputs. + if !cfg!(all(not(miri), target_vendor = "apple")) { + let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"]; + for t in invalid_templates { + let ptr = CString::new(t).unwrap().into_raw(); + let fd = unsafe { libc::mkstemp(ptr) }; + let _ = unsafe { CString::from_raw(ptr) }; + // "On error, -1 is returned, and errno is set to + // indicate the error" + assert_eq!(fd, -1, "mkstemp succeeded on invalid template {t:?}"); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } } env::set_current_dir(old_cwd).unwrap(); @@ -935,6 +940,11 @@ fn test_readv() { /// Test that vectored reads without any buffers return zero. fn test_readv_empty_bufs() { + if cfg!(all(not(miri), target_vendor = "apple")) { + // native macOS returns an error here :shrug: + return; + } + let path = utils::prepare_with_content("pass-libc-readv-empty-bufs.txt", &[1u8, 2, 3]); let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; @@ -1063,6 +1073,11 @@ fn test_writev() { /// Test that vectored writes without any buffers return zero. fn test_writev_empty_bufs() { + if cfg!(all(not(miri), target_vendor = "apple")) { + // native macOS returns an error here :shrug: + return; + } + let path = utils::prepare_with_content("pass-libc-writev-empty-bufs.txt", &[1u8, 2, 3]); let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) }; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs index 30ba4414be0c8..bb34c7de73a7f 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No libc socket on Windows //@compile-flags: -Zmiri-disable-isolation +//@run-native #[path = "../../utils/libc.rs"] mod libc_utils; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index 80b92e69707f9..9ed0b9c735979 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -1,5 +1,6 @@ //@only-target: linux android illumos //@compile-flags: -Zmiri-disable-isolation +//@run-native #![feature(io_error_inprogress)] diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index 15c3c3416e19f..de718feb47990 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -3,6 +3,7 @@ //@revisions: windows_host unix_host //@[unix_host] ignore-host: windows //@[windows_host] only-host: windows +//@run-native #![feature(io_error_inprogress)] diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index b1d0a90105ccc..edc357653d466 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No libc socket on Windows //@compile-flags: -Zmiri-disable-isolation +//@run-native #[path = "../../utils/libc.rs"] mod libc_utils; @@ -176,8 +177,8 @@ fn test_set_nosigpipe_invalid_len() { let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; // Value should be of type `libc::c_int` which has size 4 bytes. - // By providing a u64 of size 8 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u64).unwrap_err(); + // By providing a u16 of size 2 bytes we trigger an invalid length error. + let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u16).unwrap_err(); assert_eq!(err.kind(), ErrorKind::InvalidInput); // Check that it is the right kind of `InvalidInput`. assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); diff --git a/src/tools/miri/tests/pass/both_borrows/c_variadics.rs b/src/tools/miri/tests/pass/both_borrows/c_variadics.rs new file mode 100644 index 0000000000000..c3ec5de350bad --- /dev/null +++ b/src/tools/miri/tests/pass/both_borrows/c_variadics.rs @@ -0,0 +1,21 @@ +//@revisions: stack tree tree_implicit_writes +//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes +//@[tree]compile-flags: -Zmiri-tree-borrows +#![feature(c_variadic)] + +fn main() { + unsafe extern "C" fn write_with_first_arg( + ptr_to_val: *mut i32, + _hidden_mut_ref_to_val: ... + ) { + // Retagging needs to be disabled for arguments + // within the VaList. Otherwise, this write access + // will be undefined behavior. + unsafe { *ptr_to_val = 32; } + } + + let mut val: i32 = 0; + unsafe { + write_with_first_arg(&raw mut val, &val); + } +} diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index f7298a9a2f235..4f940cb0b92a2 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -1,4 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation +//@run-native #![feature(io_error_more)] #![feature(io_error_uncategorized)] @@ -95,6 +96,11 @@ fn test_file() { } fn test_file_partial_reads_writes() { + if !cfg!(miri) { + // This test is not expected to work natively. + return; + } + let path1 = utils::prepare_with_content("miri_test_fs_file1.txt", b"abcdefg"); let path2 = utils::prepare_with_content("miri_test_fs_file2.txt", b"abcdefg"); diff --git a/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs b/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs index cc96653991234..0e208b42215c0 100644 --- a/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs +++ b/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs @@ -1,9 +1,20 @@ // We're testing loongarch64-specific intrinsics //@only-target: loongarch64 -#![feature(stdarch_loongarch)] +#![feature(abi_unadjusted, link_llvm_intrinsics, stdarch_loongarch)] use std::arch::loongarch64::*; +unsafe extern "unadjusted" { + #[link_name = "llvm.loongarch.crc.w.b.w"] + fn _crc_w_b_w(a: i32, b: i32) -> i32; + #[link_name = "llvm.loongarch.crc.w.h.w"] + fn _crc_w_h_w(a: i32, b: i32) -> i32; + #[link_name = "llvm.loongarch.crcc.w.b.w"] + fn _crcc_w_b_w(a: i32, b: i32) -> i32; + #[link_name = "llvm.loongarch.crcc.w.h.w"] + fn _crcc_w_h_w(a: i32, b: i32) -> i32; +} + fn main() { test_crc_ieee(); test_crc_castagnoli(); @@ -12,13 +23,19 @@ fn main() { fn test_crc_ieee() { // crc.w.b.w: 8-bit input assert_eq!(crc_w_b_w(0x01, 0x00000000), 0x77073096); + assert_eq!(unsafe { _crc_w_b_w(0x1_01, 0x00000000) }, 0x77073096); // higher bits in the first argument are ignored assert_eq!(crc_w_b_w(0x61, 0xffffffff_u32 as i32), 0x174841bc); + assert_eq!(unsafe { _crc_w_b_w(0x2_61, 0xffffffff_u32 as i32) }, 0x174841bc); assert_eq!(crc_w_b_w(0x2a, 0x2aa1e72b), 0x772d9171); + assert_eq!(unsafe { _crc_w_b_w(0x3_2a, 0x2aa1e72b) }, 0x772d9171); // crc.w.h.w: 16-bit input assert_eq!(crc_w_h_w(0x0001, 0x00000000), 0x191b3141); + assert_eq!(unsafe { _crc_w_h_w(0x1_0001, 0x00000000) }, 0x191b3141); assert_eq!(crc_w_h_w(0x1234, 0xffffffff_u32 as i32), 0xf6b56fbf_u32 as i32); + assert_eq!(unsafe { _crc_w_h_w(0x2_1234, 0xffffffff_u32 as i32) }, 0xf6b56fbf_u32 as i32); assert_eq!(crc_w_h_w(0x022b, 0x8ecec3b5_u32 as i32), 0x03a1db7c); + assert_eq!(unsafe { _crc_w_h_w(0x3_022b, 0x8ecec3b5_u32 as i32) }, 0x03a1db7c); // crc.w.w.w: 32-bit input assert_eq!(crc_w_w_w(0x00000001, 0x00000000), 0xb8bc6765_u32 as i32); @@ -34,13 +51,19 @@ fn test_crc_ieee() { fn test_crc_castagnoli() { // crcc.w.b.w: 8-bit input assert_eq!(crcc_w_b_w(0x01, 0x00000000), 0xf26b8303_u32 as i32); + assert_eq!(unsafe { _crcc_w_b_w(0x1_01, 0x00000000) }, 0xf26b8303_u32 as i32); assert_eq!(crcc_w_b_w(0x61, 0xffffffff_u32 as i32), 0x3e2fbccf); + assert_eq!(unsafe { _crcc_w_b_w(0x2_61, 0xffffffff_u32 as i32) }, 0x3e2fbccf); assert_eq!(crcc_w_b_w(0x2a, 0x2aa1e72b), 0xf24122e4_u32 as i32); + assert_eq!(unsafe { _crcc_w_b_w(0x3_2a, 0x2aa1e72b) }, 0xf24122e4_u32 as i32); // crcc.w.h.w: 16-bit input assert_eq!(crcc_w_h_w(0x0001, 0x00000000), 0x13a29877); + assert_eq!(unsafe { _crcc_w_h_w(0x1_0001, 0x00000000) }, 0x13a29877); assert_eq!(crcc_w_h_w(0x1234, 0xffffffff_u32 as i32), 0xf13f4cea_u32 as i32); + assert_eq!(unsafe { _crcc_w_h_w(0x2_1234, 0xffffffff_u32 as i32) }, 0xf13f4cea_u32 as i32); assert_eq!(crcc_w_h_w(0x022b, 0x8ecec3b5_u32 as i32), 0x013bb2fb); + assert_eq!(unsafe { _crcc_w_h_w(0x3_022b, 0x8ecec3b5_u32 as i32) }, 0x013bb2fb); // crcc.w.w.w: 32-bit input assert_eq!(crcc_w_w_w(0x00000001, 0x00000000), 0xdd45aab8_u32 as i32); diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 785be17e2c0dc..e61e63b3c6183 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No socket support on Windows //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::io::{ErrorKind, Read, Write}; use std::net::{Shutdown, TcpListener, TcpStream}; @@ -184,7 +185,7 @@ fn test_sockopt_read_timeout() { // By default, reads on blocking sockets should block indefinitely. assert_eq!(stream.read_timeout().unwrap(), None); - let short_read_timeout = Some(Duration::from_millis(10)); + let short_read_timeout = Some(Duration::from_millis(40)); stream.set_read_timeout(short_read_timeout).unwrap(); assert_eq!(stream.read_timeout().unwrap(), short_read_timeout); @@ -207,7 +208,7 @@ fn test_sockopt_write_timeout() { // By default, writes on blocking sockets should block indefinitely. assert_eq!(stream.write_timeout().unwrap(), None); - let short_write_timeout = Some(Duration::from_millis(10)); + let short_write_timeout = Some(Duration::from_millis(40)); stream.set_write_timeout(short_write_timeout).unwrap(); assert_eq!(stream.write_timeout().unwrap(), short_write_timeout); diff --git a/src/tools/miri/tests/pass/too-big-type-behind-raw-ptr.rs b/src/tools/miri/tests/pass/too-big-type-behind-raw-ptr.rs new file mode 100644 index 0000000000000..b808ed8b979b3 --- /dev/null +++ b/src/tools/miri/tests/pass/too-big-type-behind-raw-ptr.rs @@ -0,0 +1,10 @@ +//! Regression test for : +//! the type behind a raw pointer should never have its layout computed. + +//@compile-flags: -Zmiri-permissive-provenance + +const PTR_BITS_MINUS_1: usize = std::mem::size_of::<*const ()>() * 8 - 1; + +fn main() { + std::hint::black_box(0 as *const [u64; 1 << PTR_BITS_MINUS_1]); +} diff --git a/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs b/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs new file mode 100644 index 0000000000000..e2785183ff599 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs @@ -0,0 +1,28 @@ +// This test reproduces the pattern used by `BorrowedCursor::as_mut`, which appears in `Socket::recv_with_flags` and `std::fs::read`. +// Many crates depend on similar patterns. Before https://github.com/rust-lang/rust/pull/157202 this failed under Tree +// Borrows with Implicit Writes. With the attribute `#[rustc_no_writable]` added to +// `slice::get_unchecked_mut`, both this test and the affected crates work. +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes + +struct BorrowedBuf<'a> { + buf: &'a mut [u8], +} + +impl<'a> BorrowedBuf<'a> { + fn capacity(&self) -> usize { + self.buf.len() + } + + unsafe fn as_mut(&mut self) -> &mut [u8] { + unsafe { self.buf.get_unchecked_mut(..) } + } +} + +fn main() { + let mut arr = [0u8; 4]; + let mut buf = BorrowedBuf { buf: &mut arr }; + + let ptr = unsafe { buf.as_mut() }.as_mut_ptr(); + let _ = buf.capacity(); + unsafe { ptr.write(42); } +} diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 1449013e0478e..633452f6052d7 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -1,13 +1,15 @@ -use std::env; +#![allow(clippy::let_and_return)] use std::num::NonZero; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::OnceLock; +use std::{env, fmt}; use colored::*; use regex::bytes::Regex; use ui_test::build_manager::BuildManager; use ui_test::color_eyre::eyre::{Context, Result}; +use ui_test::custom_flags::Flag; use ui_test::custom_flags::edition::Edition; use ui_test::dependencies::DependencyBuilder; use ui_test::per_test_config::TestConfig; @@ -17,14 +19,37 @@ use ui_test::{CommandBuilder, Config, Match, ignore_output_conflict}; #[derive(Copy, Clone, Debug)] enum Mode { - Pass, + Pass { + native: bool, + }, /// Requires annotations Fail, /// Not used for tests, but for `miri run --dep` - RunDep, + RunDep { + native: bool, + }, + /// Test must panic. Panic, } +impl Mode { + fn native(self) -> bool { + matches!(self, Mode::Pass { native: true } | Mode::RunDep { native: true }) + } +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Mode::Pass { native: false } => write!(f, "pass"), + Mode::Pass { native: true } => write!(f, "pass-native"), + Mode::Fail => write!(f, "fail"), + Mode::Panic => write!(f, "panic"), + Mode::RunDep { .. } => unreachable!(), + } + } +} + fn miri_path() -> PathBuf { env!("CARGO_BIN_EXE_miri").into() } @@ -79,7 +104,7 @@ struct WithDependencies { bless: bool, } -/// Does *not* set any args or env vars, since it is shared between the test runner and +/// Does *not* set args or (most) env vars, since it is shared between the test runner and /// run_dep_mode. fn miri_config( target: &str, @@ -90,6 +115,15 @@ fn miri_config( // Miri is rustc-like, so we create a default builder for rustc and modify it let mut program = CommandBuilder::rustc(); program.program = miri_path(); + if mode.native() { + // This means we build the program instead of running Miri. + program.envs.push(("MIRI_BE_RUSTC".into(), Some("host".into()))); + // Use the right linker, if necessary. We use the `CC_*` variable as that is set by CI and + // unlike `CARGO_TARGET_*_LINKER` it does not require upper-casing the target. + if let Ok(linker) = env::var(format!("CC_{target}")) { + program.args.push(format!("-Clinker={linker}").into()); + } + } let mut config = Config { target: Some(target.to_owned()), @@ -103,10 +137,17 @@ fn miri_config( ..Config::rustc(path) }; + // Register custom comments. + config.custom_comments.insert("run-native", |parser, _args, span| { + // Just remember that this is present. + parser.set_custom_once("run-native", (), span); + }); + + // Adjust comment defaults. config.comment_defaults.base().exit_status = match mode { - Mode::Pass => Some(0), + Mode::Pass { .. } => Some(0), Mode::Fail => Some(1), - Mode::RunDep => None, + Mode::RunDep { .. } => None, Mode::Panic => Some(101), } .map(Spanned::dummy) @@ -123,9 +164,12 @@ fn miri_config( // keep in sync with `./miri run` config.comment_defaults.base().add_custom("edition", Edition("2021".into())); + // Building dependencies is also a "comment default". if let Some(WithDependencies { bless }) = with_dependencies { - config.comment_defaults.base().set_custom( - "dependencies", + let crate_manifest_path = Path::new("tests/deps").join("Cargo.toml"); + let dep_builder = if mode.native() { + DependencyBuilder { crate_manifest_path, ..Default::default() } + } else { DependencyBuilder { program: CommandBuilder { // Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary. @@ -147,12 +191,65 @@ fn miri_config( ], ..CommandBuilder::cargo() }, - crate_manifest_path: Path::new("tests/deps").join("Cargo.toml"), + crate_manifest_path, build_std: None, bless_lockfile: bless, - }, - ); + } + }; + config.comment_defaults.base().set_custom("dependencies", dep_builder); + } + + // We only want this for actual test runs, not native run-dep mode. + if matches!(mode, Mode::Pass { native: true }) { + // Overwrite "compile-flags" so that it does nothing. + // FIXME: make it just skip `-Zmiri` flags. + config.custom_comments.insert("compile-flags", |_parser, _args, _span| {}); + + // Add a default comment that interprets our custom `run-native` comment. + #[derive(Debug)] + struct NativeRunner; + config.comment_defaults.base().set_custom("native-runner", NativeRunner); + + impl Flag for NativeRunner { + fn clone_inner(&self) -> Box { + Box::new(NativeRunner) + } + fn must_be_unique(&self) -> bool { + true + } + + fn test_condition( + &self, + _config: &Config, + comments: &ui_test::Comments, + revision: &str, + ) -> bool { + let should_run = comments + .for_revision(revision) + .any(|r| r.custom.iter().any(|(k, _v)| *k == "run-native")); + // We return `true` when the test should be ignored. + let ignore = !should_run; + ignore + } + + fn post_test_action( + &self, + config: &TestConfig, + output: &std::process::Output, + build_manager: &BuildManager, + ) -> Result<(), ui_test::Errored> { + // Delegate to the native run support. + use ui_test::custom_flags::run::Run; + Run::post_test_action( + &Run { exit_code: 0, output_conflict_handling: None }, + config, + output, + build_manager, + ) + } + } } + config } @@ -177,6 +274,9 @@ fn run_tests( assert!(!args.bless, "cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"); config.output_conflict_handling = ignore_output_conflict; } + if mode.native() { + config.output_conflict_handling = ignore_output_conflict; + } // Add a test env var to do environment communication tests. config.program.envs.push(("MIRI_ENV_VAR_TEST".into(), Some("0".into()))); @@ -185,23 +285,26 @@ fn run_tests( // If a test ICEs, we want to see a backtrace. config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into()))); - // Add some flags we always want. - config.program.args.push( - format!( - "--sysroot={}", - env::var("MIRI_SYSROOT").expect("MIRI_SYSROOT must be set to run the ui test suite") - ) - .into(), - ); + // Add rustc/Miri flags. config.program.args.push("-Dwarnings".into()); config.program.args.push("-Dunused".into()); config.program.args.push("-Ainternal_features".into()); - if let Ok(extra_flags) = env::var("MIRIFLAGS") { - for flag in extra_flags.split_whitespace() { - config.program.args.push(flag.into()); + config.program.args.push("-Zui-testing".into()); + if !mode.native() { + config.program.args.push( + format!( + "--sysroot={}", + env::var("MIRI_SYSROOT") + .expect("MIRI_SYSROOT must be set to run the ui test suite") + ) + .into(), + ); + if let Ok(extra_flags) = env::var("MIRIFLAGS") { + for flag in extra_flags.split_whitespace() { + config.program.args.push(flag.into()); + } } } - config.program.args.push("-Zui-testing".into()); // If we're testing the native-lib functionality, then build the shared object file for testing // external C function calls and push the relevant compiler flag. @@ -288,8 +391,8 @@ regexes! { } enum Dependencies { - WithDependencies, - WithoutDependencies, + WithDeps, + WithoutDeps, } use Dependencies::*; @@ -301,12 +404,12 @@ fn ui( with_dependencies: Dependencies, tmpdir: &Path, ) -> Result<()> { - let msg = format!("## Running ui tests in {path} for {target}"); + let msg = format!("## Running {mode} ui tests in {path} for {target}"); println!("{}", msg.green().bold()); let with_dependencies = match with_dependencies { - WithDependencies => true, - WithoutDependencies => false, + WithDeps => true, + WithoutDeps => false, }; run_tests(mode, path, target, with_dependencies, tmpdir) .with_context(|| format!("ui tests in {path} for {target} failed")) @@ -331,17 +434,27 @@ fn main() -> Result<()> { // Check whether this is a `./miri run` invocation if let Ok(mode) = env::var("MIRI_RUN_MODE") { - return run_mode(target, mode); + return run_with_deps(target, mode); } - ui(Mode::Pass, "tests/pass", &target, WithoutDependencies, tmpdir.path())?; - ui(Mode::Pass, "tests/pass-dep", &target, WithDependencies, tmpdir.path())?; - ui(Mode::Panic, "tests/panic", &target, WithDependencies, tmpdir.path())?; - ui(Mode::Fail, "tests/fail", &target, WithoutDependencies, tmpdir.path())?; - ui(Mode::Fail, "tests/fail-dep", &target, WithDependencies, tmpdir.path())?; + ui(Mode::Pass { native: false }, "tests/pass", &target, WithoutDeps, tmpdir.path())?; + ui(Mode::Pass { native: false }, "tests/pass-dep", &target, WithDeps, tmpdir.path())?; + if target == host { + ui(Mode::Pass { native: true }, "tests/pass", &target, WithoutDeps, tmpdir.path())?; + ui(Mode::Pass { native: true }, "tests/pass-dep", &target, WithDeps, tmpdir.path())?; + } + ui(Mode::Panic, "tests/panic", &target, WithDeps, tmpdir.path())?; + ui(Mode::Fail, "tests/fail", &target, WithoutDeps, tmpdir.path())?; + ui(Mode::Fail, "tests/fail-dep", &target, WithDeps, tmpdir.path())?; if cfg!(all(unix, feature = "native-lib")) && target == host { - ui(Mode::Pass, "tests/native-lib/pass", &target, WithoutDependencies, tmpdir.path())?; - ui(Mode::Fail, "tests/native-lib/fail", &target, WithoutDependencies, tmpdir.path())?; + ui( + Mode::Pass { native: false }, + "tests/native-lib/pass", + &target, + WithoutDeps, + tmpdir.path(), + )?; + ui(Mode::Fail, "tests/native-lib/fail", &target, WithoutDeps, tmpdir.path())?; } // We only enable GenMC tests when the `genmc` feature is enabled, but also only on platforms we support: @@ -354,30 +467,19 @@ fn main() -> Result<()> { target_endian = "little" )) && host == target { - ui(Mode::Pass, "tests/genmc/pass", &target, WithDependencies, tmpdir.path())?; - ui(Mode::Fail, "tests/genmc/fail", &target, WithDependencies, tmpdir.path())?; + ui(Mode::Pass { native: false }, "tests/genmc/pass", &target, WithDeps, tmpdir.path())?; + ui(Mode::Fail, "tests/genmc/fail", &target, WithDeps, tmpdir.path())?; } Ok(()) } -fn run_mode(target: String, mode: String) -> Result<()> { +fn run_with_deps(target: String, mode: String) -> Result<()> { let native = mode == "native"; let mut config = - miri_config(&target, "", Mode::RunDep, Some(WithDependencies { bless: false })); + miri_config(&target, "", Mode::RunDep { native }, Some(WithDependencies { bless: false })); config.comment_defaults.base().custom.remove("edition"); // `./miri` adds an `--edition` in `args`, so don't set it twice - if native { - // Patch things up so that we actually compile the program. - config.program.envs.push(("MIRI_BE_RUSTC".into(), Some("host".into()))); - config.comment_defaults.base().set_custom( - "dependencies", - DependencyBuilder { - crate_manifest_path: Path::new("tests/deps").join("Cargo.toml"), - ..Default::default() - }, - ); - } config.fill_host_and_target()?; // Reset `args` (otherwise we'll get JSON output). config.program.args = vec![]; @@ -385,7 +487,8 @@ fn run_mode(target: String, mode: String) -> Result<()> { // Compute the actual Miri invocation command. let test_config = TestConfig::one_off_runner(config.clone(), PathBuf::new()); let mut cmd = test_config.config.program.build(&test_config.config.out_dir); - // For some reason we need to set the target ourselves. + // We are not using `test_config.build_command` (as that would require us to know the filename + // we are invoking), so we need to set the target ourselves. cmd.arg("--target").arg(&target); // Also forward arguments to the program (skipping the binary name). // We don't put this in the `config` since we don't want it to affect the dependency build. @@ -407,8 +510,8 @@ fn run_mode(target: String, mode: String) -> Result<()> { if native { // We just built the program, we still have to run it. We can't use the ui_test `Run` flag - // as that needs an actual BuildManager, not just the one-off stub we have here. So we - // implement the core logic ourselves. + // as (a) that always captures the output, and (b) that needs an actual BuildManager, not + // just the one-off stub we have here. So we implement the core logic ourselves. // First, figure out the output binary by re-running the compiler with `--print`. cmd.arg("--print").arg("file-names"); diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml index 57c8f5782e0a5..727d3cc868742 100644 --- a/src/tools/miri/triagebot.toml +++ b/src/tools/miri/triagebot.toml @@ -17,12 +17,6 @@ allow-unauthenticated = [ [assign] warn_non_default_branch = true contributing_url = "https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#pr-review-process" -[assign.custom_welcome_messages] -welcome-message = "(unused)" -welcome-message-no-reviewer = """ -Thank you for contributing to Miri! A reviewer will take a look at your PR, typically within a week or two. -Please remember to not force-push to the PR branch except when you need to rebase due to a conflict or when the reviewer asks you for it. -""" [no-merges] exclude_titles = ["Rustup"] diff --git a/tests/assembly-llvm/targets/targets-elf.rs b/tests/assembly-llvm/targets/targets-elf.rs index ce6629b4f27ce..7cc7cb037d4ce 100644 --- a/tests/assembly-llvm/targets/targets-elf.rs +++ b/tests/assembly-llvm/targets/targets-elf.rs @@ -394,6 +394,9 @@ //@ revisions: powerpc64_unknown_linux_gnu //@ [powerpc64_unknown_linux_gnu] compile-flags: --target powerpc64-unknown-linux-gnu //@ [powerpc64_unknown_linux_gnu] needs-llvm-components: powerpc +//@ revisions: powerpc64_unknown_linux_gnuelfv2 +//@ [powerpc64_unknown_linux_gnuelfv2] compile-flags: --target powerpc64-unknown-linux-gnuelfv2 +//@ [powerpc64_unknown_linux_gnuelfv2] needs-llvm-components: powerpc //@ revisions: powerpc64_unknown_linux_musl //@ [powerpc64_unknown_linux_musl] compile-flags: --target powerpc64-unknown-linux-musl //@ [powerpc64_unknown_linux_musl] needs-llvm-components: powerpc diff --git a/tests/codegen-llvm/debuginfo-unsize-field.rs b/tests/codegen-llvm/debuginfo-unsize-field.rs new file mode 100644 index 0000000000000..5d133705aa557 --- /dev/null +++ b/tests/codegen-llvm/debuginfo-unsize-field.rs @@ -0,0 +1,66 @@ +//@ compile-flags:-g -Copt-level=0 -C panic=abort + +// Check that debug information for structs with embedded str and [u8] slices is distinct from +// structs with embedded u8 + +#![crate_type = "lib"] + +// NOTE: regex for the CHECK directives, +// depending on the target, u8/usize are basic types or typedefs +// linux: !1 = !DIBasicType(name: "u8", +// win: !1 = !DIDerivedType(tag: .*, name: "u8", +// and references types are +// linux: name: "&debuginfo_unsize_field::Foo" +// win: name: "ref$" + +// CHECK: ![[U8:[0-9]+]] = !DI{{Basic|Derived}}Type({{.*}}name: "u8", + +pub struct Foo { + a: u32, + b: str, +} +// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "{{&|ref\$<}}{{[^"]+}}::Foo{{>?}}", {{.*}}elements: ![[FOO_REF_ELEMS:[0-9]+]] +// CHECK: ![[FOO_REF_ELEMS]] = !{![[FOO_REF_PTR:[0-9]+]], ![[FOO_REF_LEN:[0-9]+]]} +// CHECK: ![[FOO_REF_PTR]] = !DIDerivedType(tag: DW_TAG_member, name: "data_ptr", {{.*}}baseType: ![[FOO_PTR:[0-9]+]] +// CHECK: ![[FOO_PTR]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[FOO:[0-9]+]] +// CHECK: ![[FOO]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Foo", {{.*}}elements: ![[FOO_ELEMS:[0-9]+]] +// CHECK: ![[FOO_ELEMS]] = !{![[FOO_A:[0-9]+]], ![[FOO_B:[0-9]+]]} +// CHECK: ![[FOO_A]] = !DIDerivedType(tag: DW_TAG_member, name: "a" +// CHECK: ![[FOO_B]] = !DIDerivedType(tag: DW_TAG_member, name: "b", {{.*}}baseType: ![[U8_SLICE:[0-9]+]] +// +// CHECK: ![[U8_SLICE]] = !DICompositeType(tag: DW_TAG_array_type, baseType: ![[U8]], {{.*}}elements: ![[U8_SLICE_ELEMS:[0-9]+]] +// CHECK: ![[U8_SLICE_ELEMS]] = !{![[U8_SLICE_RANGE:[0-9]+]]} +// this is special to embedded slices, there is no upper bound on the number of elements, +// that info is stored in the length metadata for a reference to the parent struct +// CHECK: ![[U8_SLICE_RANGE]] = !DISubrange(count: -1, lowerBound: 0) +// +// CHECK: ![[FOO_REF_LEN]] = !DIDerivedType(tag: DW_TAG_member, name: "length", {{.*}}baseType: ![[USIZE:[0-9]+]] +// CHECK: ![[USIZE]] = !DI{{Basic|Derived}}Type({{.*}}name: "usize" +pub struct Bar { + a: u32, + b: [u8], +} +// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "{{&|ref\$<}}{{[^"]+}}::Bar{{>?}}", {{.*}}elements: ![[BAR_REF_ELEMS:[0-9]+]] +// CHECK: ![[BAR_REF_ELEMS]] = !{![[BAR_REF_PTR:[0-9]+]], ![[BAR_REF_LEN:[0-9]+]]} +// CHECK: ![[BAR_REF_PTR]] = !DIDerivedType(tag: DW_TAG_member, name: "data_ptr", {{.*}}baseType: ![[BAR_PTR:[0-9]+]] +// CHECK: ![[BAR_PTR]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[BAR:[0-9]+]] +// CHECK: ![[BAR]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Bar", {{.*}}elements: ![[BAR_ELEMS:[0-9]+]] +// CHECK: ![[BAR_ELEMS]] = !{![[BAR_A:[0-9]+]], ![[BAR_B:[0-9]+]]} +// CHECK: ![[BAR_A]] = !DIDerivedType(tag: DW_TAG_member, name: "a" +// CHECK: ![[BAR_B]] = !DIDerivedType(tag: DW_TAG_member, name: "b", {{.*}}baseType: ![[U8_SLICE]] +// CHECK: ![[BAR_REF_LEN]] = !DIDerivedType(tag: DW_TAG_member, name: "length", {{.*}}baseType: ![[USIZE:[0-9]+]] +pub struct Baz { + a: u32, + b: u8, +} +// CHECK: !DIDerivedType(tag: DW_TAG_pointer_type, name: "{{&|ref\$<}}{{[^"]+}}::Baz{{>?}}", {{.*}}baseType: ![[BAZ:[0-9]+]] +// CHECK: ![[BAZ]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Baz", {{.*}}elements: ![[BAZ_ELEMS:[0-9]+]] +// CHECK: ![[BAZ_ELEMS]] = !{![[BAZ_A:[0-9]+]], ![[BAZ_B:[0-9]+]]} +// CHECK: ![[BAZ_A]] = !DIDerivedType(tag: DW_TAG_member, name: "a" +// CHECK: ![[BAZ_B]] = !DIDerivedType(tag: DW_TAG_member, name: "b", {{.*}}baseType: ![[U8]] + +#[no_mangle] +pub fn test<'a>(a: &'a Foo, b: &'a Bar, c: &'a Baz) -> &'a u8 { + // just use this somehow so the debuginfo isn't removed + &a.b.as_bytes()[0] +} diff --git a/tests/crashes/reborrow/coerce-shared-alias-projection.rs b/tests/crashes/reborrow/coerce-shared-alias-projection.rs new file mode 100644 index 0000000000000..8497455de921a --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-alias-projection.rs @@ -0,0 +1,110 @@ +//@ known-bug: unknown +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +// This test combines alias/projection normalization with the leaf `&mut T` to `&T` +// shared reborrow path. + +struct InnerMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for InnerMut<'a, T> {} + +struct InnerRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for InnerRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for InnerRef<'a, T> {} + +impl<'a, T> CoerceShared> for InnerMut<'a, T> {} + +type DirectInnerRef<'a, T> = InnerRef<'a, T>; + +trait RefFamily<'a, T> { + type Ref; +} + +struct Projected; + +impl<'a, T: 'a> RefFamily<'a, T> for Projected { + type Ref = InnerRef<'a, T>; +} + +type ProjectedInnerRef<'a, T> = >::Ref; + +struct OuterMut<'a, T> { + inner: InnerMut<'a, T>, + tag: usize, +} + +impl<'a, T> Reborrow for OuterMut<'a, T> {} + +struct OuterAliasRef<'a, T> { + inner: DirectInnerRef<'a, T>, + tag: usize, +} + +impl<'a, T> Clone for OuterAliasRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OuterAliasRef<'a, T> {} + +impl<'a, T> CoerceShared> for OuterMut<'a, T> {} + +struct OuterProjectionRef<'a, T: 'a> { + inner: ProjectedInnerRef<'a, T>, + tag: usize, +} + +impl<'a, T: 'a> Clone for OuterProjectionRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: 'a> Copy for OuterProjectionRef<'a, T> {} + +impl<'a, T: 'a> CoerceShared> for OuterMut<'a, T> {} + +fn read_alias<'a>(outer: OuterAliasRef<'a, u32>) -> (&'a u32, usize) { + (outer.inner.value, outer.tag) +} + +fn read_projection<'a>(outer: OuterProjectionRef<'a, u32>) -> (&'a u32, usize) { + (outer.inner.value, outer.tag) +} + +const fn const_accept_projection(_outer: OuterProjectionRef<'_, u32>) {} + +const fn consteval_projection_reborrow() { + let mut value = 11; + const_accept_projection(OuterMut { + inner: InnerMut { value: &mut value }, + tag: 5, + }); +} + +fn main() { + const { consteval_projection_reborrow(); } + + let mut value = 22; + let outer = OuterMut { inner: InnerMut { value: &mut value }, tag: 7 }; + + let (alias_value, alias_tag) = read_alias(outer); + assert_eq!((*alias_value, alias_tag), (22, 7)); + + let (projection_value, projection_tag) = read_projection(outer); + assert_eq!((*projection_value, projection_tag), (22, 7)); +} diff --git a/tests/crashes/reborrow/coerce-shared-different-layout.rs b/tests/crashes/reborrow/coerce-shared-different-layout.rs new file mode 100644 index 0000000000000..ad285bf0130d1 --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-different-layout.rs @@ -0,0 +1,41 @@ +//@ known-bug: unknown +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; +use std::ptr::NonNull; + +struct ImbrisMut<'a, T> { + ptr: NonNull, + metadata: usize, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for ImbrisMut<'a, T> {} + +struct ImbrisRef<'a, T> { + ptr: NonNull, + marker: PhantomData<&'a T>, +} + +impl<'a, T> Clone for ImbrisRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for ImbrisRef<'a, T> {} + +impl<'a, T> CoerceShared> for ImbrisMut<'a, T> {} + +fn ptr(value: ImbrisRef<'_, i32>) -> NonNull { + value.ptr +} + +fn main() { + let mut value = 1; + let raw = NonNull::from(&mut value); + let wrapped = ImbrisMut { ptr: raw, metadata: 32, marker: PhantomData }; + + assert_eq!(ptr(wrapped), raw); +} diff --git a/tests/crashes/reborrow/coerce-shared-marker-no-target-lifetime.rs b/tests/crashes/reborrow/coerce-shared-marker-no-target-lifetime.rs new file mode 100644 index 0000000000000..3cdbf515371f2 --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-marker-no-target-lifetime.rs @@ -0,0 +1,21 @@ +//@ known-bug: unknown +//@ edition: 2024 + +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +struct CustomMarkerRef; + +impl<'a> Reborrow for CustomMarker<'a> {} +impl<'a> CoerceShared for CustomMarker<'a> {} +//~^ ERROR + +fn method(_a: CustomMarkerRef) {} + +fn main() { + let a = CustomMarker(PhantomData); + method(a); + //~^ ERROR +} diff --git a/tests/crashes/reborrow/coerce-shared-multi-field.rs b/tests/crashes/reborrow/coerce-shared-multi-field.rs new file mode 100644 index 0000000000000..3bf5b64064d4a --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-multi-field.rs @@ -0,0 +1,58 @@ +//@ known-bug: unknown +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; +use std::ptr::NonNull; + +struct MatMut<'a, T> { + ptr: NonNull, + rows: usize, + cols: usize, + row_stride: usize, + col_stride: usize, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for MatMut<'a, T> {} + +struct MatRef<'a, T> { + ptr: NonNull, + rows: usize, + cols: usize, + row_stride: usize, + col_stride: usize, + marker: PhantomData<&'a T>, +} + +impl<'a, T> Clone for MatRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for MatRef<'a, T> {} + +impl<'a, T> CoerceShared> for MatMut<'a, T> {} + +fn dims(mat: MatRef<'_, T>) -> (usize, usize, usize, usize) { + let _ = mat.ptr; + (mat.rows, mat.cols, mat.row_stride, mat.col_stride) +} + +fn main() { + let mut value = 0; + let mat = MatMut { + ptr: NonNull::from(&mut value), + rows: 2, + cols: 3, + row_stride: 4, + col_stride: 5, + marker: PhantomData, + }; + + assert_eq!(dims(mat), (2, 3, 4, 5)); + // Reusing the same source proves repeated shared reborrows keep source-only data protected + // without consuming the reborrowable value. + assert_eq!(dims(mat), (2, 3, 4, 5)); +} diff --git a/tests/crashes/reborrow/coerce-shared-nested.rs b/tests/crashes/reborrow/coerce-shared-nested.rs new file mode 100644 index 0000000000000..a916dde881ec5 --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-nested.rs @@ -0,0 +1,62 @@ +//@ known-bug: unknown +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct InnerMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for InnerMut<'a, T> {} + +struct InnerRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for InnerRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for InnerRef<'a, T> {} + +impl<'a, T> CoerceShared> for InnerMut<'a, T> {} + +struct OuterMut<'a, T> { + inner: InnerMut<'a, T>, + tag: usize, +} + +impl<'a, T> Reborrow for OuterMut<'a, T> {} + +struct OuterRef<'a, T> { + inner: InnerRef<'a, T>, + tag: usize, +} + +impl<'a, T> Clone for OuterRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OuterRef<'a, T> {} + +impl<'a, T> CoerceShared> for OuterMut<'a, T> {} + +fn get<'a>(outer: OuterRef<'a, i32>) -> (&'a i32, usize) { + (outer.inner.value, outer.tag) +} + +fn main() { + let mut value = 22; + let outer = OuterMut { inner: InnerMut { value: &mut value }, tag: 7 }; + + let (first, tag) = get(outer); + assert_eq!((*first, tag), (22, 7)); + + let (second, tag) = get(outer); + assert_eq!((*second, tag), (22, 7)); +} diff --git a/tests/crashes/reborrow/coerce-shared-tuple-phantom-position.rs b/tests/crashes/reborrow/coerce-shared-tuple-phantom-position.rs new file mode 100644 index 0000000000000..0ada68c9c20f7 --- /dev/null +++ b/tests/crashes/reborrow/coerce-shared-tuple-phantom-position.rs @@ -0,0 +1,86 @@ +//@ known-bug: unknown +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct SourceLeadingMut<'a, T>(PhantomData<&'a mut T>, &'a mut T); + +impl<'a, T> Reborrow for SourceLeadingMut<'a, T> {} + +struct SourceLeadingRef<'a, T>(&'a T); + +impl<'a, T> Clone for SourceLeadingRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for SourceLeadingRef<'a, T> {} + +impl<'a, T> CoerceShared> for SourceLeadingMut<'a, T> {} + +struct TargetLeadingMut<'a, T>(&'a mut T); + +impl<'a, T> Reborrow for TargetLeadingMut<'a, T> {} + +struct TargetLeadingRef<'a, T>(PhantomData<&'a T>, &'a T); + +impl<'a, T> Clone for TargetLeadingRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for TargetLeadingRef<'a, T> {} + +impl<'a, T> CoerceShared> for TargetLeadingMut<'a, T> {} + +struct InterleavedMut<'a, T, U>( + PhantomData<&'a mut T>, + &'a mut T, + PhantomData<&'a mut U>, + &'a mut U, +); + +impl<'a, T, U> Reborrow for InterleavedMut<'a, T, U> {} + +struct InterleavedRef<'a, T, U>(&'a T, PhantomData<&'a T>, &'a U, PhantomData<&'a U>); + +impl<'a, T, U> Clone for InterleavedRef<'a, T, U> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T, U> Copy for InterleavedRef<'a, T, U> {} + +impl<'a, T, U> CoerceShared> for InterleavedMut<'a, T, U> {} + +fn read_source_leading<'a>(value: SourceLeadingRef<'a, i32>) -> &'a i32 { + value.0 +} + +fn read_target_leading<'a>(value: TargetLeadingRef<'a, i32>) -> &'a i32 { + value.1 +} + +fn read_interleaved<'a>(value: InterleavedRef<'a, i32, i64>) -> (&'a i32, &'a i64) { + (value.0, value.2) +} + +fn main() { + let mut source_leading = 10; + let wrapped = SourceLeadingMut(PhantomData, &mut source_leading); + assert_eq!(*read_source_leading(wrapped), 10); + + let mut target_leading = 20; + let wrapped = TargetLeadingMut(&mut target_leading); + assert_eq!(*read_target_leading(wrapped), 20); + + let mut first = 30; + let mut second = 40_i64; + let wrapped = InterleavedMut(PhantomData, &mut first, PhantomData, &mut second); + let (first, second) = read_interleaved(wrapped); + assert_eq!((*first, *second), (30, 40)); +} diff --git a/tests/crashes/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs b/tests/crashes/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs new file mode 100644 index 0000000000000..59e76ec36ab90 --- /dev/null +++ b/tests/crashes/reborrow/corrected-field-mismatch-coerce-shared-issue-156315.rs @@ -0,0 +1,21 @@ +//@ known-bug: unknown + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T>(&'a mut T); + +impl<'a, T> Reborrow for CustomMut<'a, T> {} + +struct CustomRef<'a, T>(&'a CustomMut<'a, T>); +//~^ ERROR + +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +fn method(_a: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + method(a); +} diff --git a/tests/crashes/reborrow/malformed-marker-coerce-shared-issue-156309.rs b/tests/crashes/reborrow/malformed-marker-coerce-shared-issue-156309.rs new file mode 100644 index 0000000000000..14b7ec820a180 --- /dev/null +++ b/tests/crashes/reborrow/malformed-marker-coerce-shared-issue-156309.rs @@ -0,0 +1,30 @@ +//@ known-bug: unknown +//@ edition: 2024 + +#![feature(reborrow)] + +// Malformed no-ICE regression: the unrelated name and signature errors are intentional because the +// original issue combined them with CoerceShared validation. + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); + +struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); +//~^ ERROR +//~| ERROR +//~| ERROR + +impl<'a> Reborrow for CustomMarker<'a> {} +impl<'a> CoerceShared> for CustomMarker<'a> {} +//~^ ERROR + +fn method<'a>(_a: CustomMarkerRef<'a>) -> 'a () { + //~^ ERROR + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); +} diff --git a/tests/crashes/reborrow/missing-generic-args-coerce-shared-issue-156315.rs b/tests/crashes/reborrow/missing-generic-args-coerce-shared-issue-156315.rs new file mode 100644 index 0000000000000..d365986901fe3 --- /dev/null +++ b/tests/crashes/reborrow/missing-generic-args-coerce-shared-issue-156315.rs @@ -0,0 +1,24 @@ +//@ known-bug: unknown +#![feature(reborrow)] + +// Malformed no-ICE regression: this intentionally keeps the missing generic arguments from the +// original reproducer while the corrected test isolates the CoerceShared field error. + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T>(&'a mut T); + +impl<'a, T> Reborrow for CustomMut<'a, T> {} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +struct CustomRef<'a, T>(&'a CustomMut); +//~^ ERROR +//~| ERROR +//~| ERROR + +fn method(_a: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + method(a); +} diff --git a/tests/debuginfo/strings-and-strs.rs b/tests/debuginfo/strings-and-strs.rs index 165cfcd968a67..ee2702d1bcf74 100644 --- a/tests/debuginfo/strings-and-strs.rs +++ b/tests/debuginfo/strings-and-strs.rs @@ -1,4 +1,4 @@ -//@ min-gdb-version: 14.0 +//@ min-gdb-version: 15.1 // LLDB 1800+ tests were not tested in CI, broke, and now are disabled //@ ignore-lldb @@ -23,6 +23,12 @@ //@ gdb-command:print str_in_rc //@ gdb-check:$5 = alloc::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +//@ gdb-command:print box_str +//@ gdb-check:$6 = alloc::boxed::Box [87, 111, 114, 108, 100] + +//@ gdb-command:print rc_str +//@ gdb-check:$7 = alloc::rc::Rc {ptr: core::ptr::non_null::NonNull> {pointer: alloc::rc::RcInner {strong: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, weak: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, value: 0x[...]}}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} + // === LLDB TESTS ================================================================================== //@ lldb-command:run //@ lldb-command:v plain_string @@ -40,6 +46,12 @@ //@ lldb-command:v str_in_rc //@ lldb-check:(alloc::rc::Rc<&str, alloc::alloc::Global>) str_in_rc = strong=1, weak=0 { value = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } } +//@ lldb-command:v box_str +//@ lldb-check:(alloc::boxed::Box) box_str = { __0 = { pointer = { pointer = { data_ptr = 0x[...] "World" length = 5 } } _marker = } __1 = } + +//@ lldb-command:v rc_str +//@ lldb-check:(alloc::rc::Rc) rc_str = strong=1, weak=0 { value = "World" } + #![allow(unused_variables)] pub struct Foo<'a> { @@ -53,6 +65,8 @@ fn main() { let str_in_tuple = ("Hello", "World"); let str_in_rc = std::rc::Rc::new("Hello"); + let box_str: Box = "World".into(); + let rc_str: std::rc::Rc = "World".into(); zzz(); // #break } diff --git a/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-1.rs b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-1.rs new file mode 100644 index 0000000000000..4d7427ab4436e --- /dev/null +++ b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-1.rs @@ -0,0 +1,23 @@ +//@ check-pass +//@ compile-flags: -Znext-solver -Zassumptions-on-binders + +#![crate_type = "lib"] + +// When entering binders if we don't insert assumptions corresponding to the +// binder then we'll ICE when later trying to eagerly handle placeholders. This +// test checks that type relations involving higher ranked types insert assumptions +// for the binders of the higher ranked types. +// +// This is specifically checking that type relations used from *inside* the trait +// solver do this. In this case the type relation occurs as part of an `AliasRelate` +// involving the rigid alias: `::Assoc fn('b ()) -> &'?0 ()>`. + +trait Trait { type Assoc; } + +fn foo(_: U) -> ::Assoc:: { loop {} } + +fn mk<'a>() -> for<'b> fn(&'b ()) -> &'a () { loop {} } + +fn bar() { + foo::(mk()); +} diff --git a/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-2.rs b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-2.rs new file mode 100644 index 0000000000000..e27339f233015 --- /dev/null +++ b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-2.rs @@ -0,0 +1,15 @@ +//@ check-pass +//@ compile-flags: -Znext-solver -Zassumptions-on-binders + +#![crate_type = "lib"] + +// Slight derivative of `type_relation_binders_inside_solver-1.rs` except this time the +// rigid alias is the opaque type `(impl Sized) fn(&'b ()) -> &'?0 ()>` instead. + +fn foo(_: T) -> impl Sized + use {} + +fn mk<'a>() -> for<'b> fn(&'b ()) -> &'a () { loop {} } + +fn bar() { + foo(mk()); +} diff --git a/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-3.rs b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-3.rs new file mode 100644 index 0000000000000..4d223152866d3 --- /dev/null +++ b/tests/ui/assumptions_on_binders/type_relation_binders_inside_solver-3.rs @@ -0,0 +1,21 @@ +//@ check-pass +//@ compile-flags: -Znext-solver -Zassumptions-on-binders + +#![crate_type = "lib"] + +// Same concept as `type_relation_binders_inside_solver-1.rs`, this time the type relation +// occuring when checking `for<'b> fn(&'b ()): Trait` holds and we have to equate the two +// higher ranked function pointer types. + +trait Trait { } + +fn req_trait(_: T) { } + +fn mk() -> for<'b> fn(&'b ()) { loop {} } + +fn ice() +where + (for<'b> fn(&'b ())): Trait +{ + req_trait(mk()); +} diff --git a/tests/ui/impl-trait/unpin-for-future.rs b/tests/ui/impl-trait/unpin-for-future.rs new file mode 100644 index 0000000000000..1d5918a12ad87 --- /dev/null +++ b/tests/ui/impl-trait/unpin-for-future.rs @@ -0,0 +1,22 @@ +//@ edition:2024 +// +// Tests that you can't implement Unpin for a compiler-generated future using TAIT. + +#![feature(type_alias_impl_trait)] + +use core::marker::PhantomPinned; +use core::pin::Pin; + +type MyFut = impl Future; + +async fn my_async_fn() {} + +#[define_opaque(MyFut)] +fn fut() -> MyFut { + my_async_fn() +} + +impl Unpin for MyFut {} +//~^ ERROR: only traits defined in the current crate can be implemented for arbitrary types + +fn main() {} diff --git a/tests/ui/impl-trait/unpin-for-future.stderr b/tests/ui/impl-trait/unpin-for-future.stderr new file mode 100644 index 0000000000000..22278aa1eb333 --- /dev/null +++ b/tests/ui/impl-trait/unpin-for-future.stderr @@ -0,0 +1,15 @@ +error[E0117]: only traits defined in the current crate can be implemented for arbitrary types + --> $DIR/unpin-for-future.rs:19:1 + | +LL | impl Unpin for MyFut {} + | ^^^^^^^^^^^^^^^----- + | | + | type alias impl trait is treated as if it were foreign, because its hidden type could be from a foreign crate + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0117`. diff --git a/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.rs b/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.rs index 92295704615f2..a9ef59d8df4e3 100644 --- a/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.rs +++ b/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.rs @@ -5,7 +5,7 @@ use std::ops::Deref; struct ArenaSet::Target>(V, U); //~^ ERROR the size for values of type `V` cannot be known at compilation time -const DATA: *const ArenaSet> = std::ptr::null_mut(); +const DATA: &ArenaSet> = unsafe { &* std::ptr::null_mut() }; //~^ ERROR the type `ArenaSet, [u8]>` has an unknown layout pub fn main() {} diff --git a/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.stderr b/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.stderr index ea29320002b8e..0875f5a347e16 100644 --- a/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.stderr +++ b/tests/ui/layout/issue-unsized-tail-restatic-ice-122488.stderr @@ -23,10 +23,10 @@ LL | struct ArenaSet::Target>(Box, U); | ++++ + error[E0080]: the type `ArenaSet, [u8]>` has an unknown layout - --> $DIR/issue-unsized-tail-restatic-ice-122488.rs:8:1 + --> $DIR/issue-unsized-tail-restatic-ice-122488.rs:8:43 | -LL | const DATA: *const ArenaSet> = std::ptr::null_mut(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `DATA` failed here +LL | const DATA: &ArenaSet> = unsafe { &* std::ptr::null_mut() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `DATA` failed here error: aborting due to 2 previous errors diff --git a/tests/ui/parser/recover/raw-no-const-mut-arg-list.rs b/tests/ui/parser/recover/raw-no-const-mut-arg-list.rs new file mode 100644 index 0000000000000..7ed740cb02804 --- /dev/null +++ b/tests/ui/parser/recover/raw-no-const-mut-arg-list.rs @@ -0,0 +1,12 @@ +// Regression test for https://github.com/rust-lang/rust/issues/157015. + +fn takes_raw_ptr(_: *const u32) {} +fn takes_raw_ptr_args(_: *const u32, _: *const u32) {} + +fn main() { + let x = 0u32; + takes_raw_ptr(&raw x); + //~^ ERROR expected one of + takes_raw_ptr_args(&raw x, &raw x); + //~^ ERROR expected one of +} diff --git a/tests/ui/parser/recover/raw-no-const-mut-arg-list.stderr b/tests/ui/parser/recover/raw-no-const-mut-arg-list.stderr new file mode 100644 index 0000000000000..3bb6fe317a6f7 --- /dev/null +++ b/tests/ui/parser/recover/raw-no-const-mut-arg-list.stderr @@ -0,0 +1,28 @@ +error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `const`, `mut`, `{`, or an operator, found `x` + --> $DIR/raw-no-const-mut-arg-list.rs:8:24 + | +LL | takes_raw_ptr(&raw x); + | ^ expected one of 10 possible tokens + | +help: `&raw` must be followed by `const` or `mut` to be a raw reference expression + | +LL | takes_raw_ptr(&raw const x); + | +++++ +LL | takes_raw_ptr(&raw mut x); + | +++ + +error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `const`, `mut`, `{`, or an operator, found `x` + --> $DIR/raw-no-const-mut-arg-list.rs:10:29 + | +LL | takes_raw_ptr_args(&raw x, &raw x); + | ^ expected one of 10 possible tokens + | +help: `&raw` must be followed by `const` or `mut` to be a raw reference expression + | +LL | takes_raw_ptr_args(&raw const x, &raw x); + | +++++ +LL | takes_raw_ptr_args(&raw mut x, &raw x); + | +++ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/recover/raw-no-const-mut.rs b/tests/ui/parser/recover/raw-no-const-mut.rs index d0ae69cc30848..195de7d76029a 100644 --- a/tests/ui/parser/recover/raw-no-const-mut.rs +++ b/tests/ui/parser/recover/raw-no-const-mut.rs @@ -6,8 +6,6 @@ fn a() { fn b() { [&raw const 1, &raw 2] //~^ ERROR expected one of - //~| ERROR cannot find value `raw` in this scope - //~| ERROR cannot take address of a temporary } fn c() { @@ -18,7 +16,6 @@ fn c() { fn d() { f(&raw 2); //~^ ERROR expected one of - //~| ERROR cannot find value `raw` in this scope //~| ERROR cannot find function `f` in this scope } diff --git a/tests/ui/parser/recover/raw-no-const-mut.stderr b/tests/ui/parser/recover/raw-no-const-mut.stderr index 3007134f7f5c0..a6a47e651cd17 100644 --- a/tests/ui/parser/recover/raw-no-const-mut.stderr +++ b/tests/ui/parser/recover/raw-no-const-mut.stderr @@ -23,19 +23,15 @@ LL | [&raw const 1, &raw const 2] | +++++ LL | [&raw const 1, &raw mut 2] | +++ -help: missing `,` - | -LL | [&raw const 1, &raw, 2] - | + error: expected `{`, found `z` - --> $DIR/raw-no-const-mut.rs:14:18 + --> $DIR/raw-no-const-mut.rs:12:18 | LL | if x == &raw z {} | ^ expected `{` | note: the `if` expression is missing a block after this condition - --> $DIR/raw-no-const-mut.rs:14:8 + --> $DIR/raw-no-const-mut.rs:12:8 | LL | if x == &raw z {} | ^^^^^^^^^ @@ -47,7 +43,7 @@ LL | if x == &raw mut z {} | +++ error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `const`, `mut`, `{`, or an operator, found `2` - --> $DIR/raw-no-const-mut.rs:19:12 + --> $DIR/raw-no-const-mut.rs:17:12 | LL | f(&raw 2); | ^ expected one of 10 possible tokens @@ -58,13 +54,9 @@ LL | f(&raw const 2); | +++++ LL | f(&raw mut 2); | +++ -help: missing `,` - | -LL | f(&raw, 2); - | + error: expected one of `!`, `.`, `::`, `;`, `?`, `const`, `mut`, `{`, `}`, or an operator, found `1` - --> $DIR/raw-no-const-mut.rs:27:14 + --> $DIR/raw-no-const-mut.rs:24:14 | LL | x = &raw 1; | ^ expected one of 10 possible tokens @@ -76,26 +68,8 @@ LL | x = &raw const 1; LL | x = &raw mut 1; | +++ -error[E0425]: cannot find value `raw` in this scope - --> $DIR/raw-no-const-mut.rs:7:21 - | -LL | [&raw const 1, &raw 2] - | ^^^ not found in this scope - -error[E0425]: cannot find value `raw` in this scope - --> $DIR/raw-no-const-mut.rs:19:8 - | -LL | f(&raw 2); - | ^^^ not found in this scope - -error[E0745]: cannot take address of a temporary - --> $DIR/raw-no-const-mut.rs:7:17 - | -LL | [&raw const 1, &raw 2] - | ^ temporary value - error[E0425]: cannot find function `f` in this scope - --> $DIR/raw-no-const-mut.rs:19:5 + --> $DIR/raw-no-const-mut.rs:17:5 | LL | fn a() { | ------ similarly named function `a` defined here @@ -109,7 +83,6 @@ LL - f(&raw 2); LL + a(&raw 2); | -error: aborting due to 9 previous errors +error: aborting due to 6 previous errors -Some errors have detailed explanations: E0425, E0745. -For more information about an error, try `rustc --explain E0425`. +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/pin-ergonomics/auxiliary/non_pin_project.rs b/tests/ui/pin-ergonomics/auxiliary/non_pin_project.rs new file mode 100644 index 0000000000000..75a97aaa6a53f --- /dev/null +++ b/tests/ui/pin-ergonomics/auxiliary/non_pin_project.rs @@ -0,0 +1,3 @@ +// A plain, non-`#[pin_v2]` type defined in another crate, so it has no local span in the +// downstream crate that projects through it. +pub struct Foreign(pub T); diff --git a/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.rs b/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.rs new file mode 100644 index 0000000000000..d968e4394c079 --- /dev/null +++ b/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.rs @@ -0,0 +1,22 @@ +//@ edition:2024 +//@ aux-build:non_pin_project.rs +#![feature(pin_ergonomics)] +#![allow(incomplete_features)] + +// Regression test for #157634, exercising the diagnostic good-practice raised in the #157542 +// review: the projection error must be emitted even when the projected-through type comes from +// another crate and therefore has no local span. `ProjectOnNonPinProjectType` carries its +// `def_span`/`sugg_span` as `Option`, so for a foreign type the "type defined here" note +// and the `#[pin_v2]` suggestion are dropped rather than suppressing the error itself. + +extern crate non_pin_project; + +use non_pin_project::Foreign; +use std::pin::Pin; + +fn project(p: Pin<&mut Foreign>) { + let &pin mut Foreign(ref pin mut _x) = p; + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn main() {} diff --git a/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.stderr b/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.stderr new file mode 100644 index 0000000000000..493de31d88240 --- /dev/null +++ b/tests/ui/pin-ergonomics/pin-pattern-foreign-non-pin-project.stderr @@ -0,0 +1,8 @@ +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-foreign-non-pin-project.rs:18:18 + | +LL | let &pin mut Foreign(ref pin mut _x) = p; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.rs b/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.rs new file mode 100644 index 0000000000000..a51bbe08eab94 --- /dev/null +++ b/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.rs @@ -0,0 +1,61 @@ +//@ edition:2024 +#![feature(pin_ergonomics)] +#![allow(incomplete_features)] + +// Regression test for #157634. +// +// The implicit pin-projection (via match ergonomics) is only allowed on `#[pin_v2]` types, but +// the explicit `&pin mut` / `ref pin` pattern forms used to project through *any* type. That is +// unsound: it lets safe code form a `Pin<&mut Field>` for a type that never opted into structural +// pinning, breaking the `Pin` guarantee. Check that the explicit forms are now gated the same way +// as the implicit one. + +use std::pin::Pin; + +struct NotPinProject(T); + +struct NotPinProjectStruct { + x: T, +} + +enum NotPinProjectEnum { + Tuple(T), + Struct { x: T }, +} + +fn tuple_struct(p: Pin<&mut NotPinProject>) { + let &pin mut NotPinProject(ref pin mut _x) = p; + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn struct_field(p: Pin<&mut NotPinProjectStruct>) { + let &pin mut NotPinProjectStruct { x: ref pin mut _x } = p; + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn shared(p: Pin<&NotPinProject>) { + let &pin const NotPinProject(ref pin const _x) = p; + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn enum_tuple(p: Pin<&mut NotPinProjectEnum>) { + if let &pin mut NotPinProjectEnum::Tuple(ref pin mut _x) = p {} + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn enum_struct(p: Pin<&mut NotPinProjectEnum>) { + if let &pin mut NotPinProjectEnum::Struct { x: ref pin mut _x } = p {} + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +// The exact shape from the issue: `Thing` unconditionally implements `Unpin`, so it must not be +// possible to project a pinned reference to one of its fields. +struct Thing(T); +impl Unpin for Thing {} + +fn issue_157634(pinned_thing: Pin<&mut Thing>>) { + let &pin mut Thing(ref pin mut _pinned_option) = pinned_thing; + //~^ ERROR cannot project on type that is not `#[pin_v2]` +} + +fn main() {} diff --git a/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.stderr b/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.stderr new file mode 100644 index 0000000000000..c0fe4428ea53d --- /dev/null +++ b/tests/ui/pin-ergonomics/pin-pattern-non-pin-project.stderr @@ -0,0 +1,104 @@ +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:27:18 + | +LL | let &pin mut NotPinProject(ref pin mut _x) = p; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:15:1 + | +LL | struct NotPinProject(T); + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | struct NotPinProject(T); + | + +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:32:18 + | +LL | let &pin mut NotPinProjectStruct { x: ref pin mut _x } = p; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:17:1 + | +LL | struct NotPinProjectStruct { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | struct NotPinProjectStruct { + | + +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:37:20 + | +LL | let &pin const NotPinProject(ref pin const _x) = p; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:15:1 + | +LL | struct NotPinProject(T); + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | struct NotPinProject(T); + | + +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:42:21 + | +LL | if let &pin mut NotPinProjectEnum::Tuple(ref pin mut _x) = p {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:21:1 + | +LL | enum NotPinProjectEnum { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | enum NotPinProjectEnum { + | + +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:47:21 + | +LL | if let &pin mut NotPinProjectEnum::Struct { x: ref pin mut _x } = p {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:21:1 + | +LL | enum NotPinProjectEnum { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | enum NotPinProjectEnum { + | + +error: cannot project on type that is not `#[pin_v2]` + --> $DIR/pin-pattern-non-pin-project.rs:57:18 + | +LL | let &pin mut Thing(ref pin mut _pinned_option) = pinned_thing; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: type defined here + --> $DIR/pin-pattern-non-pin-project.rs:53:1 + | +LL | struct Thing(T); + | ^^^^^^^^^^^^^^^ +help: add `#[pin_v2]` here + | +LL + #[pin_v2] +LL | struct Thing(T); + | + +error: aborting due to 6 previous errors + diff --git a/tests/ui/pin-ergonomics/user-type-projection.rs b/tests/ui/pin-ergonomics/user-type-projection.rs index f482586b6ebcc..4e8ef9887425e 100644 --- a/tests/ui/pin-ergonomics/user-type-projection.rs +++ b/tests/ui/pin-ergonomics/user-type-projection.rs @@ -9,6 +9,7 @@ // Historically, this could occur when the code handling those projections did not know // about `&pin` patterns, and incorrectly treated them as plain `&`/`&mut` patterns instead. +#[pin_v2] struct Data { x: u32 } diff --git a/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs b/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs new file mode 100644 index 0000000000000..d9a1029211136 --- /dev/null +++ b/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs @@ -0,0 +1,6 @@ +#![allow(dead_code)] + +#[derive(Clone, Copy)] +pub struct ForeignRef<'a> { + value: &'a i32, +} diff --git a/tests/ui/reborrow/coerce-shared-associated-type-field.rs b/tests/ui/reborrow/coerce-shared-associated-type-field.rs new file mode 100644 index 0000000000000..df744e9442dd8 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-associated-type-field.rs @@ -0,0 +1,30 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +trait Trait { + type Assoc; +} + +impl Trait for i32 { + type Assoc = i64; +} + +struct MyMut<'a> { + x: &'a (), + y: i64, +} + +#[derive(Copy, Clone)] +struct MyRef<'a> { + x: &'a (), + y: ::Assoc, +} + +impl Reborrow for MyMut<'_> {} + +impl<'a> CoerceShared> for MyMut<'a> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-associated-type-field.stderr b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr new file mode 100644 index 0000000000000..d533fa2eb8748 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-associated-type-field.rs:27:10 + | +LL | impl<'a> CoerceShared> for MyMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs new file mode 100644 index 0000000000000..3b6e9e25d8bb4 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs @@ -0,0 +1,26 @@ +#![feature(reborrow, decl_macro)] +#![allow(incomplete_features)] + +use std::marker::{CoerceShared, Reborrow}; + +macro my_macro($field:ident) { + pub struct MyMut<'a> { + $field: &'a i32, + field: &'a i64, + } + + #[derive(Clone, Copy)] + pub struct MyRef<'a> { + $field: &'a i32, + field: &'a i64, + } + + impl Reborrow for MyMut<'_> {} + + impl<'a> CoerceShared> for MyMut<'a> {} + //~^ ERROR +} + +my_macro!(field); + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr new file mode 100644 index 0000000000000..0b0b7bf048591 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr @@ -0,0 +1,13 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-decl-macro-hygiene.rs:20:14 + | +LL | impl<'a> CoerceShared> for MyMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | my_macro!(field); + | ---------------- in this macro invocation + | + = note: this error originates in the macro `my_macro` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-extra-marker.rs b/tests/ui/reborrow/coerce-shared-extra-marker.rs new file mode 100644 index 0000000000000..40026d68d5dca --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-extra-marker.rs @@ -0,0 +1,37 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct MarkerExtraMut<'a, T> { + value: &'a mut T, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for MarkerExtraMut<'a, T> {} + +struct MarkerExtraRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for MarkerExtraRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for MarkerExtraRef<'a, T> {} + +impl<'a, T> CoerceShared> for MarkerExtraMut<'a, T> {} + +fn get<'a>(value: MarkerExtraRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let wrapped = MarkerExtraMut { value: &mut value, marker: PhantomData }; + assert_eq!(*get(wrapped), 1); +} diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs new file mode 100644 index 0000000000000..d4558dd35d775 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs @@ -0,0 +1,21 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct MyMut<'a> { + x: &'static (), + y: &'a (), +} + +impl Reborrow for MyMut<'_> {} + +#[derive(Copy, Clone)] +struct MyRef<'a> { + x: &'a (), + y: &'static (), +} + +impl<'a> CoerceShared> for MyMut<'a> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr new file mode 100644 index 0000000000000..bce87c68eb912 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-field-lifetime-swap.rs:18:10 + | +LL | impl<'a> CoerceShared> for MyMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-field-relations.rs b/tests/ui/reborrow/coerce-shared-field-relations.rs new file mode 100644 index 0000000000000..b2104efb6028d --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-relations.rs @@ -0,0 +1,51 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for CustomMut<'a, T> {} + +#[derive(Clone, Copy)] +struct CustomRef<'a, T> { + value: &'a T, +} + +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +struct RenamedMut<'a, T> { + source: &'a mut T, +} + +impl<'a, T> Reborrow for RenamedMut<'a, T> {} + +#[derive(Clone, Copy)] +struct RenamedRef<'a, T> { + target: &'a T, +} + +impl<'a, T> CoerceShared> for RenamedMut<'a, T> {} + +struct BadMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for BadMut<'a, T> {} + +#[derive(Clone, Copy)] +struct BadRef<'a, T> { + value: &'a u32, + _marker: std::marker::PhantomData, +} + +impl<'a, T> CoerceShared> for BadMut<'a, T> {} +//~^ ERROR + +fn good(_value: CustomRef<'_, u32>) {} + +fn main() { + let mut value = 1; + good(CustomMut { value: &mut value }); +} diff --git a/tests/ui/reborrow/coerce-shared-field-relations.stderr b/tests/ui/reborrow/coerce-shared-field-relations.stderr new file mode 100644 index 0000000000000..033a29e1e554e --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-relations.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `&'a mut T: CoerceShared<&'a u32>` is not satisfied + --> $DIR/coerce-shared-field-relations.rs:43:1 + | +LL | impl<'a, T> CoerceShared> for BadMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a u32>` is not implemented for `&'a mut T` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs new file mode 100644 index 0000000000000..3adda6733e16f --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs @@ -0,0 +1,20 @@ +//@ check-pass + +//@ aux-build: reborrow_foreign_private.rs + +#![feature(reborrow)] + +extern crate reborrow_foreign_private; + +use reborrow_foreign_private::ForeignRef; +use std::marker::{CoerceShared, Reborrow}; + +struct LocalMut<'a> { + value: &'a mut i32, +} + +impl<'a> Reborrow for LocalMut<'a> {} + +impl<'a> CoerceShared> for LocalMut<'a> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs new file mode 100644 index 0000000000000..4a70fd49e38ff --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs @@ -0,0 +1,22 @@ +//@ check-pass + +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +mod foreign_ptr { + use std::marker::PhantomData; + + #[derive(Clone, Copy)] + pub struct ForeignPtrRef<'a>(*const i32, PhantomData<&'a ()>); +} + +use foreign_ptr::ForeignPtrRef; + +struct LocalPtrMut<'a>(*const i32, PhantomData<&'a ()>); + +impl<'a> Reborrow for LocalPtrMut<'a> {} + +impl<'a> CoerceShared> for LocalPtrMut<'a> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-generics.rs b/tests/ui/reborrow/coerce-shared-generics.rs new file mode 100644 index 0000000000000..9edab02835761 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-generics.rs @@ -0,0 +1,41 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct BufferMut<'a, T, U, const N: usize> { + data: &'a mut [T; N], + meta: U, +} + +impl<'a, T, U: Copy, const N: usize> Reborrow for BufferMut<'a, T, U, N> {} + +struct BufferRef<'a, T, U, const N: usize> { + data: &'a [T; N], + meta: U, +} + +impl<'a, T, U: Copy, const N: usize> Clone for BufferRef<'a, T, U, N> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T, U: Copy, const N: usize> Copy for BufferRef<'a, T, U, N> {} + +impl<'a, T, U: Copy, const N: usize> CoerceShared> +//~^ ERROR + for BufferMut<'a, T, U, N> +{ +} + +fn inspect(buffer: BufferRef<'_, u8, u16, N>) -> (usize, u16) { + (buffer.data.len(), buffer.meta) +} + +fn main() { + let mut data = [1, 2, 3, 4]; + let buffer = BufferMut { data: &mut data, meta: 9_u16 }; + + assert_eq!(inspect(buffer), (4, 9)); +} diff --git a/tests/ui/reborrow/coerce-shared-generics.stderr b/tests/ui/reborrow/coerce-shared-generics.stderr new file mode 100644 index 0000000000000..72efdd475fc10 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-generics.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-generics.rs:26:38 + | +LL | impl<'a, T, U: Copy, const N: usize> CoerceShared> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs new file mode 100644 index 0000000000000..90bb4be4c1894 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs @@ -0,0 +1,23 @@ +#![feature(reborrow)] + +// The impl is accepted, but using it to coerce a local marker into a `'static` +// target still requires the local borrow to live for `'static`. + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); + +impl<'a> Reborrow for CustomMarker<'a> {} + +#[derive(Clone, Copy)] +struct StaticMarkerRef<'a>(PhantomData<&'a ()>); + +impl<'a> CoerceShared> for CustomMarker<'a> {} + +fn method(_a: StaticMarkerRef<'static>) {} + +fn main() { + let a = CustomMarker(PhantomData); + method(a); + //~^ ERROR +} diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr new file mode 100644 index 0000000000000..337e4b6938944 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr @@ -0,0 +1,17 @@ +error[E0597]: `a` does not live long enough + --> $DIR/coerce-shared-lifetime-mismatch.rs:21:12 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | method(a); + | -------^- + | | | + | | borrowed value does not live long enough + | argument requires that `a` is borrowed for `'static` +LL | +LL | } + | - `a` dropped here while still borrowed + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.rs b/tests/ui/reborrow/coerce-shared-missing-target-field.rs new file mode 100644 index 0000000000000..c528fb85340ac --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.rs @@ -0,0 +1,20 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct MissingSourceMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for MissingSourceMut<'a, T> {} + +#[derive(Clone, Copy)] +struct MissingSourceRef<'a, T> { + value: &'a T, + len: usize, +} + +impl<'a, T> CoerceShared> for MissingSourceMut<'a, T> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr new file mode 100644 index 0000000000000..15146341bc56c --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-missing-target-field.rs:17:13 + | +LL | impl<'a, T> CoerceShared> for MissingSourceMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs new file mode 100644 index 0000000000000..9d87ff0bb938a --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs @@ -0,0 +1,43 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +trait Trait { + type Assoc; +} + +impl Trait for i32 { + type Assoc = u32; +} + +type AliasMutField<'a> = &'a mut ::Assoc; +type AliasRefField<'a> = &'a u32; + +struct AliasMut<'a> { + value: AliasMutField<'a>, +} + +impl Reborrow for AliasMut<'_> {} + +#[derive(Copy, Clone)] +struct AliasRef<'a> { + value: AliasRefField<'a>, +} + +impl<'a> CoerceShared> for AliasMut<'a> {} +//~^ ERROR + +struct InnerLifetimeMut<'a> { + value: &'a mut &'static (), +} + +impl Reborrow for InnerLifetimeMut<'_> {} + +#[derive(Copy, Clone)] +struct InnerLifetimeRef<'a> { + value: &'a &'a (), +} + +impl<'a> CoerceShared> for InnerLifetimeMut<'a> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr new file mode 100644 index 0000000000000..63a96d65e176d --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `&'a mut u32: CoerceShared<&'a u32>` is not satisfied + --> $DIR/coerce-shared-mut-ref-field-validation.rs:27:1 + | +LL | impl<'a> CoerceShared> for AliasMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a u32>` is not implemented for `&'a mut u32` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs new file mode 100644 index 0000000000000..4f066079c749b --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs @@ -0,0 +1,51 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} +//~^ ERROR + +fn read(value: OmitRef<'_, i32>) { + assert_eq!(*value.value, 1); +} + +fn main() { + let mut value = 1; + let mut extra_value = 2; + + { + let extra = ExtraMut { value: &mut extra_value }; + let wrapped = OmitMut { value: &mut value, extra }; + + read(wrapped); + } + + extra_value = 3; + assert_eq!(extra_value, 3); +} diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr new file mode 100644 index 0000000000000..70a0db88319a7 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-omitted-reborrow-field-after-dead.rs:31:13 + | +LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs new file mode 100644 index 0000000000000..ade1890068d76 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs @@ -0,0 +1,53 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} +//~^ ERROR + +fn get<'a>(value: OmitRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let mut extra_value = 2; + let extra = ExtraMut { value: &mut extra_value }; + + let mut wrapped = OmitMut { + value: &mut value, + extra, + }; + + let shared = get(wrapped); + + *wrapped.extra.value = 3; + //~^ ERROR cannot assign to `*wrapped.extra.value` because it is borrowed + + let _ = shared; +} diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr new file mode 100644 index 0000000000000..d718103c80e7f --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr @@ -0,0 +1,21 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:30:13 + | +LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0506]: cannot assign to `*wrapped.extra.value` because it is borrowed + --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:49:5 + | +LL | let shared = get(wrapped); + | ------- `*wrapped.extra.value` is borrowed here +LL | +LL | *wrapped.extra.value = 3; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `*wrapped.extra.value` is assigned to here but it was already borrowed + | borrow later used here + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs new file mode 100644 index 0000000000000..55cc010c19af4 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs @@ -0,0 +1,45 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} +//~^ ERROR + +fn get<'a>(value: OmitRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let mut extra = 2; + let extra = ExtraMut { value: &mut extra }; + let wrapped = OmitMut { value: &mut value, extra }; + + assert_eq!(*get(wrapped), 1); +} diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr new file mode 100644 index 0000000000000..c663576228f62 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-omitted-reborrow-field.rs:31:13 + | +LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.rs b/tests/ui/reborrow/coerce-shared-reordered-field.rs new file mode 100644 index 0000000000000..f4630fe1f7d83 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-reordered-field.rs @@ -0,0 +1,32 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ReorderMut<'a> { + a: &'a mut u8, + b: &'a mut u16, +} + +impl<'a> Reborrow for ReorderMut<'a> {} + +#[derive(Clone, Copy)] +struct ReorderRef<'a> { + b: &'a u16, + a: &'a u8, +} + +impl<'a> CoerceShared> for ReorderMut<'a> {} +//~^ ERROR + +fn read(value: ReorderRef<'_>) -> (u16, u8) { + (*value.b, *value.a) +} + +fn main() { + let mut a = 1; + let mut b = 2; + let wrapped = ReorderMut { a: &mut a, b: &mut b }; + + assert_eq!(read(wrapped), (2, 1)); +} diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.stderr b/tests/ui/reborrow/coerce-shared-reordered-field.stderr new file mode 100644 index 0000000000000..e3cd68547c428 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-reordered-field.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced + --> $DIR/coerce-shared-reordered-field.rs:19:10 + | +LL | impl<'a> CoerceShared> for ReorderMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.rs b/tests/ui/reborrow/coerce-shared-wrong-generic.rs new file mode 100644 index 0000000000000..2dc818a7015b2 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.rs @@ -0,0 +1,21 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct GenericMut<'a, T, U> { + value: &'a mut T, + marker: PhantomData, +} + +impl<'a, T, U> Reborrow for GenericMut<'a, T, U> {} + +#[derive(Clone, Copy)] +struct GenericRef<'a, T, U> { + value: &'a U, + marker: PhantomData, +} + +impl<'a, T, U> CoerceShared> for GenericMut<'a, T, U> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr new file mode 100644 index 0000000000000..d0031a7c8a99e --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `&'a mut T: CoerceShared<&'a U>` is not satisfied + --> $DIR/coerce-shared-wrong-generic.rs:18:1 + | +LL | impl<'a, T, U> CoerceShared> for GenericMut<'a, T, U> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a U>` is not implemented for `&'a mut T` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/share-trait/share-trait-final-method.rs b/tests/ui/share-trait/share-trait-final-method.rs new file mode 100644 index 0000000000000..35b383c1fe3a0 --- /dev/null +++ b/tests/ui/share-trait/share-trait-final-method.rs @@ -0,0 +1,21 @@ +#![feature(final_associated_functions)] +#![feature(share_trait)] + +use std::clone::Share; + +struct Alias; + +impl Clone for Alias { + fn clone(&self) -> Self { + Alias + } +} + +impl Share for Alias { + fn share(&self) -> Self { + //~^ ERROR cannot override `share` because it already has a `final` definition in the trait + Alias + } +} + +fn main() {} diff --git a/tests/ui/share-trait/share-trait-final-method.stderr b/tests/ui/share-trait/share-trait-final-method.stderr new file mode 100644 index 0000000000000..c20f3d1aefb62 --- /dev/null +++ b/tests/ui/share-trait/share-trait-final-method.stderr @@ -0,0 +1,11 @@ +error: cannot override `share` because it already has a `final` definition in the trait + --> $DIR/share-trait-final-method.rs:15:5 + | +LL | fn share(&self) -> Self { + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `share` is marked final here + --> $SRC_DIR/core/src/clone.rs:LL:COL + +error: aborting due to 1 previous error + diff --git a/tests/ui/sized/stack-overflow-trait-infer-98842.stderr b/tests/ui/sized/stack-overflow-trait-infer-98842.stderr index b4d21de4fadc8..d0a361b40a704 100644 --- a/tests/ui/sized/stack-overflow-trait-infer-98842.stderr +++ b/tests/ui/sized/stack-overflow-trait-infer-98842.stderr @@ -7,11 +7,8 @@ LL | struct Foo(<&'static Foo as ::core::ops::Deref>::Target); note: ...which requires computing layout of `<&'static Foo as core::ops::deref::Deref>::Target`... --> $SRC_DIR/core/src/ops/deref.rs:LL:COL = note: ...which again requires computing layout of `Foo`, completing the cycle -note: cycle used when const-evaluating + checking `_` - --> $DIR/stack-overflow-trait-infer-98842.rs:13:1 - | -LL | const _: *const Foo = 0 as _; - | ^^^^^^^^^^^^^^^^^^^ +note: cycle used when computing layout of `<&'static Foo as core::ops::deref::Deref>::Target` + --> $SRC_DIR/core/src/ops/deref.rs:LL:COL = note: for more information, see and error: aborting due to 1 previous error diff --git a/tests/ui/wf/let-pat-inferred-non-wf.rs b/tests/ui/wf/let-pat-inferred-non-wf.rs deleted file mode 100644 index ab032161ebb51..0000000000000 --- a/tests/ui/wf/let-pat-inferred-non-wf.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Regression test for https://github.com/rust-lang/rust/issues/150040 -// When a `let PAT;` has no explicit type, later assignments can infer a non-well-formed -// pattern type such as `[str; 2]` or `(str, i32)`. We must reject those array and tuple -// patterns instead of accepting the invalid type or causing ICE. - -#![allow(unused)] - -struct S(T); - -fn should_fail_1() { - let ref y @ [ref x, _]; //~ ERROR E0277 - x = ""; -} - -fn should_fail_2() { - let [ref x]; //~ ERROR E0277 - x = ""; -} - -fn should_fail_3() { - let [[ref x], [_, y @ ..]]; //~ ERROR E0277 - x = ""; - y = []; -} - -fn should_fail_4() { - let [(ref a, b), x]; //~ ERROR E0277 - a = ""; - b = 5; -} - -fn should_fail_5() { - let (ref a, b); //~ ERROR E0277 - a = ""; - b = 5; -} - -fn should_fail_6() { - let [S(ref x)]; //~ ERROR E0277 - x = ""; -} - -fn should_pass_1() { - let ref x; - x = ""; -} - -fn should_pass_2() { - let ref y @ (ref x,); - x = ""; -} - -fn should_pass_3() { - let S(ref x); - x = ""; -} - -fn main() {} diff --git a/tests/ui/wf/let-pat-inferred-non-wf.stderr b/tests/ui/wf/let-pat-inferred-non-wf.stderr deleted file mode 100644 index 1c524a22b432b..0000000000000 --- a/tests/ui/wf/let-pat-inferred-non-wf.stderr +++ /dev/null @@ -1,62 +0,0 @@ -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:11:9 - | -LL | let ref y @ [ref x, _]; - | ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `str` - = note: slice and array elements must have `Sized` type - -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:16:9 - | -LL | let [ref x]; - | ^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `str` - = note: slice and array elements must have `Sized` type - -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:21:9 - | -LL | let [[ref x], [_, y @ ..]]; - | ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `str` - = note: slice and array elements must have `Sized` type - -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:27:9 - | -LL | let [(ref a, b), x]; - | ^^^^^^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `str` - = note: only the last element of a tuple may have a dynamically sized type - -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:33:9 - | -LL | let (ref a, b); - | ^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `str` - = note: only the last element of a tuple may have a dynamically sized type - -error[E0277]: the size for values of type `str` cannot be known at compilation time - --> $DIR/let-pat-inferred-non-wf.rs:39:9 - | -LL | let [S(ref x)]; - | ^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: within `S`, the trait `Sized` is not implemented for `str` -note: required because it appears within the type `S` - --> $DIR/let-pat-inferred-non-wf.rs:8:8 - | -LL | struct S(T); - | ^ - = note: slice and array elements must have `Sized` type - -error: aborting due to 6 previous errors - -For more information about this error, try `rustc --explain E0277`.