diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 93d50bf3d7b3b..6eb6792a1196f 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -851,6 +851,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } + // If we have `rustc_do_not_const_check`, do not check `~const` bounds. + if self.tcx.has_attr(self.body_id, sym::rustc_do_not_const_check) { + return; + } + let host = match self.tcx.hir().body_const_context(self.body_id) { Some(hir::ConstContext::Const { .. } | hir::ConstContext::Static(_)) => { ty::BoundConstness::Const diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index 80151b8ba2dec..af2d514fc76e1 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -1,38 +1,3 @@ -//! This module provides a framework on top of the normal MIR dataflow framework to simplify the -//! implementation of analyses that track information about the values stored in certain places. -//! We are using the term "place" here to refer to a `mir::Place` (a place expression) instead of -//! an `interpret::Place` (a memory location). -//! -//! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`) -//! provide some behavior that should be valid for all abstract domains that are based only on the -//! value stored in a certain place. On top of these default rules, an implementation should -//! override some of the `handle_` methods. For an example, see `ConstAnalysis`. -//! -//! An implementation must also provide a [`Map`]. Before the analysis begins, all places that -//! should be tracked during the analysis must be registered. During the analysis, no new places -//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a -//! certain place by passing the map. -//! -//! This framework is currently experimental. Originally, it supported shared references and enum -//! variants. However, it was discovered that both of these were unsound, and especially references -//! had subtle but serious issues. In the future, they could be added back in, but we should clarify -//! the rules for optimizations that rely on the aliasing model first. -//! -//! -//! # Notes -//! -//! - The bottom state denotes uninitialized memory. Because we are only doing a sound approximation -//! of the actual execution, we can also use this state for places where access would be UB. -//! -//! - The assignment logic in `State::insert_place_idx` assumes that the places are non-overlapping, -//! or identical. Note that this refers to place expressions, not memory locations. -//! -//! - Currently, places that have their reference taken cannot be tracked. Although this would be -//! possible, it has to rely on some aliasing model, which we are not ready to commit to yet. -//! Because of that, we can assume that the only way to change the value behind a tracked place is -//! by direct assignment. - -use std::assert_matches::assert_matches; use std::fmt::{Debug, Formatter}; use std::ops::Range; @@ -42,359 +7,14 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_index::IndexVec; use rustc_index::bit_set::BitSet; -use rustc_middle::bug; use rustc_middle::mir::tcx::PlaceTy; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty, TyCtxt}; use tracing::debug; -use crate::fmt::DebugWithContext; +use crate::JoinSemiLattice; use crate::lattice::{HasBottom, HasTop}; -use crate::{Analysis, JoinSemiLattice, SwitchIntEdgeEffects}; - -pub trait ValueAnalysis<'tcx> { - /// For each place of interest, the analysis tracks a value of the given type. - type Value: Clone + JoinSemiLattice + HasBottom + HasTop + Debug; - - const NAME: &'static str; - - fn map(&self) -> &Map<'tcx>; - - fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State) { - self.super_statement(statement, state) - } - - fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State) { - match &statement.kind { - StatementKind::Assign(box (place, rvalue)) => { - self.handle_assign(*place, rvalue, state); - } - StatementKind::SetDiscriminant { box place, variant_index } => { - self.handle_set_discriminant(*place, *variant_index, state); - } - StatementKind::Intrinsic(box intrinsic) => { - self.handle_intrinsic(intrinsic, state); - } - StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { - // StorageLive leaves the local in an uninitialized state. - // StorageDead makes it UB to access the local afterwards. - state.flood_with(Place::from(*local).as_ref(), self.map(), Self::Value::BOTTOM); - } - StatementKind::Deinit(box place) => { - // Deinit makes the place uninitialized. - state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM); - } - StatementKind::Retag(..) => { - // We don't track references. - } - StatementKind::ConstEvalCounter - | StatementKind::Nop - | StatementKind::FakeRead(..) - | StatementKind::PlaceMention(..) - | StatementKind::Coverage(..) - | StatementKind::AscribeUserType(..) => (), - } - } - - fn handle_set_discriminant( - &self, - place: Place<'tcx>, - variant_index: VariantIdx, - state: &mut State, - ) { - self.super_set_discriminant(place, variant_index, state) - } - - fn super_set_discriminant( - &self, - place: Place<'tcx>, - _variant_index: VariantIdx, - state: &mut State, - ) { - state.flood_discr(place.as_ref(), self.map()); - } - - fn handle_intrinsic( - &self, - intrinsic: &NonDivergingIntrinsic<'tcx>, - state: &mut State, - ) { - self.super_intrinsic(intrinsic, state); - } - - fn super_intrinsic( - &self, - intrinsic: &NonDivergingIntrinsic<'tcx>, - _state: &mut State, - ) { - match intrinsic { - NonDivergingIntrinsic::Assume(..) => { - // Could use this, but ignoring it is sound. - } - NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { - dst: _, - src: _, - count: _, - }) => { - // This statement represents `*dst = *src`, `count` times. - } - } - } - - fn handle_assign( - &self, - target: Place<'tcx>, - rvalue: &Rvalue<'tcx>, - state: &mut State, - ) { - self.super_assign(target, rvalue, state) - } - - fn super_assign( - &self, - target: Place<'tcx>, - rvalue: &Rvalue<'tcx>, - state: &mut State, - ) { - let result = self.handle_rvalue(rvalue, state); - state.assign(target.as_ref(), result, self.map()); - } - - fn handle_rvalue( - &self, - rvalue: &Rvalue<'tcx>, - state: &mut State, - ) -> ValueOrPlace { - self.super_rvalue(rvalue, state) - } - - fn super_rvalue( - &self, - rvalue: &Rvalue<'tcx>, - state: &mut State, - ) -> ValueOrPlace { - match rvalue { - Rvalue::Use(operand) => self.handle_operand(operand, state), - Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state), - Rvalue::Ref(..) | Rvalue::RawPtr(..) => { - // We don't track such places. - ValueOrPlace::TOP - } - Rvalue::Repeat(..) - | Rvalue::ThreadLocalRef(..) - | Rvalue::Len(..) - | Rvalue::Cast(..) - | Rvalue::BinaryOp(..) - | Rvalue::NullaryOp(..) - | Rvalue::UnaryOp(..) - | Rvalue::Discriminant(..) - | Rvalue::Aggregate(..) - | Rvalue::ShallowInitBox(..) => { - // No modification is possible through these r-values. - ValueOrPlace::TOP - } - } - } - - fn handle_operand( - &self, - operand: &Operand<'tcx>, - state: &mut State, - ) -> ValueOrPlace { - self.super_operand(operand, state) - } - - fn super_operand( - &self, - operand: &Operand<'tcx>, - state: &mut State, - ) -> ValueOrPlace { - match operand { - Operand::Constant(box constant) => { - ValueOrPlace::Value(self.handle_constant(constant, state)) - } - Operand::Copy(place) | Operand::Move(place) => { - // On move, we would ideally flood the place with bottom. But with the current - // framework this is not possible (similar to `InterpCx::eval_operand`). - self.map() - .find(place.as_ref()) - .map(ValueOrPlace::Place) - .unwrap_or(ValueOrPlace::TOP) - } - } - } - - fn handle_constant( - &self, - constant: &ConstOperand<'tcx>, - state: &mut State, - ) -> Self::Value { - self.super_constant(constant, state) - } - - fn super_constant( - &self, - _constant: &ConstOperand<'tcx>, - _state: &mut State, - ) -> Self::Value { - Self::Value::TOP - } - - /// The effect of a successful function call return should not be - /// applied here, see [`Analysis::apply_terminator_effect`]. - fn handle_terminator<'mir>( - &self, - terminator: &'mir Terminator<'tcx>, - state: &mut State, - ) -> TerminatorEdges<'mir, 'tcx> { - self.super_terminator(terminator, state) - } - - fn super_terminator<'mir>( - &self, - terminator: &'mir Terminator<'tcx>, - state: &mut State, - ) -> TerminatorEdges<'mir, 'tcx> { - match &terminator.kind { - TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => { - // Effect is applied by `handle_call_return`. - } - TerminatorKind::Drop { place, .. } => { - state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM); - } - TerminatorKind::Yield { .. } => { - // They would have an effect, but are not allowed in this phase. - bug!("encountered disallowed terminator"); - } - TerminatorKind::SwitchInt { discr, targets } => { - return self.handle_switch_int(discr, targets, state); - } - TerminatorKind::TailCall { .. } => { - // FIXME(explicit_tail_calls): determine if we need to do something here (probably not) - } - TerminatorKind::Goto { .. } - | TerminatorKind::UnwindResume - | TerminatorKind::UnwindTerminate(_) - | TerminatorKind::Return - | TerminatorKind::Unreachable - | TerminatorKind::Assert { .. } - | TerminatorKind::CoroutineDrop - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } => { - // These terminators have no effect on the analysis. - } - } - terminator.edges() - } - - fn handle_call_return( - &self, - return_places: CallReturnPlaces<'_, 'tcx>, - state: &mut State, - ) { - self.super_call_return(return_places, state) - } - - fn super_call_return( - &self, - return_places: CallReturnPlaces<'_, 'tcx>, - state: &mut State, - ) { - return_places.for_each(|place| { - state.flood(place.as_ref(), self.map()); - }) - } - - fn handle_switch_int<'mir>( - &self, - discr: &'mir Operand<'tcx>, - targets: &'mir SwitchTargets, - state: &mut State, - ) -> TerminatorEdges<'mir, 'tcx> { - self.super_switch_int(discr, targets, state) - } - - fn super_switch_int<'mir>( - &self, - discr: &'mir Operand<'tcx>, - targets: &'mir SwitchTargets, - _state: &mut State, - ) -> TerminatorEdges<'mir, 'tcx> { - TerminatorEdges::SwitchInt { discr, targets } - } - - fn wrap(self) -> ValueAnalysisWrapper - where - Self: Sized, - { - ValueAnalysisWrapper(self) - } -} - -pub struct ValueAnalysisWrapper(pub T); - -impl<'tcx, T: ValueAnalysis<'tcx>> Analysis<'tcx> for ValueAnalysisWrapper { - type Domain = State; - - const NAME: &'static str = T::NAME; - - fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - State::Unreachable - } - - fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { - // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥. - assert_matches!(state, State::Unreachable); - *state = State::new_reachable(); - for arg in body.args_iter() { - state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map()); - } - } - - fn apply_statement_effect( - &mut self, - state: &mut Self::Domain, - statement: &Statement<'tcx>, - _location: Location, - ) { - if state.is_reachable() { - self.0.handle_statement(statement, state); - } - } - - fn apply_terminator_effect<'mir>( - &mut self, - state: &mut Self::Domain, - terminator: &'mir Terminator<'tcx>, - _location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { - if state.is_reachable() { - self.0.handle_terminator(terminator, state) - } else { - TerminatorEdges::None - } - } - - fn apply_call_return_effect( - &mut self, - state: &mut Self::Domain, - _block: BasicBlock, - return_places: CallReturnPlaces<'_, 'tcx>, - ) { - if state.is_reachable() { - self.0.handle_call_return(return_places, state) - } - } - - fn apply_switch_int_edge_effects( - &mut self, - _block: BasicBlock, - _discr: &Operand<'tcx>, - _apply_edge_effects: &mut impl SwitchIntEdgeEffects, - ) { - } -} rustc_index::newtype_index!( /// This index uniquely identifies a place. @@ -464,7 +84,7 @@ impl JoinSemiLattice for StateData { } } -/// The dataflow state for an instance of [`ValueAnalysis`]. +/// Dataflow state. /// /// Every instance specifies a lattice that represents the possible values of a single tracked /// place. If we call this lattice `V` and set of tracked places `P`, then a [`State`] is an @@ -514,7 +134,7 @@ impl State { } } - fn is_reachable(&self) -> bool { + pub fn is_reachable(&self) -> bool { matches!(self, State::Reachable(_)) } @@ -1317,34 +937,6 @@ pub fn excluded_locals(body: &Body<'_>) -> BitSet { collector.result } -/// This is used to visualize the dataflow analysis. -impl<'tcx, T> DebugWithContext> for State -where - T: ValueAnalysis<'tcx>, - T::Value: Debug, -{ - fn fmt_with(&self, ctxt: &ValueAnalysisWrapper, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - State::Reachable(values) => debug_with_context(values, None, ctxt.0.map(), f), - State::Unreachable => write!(f, "unreachable"), - } - } - - fn fmt_diff_with( - &self, - old: &Self, - ctxt: &ValueAnalysisWrapper, - f: &mut Formatter<'_>, - ) -> std::fmt::Result { - match (self, old) { - (State::Reachable(this), State::Reachable(old)) => { - debug_with_context(this, Some(old), ctxt.0.map(), f) - } - _ => Ok(()), // Consider printing something here. - } - } -} - fn debug_with_context_rec( place: PlaceIndex, place_str: &str, @@ -1391,7 +983,7 @@ fn debug_with_context_rec( Ok(()) } -fn debug_with_context( +pub fn debug_with_context( new: &StateData, old: Option<&StateData>, map: &Map<'_>, diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index c084b20772faa..dd85d06540d12 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -2,6 +2,9 @@ //! //! Currently, this pass only propagates scalar values. +use std::assert_matches::assert_matches; +use std::fmt::Formatter; + use rustc_abi::{BackendRepr, FIRST_VARIANT, FieldIdx, Size, VariantIdx}; use rustc_const_eval::const_eval::{DummyMachine, throw_machine_stop_str}; use rustc_const_eval::interpret::{ @@ -15,9 +18,10 @@ use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::layout::{HasParamEnv, LayoutOf}; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_mir_dataflow::lattice::FlatSet; +use rustc_mir_dataflow::fmt::DebugWithContext; +use rustc_mir_dataflow::lattice::{FlatSet, HasBottom}; use rustc_mir_dataflow::value_analysis::{ - Map, PlaceIndex, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace, + Map, PlaceIndex, State, TrackElem, ValueOrPlace, debug_with_context, }; use rustc_mir_dataflow::{Analysis, Results, ResultsVisitor}; use rustc_span::DUMMY_SP; @@ -58,8 +62,8 @@ impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp { // Perform the actual dataflow analysis. let analysis = ConstAnalysis::new(tcx, body, map); - let mut results = debug_span!("analyze") - .in_scope(|| analysis.wrap().iterate_to_fixpoint(tcx, body, None)); + let mut results = + debug_span!("analyze").in_scope(|| analysis.iterate_to_fixpoint(tcx, body, None)); // Collect results and patch the body afterwards. let mut visitor = Collector::new(tcx, &body.local_decls); @@ -69,6 +73,10 @@ impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp { } } +// Note: Currently, places that have their reference taken cannot be tracked. Although this would +// be possible, it has to rely on some aliasing model, which we are not ready to commit to yet. +// Because of that, we can assume that the only way to change the value behind a tracked place is +// by direct assignment. struct ConstAnalysis<'a, 'tcx> { map: Map<'tcx>, tcx: TyCtxt<'tcx>, @@ -77,20 +85,198 @@ struct ConstAnalysis<'a, 'tcx> { param_env: ty::ParamEnv<'tcx>, } -impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { - type Value = FlatSet; +impl<'tcx> Analysis<'tcx> for ConstAnalysis<'_, 'tcx> { + type Domain = State>; const NAME: &'static str = "ConstAnalysis"; - fn map(&self) -> &Map<'tcx> { - &self.map + // The bottom state denotes uninitialized memory. Because we are only doing a sound + // approximation of the actual execution, we can also use this state for places where access + // would be UB. + fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { + State::Unreachable + } + + fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥. + assert_matches!(state, State::Unreachable); + *state = State::new_reachable(); + for arg in body.args_iter() { + state.flood(PlaceRef { local: arg, projection: &[] }, &self.map); + } + } + + fn apply_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + _location: Location, + ) { + if state.is_reachable() { + self.handle_statement(statement, state); + } + } + + fn apply_terminator_effect<'mir>( + &mut self, + state: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + _location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + if state.is_reachable() { + self.handle_terminator(terminator, state) + } else { + TerminatorEdges::None + } + } + + fn apply_call_return_effect( + &mut self, + state: &mut Self::Domain, + _block: BasicBlock, + return_places: CallReturnPlaces<'_, 'tcx>, + ) { + if state.is_reachable() { + self.handle_call_return(return_places, state) + } + } +} + +impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { + fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self { + let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id()); + Self { + map, + tcx, + local_decls: &body.local_decls, + ecx: InterpCx::new(tcx, DUMMY_SP, param_env, DummyMachine), + param_env, + } + } + + fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State>) { + match &statement.kind { + StatementKind::Assign(box (place, rvalue)) => { + self.handle_assign(*place, rvalue, state); + } + StatementKind::SetDiscriminant { box place, variant_index } => { + self.handle_set_discriminant(*place, *variant_index, state); + } + StatementKind::Intrinsic(box intrinsic) => { + self.handle_intrinsic(intrinsic); + } + StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { + // StorageLive leaves the local in an uninitialized state. + // StorageDead makes it UB to access the local afterwards. + state.flood_with( + Place::from(*local).as_ref(), + &self.map, + FlatSet::::BOTTOM, + ); + } + StatementKind::Deinit(box place) => { + // Deinit makes the place uninitialized. + state.flood_with(place.as_ref(), &self.map, FlatSet::::BOTTOM); + } + StatementKind::Retag(..) => { + // We don't track references. + } + StatementKind::ConstEvalCounter + | StatementKind::Nop + | StatementKind::FakeRead(..) + | StatementKind::PlaceMention(..) + | StatementKind::Coverage(..) + | StatementKind::AscribeUserType(..) => (), + } + } + + fn handle_intrinsic(&self, intrinsic: &NonDivergingIntrinsic<'tcx>) { + match intrinsic { + NonDivergingIntrinsic::Assume(..) => { + // Could use this, but ignoring it is sound. + } + NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { + dst: _, + src: _, + count: _, + }) => { + // This statement represents `*dst = *src`, `count` times. + } + } + } + + fn handle_operand( + &self, + operand: &Operand<'tcx>, + state: &mut State>, + ) -> ValueOrPlace> { + match operand { + Operand::Constant(box constant) => { + ValueOrPlace::Value(self.handle_constant(constant, state)) + } + Operand::Copy(place) | Operand::Move(place) => { + // On move, we would ideally flood the place with bottom. But with the current + // framework this is not possible (similar to `InterpCx::eval_operand`). + self.map.find(place.as_ref()).map(ValueOrPlace::Place).unwrap_or(ValueOrPlace::TOP) + } + } + } + + /// The effect of a successful function call return should not be + /// applied here, see [`Analysis::apply_terminator_effect`]. + fn handle_terminator<'mir>( + &self, + terminator: &'mir Terminator<'tcx>, + state: &mut State>, + ) -> TerminatorEdges<'mir, 'tcx> { + match &terminator.kind { + TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => { + // Effect is applied by `handle_call_return`. + } + TerminatorKind::Drop { place, .. } => { + state.flood_with(place.as_ref(), &self.map, FlatSet::::BOTTOM); + } + TerminatorKind::Yield { .. } => { + // They would have an effect, but are not allowed in this phase. + bug!("encountered disallowed terminator"); + } + TerminatorKind::SwitchInt { discr, targets } => { + return self.handle_switch_int(discr, targets, state); + } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): determine if we need to do something here (probably + // not) + } + TerminatorKind::Goto { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::UnwindTerminate(_) + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Assert { .. } + | TerminatorKind::CoroutineDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } => { + // These terminators have no effect on the analysis. + } + } + terminator.edges() + } + + fn handle_call_return( + &self, + return_places: CallReturnPlaces<'_, 'tcx>, + state: &mut State>, + ) { + return_places.for_each(|place| { + state.flood(place.as_ref(), &self.map); + }) } fn handle_set_discriminant( &self, place: Place<'tcx>, variant_index: VariantIdx, - state: &mut State, + state: &mut State>, ) { state.flood_discr(place.as_ref(), &self.map); if self.map.find_discr(place.as_ref()).is_some() { @@ -109,17 +295,17 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { &self, target: Place<'tcx>, rvalue: &Rvalue<'tcx>, - state: &mut State, + state: &mut State>, ) { match rvalue { Rvalue::Use(operand) => { - state.flood(target.as_ref(), self.map()); + state.flood(target.as_ref(), &self.map); if let Some(target) = self.map.find(target.as_ref()) { self.assign_operand(state, target, operand); } } Rvalue::CopyForDeref(rhs) => { - state.flood(target.as_ref(), self.map()); + state.flood(target.as_ref(), &self.map); if let Some(target) = self.map.find(target.as_ref()) { self.assign_operand(state, target, &Operand::Copy(*rhs)); } @@ -127,9 +313,9 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { Rvalue::Aggregate(kind, operands) => { // If we assign `target = Enum::Variant#0(operand)`, // we must make sure that all `target as Variant#i` are `Top`. - state.flood(target.as_ref(), self.map()); + state.flood(target.as_ref(), &self.map); - let Some(target_idx) = self.map().find(target.as_ref()) else { return }; + let Some(target_idx) = self.map.find(target.as_ref()) else { return }; let (variant_target, variant_index) = match **kind { AggregateKind::Tuple | AggregateKind::Closure(..) => (Some(target_idx), None), @@ -148,14 +334,14 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { if let Some(variant_target_idx) = variant_target { for (field_index, operand) in operands.iter_enumerated() { if let Some(field) = - self.map().apply(variant_target_idx, TrackElem::Field(field_index)) + self.map.apply(variant_target_idx, TrackElem::Field(field_index)) { self.assign_operand(state, field, operand); } } } if let Some(variant_index) = variant_index - && let Some(discr_idx) = self.map().apply(target_idx, TrackElem::Discriminant) + && let Some(discr_idx) = self.map.apply(target_idx, TrackElem::Discriminant) { // We are assigning the discriminant as part of an aggregate. // This discriminant can only alias a variant field's value if the operand @@ -170,23 +356,23 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { } Rvalue::BinaryOp(op, box (left, right)) if op.is_overflowing() => { // Flood everything now, so we can use `insert_value_idx` directly later. - state.flood(target.as_ref(), self.map()); + state.flood(target.as_ref(), &self.map); - let Some(target) = self.map().find(target.as_ref()) else { return }; + let Some(target) = self.map.find(target.as_ref()) else { return }; - let value_target = self.map().apply(target, TrackElem::Field(0_u32.into())); - let overflow_target = self.map().apply(target, TrackElem::Field(1_u32.into())); + let value_target = self.map.apply(target, TrackElem::Field(0_u32.into())); + let overflow_target = self.map.apply(target, TrackElem::Field(1_u32.into())); if value_target.is_some() || overflow_target.is_some() { let (val, overflow) = self.binary_op(state, *op, left, right); if let Some(value_target) = value_target { // We have flooded `target` earlier. - state.insert_value_idx(value_target, val, self.map()); + state.insert_value_idx(value_target, val, &self.map); } if let Some(overflow_target) = overflow_target { // We have flooded `target` earlier. - state.insert_value_idx(overflow_target, overflow, self.map()); + state.insert_value_idx(overflow_target, overflow, &self.map); } } } @@ -196,27 +382,30 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { _, ) => { let pointer = self.handle_operand(operand, state); - state.assign(target.as_ref(), pointer, self.map()); + state.assign(target.as_ref(), pointer, &self.map); - if let Some(target_len) = self.map().find_len(target.as_ref()) + if let Some(target_len) = self.map.find_len(target.as_ref()) && let operand_ty = operand.ty(self.local_decls, self.tcx) && let Some(operand_ty) = operand_ty.builtin_deref(true) && let ty::Array(_, len) = operand_ty.kind() && let Some(len) = Const::Ty(self.tcx.types.usize, *len) .try_eval_scalar_int(self.tcx, self.param_env) { - state.insert_value_idx(target_len, FlatSet::Elem(len.into()), self.map()); + state.insert_value_idx(target_len, FlatSet::Elem(len.into()), &self.map); } } - _ => self.super_assign(target, rvalue, state), + _ => { + let result = self.handle_rvalue(rvalue, state); + state.assign(target.as_ref(), result, &self.map); + } } } fn handle_rvalue( &self, rvalue: &Rvalue<'tcx>, - state: &mut State, - ) -> ValueOrPlace { + state: &mut State>, + ) -> ValueOrPlace> { let val = match rvalue { Rvalue::Len(place) => { let place_ty = place.ty(self.local_decls, self.tcx); @@ -225,7 +414,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { .try_eval_scalar(self.tcx, self.param_env) .map_or(FlatSet::Top, FlatSet::Elem) } else if let [ProjectionElem::Deref] = place.projection[..] { - state.get_len(place.local.into(), self.map()) + state.get_len(place.local.into(), &self.map) } else { FlatSet::Top } @@ -296,8 +485,24 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { }; FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx)) } - Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), self.map()), - _ => return self.super_rvalue(rvalue, state), + Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map), + Rvalue::Use(operand) => return self.handle_operand(operand, state), + Rvalue::CopyForDeref(place) => { + return self.handle_operand(&Operand::Copy(*place), state); + } + Rvalue::Ref(..) | Rvalue::RawPtr(..) => { + // We don't track such places. + return ValueOrPlace::TOP; + } + Rvalue::Repeat(..) + | Rvalue::ThreadLocalRef(..) + | Rvalue::Cast(..) + | Rvalue::BinaryOp(..) + | Rvalue::Aggregate(..) + | Rvalue::ShallowInitBox(..) => { + // No modification is possible through these r-values. + return ValueOrPlace::TOP; + } }; ValueOrPlace::Value(val) } @@ -305,8 +510,8 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { fn handle_constant( &self, constant: &ConstOperand<'tcx>, - _state: &mut State, - ) -> Self::Value { + _state: &mut State>, + ) -> FlatSet { constant .const_ .try_eval_scalar(self.tcx, self.param_env) @@ -317,11 +522,11 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { &self, discr: &'mir Operand<'tcx>, targets: &'mir SwitchTargets, - state: &mut State, + state: &mut State>, ) -> TerminatorEdges<'mir, 'tcx> { let value = match self.handle_operand(discr, state) { ValueOrPlace::Value(value) => value, - ValueOrPlace::Place(place) => state.get_idx(place, self.map()), + ValueOrPlace::Place(place) => state.get_idx(place, &self.map), }; match value { // We are branching on uninitialized data, this is UB, treat it as unreachable. @@ -334,19 +539,6 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets }, } } -} - -impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { - fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self { - let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id()); - Self { - map, - tcx, - local_decls: &body.local_decls, - ecx: InterpCx::new(tcx, DUMMY_SP, param_env, DummyMachine), - param_env, - } - } /// The caller must have flooded `place`. fn assign_operand( @@ -537,16 +729,40 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } } -pub(crate) struct Patch<'tcx> { +/// This is used to visualize the dataflow analysis. +impl<'tcx> DebugWithContext> for State> { + fn fmt_with(&self, ctxt: &ConstAnalysis<'_, 'tcx>, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + State::Reachable(values) => debug_with_context(values, None, &ctxt.map, f), + State::Unreachable => write!(f, "unreachable"), + } + } + + fn fmt_diff_with( + &self, + old: &Self, + ctxt: &ConstAnalysis<'_, 'tcx>, + f: &mut Formatter<'_>, + ) -> std::fmt::Result { + match (self, old) { + (State::Reachable(this), State::Reachable(old)) => { + debug_with_context(this, Some(old), &ctxt.map, f) + } + _ => Ok(()), // Consider printing something here. + } + } +} + +struct Patch<'tcx> { tcx: TyCtxt<'tcx>, /// For a given MIR location, this stores the values of the operands used by that location. In /// particular, this is before the effect, such that the operands of `_1 = _1 + _2` are /// properly captured. (This may become UB soon, but it is currently emitted even by safe code.) - pub(crate) before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>, + before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>, /// Stores the assigned values for assignments where the Rvalue is constant. - pub(crate) assignments: FxHashMap>, + assignments: FxHashMap>, } impl<'tcx> Patch<'tcx> { @@ -725,8 +941,7 @@ fn try_write_constant<'tcx>( interp_ok(()) } -impl<'mir, 'tcx> - ResultsVisitor<'mir, 'tcx, Results<'tcx, ValueAnalysisWrapper>>> +impl<'mir, 'tcx> ResultsVisitor<'mir, 'tcx, Results<'tcx, ConstAnalysis<'_, 'tcx>>> for Collector<'_, 'tcx> { type Domain = State>; @@ -734,7 +949,7 @@ impl<'mir, 'tcx> #[instrument(level = "trace", skip(self, results, statement))] fn visit_statement_before_primary_effect( &mut self, - results: &mut Results<'tcx, ValueAnalysisWrapper>>, + results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>, state: &Self::Domain, statement: &'mir Statement<'tcx>, location: Location, @@ -744,8 +959,8 @@ impl<'mir, 'tcx> OperandCollector { state, visitor: self, - ecx: &mut results.analysis.0.ecx, - map: &results.analysis.0.map, + ecx: &mut results.analysis.ecx, + map: &results.analysis.map, } .visit_rvalue(rvalue, location); } @@ -756,7 +971,7 @@ impl<'mir, 'tcx> #[instrument(level = "trace", skip(self, results, statement))] fn visit_statement_after_primary_effect( &mut self, - results: &mut Results<'tcx, ValueAnalysisWrapper>>, + results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>, state: &Self::Domain, statement: &'mir Statement<'tcx>, location: Location, @@ -767,10 +982,10 @@ impl<'mir, 'tcx> } StatementKind::Assign(box (place, _)) => { if let Some(value) = self.try_make_constant( - &mut results.analysis.0.ecx, + &mut results.analysis.ecx, place, state, - &results.analysis.0.map, + &results.analysis.map, ) { self.patch.assignments.insert(location, value); } @@ -781,7 +996,7 @@ impl<'mir, 'tcx> fn visit_terminator_before_primary_effect( &mut self, - results: &mut Results<'tcx, ValueAnalysisWrapper>>, + results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>, state: &Self::Domain, terminator: &'mir Terminator<'tcx>, location: Location, @@ -789,8 +1004,8 @@ impl<'mir, 'tcx> OperandCollector { state, visitor: self, - ecx: &mut results.analysis.0.ecx, - map: &results.analysis.0.map, + ecx: &mut results.analysis.ecx, + map: &results.analysis.map, } .visit_terminator(terminator, location); } diff --git a/src/bootstrap/src/bin/rustc.rs b/src/bootstrap/src/bin/rustc.rs index 18f5a1a58db93..88595ff7e5198 100644 --- a/src/bootstrap/src/bin/rustc.rs +++ b/src/bootstrap/src/bin/rustc.rs @@ -175,9 +175,7 @@ fn main() { // Find any host flags that were passed by bootstrap. // The flags are stored in a RUSTC_HOST_FLAGS variable, separated by spaces. if let Ok(flags) = std::env::var("RUSTC_HOST_FLAGS") { - for flag in flags.split(' ') { - cmd.arg(flag); - } + cmd.args(flags.split(' ')); } } diff --git a/tests/ui/json/json-bom-plus-crlf-multifile.rs b/tests/ui/json/json-bom-plus-crlf-multifile.rs index ae608770aae37..79c4b2430e1d1 100644 --- a/tests/ui/json/json-bom-plus-crlf-multifile.rs +++ b/tests/ui/json/json-bom-plus-crlf-multifile.rs @@ -1,6 +1,8 @@ // (This line has BOM so it's ignored by compiletest for directives) // //@ compile-flags: --json=diagnostic-short --error-format=json +//@ reference: input.byte-order-mark +//@ reference: input.crlf // ignore-tidy-cr #[path = "json-bom-plus-crlf-multifile-aux.rs"] diff --git a/tests/ui/json/json-bom-plus-crlf.rs b/tests/ui/json/json-bom-plus-crlf.rs index 4a309128199fc..9e250c256ebe1 100644 --- a/tests/ui/json/json-bom-plus-crlf.rs +++ b/tests/ui/json/json-bom-plus-crlf.rs @@ -1,6 +1,8 @@ // (This line has BOM so it's ignored by compiletest for directives) // //@ compile-flags: --json=diagnostic-short --error-format=json +//@ reference: input.byte-order-mark +//@ reference: input.crlf // ignore-tidy-cr // For easier verifying, the byte offsets in this file should match those diff --git a/tests/ui/json/json-bom-plus-crlf.stderr b/tests/ui/json/json-bom-plus-crlf.stderr index cd1e3665b3e58..fee70431bdfbf 100644 --- a/tests/ui/json/json-bom-plus-crlf.stderr +++ b/tests/ui/json/json-bom-plus-crlf.stderr @@ -24,7 +24,7 @@ This error occurs when an expression was used in a place where the compiler expected an expression of a different type. It can occur in several cases, the most common being when calling a function and passing an argument which has a different type than the matching type in the function declaration. -"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":607,"byte_end":608,"line_start":16,"line_end":16,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":598,"byte_end":604,"line_start":16,"line_end":16,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":608,"byte_end":608,"line_start":16,"line_end":16,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:16:22: error[E0308]: mismatched types: expected `String`, found integer +"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":672,"byte_end":673,"line_start":18,"line_end":18,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":663,"byte_end":669,"line_start":18,"line_end":18,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":673,"byte_end":673,"line_start":18,"line_end":18,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:18:22: error[E0308]: mismatched types: expected `String`, found integer "} {"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type. @@ -52,7 +52,7 @@ This error occurs when an expression was used in a place where the compiler expected an expression of a different type. It can occur in several cases, the most common being when calling a function and passing an argument which has a different type than the matching type in the function declaration. -"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":667,"byte_end":668,"line_start":18,"line_end":18,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":658,"byte_end":664,"line_start":18,"line_end":18,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":668,"byte_end":668,"line_start":18,"line_end":18,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:18:22: error[E0308]: mismatched types: expected `String`, found integer +"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":732,"byte_end":733,"line_start":20,"line_end":20,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":723,"byte_end":729,"line_start":20,"line_end":20,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":733,"byte_end":733,"line_start":20,"line_end":20,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:20:22: error[E0308]: mismatched types: expected `String`, found integer "} {"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type. @@ -80,7 +80,7 @@ This error occurs when an expression was used in a place where the compiler expected an expression of a different type. It can occur in several cases, the most common being when calling a function and passing an argument which has a different type than the matching type in the function declaration. -"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":731,"byte_end":732,"line_start":22,"line_end":22,"column_start":1,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":1,"highlight_end":2}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":721,"byte_end":727,"line_start":21,"line_end":21,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String =","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":732,"byte_end":732,"line_start":22,"line_end":22,"column_start":2,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":2,"highlight_end":2}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:22:1: error[E0308]: mismatched types: expected `String`, found integer +"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":796,"byte_end":797,"line_start":24,"line_end":24,"column_start":1,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":1,"highlight_end":2}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":786,"byte_end":792,"line_start":23,"line_end":23,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String =","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":797,"byte_end":797,"line_start":24,"line_end":24,"column_start":2,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":2,"highlight_end":2}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf.rs:24:1: error[E0308]: mismatched types: expected `String`, found integer "} {"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type. @@ -108,7 +108,7 @@ This error occurs when an expression was used in a place where the compiler expected an expression of a different type. It can occur in several cases, the most common being when calling a function and passing an argument which has a different type than the matching type in the function declaration. -"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":787,"byte_end":795,"line_start":24,"line_end":25,"column_start":22,"column_end":6,"is_primary":true,"text":[{"text":" let s : String = (","highlight_start":22,"highlight_end":23},{"text":" ); // Error spanning the newline.","highlight_start":1,"highlight_end":6}],"label":"expected `String`, found `()`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":778,"byte_end":784,"line_start":24,"line_end":24,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = (","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"$DIR/json-bom-plus-crlf.rs:24:22: error[E0308]: mismatched types: expected `String`, found `()` +"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":852,"byte_end":860,"line_start":26,"line_end":27,"column_start":22,"column_end":6,"is_primary":true,"text":[{"text":" let s : String = (","highlight_start":22,"highlight_end":23},{"text":" ); // Error spanning the newline.","highlight_start":1,"highlight_end":6}],"label":"expected `String`, found `()`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf.rs","byte_start":843,"byte_end":849,"line_start":26,"line_end":26,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = (","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"$DIR/json-bom-plus-crlf.rs:26:22: error[E0308]: mismatched types: expected `String`, found `()` "} {"$message_type":"diagnostic","message":"aborting due to 4 previous errors","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 4 previous errors "} diff --git a/tests/ui/lexer/lexer-crlf-line-endings-string-literal-doc-comment.rs b/tests/ui/lexer/lexer-crlf-line-endings-string-literal-doc-comment.rs index b355997a4b370..ee193a58ce1b2 100644 --- a/tests/ui/lexer/lexer-crlf-line-endings-string-literal-doc-comment.rs +++ b/tests/ui/lexer/lexer-crlf-line-endings-string-literal-doc-comment.rs @@ -1,4 +1,5 @@ //@ run-pass +//@ reference: input.crlf // ignore-tidy-cr // ignore-tidy-cr (repeated again because of tidy bug) // license is ignored because tidy can't handle the CRLF here properly. diff --git a/tests/ui/macros/not-utf8.rs b/tests/ui/macros/not-utf8.rs index 3c433a4e27c97..8100d65a9f811 100644 --- a/tests/ui/macros/not-utf8.rs +++ b/tests/ui/macros/not-utf8.rs @@ -1,4 +1,6 @@ //@ error-pattern: did not contain valid UTF-8 +//@ reference: input.encoding.utf8 +//@ reference: input.encoding.invalid fn foo() { include!("not-utf8.bin") diff --git a/tests/ui/macros/not-utf8.stderr b/tests/ui/macros/not-utf8.stderr index bf4704285c0dd..0d587cab5f3ed 100644 --- a/tests/ui/macros/not-utf8.stderr +++ b/tests/ui/macros/not-utf8.stderr @@ -1,5 +1,5 @@ error: couldn't read $DIR/not-utf8.bin: stream did not contain valid UTF-8 - --> $DIR/not-utf8.rs:4:5 + --> $DIR/not-utf8.rs:6:5 | LL | include!("not-utf8.bin") | ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/parser/shebang/issue-71471-ignore-tidy.rs b/tests/ui/parser/shebang/issue-71471-ignore-tidy.rs index a2505180884aa..e9bff91f1e8ce 100644 --- a/tests/ui/parser/shebang/issue-71471-ignore-tidy.rs +++ b/tests/ui/parser/shebang/issue-71471-ignore-tidy.rs @@ -1,2 +1,4 @@ #!B //~ expected `[`, found `B` + +//@ reference: input.shebang diff --git a/tests/ui/parser/shebang/multiline-attrib.rs b/tests/ui/parser/shebang/multiline-attrib.rs index bb083610e551b..2d2e02986386a 100644 --- a/tests/ui/parser/shebang/multiline-attrib.rs +++ b/tests/ui/parser/shebang/multiline-attrib.rs @@ -1,6 +1,7 @@ #! [allow(unused_variables)] //@ check-pass +//@ reference: input.shebang.inner-attribute fn main() { let x = 5; diff --git a/tests/ui/parser/shebang/regular-attrib.rs b/tests/ui/parser/shebang/regular-attrib.rs index aed633d3ef1e0..c2ac25661ef03 100644 --- a/tests/ui/parser/shebang/regular-attrib.rs +++ b/tests/ui/parser/shebang/regular-attrib.rs @@ -1,5 +1,6 @@ #![allow(unused_variables)] //@ check-pass +//@ reference: input.shebang.inner-attribute fn main() { let x = 5; } diff --git a/tests/ui/parser/shebang/shebang-and-attrib.rs b/tests/ui/parser/shebang/shebang-and-attrib.rs index a66c10db5321b..d73db6b22f054 100644 --- a/tests/ui/parser/shebang/shebang-and-attrib.rs +++ b/tests/ui/parser/shebang/shebang-and-attrib.rs @@ -1,6 +1,7 @@ #!/usr/bin/env run-cargo-script //@ check-pass +//@ reference: input.shebang.inner-attribute #![allow(unused_variables)] diff --git a/tests/ui/parser/shebang/shebang-comment.rs b/tests/ui/parser/shebang/shebang-comment.rs index 37bcac8b29eeb..6f89e8954c3aa 100644 --- a/tests/ui/parser/shebang/shebang-comment.rs +++ b/tests/ui/parser/shebang/shebang-comment.rs @@ -1,6 +1,7 @@ #!//bin/bash //@ check-pass +//@ reference: input.shebang fn main() { println!("a valid shebang (that is also a rust comment)") } diff --git a/tests/ui/parser/shebang/shebang-doc-comment.rs b/tests/ui/parser/shebang/shebang-doc-comment.rs index 72866753e0e6e..4992c758325a1 100644 --- a/tests/ui/parser/shebang/shebang-doc-comment.rs +++ b/tests/ui/parser/shebang/shebang-doc-comment.rs @@ -1,3 +1,5 @@ #!///bin/bash [allow(unused_variables)] //~^ ERROR expected item, found `[` + +//@ reference: input.shebang.inner-attribute diff --git a/tests/ui/parser/shebang/shebang-empty.rs b/tests/ui/parser/shebang/shebang-empty.rs index bb0df599783ee..51bf8a07c907f 100644 --- a/tests/ui/parser/shebang/shebang-empty.rs +++ b/tests/ui/parser/shebang/shebang-empty.rs @@ -1,4 +1,5 @@ #! //@ check-pass +//@ reference: input.shebang fn main() {} diff --git a/tests/ui/parser/shebang/shebang-must-start-file.rs b/tests/ui/parser/shebang/shebang-must-start-file.rs index e0392572dc81d..f91e32f744e15 100644 --- a/tests/ui/parser/shebang/shebang-must-start-file.rs +++ b/tests/ui/parser/shebang/shebang-must-start-file.rs @@ -1,6 +1,8 @@ // something on the first line for tidy #!/bin/bash //~ expected `[`, found `/` +//@ reference: input.shebang + fn main() { println!("ok!"); } diff --git a/tests/ui/parser/shebang/shebang-space.rs b/tests/ui/parser/shebang/shebang-space.rs index cc58eed8b8abb..803d4e040572e 100644 --- a/tests/ui/parser/shebang/shebang-space.rs +++ b/tests/ui/parser/shebang/shebang-space.rs @@ -1,5 +1,6 @@ #! //@ check-pass +//@ reference: input.shebang // ignore-tidy-end-whitespace fn main() {} diff --git a/tests/ui/parser/shebang/sneaky-attrib.rs b/tests/ui/parser/shebang/sneaky-attrib.rs index eb814c6af2475..e22c45cc39f58 100644 --- a/tests/ui/parser/shebang/sneaky-attrib.rs +++ b/tests/ui/parser/shebang/sneaky-attrib.rs @@ -11,6 +11,7 @@ [allow(unused_variables)] //@ check-pass +//@ reference: input.shebang.inner-attribute fn main() { let x = 5; } diff --git a/tests/ui/parser/shebang/valid-shebang.rs b/tests/ui/parser/shebang/valid-shebang.rs index e59d4074ddf9c..f93b0e15d7729 100644 --- a/tests/ui/parser/shebang/valid-shebang.rs +++ b/tests/ui/parser/shebang/valid-shebang.rs @@ -1,6 +1,7 @@ #!/usr/bin/env run-cargo-script //@ check-pass +//@ reference: input.shebang fn main() { println!("Hello World!"); } diff --git a/tests/ui/parser/utf16-be-without-bom.rs b/tests/ui/parser/utf16-be-without-bom.rs index 68e89bc7c223c..f5fe8dc5a8c79 100644 Binary files a/tests/ui/parser/utf16-be-without-bom.rs and b/tests/ui/parser/utf16-be-without-bom.rs differ diff --git a/tests/ui/parser/utf16-be-without-bom.stderr b/tests/ui/parser/utf16-be-without-bom.stderr index 55ebf7aacd237..b915c7941b3bb 100644 --- a/tests/ui/parser/utf16-be-without-bom.stderr +++ b/tests/ui/parser/utf16-be-without-bom.stderr @@ -1,5 +1,5 @@ error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:1 + --> $DIR/utf16-be-without-bom.rs:5:1 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -7,7 +7,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:3 + --> $DIR/utf16-be-without-bom.rs:5:3 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -15,7 +15,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:5 + --> $DIR/utf16-be-without-bom.rs:5:5 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -23,7 +23,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:7 + --> $DIR/utf16-be-without-bom.rs:5:7 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -31,7 +31,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:9 + --> $DIR/utf16-be-without-bom.rs:5:9 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -39,7 +39,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:11 + --> $DIR/utf16-be-without-bom.rs:5:11 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -47,7 +47,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:13 + --> $DIR/utf16-be-without-bom.rs:5:13 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -55,7 +55,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:15 + --> $DIR/utf16-be-without-bom.rs:5:15 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -63,7 +63,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:17 + --> $DIR/utf16-be-without-bom.rs:5:17 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -71,7 +71,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:19 + --> $DIR/utf16-be-without-bom.rs:5:19 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -79,7 +79,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:21 + --> $DIR/utf16-be-without-bom.rs:5:21 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -87,7 +87,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:23 + --> $DIR/utf16-be-without-bom.rs:5:23 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -95,7 +95,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-be-without-bom.rs:4:25 + --> $DIR/utf16-be-without-bom.rs:5:25 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -103,7 +103,7 @@ LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: expected one of `!` or `::`, found `n` - --> $DIR/utf16-be-without-bom.rs:4:4 + --> $DIR/utf16-be-without-bom.rs:5:4 | LL | ␀f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ expected one of `!` or `::` diff --git a/tests/ui/parser/utf16-le-without-bom.rs b/tests/ui/parser/utf16-le-without-bom.rs index bdf4860d016f3..8c781b27dc02e 100644 Binary files a/tests/ui/parser/utf16-le-without-bom.rs and b/tests/ui/parser/utf16-le-without-bom.rs differ diff --git a/tests/ui/parser/utf16-le-without-bom.stderr b/tests/ui/parser/utf16-le-without-bom.stderr index ad272a70f0627..d937a07bc6602 100644 --- a/tests/ui/parser/utf16-le-without-bom.stderr +++ b/tests/ui/parser/utf16-le-without-bom.stderr @@ -1,5 +1,5 @@ error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:2 + --> $DIR/utf16-le-without-bom.rs:5:2 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -7,7 +7,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:4 + --> $DIR/utf16-le-without-bom.rs:5:4 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -15,7 +15,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:6 + --> $DIR/utf16-le-without-bom.rs:5:6 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -23,7 +23,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:8 + --> $DIR/utf16-le-without-bom.rs:5:8 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -31,7 +31,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:10 + --> $DIR/utf16-le-without-bom.rs:5:10 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -39,7 +39,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:12 + --> $DIR/utf16-le-without-bom.rs:5:12 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -47,7 +47,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:14 + --> $DIR/utf16-le-without-bom.rs:5:14 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -55,7 +55,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:16 + --> $DIR/utf16-le-without-bom.rs:5:16 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -63,7 +63,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:18 + --> $DIR/utf16-le-without-bom.rs:5:18 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -71,7 +71,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:20 + --> $DIR/utf16-le-without-bom.rs:5:20 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -79,7 +79,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:22 + --> $DIR/utf16-le-without-bom.rs:5:22 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -87,7 +87,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:4:24 + --> $DIR/utf16-le-without-bom.rs:5:24 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ @@ -95,7 +95,7 @@ LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: unknown start of token: \u{0} - --> $DIR/utf16-le-without-bom.rs:5:1 + --> $DIR/utf16-le-without-bom.rs:6:1 | LL | ␀ | ^ @@ -103,7 +103,7 @@ LL | ␀ = help: source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used error: expected one of `!` or `::`, found `n` - --> $DIR/utf16-le-without-bom.rs:4:3 + --> $DIR/utf16-le-without-bom.rs:5:3 | LL | f␀n␀ ␀m␀a␀i␀n␀(␀)␀ ␀{␀}␀ | ^ expected one of `!` or `::` diff --git a/tests/ui/traits/const-traits/do-not-const-check.rs b/tests/ui/traits/const-traits/do-not-const-check.rs index 443b638573576..d227a9a9c094b 100644 --- a/tests/ui/traits/const-traits/do-not-const-check.rs +++ b/tests/ui/traits/const-traits/do-not-const-check.rs @@ -1,5 +1,6 @@ //@ check-pass -#![feature(const_trait_impl, rustc_attrs)] +#![feature(const_trait_impl, rustc_attrs, effects)] +//~^ WARN the feature `effects` is incomplete #[const_trait] trait IntoIter { diff --git a/tests/ui/traits/const-traits/do-not-const-check.stderr b/tests/ui/traits/const-traits/do-not-const-check.stderr new file mode 100644 index 0000000000000..0d81ef74e8d31 --- /dev/null +++ b/tests/ui/traits/const-traits/do-not-const-check.stderr @@ -0,0 +1,11 @@ +warning: the feature `effects` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/do-not-const-check.rs:2:43 + | +LL | #![feature(const_trait_impl, rustc_attrs, effects)] + | ^^^^^^^ + | + = note: see issue #102090 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/utf8-bom.rs b/tests/ui/utf8-bom.rs index 5b9e27fb7b942..eb82f6652cb29 100644 --- a/tests/ui/utf8-bom.rs +++ b/tests/ui/utf8-bom.rs @@ -1,4 +1,5 @@ // This file has utf-8 BOM, it should be compiled normally without error. //@ run-pass +//@ reference: input.byte-order-mark pub fn main() {}