Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2025-06-29 05:06:26 +00:00
commit 7b985d5435
66 changed files with 1363 additions and 273 deletions

View file

@ -407,6 +407,18 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
source_info.span,
)
}
AssertKind::InvalidEnumConstruction(source) => {
let source = codegen_operand(fx, source).load_scalar(fx);
let location = fx.get_caller_location(source_info).load_scalar(fx);
codegen_panic_inner(
fx,
rustc_hir::LangItem::PanicInvalidEnumConstruction,
&[source, location],
*unwind,
source_info.span,
)
}
_ => {
let location = fx.get_caller_location(source_info).load_scalar(fx);

View file

@ -776,6 +776,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// `#[track_caller]` adds an implicit argument.
(LangItem::PanicNullPointerDereference, vec![location])
}
AssertKind::InvalidEnumConstruction(source) => {
let source = self.codegen_operand(bx, source).immediate();
// It's `fn panic_invalid_enum_construction(source: u128)`,
// `#[track_caller]` adds an implicit argument.
(LangItem::PanicInvalidEnumConstruction, vec![source, location])
}
_ => {
// It's `pub fn panic_...()` and `#[track_caller]` adds an implicit argument.
(msg.panic_function(), vec![location])

View file

@ -508,6 +508,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
found: eval_to_int(found)?,
},
NullPointerDereference => NullPointerDereference,
InvalidEnumConstruction(source) => InvalidEnumConstruction(eval_to_int(source)?),
};
Err(ConstEvalErrKind::AssertFailure(err)).into()
}

View file

@ -21,13 +21,14 @@ fn alloc_caller_location<'tcx>(
assert!(!filename.as_str().as_bytes().contains(&0));
let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail;
let file_wide_ptr = {
let filename = {
let filename = if loc_details.file { filename.as_str() } else { "<redacted>" };
let filename_with_nul = filename.to_owned() + "\0";
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
// pointless, since that would require allocating more memory than these short strings.
let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap();
Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx)
let file_len = u64::try_from(filename.len()).unwrap();
Immediate::new_slice(file_ptr.into(), file_len, ecx)
};
let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };
let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) };
@ -41,11 +42,8 @@ fn alloc_caller_location<'tcx>(
let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();
// Initialize fields.
ecx.write_immediate(
file_wide_ptr,
&ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap(),
)
.expect("writing to memory we just allocated cannot fail");
ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap())
.expect("writing to memory we just allocated cannot fail");
ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap())
.expect("writing to memory we just allocated cannot fail");
ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap())

View file

@ -312,6 +312,7 @@ language_item_table! {
PanicAsyncGenFnResumedPanic, sym::panic_const_async_gen_fn_resumed_panic, panic_const_async_gen_fn_resumed_panic, Target::Fn, GenericRequirement::None;
PanicGenFnNonePanic, sym::panic_const_gen_fn_none_panic, panic_const_gen_fn_none_panic, Target::Fn, GenericRequirement::None;
PanicNullPointerDereference, sym::panic_null_pointer_dereference, panic_null_pointer_dereference, Target::Fn, GenericRequirement::None;
PanicInvalidEnumConstruction, sym::panic_invalid_enum_construction, panic_invalid_enum_construction, Target::Fn, GenericRequirement::None;
PanicCoroutineResumedDrop, sym::panic_const_coroutine_resumed_drop, panic_const_coroutine_resumed_drop, Target::Fn, GenericRequirement::None;
PanicAsyncFnResumedDrop, sym::panic_const_async_fn_resumed_drop, panic_const_async_fn_resumed_drop, Target::Fn, GenericRequirement::None;
PanicAsyncGenFnResumedDrop, sym::panic_const_async_gen_fn_resumed_drop, panic_const_async_gen_fn_resumed_drop, Target::Fn, GenericRequirement::None;

View file

@ -190,17 +190,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
let mut symbols_stream = quote! {};
let mut prefill_stream = quote! {};
let mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10);
let mut prev_key: Option<(Span, String)> = None;
let mut check_order = |span: Span, s: &str, errors: &mut Errors| {
if let Some((prev_span, ref prev_str)) = prev_key {
if s < prev_str {
errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`"));
errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
}
}
prev_key = Some((span, s.to_string()));
};
// Generate the listed keywords.
for keyword in input.keywords.iter() {
@ -219,7 +208,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
// Generate the listed symbols.
for symbol in input.symbols.iter() {
let name = &symbol.name;
check_order(symbol.name.span(), &name.to_string(), &mut errors);
let value = match &symbol.value {
Value::SameAsName => name.to_string(),

View file

@ -84,18 +84,3 @@ fn check_dup_symbol_and_keyword() {
};
test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]);
}
#[test]
fn check_symbol_order() {
let input = quote! {
Keywords {}
Symbols {
zebra,
aardvark,
}
};
test_symbols_macro(
input,
&["Symbol `aardvark` must precede `zebra`", "location of previous symbol `zebra`"],
);
}

View file

@ -17,6 +17,9 @@ middle_assert_gen_resume_after_drop = `gen` fn or block cannot be further iterat
middle_assert_gen_resume_after_panic = `gen` fn or block cannot be further iterated on after it panicked
middle_assert_invalid_enum_construction =
trying to construct an enum from an invalid value `{$source}`
middle_assert_misaligned_ptr_deref =
misaligned pointer dereference: address must be a multiple of {$required} but is {$found}

View file

@ -1075,6 +1075,7 @@ pub enum AssertKind<O> {
ResumedAfterDrop(CoroutineKind),
MisalignedPointerDereference { required: O, found: O },
NullPointerDereference,
InvalidEnumConstruction(O),
}
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]

View file

@ -208,6 +208,7 @@ impl<O> AssertKind<O> {
LangItem::PanicGenFnNonePanic
}
NullPointerDereference => LangItem::PanicNullPointerDereference,
InvalidEnumConstruction(_) => LangItem::PanicInvalidEnumConstruction,
ResumedAfterDrop(CoroutineKind::Coroutine(_)) => LangItem::PanicCoroutineResumedDrop,
ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
LangItem::PanicAsyncFnResumedDrop
@ -284,6 +285,9 @@ impl<O> AssertKind<O> {
)
}
NullPointerDereference => write!(f, "\"null pointer dereference occurred\""),
InvalidEnumConstruction(source) => {
write!(f, "\"trying to construct an enum from an invalid value {{}}\", {source:?}")
}
ResumedAfterReturn(CoroutineKind::Coroutine(_)) => {
write!(f, "\"coroutine resumed after completion\"")
}
@ -367,6 +371,7 @@ impl<O> AssertKind<O> {
middle_assert_coroutine_resume_after_panic
}
NullPointerDereference => middle_assert_null_ptr_deref,
InvalidEnumConstruction(_) => middle_assert_invalid_enum_construction,
ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
middle_assert_async_resume_after_drop
}
@ -420,6 +425,9 @@ impl<O> AssertKind<O> {
add!("required", format!("{required:#?}"));
add!("found", format!("{found:#?}"));
}
InvalidEnumConstruction(source) => {
add!("source", format!("{source:#?}"));
}
}
}
}

View file

@ -642,7 +642,7 @@ macro_rules! make_mir_visitor {
self.visit_operand(l, location);
self.visit_operand(r, location);
}
OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => {
OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) | InvalidEnumConstruction(op) => {
self.visit_operand(op, location);
}
ResumedAfterReturn(_) | ResumedAfterPanic(_) | NullPointerDereference | ResumedAfterDrop(_) => {

View file

@ -1283,15 +1283,15 @@ rustc_queries! {
return_result_from_ensure_ok
}
/// Check whether the function has any recursion that could cause the inliner to trigger
/// a cycle.
query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool {
/// Return the set of (transitive) callees that may result in a recursive call to `key`.
query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet<LocalDefId> {
fatal_cycle
arena_cache
desc { |tcx|
"computing if `{}` (transitively) calls `{}`",
key.0,
tcx.def_path_str(key.1),
"computing (transitive) callees of `{}` that may recurse",
tcx.def_path_str(key),
}
cache_on_disk_if { true }
}
/// Obtain all the calls into other local functions

View file

@ -0,0 +1,501 @@
use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange};
use rustc_hir::LangItem;
use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::layout::PrimitiveExt;
use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv};
use rustc_session::Session;
use tracing::debug;
/// This pass inserts checks for a valid enum discriminant where they are most
/// likely to find UB, because checking everywhere like Miri would generate too
/// much MIR.
pub(super) struct CheckEnums;
impl<'tcx> crate::MirPass<'tcx> for CheckEnums {
fn is_enabled(&self, sess: &Session) -> bool {
sess.ub_checks()
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// This pass emits new panics. If for whatever reason we do not have a panic
// implementation, running this pass may cause otherwise-valid code to not compile.
if tcx.lang_items().get(LangItem::PanicImpl).is_none() {
return;
}
let typing_env = body.typing_env(tcx);
let basic_blocks = body.basic_blocks.as_mut();
let local_decls = &mut body.local_decls;
// This operation inserts new blocks. Each insertion changes the Location for all
// statements/blocks after. Iterating or visiting the MIR in order would require updating
// our current location after every insertion. By iterating backwards, we dodge this issue:
// The only Locations that an insertion changes have already been handled.
for block in basic_blocks.indices().rev() {
for statement_index in (0..basic_blocks[block].statements.len()).rev() {
let location = Location { block, statement_index };
let statement = &basic_blocks[block].statements[statement_index];
let source_info = statement.source_info;
let mut finder = EnumFinder::new(tcx, local_decls, typing_env);
finder.visit_statement(statement, location);
for check in finder.into_found_enums() {
debug!("Inserting enum check");
let new_block = split_block(basic_blocks, location);
match check {
EnumCheckType::Direct { source_op, discr, op_size, valid_discrs } => {
insert_direct_enum_check(
tcx,
local_decls,
basic_blocks,
block,
source_op,
discr,
op_size,
valid_discrs,
source_info,
new_block,
)
}
EnumCheckType::Uninhabited => insert_uninhabited_enum_check(
tcx,
local_decls,
&mut basic_blocks[block],
source_info,
new_block,
),
EnumCheckType::WithNiche {
source_op,
discr,
op_size,
offset,
valid_range,
} => insert_niche_check(
tcx,
local_decls,
&mut basic_blocks[block],
source_op,
valid_range,
discr,
op_size,
offset,
source_info,
new_block,
),
}
}
}
}
}
fn is_required(&self) -> bool {
true
}
}
/// Represent the different kind of enum checks we can insert.
enum EnumCheckType<'tcx> {
/// We know we try to create an uninhabited enum from an inhabited variant.
Uninhabited,
/// We know the enum does no niche optimizations and can thus easily compute
/// the valid discriminants.
Direct {
source_op: Operand<'tcx>,
discr: TyAndSize<'tcx>,
op_size: Size,
valid_discrs: Vec<u128>,
},
/// We try to construct an enum that has a niche.
WithNiche {
source_op: Operand<'tcx>,
discr: TyAndSize<'tcx>,
op_size: Size,
offset: Size,
valid_range: WrappingRange,
},
}
struct TyAndSize<'tcx> {
pub ty: Ty<'tcx>,
pub size: Size,
}
/// A [Visitor] that finds the construction of enums and evaluates which checks
/// we should apply.
struct EnumFinder<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
local_decls: &'a mut LocalDecls<'tcx>,
typing_env: TypingEnv<'tcx>,
enums: Vec<EnumCheckType<'tcx>>,
}
impl<'a, 'tcx> EnumFinder<'a, 'tcx> {
fn new(
tcx: TyCtxt<'tcx>,
local_decls: &'a mut LocalDecls<'tcx>,
typing_env: TypingEnv<'tcx>,
) -> Self {
EnumFinder { tcx, local_decls, typing_env, enums: Vec::new() }
}
/// Returns the found enum creations and which checks should be inserted.
fn into_found_enums(self) -> Vec<EnumCheckType<'tcx>> {
self.enums
}
}
impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> {
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue {
let ty::Adt(adt_def, _) = ty.kind() else {
return;
};
if !adt_def.is_enum() {
return;
}
let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else {
return;
};
let Ok(op_layout) = self
.tcx
.layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx)))
else {
return;
};
match enum_layout.variants {
Variants::Empty if op_layout.is_uninhabited() => return,
// An empty enum that tries to be constructed from an inhabited value, this
// is never correct.
Variants::Empty => {
// The enum layout is uninhabited but we construct it from sth inhabited.
// This is always UB.
self.enums.push(EnumCheckType::Uninhabited);
}
// Construction of Single value enums is always fine.
Variants::Single { .. } => {}
// Construction of an enum with multiple variants but no niche optimizations.
Variants::Multiple {
tag_encoding: TagEncoding::Direct,
tag: Scalar::Initialized { value, .. },
..
} => {
let valid_discrs =
adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect();
let discr =
TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
self.enums.push(EnumCheckType::Direct {
source_op: op.to_copy(),
discr,
op_size: op_layout.size,
valid_discrs,
});
}
// Construction of an enum with multiple variants and niche optimizations.
Variants::Multiple {
tag_encoding: TagEncoding::Niche { .. },
tag: Scalar::Initialized { value, valid_range, .. },
tag_field,
..
} => {
let discr =
TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
self.enums.push(EnumCheckType::WithNiche {
source_op: op.to_copy(),
discr,
op_size: op_layout.size,
offset: enum_layout.fields.offset(tag_field.as_usize()),
valid_range,
});
}
_ => return,
}
self.super_rvalue(rvalue, location);
}
}
}
fn split_block(
basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'_>>,
location: Location,
) -> BasicBlock {
let block_data = &mut basic_blocks[location.block];
// Drain every statement after this one and move the current terminator to a new basic block.
let new_block = BasicBlockData {
statements: block_data.statements.split_off(location.statement_index),
terminator: block_data.terminator.take(),
is_cleanup: block_data.is_cleanup,
};
basic_blocks.push(new_block)
}
/// Inserts the cast of an operand (any type) to a u128 value that holds the discriminant value.
fn insert_discr_cast_to_u128<'tcx>(
tcx: TyCtxt<'tcx>,
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
block_data: &mut BasicBlockData<'tcx>,
source_op: Operand<'tcx>,
discr: TyAndSize<'tcx>,
op_size: Size,
offset: Option<Size>,
source_info: SourceInfo,
) -> Place<'tcx> {
let get_ty_for_size = |tcx: TyCtxt<'tcx>, size: Size| -> Ty<'tcx> {
match size.bytes() {
1 => tcx.types.u8,
2 => tcx.types.u16,
4 => tcx.types.u32,
8 => tcx.types.u64,
16 => tcx.types.u128,
invalid => bug!("Found discriminant with invalid size, has {} bytes", invalid),
}
};
let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() {
// The discriminant is less wide than the operand, cast the operand into
// [MaybeUninit; N] and then index into it.
let mu = Ty::new_maybe_uninit(tcx, tcx.types.u8);
let array_len = op_size.bytes();
let mu_array_ty = Ty::new_array(tcx, mu, array_len);
let mu_array =
local_decls.push(LocalDecl::with_source_info(mu_array_ty, source_info)).into();
let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_array_ty);
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((mu_array, rvalue))),
});
// Index into the array of MaybeUninit to get something that is actually
// as wide as the discriminant.
let offset = offset.unwrap_or(Size::ZERO);
let smaller_mu_array = mu_array.project_deeper(
&[ProjectionElem::Subslice {
from: offset.bytes(),
to: offset.bytes() + discr.size.bytes(),
from_end: false,
}],
tcx,
);
(CastKind::Transmute, Operand::Copy(smaller_mu_array))
} else {
let operand_int_ty = get_ty_for_size(tcx, op_size);
let op_as_int =
local_decls.push(LocalDecl::with_source_info(operand_int_ty, source_info)).into();
let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, operand_int_ty);
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((op_as_int, rvalue))),
});
(CastKind::IntToInt, Operand::Copy(op_as_int))
};
// Cast the resulting value to the actual discriminant integer type.
let rvalue = Rvalue::Cast(cast_kind, discr_ty_bits, discr.ty);
let discr_in_discr_ty =
local_decls.push(LocalDecl::with_source_info(discr.ty, source_info)).into();
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((discr_in_discr_ty, rvalue))),
});
// Cast the discriminant to a u128 (base for comparisions of enum discriminants).
let const_u128 = Ty::new_uint(tcx, ty::UintTy::U128);
let rvalue = Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_in_discr_ty), const_u128);
let discr = local_decls.push(LocalDecl::with_source_info(const_u128, source_info)).into();
block_data
.statements
.push(Statement { source_info, kind: StatementKind::Assign(Box::new((discr, rvalue))) });
discr
}
fn insert_direct_enum_check<'tcx>(
tcx: TyCtxt<'tcx>,
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'tcx>>,
current_block: BasicBlock,
source_op: Operand<'tcx>,
discr: TyAndSize<'tcx>,
op_size: Size,
discriminants: Vec<u128>,
source_info: SourceInfo,
new_block: BasicBlock,
) {
// Insert a new target block that is branched to in case of an invalid discriminant.
let invalid_discr_block_data = BasicBlockData::new(None, false);
let invalid_discr_block = basic_blocks.push(invalid_discr_block_data);
let block_data = &mut basic_blocks[current_block];
let discr = insert_discr_cast_to_u128(
tcx,
local_decls,
block_data,
source_op,
discr,
op_size,
None,
source_info,
);
// Branch based on the discriminant value.
block_data.terminator = Some(Terminator {
source_info,
kind: TerminatorKind::SwitchInt {
discr: Operand::Copy(discr),
targets: SwitchTargets::new(
discriminants.into_iter().map(|discr| (discr, new_block)),
invalid_discr_block,
),
},
});
// Abort in case of an invalid enum discriminant.
basic_blocks[invalid_discr_block].terminator = Some(Terminator {
source_info,
kind: TerminatorKind::Assert {
cond: Operand::Constant(Box::new(ConstOperand {
span: source_info.span,
user_ty: None,
const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
})),
expected: true,
target: new_block,
msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))),
// This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
// We never want to insert an unwind into unsafe code, because unwinding could
// make a failing UB check turn into much worse UB when we start unwinding.
unwind: UnwindAction::Unreachable,
},
});
}
fn insert_uninhabited_enum_check<'tcx>(
tcx: TyCtxt<'tcx>,
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
block_data: &mut BasicBlockData<'tcx>,
source_info: SourceInfo,
new_block: BasicBlock,
) {
let is_ok: Place<'_> =
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((
is_ok,
Rvalue::Use(Operand::Constant(Box::new(ConstOperand {
span: source_info.span,
user_ty: None,
const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
}))),
))),
});
block_data.terminator = Some(Terminator {
source_info,
kind: TerminatorKind::Assert {
cond: Operand::Copy(is_ok),
expected: true,
target: new_block,
msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new(
ConstOperand {
span: source_info.span,
user_ty: None,
const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128),
},
)))),
// This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
// We never want to insert an unwind into unsafe code, because unwinding could
// make a failing UB check turn into much worse UB when we start unwinding.
unwind: UnwindAction::Unreachable,
},
});
}
fn insert_niche_check<'tcx>(
tcx: TyCtxt<'tcx>,
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
block_data: &mut BasicBlockData<'tcx>,
source_op: Operand<'tcx>,
valid_range: WrappingRange,
discr: TyAndSize<'tcx>,
op_size: Size,
offset: Size,
source_info: SourceInfo,
new_block: BasicBlock,
) {
let discr = insert_discr_cast_to_u128(
tcx,
local_decls,
block_data,
source_op,
discr,
op_size,
Some(offset),
source_info,
);
// Compare the discriminant agains the valid_range.
let start_const = Operand::Constant(Box::new(ConstOperand {
span: source_info.span,
user_ty: None,
const_: Const::Val(ConstValue::from_u128(valid_range.start), tcx.types.u128),
}));
let end_start_diff_const = Operand::Constant(Box::new(ConstOperand {
span: source_info.span,
user_ty: None,
const_: Const::Val(
ConstValue::from_u128(u128::wrapping_sub(valid_range.end, valid_range.start)),
tcx.types.u128,
),
}));
let discr_diff: Place<'_> =
local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into();
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((
discr_diff,
Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(discr), start_const))),
))),
});
let is_ok: Place<'_> =
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
block_data.statements.push(Statement {
source_info,
kind: StatementKind::Assign(Box::new((
is_ok,
Rvalue::BinaryOp(
// This is a `WrappingRange`, so make sure to get the wrapping right.
BinOp::Le,
Box::new((Operand::Copy(discr_diff), end_start_diff_const)),
),
))),
});
block_data.terminator = Some(Terminator {
source_info,
kind: TerminatorKind::Assert {
cond: Operand::Copy(is_ok),
expected: true,
target: new_block,
msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))),
// This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
// We never want to insert an unwind into unsafe code, because unwinding could
// make a failing UB check turn into much worse UB when we start unwinding.
unwind: UnwindAction::Unreachable,
},
});
}

View file

@ -770,14 +770,15 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>(
return Ok(());
}
if callee_def_id.is_local()
if let Some(callee_def_id) = callee_def_id.as_local()
&& !inliner
.tcx()
.is_lang_item(inliner.tcx().parent(caller_def_id), rustc_hir::LangItem::FnOnce)
{
// If we know for sure that the function we're calling will itself try to
// call us, then we avoid inlining that function.
if inliner.tcx().mir_callgraph_reachable((callee, caller_def_id.expect_local())) {
if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id)
{
debug!("query cycle avoidance");
return Err("caller might be reachable from callee");
}

View file

@ -1,5 +1,6 @@
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_data_structures::unord::UnordSet;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::mir::TerminatorKind;
use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt};
@ -7,137 +8,143 @@ use rustc_session::Limit;
use rustc_span::sym;
use tracing::{instrument, trace};
// FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking
// this query ridiculously often.
#[instrument(level = "debug", skip(tcx, root, target))]
pub(crate) fn mir_callgraph_reachable<'tcx>(
tcx: TyCtxt<'tcx>,
(root, target): (ty::Instance<'tcx>, LocalDefId),
) -> bool {
trace!(%root, target = %tcx.def_path_str(target));
assert_ne!(
root.def_id().expect_local(),
target,
"you should not call `mir_callgraph_reachable` on immediate self recursion"
);
assert!(
matches!(root.def, InstanceKind::Item(_)),
"you should not call `mir_callgraph_reachable` on shims"
);
assert!(
!tcx.is_constructor(root.def_id()),
"you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
);
#[instrument(
level = "debug",
skip(tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit)
)]
fn process<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
caller: ty::Instance<'tcx>,
target: LocalDefId,
stack: &mut Vec<ty::Instance<'tcx>>,
seen: &mut FxHashSet<ty::Instance<'tcx>>,
recursion_limiter: &mut FxHashMap<DefId, usize>,
recursion_limit: Limit,
) -> bool {
trace!(%caller);
for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
tcx,
typing_env,
ty::EarlyBinder::bind(args),
) else {
trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
continue;
};
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
trace!(?callee, "cannot resolve, skipping");
continue;
};
// Found a path.
if callee.def_id() == target.to_def_id() {
return true;
}
if tcx.is_constructor(callee.def_id()) {
trace!("constructors always have MIR");
// Constructor functions cannot cause a query cycle.
continue;
}
match callee.def {
InstanceKind::Item(_) => {
// If there is no MIR available (either because it was not in metadata or
// because it has no MIR because it's an extern function), then the inliner
// won't cause cycles on this.
if !tcx.is_mir_available(callee.def_id()) {
trace!(?callee, "no mir available, skipping");
continue;
}
}
// These have no own callable MIR.
InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => continue,
// These have MIR and if that MIR is inlined, instantiated and then inlining is run
// again, a function item can end up getting inlined. Thus we'll be able to cause
// a cycle that way
InstanceKind::VTableShim(_)
| InstanceKind::ReifyShim(..)
| InstanceKind::FnPtrShim(..)
| InstanceKind::ClosureOnceShim { .. }
| InstanceKind::ConstructCoroutineInClosureShim { .. }
| InstanceKind::ThreadLocalShim { .. }
| InstanceKind::CloneShim(..) => {}
// This shim does not call any other functions, thus there can be no recursion.
InstanceKind::FnPtrAddrShim(..) => {
continue;
}
InstanceKind::DropGlue(..)
| InstanceKind::FutureDropPollShim(..)
| InstanceKind::AsyncDropGlue(..)
| InstanceKind::AsyncDropGlueCtorShim(..) => {
// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
// needs some more analysis.
if callee.has_param() {
continue;
}
}
}
if seen.insert(callee) {
let recursion = recursion_limiter.entry(callee.def_id()).or_default();
trace!(?callee, recursion = *recursion);
if recursion_limit.value_within_limit(*recursion) {
*recursion += 1;
stack.push(callee);
let found_recursion = ensure_sufficient_stack(|| {
process(
tcx,
typing_env,
callee,
target,
stack,
seen,
recursion_limiter,
recursion_limit,
)
});
if found_recursion {
return true;
}
stack.pop();
} else {
// Pessimistically assume that there could be recursion.
return true;
}
#[instrument(level = "debug", skip(tcx), ret)]
fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool {
match callee.def {
// If there is no MIR available (either because it was not in metadata or
// because it has no MIR because it's an extern function), then the inliner
// won't cause cycles on this.
InstanceKind::Item(_) => {
if !tcx.is_mir_available(callee.def_id()) {
return false;
}
}
// These have no own callable MIR.
InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => return false,
// These have MIR and if that MIR is inlined, instantiated and then inlining is run
// again, a function item can end up getting inlined. Thus we'll be able to cause
// a cycle that way
InstanceKind::VTableShim(_)
| InstanceKind::ReifyShim(..)
| InstanceKind::FnPtrShim(..)
| InstanceKind::ClosureOnceShim { .. }
| InstanceKind::ConstructCoroutineInClosureShim { .. }
| InstanceKind::ThreadLocalShim { .. }
| InstanceKind::CloneShim(..) => {}
// This shim does not call any other functions, thus there can be no recursion.
InstanceKind::FnPtrAddrShim(..) => return false,
// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
// needs some more analysis.
InstanceKind::DropGlue(..)
| InstanceKind::FutureDropPollShim(..)
| InstanceKind::AsyncDropGlue(..)
| InstanceKind::AsyncDropGlueCtorShim(..) => {
if callee.has_param() {
return false;
}
}
false
}
crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed)
|| crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id())
}
#[instrument(
level = "debug",
skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit),
ret
)]
fn process<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
caller: ty::Instance<'tcx>,
target: LocalDefId,
seen: &mut FxHashSet<ty::Instance<'tcx>>,
involved: &mut FxHashSet<LocalDefId>,
recursion_limiter: &mut FxHashMap<DefId, usize>,
recursion_limit: Limit,
) -> bool {
trace!(%caller);
let mut cycle_found = false;
for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
tcx,
typing_env,
ty::EarlyBinder::bind(args),
) else {
trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
continue;
};
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
trace!(?callee, "cannot resolve, skipping");
continue;
};
// Found a path.
if callee.def_id() == target.to_def_id() {
cycle_found = true;
}
if tcx.is_constructor(callee.def_id()) {
trace!("constructors always have MIR");
// Constructor functions cannot cause a query cycle.
continue;
}
if !should_recurse(tcx, callee) {
continue;
}
if seen.insert(callee) {
let recursion = recursion_limiter.entry(callee.def_id()).or_default();
trace!(?callee, recursion = *recursion);
let found_recursion = if recursion_limit.value_within_limit(*recursion) {
*recursion += 1;
ensure_sufficient_stack(|| {
process(
tcx,
typing_env,
callee,
target,
seen,
involved,
recursion_limiter,
recursion_limit,
)
})
} else {
// Pessimistically assume that there could be recursion.
true
};
if found_recursion {
if let Some(callee) = callee.def_id().as_local() {
// Calling `optimized_mir` of a non-local definition cannot cycle.
involved.insert(callee);
}
cycle_found = true;
}
}
}
cycle_found
}
#[instrument(level = "debug", skip(tcx), ret)]
pub(crate) fn mir_callgraph_cyclic<'tcx>(
tcx: TyCtxt<'tcx>,
root: LocalDefId,
) -> UnordSet<LocalDefId> {
assert!(
!tcx.is_constructor(root.to_def_id()),
"you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
);
// FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error.
// In code like that pointed out in #128887, the type complexity we ask the solver to deal with
// grows as we recurse into the call graph. If we use the same recursion limit here and in the
@ -146,16 +153,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
// the default recursion limits are quite generous for us. If we need to recurse 64 times
// into the call graph, we're probably not going to find any useful MIR inlining.
let recursion_limit = tcx.recursion_limit() / 2;
let mut involved = FxHashSet::default();
let typing_env = ty::TypingEnv::post_analysis(tcx, root);
let Ok(Some(root_instance)) = ty::Instance::try_resolve(
tcx,
typing_env,
root.to_def_id(),
ty::GenericArgs::identity_for_item(tcx, root.to_def_id()),
) else {
trace!("cannot resolve, skipping");
return involved.into();
};
if !should_recurse(tcx, root_instance) {
trace!("cannot walk, skipping");
return involved.into();
}
process(
tcx,
ty::TypingEnv::post_analysis(tcx, target),
typing_env,
root_instance,
root,
target,
&mut Vec::new(),
&mut FxHashSet::default(),
&mut involved,
&mut FxHashMap::default(),
recursion_limit,
)
);
involved.into()
}
pub(crate) fn mir_inliner_callees<'tcx>(

View file

@ -117,6 +117,7 @@ declare_passes! {
mod check_inline : CheckForceInline;
mod check_call_recursion : CheckCallRecursion, CheckDropRecursion;
mod check_alignment : CheckAlignment;
mod check_enums : CheckEnums;
mod check_const_item_mutation : CheckConstItemMutation;
mod check_null : CheckNull;
mod check_packed_ref : CheckPackedRef;
@ -215,7 +216,7 @@ pub fn provide(providers: &mut Providers) {
optimized_mir,
is_mir_available,
is_ctfe_mir_available: is_mir_available,
mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable,
mir_callgraph_cyclic: inline::cycle::mir_callgraph_cyclic,
mir_inliner_callees: inline::cycle::mir_inliner_callees,
promoted_mir,
deduced_param_attrs: deduce_param_attrs::deduced_param_attrs,
@ -666,6 +667,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'
// Add some UB checks before any UB gets optimized away.
&check_alignment::CheckAlignment,
&check_null::CheckNull,
&check_enums::CheckEnums,
// Before inlining: trim down MIR with passes to reduce inlining work.
// Has to be done before inlining, otherwise actual call will be almost always inlined.

View file

@ -834,6 +834,9 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
mir::AssertKind::NullPointerDereference => {
push_mono_lang_item(self, LangItem::PanicNullPointerDereference);
}
mir::AssertKind::InvalidEnumConstruction(_) => {
push_mono_lang_item(self, LangItem::PanicInvalidEnumConstruction);
}
_ => {
push_mono_lang_item(self, msg.panic_function());
}

View file

@ -506,6 +506,9 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> {
AssertKind::NullPointerDereference => {
stable_mir::mir::AssertMessage::NullPointerDereference
}
AssertKind::InvalidEnumConstruction(source) => {
stable_mir::mir::AssertMessage::InvalidEnumConstruction(source.stable(tables))
}
}
}
}

View file

@ -270,6 +270,7 @@ pub enum AssertMessage {
ResumedAfterDrop(CoroutineKind),
MisalignedPointerDereference { required: Operand, found: Operand },
NullPointerDereference,
InvalidEnumConstruction(Operand),
}
impl AssertMessage {
@ -342,6 +343,9 @@ impl AssertMessage {
Ok("misaligned pointer dereference")
}
AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"),
AssertMessage::InvalidEnumConstruction(_) => {
Ok("trying to construct an enum from an invalid value")
}
}
}
}

View file

@ -313,6 +313,10 @@ fn pretty_assert_message<W: Write>(writer: &mut W, msg: &AssertMessage) -> io::R
AssertMessage::NullPointerDereference => {
write!(writer, "\"null pointer dereference occurred\"")
}
AssertMessage::InvalidEnumConstruction(op) => {
let pretty_op = pretty_operand(op);
write!(writer, "\"trying to construct an enum from an invalid value {{}}\",{pretty_op}")
}
AssertMessage::ResumedAfterReturn(_)
| AssertMessage::ResumedAfterPanic(_)
| AssertMessage::ResumedAfterDrop(_) => {

View file

@ -367,7 +367,8 @@ macro_rules! make_mir_visitor {
}
AssertMessage::OverflowNeg(op)
| AssertMessage::DivisionByZero(op)
| AssertMessage::RemainderByZero(op) => {
| AssertMessage::RemainderByZero(op)
| AssertMessage::InvalidEnumConstruction(op) => {
self.visit_operand(op, location);
}
AssertMessage::ResumedAfterReturn(_)

View file

@ -150,14 +150,10 @@ symbols! {
// As well as the symbols listed, there are symbols for the strings
// "0", "1", ..., "9", which are accessible via `sym::integer`.
//
// The proc macro will abort if symbols are not in alphabetical order (as
// defined by `impl Ord for str`) or if any symbols are duplicated. Vim
// users can sort the list by selecting it and executing the command
// `:'<,'>!LC_ALL=C sort`.
//
// There is currently no checking that all symbols are used; that would be
// nice to have.
Symbols {
// tidy-alphabetical-start
Abi,
AcqRel,
Acquire,
@ -175,18 +171,18 @@ symbols! {
AsyncGenPending,
AsyncGenReady,
AtomicBool,
AtomicI128,
AtomicI8,
AtomicI16,
AtomicI32,
AtomicI64,
AtomicI8,
AtomicI128,
AtomicIsize,
AtomicPtr,
AtomicU128,
AtomicU8,
AtomicU16,
AtomicU32,
AtomicU64,
AtomicU8,
AtomicU128,
AtomicUsize,
BTreeEntry,
BTreeMap,
@ -607,10 +603,10 @@ symbols! {
catch_unwind,
cause,
cdylib,
ceilf128,
ceilf16,
ceilf32,
ceilf64,
ceilf128,
cfg,
cfg_accessible,
cfg_attr,
@ -747,10 +743,10 @@ symbols! {
copy,
copy_closures,
copy_nonoverlapping,
copysignf128,
copysignf16,
copysignf32,
copysignf64,
copysignf128,
core,
core_panic,
core_panic_2015_macro,
@ -763,10 +759,10 @@ symbols! {
coroutine_state,
coroutine_yield,
coroutines,
cosf128,
cosf16,
cosf32,
cosf64,
cosf128,
count,
coverage,
coverage_attribute,
@ -874,8 +870,8 @@ symbols! {
dotdot_in_tuple_patterns,
dotdoteq_in_patterns,
dreg,
dreg_low16,
dreg_low8,
dreg_low16,
drop,
drop_in_place,
drop_types_in_const,
@ -928,16 +924,16 @@ symbols! {
exhaustive_integer_patterns,
exhaustive_patterns,
existential_type,
exp2f128,
exp2f16,
exp2f32,
exp2f64,
exp2f128,
expect,
expected,
expf128,
expf16,
expf32,
expf64,
expf128,
explicit_extern_abis,
explicit_generic_args_with_impl_trait,
explicit_tail_calls,
@ -958,9 +954,6 @@ symbols! {
external,
external_doc,
f,
f128,
f128_epsilon,
f128_nan,
f16,
f16_epsilon,
f16_nan,
@ -999,10 +992,13 @@ symbols! {
f64_legacy_const_neg_infinity,
f64_legacy_const_radix,
f64_nan,
fabsf128,
f128,
f128_epsilon,
f128_nan,
fabsf16,
fabsf32,
fabsf64,
fabsf128,
fadd_algebraic,
fadd_fast,
fake_variadic,
@ -1024,22 +1020,22 @@ symbols! {
flags,
float,
float_to_int_unchecked,
floorf128,
floorf16,
floorf32,
floorf64,
fmaf128,
floorf128,
fmaf16,
fmaf32,
fmaf64,
fmaf128,
fmt,
fmt_debug,
fmul_algebraic,
fmul_fast,
fmuladdf128,
fmuladdf16,
fmuladdf32,
fmuladdf64,
fmuladdf128,
fn_align,
fn_body,
fn_delegation,
@ -1140,13 +1136,12 @@ symbols! {
html_root_url,
hwaddress,
i,
i128,
i128_legacy_const_max,
i128_legacy_const_min,
i128_legacy_fn_max_value,
i128_legacy_fn_min_value,
i128_legacy_mod,
i128_type,
i8,
i8_legacy_const_max,
i8_legacy_const_min,
i8_legacy_fn_max_value,
i8_legacy_fn_min_value,
i8_legacy_mod,
i16,
i16_legacy_const_max,
i16_legacy_const_min,
@ -1165,12 +1160,13 @@ symbols! {
i64_legacy_fn_max_value,
i64_legacy_fn_min_value,
i64_legacy_mod,
i8,
i8_legacy_const_max,
i8_legacy_const_min,
i8_legacy_fn_max_value,
i8_legacy_fn_min_value,
i8_legacy_mod,
i128,
i128_legacy_const_max,
i128_legacy_const_min,
i128_legacy_fn_max_value,
i128_legacy_fn_min_value,
i128_legacy_mod,
i128_type,
ident,
if_let,
if_let_guard,
@ -1292,19 +1288,19 @@ symbols! {
loaded_from_disk,
local,
local_inner_macros,
log10f128,
log10f16,
log10f32,
log10f64,
log2f128,
log2f16,
log2f32,
log2f64,
log2f128,
log10f16,
log10f32,
log10f64,
log10f128,
log_syntax,
logf128,
logf16,
logf32,
logf64,
logf128,
loongarch_target_feature,
loop_break_value,
loop_match,
@ -1334,14 +1330,14 @@ symbols! {
match_beginning_vert,
match_default_bindings,
matches_macro,
maximumf128,
maximumf16,
maximumf32,
maximumf64,
maxnumf128,
maximumf128,
maxnumf16,
maxnumf32,
maxnumf64,
maxnumf128,
may_dangle,
may_unwind,
maybe_uninit,
@ -1372,14 +1368,14 @@ symbols! {
min_generic_const_args,
min_specialization,
min_type_alias_impl_trait,
minimumf128,
minimumf16,
minimumf32,
minimumf64,
minnumf128,
minimumf128,
minnumf16,
minnumf32,
minnumf64,
minnumf128,
mips_target_feature,
mir_assume,
mir_basic_block,
@ -1586,6 +1582,7 @@ symbols! {
panic_implementation,
panic_in_cleanup,
panic_info,
panic_invalid_enum_construction,
panic_location,
panic_misaligned_pointer_dereference,
panic_nounwind,
@ -1633,14 +1630,14 @@ symbols! {
post_dash_lto: "post-lto",
postfix_match,
powerpc_target_feature,
powf128,
powf16,
powf32,
powf64,
powif128,
powf128,
powif16,
powif32,
powif64,
powif128,
pre_dash_lto: "pre-lto",
precise_capturing,
precise_capturing_in_traits,
@ -1785,14 +1782,14 @@ symbols! {
ropi_rwpi: "ropi-rwpi",
rotate_left,
rotate_right,
round_ties_even_f128,
round_ties_even_f16,
round_ties_even_f32,
round_ties_even_f64,
roundf128,
round_ties_even_f128,
roundf16,
roundf32,
roundf64,
roundf128,
rt,
rtm_target_feature,
rust,
@ -1972,8 +1969,8 @@ symbols! {
simd_fexp2,
simd_ffi,
simd_flog,
simd_flog10,
simd_flog2,
simd_flog10,
simd_floor,
simd_fma,
simd_fmax,
@ -2021,10 +2018,10 @@ symbols! {
simd_with_exposed_provenance,
simd_xor,
since,
sinf128,
sinf16,
sinf32,
sinf64,
sinf128,
size,
size_of,
size_of_val,
@ -2046,10 +2043,10 @@ symbols! {
specialization,
speed,
spotlight,
sqrtf128,
sqrtf16,
sqrtf32,
sqrtf64,
sqrtf128,
sreg,
sreg_low16,
sse,
@ -2127,10 +2124,10 @@ symbols! {
target_has_atomic,
target_has_atomic_equal_alignment,
target_has_atomic_load_store,
target_has_reliable_f128,
target_has_reliable_f128_math,
target_has_reliable_f16,
target_has_reliable_f16_math,
target_has_reliable_f128,
target_has_reliable_f128_math,
target_os,
target_pointer_width,
target_thread_local,
@ -2173,10 +2170,10 @@ symbols! {
transparent_enums,
transparent_unions,
trivial_bounds,
truncf128,
truncf16,
truncf32,
truncf64,
truncf128,
try_blocks,
try_capture,
try_from,
@ -2205,12 +2202,12 @@ symbols! {
type_name,
type_privacy_lints,
typed_swap_nonoverlapping,
u128,
u128_legacy_const_max,
u128_legacy_const_min,
u128_legacy_fn_max_value,
u128_legacy_fn_min_value,
u128_legacy_mod,
u8,
u8_legacy_const_max,
u8_legacy_const_min,
u8_legacy_fn_max_value,
u8_legacy_fn_min_value,
u8_legacy_mod,
u16,
u16_legacy_const_max,
u16_legacy_const_min,
@ -2229,12 +2226,12 @@ symbols! {
u64_legacy_fn_max_value,
u64_legacy_fn_min_value,
u64_legacy_mod,
u8,
u8_legacy_const_max,
u8_legacy_const_min,
u8_legacy_fn_max_value,
u8_legacy_fn_min_value,
u8_legacy_mod,
u128,
u128_legacy_const_max,
u128_legacy_const_min,
u128_legacy_fn_max_value,
u128_legacy_fn_min_value,
u128_legacy_mod,
ub_checks,
unaligned_volatile_load,
unaligned_volatile_store,
@ -2387,6 +2384,7 @@ symbols! {
zfh,
zfhmin,
zmm_reg,
// tidy-alphabetical-end
}
}

View file

@ -622,6 +622,17 @@ impl<I: Interner, T: TypeFoldable<I>> ty::EarlyBinder<I, T> {
where
A: SliceLike<Item = I::GenericArg>,
{
// Nothing to fold, so let's avoid visiting things and possibly re-hashing/equating
// them when interning. Perf testing found this to be a modest improvement.
// See: <https://github.com/rust-lang/rust/pull/142317>
if args.is_empty() {
assert!(
!self.value.has_param(),
"{:?} has parameters, but no args were provided in instantiate",
self.value,
);
return self.value;
}
let mut folder = ArgFolder { cx, args: args.as_slice(), binders_passed: 0 };
self.value.fold_with(&mut folder)
}

View file

@ -1517,9 +1517,7 @@ impl<T: Ord, const N: usize> From<[T; N]> for BTreeSet<T> {
// use stable sort to preserve the insertion order.
arr.sort();
let iter = IntoIterator::into_iter(arr).map(|k| (k, SetValZST::default()));
let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global);
BTreeSet { map }
BTreeSet::from_sorted_iter(IntoIterator::into_iter(arr), Global)
}
}

View file

@ -1,5 +1,7 @@
use crate::ffi::CStr;
use crate::fmt;
use crate::marker::PhantomData;
use crate::ptr::NonNull;
/// A struct containing information about the location of a panic.
///
@ -33,14 +35,13 @@ use crate::fmt;
#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[stable(feature = "panic_hooks", since = "1.10.0")]
pub struct Location<'a> {
// Note: this filename will have exactly one nul byte at its end, but otherwise
// it must never contain interior nul bytes. This is relied on for the conversion
// to `CStr` below.
//
// The prefix of the string without the trailing nul byte will be a regular UTF8 `str`.
file_bytes_with_nul: &'a [u8],
// A raw pointer is used rather than a reference because the pointer is valid for one more byte
// than the length stored in this pointer; the additional byte is the NUL-terminator used by
// `Location::file_with_nul`.
filename: NonNull<str>,
line: u32,
col: u32,
_filename: PhantomData<&'a str>,
}
#[stable(feature = "panic_hooks", since = "1.10.0")]
@ -143,10 +144,8 @@ impl<'a> Location<'a> {
#[stable(feature = "panic_hooks", since = "1.10.0")]
#[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
pub const fn file(&self) -> &str {
let str_len = self.file_bytes_with_nul.len() - 1;
// SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
// valid UTF8.
unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
// SAFETY: The filename is valid.
unsafe { self.filename.as_ref() }
}
/// Returns the name of the source file as a nul-terminated `CStr`.
@ -157,9 +156,17 @@ impl<'a> Location<'a> {
#[unstable(feature = "file_with_nul", issue = "141727")]
#[inline]
pub const fn file_with_nul(&self) -> &CStr {
// SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
// interior nul bytes.
unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
let filename = self.filename.as_ptr();
// SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't
// overflow.
let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) };
// SAFETY: The filename is valid for `filename_len+1` bytes.
let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) };
// SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes.
unsafe { CStr::from_bytes_with_nul_unchecked(slice) }
}
/// Returns the line number from which the panic originated.
@ -220,3 +227,8 @@ impl fmt::Display for Location<'_> {
write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
}
}
#[stable(feature = "panic_hooks", since = "1.10.0")]
unsafe impl Send for Location<'_> {}
#[stable(feature = "panic_hooks", since = "1.10.0")]
unsafe impl Sync for Location<'_> {}

View file

@ -314,6 +314,22 @@ fn panic_null_pointer_dereference() -> ! {
)
}
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold, optimize(size))]
#[cfg_attr(feature = "panic_immediate_abort", inline)]
#[track_caller]
#[lang = "panic_invalid_enum_construction"] // needed by codegen for panic on invalid enum construction.
#[rustc_nounwind] // `CheckEnums` MIR pass requires this function to never unwind
fn panic_invalid_enum_construction(source: u128) -> ! {
if cfg!(feature = "panic_immediate_abort") {
super::intrinsics::abort()
}
panic_nounwind_fmt(
format_args!("trying to construct an enum from an invalid value {source:#x}"),
/* force_no_backtrace */ false,
)
}
/// Panics because we cannot unwind out of a function.
///
/// This is a separate function to avoid the codesize impact of each crate containing the string to

View file

@ -3,4 +3,5 @@
#![stable(feature = "raw_ext", since = "1.1.0")]
pub mod fs;
pub mod net;
pub mod raw;

View file

@ -0,0 +1,50 @@
//! illumos-specific networking functionality.
#![unstable(feature = "unix_socket_exclbind", issue = "123481")]
use crate::io;
use crate::os::unix::net;
use crate::sealed::Sealed;
use crate::sys_common::AsInner;
/// illumos-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
/// and [`UnixStream`].
///
/// [`UnixDatagram`]: net::UnixDatagram
/// [`UnixStream`]: net::UnixStream
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
pub trait UnixSocketExt: Sealed {
/// Enables exclusive binding on the socket.
///
/// If true and if the socket had been set with `SO_REUSEADDR`,
/// it neutralises its effect.
/// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html)
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
fn so_exclbind(&self, excl: bool) -> io::Result<()>;
/// Get the bind exclusivity bind state of the socket.
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
fn exclbind(&self) -> io::Result<bool>;
}
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
impl UnixSocketExt for net::UnixDatagram {
fn exclbind(&self) -> io::Result<bool> {
self.as_inner().exclbind()
}
fn so_exclbind(&self, excl: bool) -> io::Result<()> {
self.as_inner().set_exclbind(excl)
}
}
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
impl UnixSocketExt for net::UnixStream {
fn exclbind(&self) -> io::Result<bool> {
self.as_inner().exclbind()
}
fn so_exclbind(&self, excl: bool) -> io::Result<()> {
self.as_inner().set_exclbind(excl)
}
}

View file

@ -3,4 +3,5 @@
#![stable(feature = "raw_ext", since = "1.1.0")]
pub mod fs;
pub mod net;
pub mod raw;

View file

@ -0,0 +1,50 @@
//! solaris-specific networking functionality.
#![unstable(feature = "unix_socket_exclbind", issue = "123481")]
use crate::io;
use crate::os::unix::net;
use crate::sealed::Sealed;
use crate::sys_common::AsInner;
/// solaris-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
/// and [`UnixStream`].
///
/// [`UnixDatagram`]: net::UnixDatagram
/// [`UnixStream`]: net::UnixStream
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
pub trait UnixSocketExt: Sealed {
/// Enables exclusive binding on the socket.
///
/// If true and if the socket had been set with `SO_REUSEADDR`,
/// it neutralises its effect.
/// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html)
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
fn so_exclbind(&self, excl: bool) -> io::Result<()>;
/// Get the bind exclusivity bind state of the socket.
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
fn exclbind(&self) -> io::Result<bool>;
}
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
impl UnixSocketExt for net::UnixDatagram {
fn exclbind(&self) -> io::Result<bool> {
self.as_inner().exclbind()
}
fn so_exclbind(&self, excl: bool) -> io::Result<()> {
self.as_inner().set_exclbind(excl)
}
}
#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
impl UnixSocketExt for net::UnixStream {
fn exclbind(&self) -> io::Result<bool> {
self.as_inner().exclbind()
}
fn so_exclbind(&self, excl: bool) -> io::Result<()> {
self.as_inner().set_exclbind(excl)
}
}

View file

@ -522,6 +522,21 @@ impl Socket {
Ok(name)
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
pub fn set_exclbind(&self, excl: bool) -> io::Result<()> {
// not yet on libc crate
const SO_EXCLBIND: i32 = 0x1015;
setsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND, excl)
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
pub fn exclbind(&self) -> io::Result<bool> {
// not yet on libc crate
const SO_EXCLBIND: i32 = 0x1015;
let raw: c_int = getsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND)?;
Ok(raw != 0)
}
#[cfg(any(target_os = "android", target_os = "linux",))]
pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int)

View file

@ -968,8 +968,8 @@ impl Process {
}
pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
// If we've already waited on this process then the pid can be recycled
// and used for another process, and we probably shouldn't be signaling
// If we've already waited on this process then the pid can be recycled and
// used for another process, and we probably shouldn't be sending signals to
// random processes, so return Ok because the process has exited already.
if self.status.is_some() {
return Ok(());

View file

@ -151,8 +151,8 @@ impl Process {
}
pub fn send_signal(&self, signal: i32) -> io::Result<()> {
// If we've already waited on this process then the pid can be recycled
// and used for another process, and we probably shouldn't be killing
// If we've already waited on this process then the pid can be recycled and
// used for another process, and we probably shouldn't be sending signals to
// random processes, so return Ok because the process has exited already.
if self.status.is_some() {
Ok(())

View file

@ -10,4 +10,7 @@ python3 ../x.py build --set rust.debug=true opt-dist
build-manifest bootstrap
# Use GCC for building GCC, as it seems to behave badly when built with Clang
CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc
# Only build GCC on full builds, not try builds
if [ "${DIST_TRY_BUILD:-0}" == "0" ]; then
CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc
fi

View file

@ -1703,6 +1703,7 @@ fn render_enum_fields(
if v.is_stripped() {
continue;
}
write!(w, "{}", render_attributes_in_pre(v, TAB, cx))?;
w.write_str(TAB)?;
match v.kind {
clean::VariantItem(ref var) => match var.kind {

View file

@ -14,6 +14,7 @@ use crate::versions::{PkgType, Versions};
static HOSTS: &[&str] = &[
"aarch64-apple-darwin",
"aarch64-pc-windows-gnullvm",
"aarch64-pc-windows-msvc",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
@ -44,6 +45,7 @@ static HOSTS: &[&str] = &[
"x86_64-apple-darwin",
"x86_64-pc-solaris",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-gnullvm",
"x86_64-pc-windows-msvc",
"x86_64-unknown-freebsd",
"x86_64-unknown-illumos",
@ -470,7 +472,7 @@ impl Builder {
}
// so is rust-mingw if it's available for the target
PkgType::RustMingw => {
if host.contains("pc-windows-gnu") {
if host.ends_with("pc-windows-gnu") {
components.push(host_component(pkg));
}
}

@ -1 +1 @@
Subproject commit 409fed7dc1553d49cb9a8c0637d12d65571346ce
Subproject commit 930b4f62cfcd1f0eabdb30a56d91bf6844b739bf

View file

@ -595,7 +595,7 @@ Definite bugs found:
* [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084))
* [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281)
* [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo)
* [Mockall reading unintialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
* [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248)
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):

View file

@ -173,7 +173,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[
"-Zmir-emit-retag",
"-Zmir-preserve-ub",
"-Zmir-opt-level=0",
"-Zmir-enable-passes=-CheckAlignment,-CheckNull",
"-Zmir-enable-passes=-CheckAlignment,-CheckNull,-CheckEnums",
// Deduplicating diagnostics means we miss events when tracking what happens during an
// execution. Let's not do that.
"-Zdeduplicate-diagnostics=no",

View file

@ -6,7 +6,7 @@ fn direct_raw(x: *const (i32, i32)) -> *const i32 {
// Ensure that if a raw pointer is created via an intermediate
// reference, we catch that. (Just in case someone decides to
// desugar this differenly or so.)
// desugar this differently or so.)
fn via_ref(x: *const (i32, i32)) -> *const i32 {
unsafe { &(*x).0 as *const i32 } //~ERROR: dangling pointer
}

View file

@ -122,7 +122,12 @@ impl Bootstrap {
let metrics_path = env.build_root().join("build").join("metrics.json");
let args = dist_args.iter().map(|arg| arg.as_str()).collect::<Vec<_>>();
let cmd = cmd(&args).env("RUST_BACKTRACE", "full");
let cmd = add_shared_x_flags(env, cmd);
let mut cmd = add_shared_x_flags(env, cmd);
if env.is_fast_try_build() {
// We set build.extended=false for fast try builds, but we still need Cargo
cmd = cmd.arg("cargo");
}
Self { cmd, metrics_path }
}
@ -189,5 +194,18 @@ impl Bootstrap {
}
fn add_shared_x_flags(env: &Environment, cmd: CmdBuilder) -> CmdBuilder {
if env.is_fast_try_build() { cmd.arg("--set").arg("rust.deny-warnings=false") } else { cmd }
if env.is_fast_try_build() {
// Skip things that cannot be skipped through `x ... --skip`
cmd.arg("--set")
.arg("rust.llvm-bitcode-linker=false")
// Skip wasm-component-ld. This also skips cargo, which we need to re-enable for dist
.arg("--set")
.arg("build.extended=false")
.arg("--set")
.arg("rust.codegen-backends=['llvm']")
.arg("--set")
.arg("rust.deny-warnings=false")
} else {
cmd
}
}

View file

@ -407,13 +407,18 @@ fn main() -> anyhow::Result<()> {
for target in [
"rust-docs",
"rustc-docs",
"rustc-dev",
"rust-dev",
"rust-docs-json",
"rust-analyzer",
"rustc-src",
"extended",
"clippy",
"miri",
"rustfmt",
"gcc",
"generate-copyright",
"bootstrap",
] {
build_args.extend(["--skip".to_string(), target.to_string()]);
}

View file

@ -118,7 +118,7 @@ pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) ->
match result {
Ok(FinishedAll) => (),
Err(EarlyExit::Timeout) => {
println!(" exited early; exceded {:?} timeout", cfg.timeout)
println!(" exited early; exceeded {:?} timeout", cfg.timeout)
}
Err(EarlyExit::MaxFailures) => {
println!(" exited early; exceeded {:?} max failures", cfg.max_failures)

View file

@ -0,0 +1,17 @@
// regression test for https://github.com/rust-lang/rust/issues/142599
#![crate_name = "foo"]
//@ snapshot type-code 'foo/enum.Type.html' '//pre[@class="rust item-decl"]/code'
pub enum Type {
#[non_exhaustive]
// attribute that should not be shown
#[warn(unsafe_code)]
Variant,
}
// we would love to use the `following-sibling::` axis
// (along with an `h2[@id="aliased-type"]` query),
// but unfortunately python doesn't implement that.
//@ snapshot type-alias-code 'foo/type.TypeAlias.html' '//pre[@class="rust item-decl"][2]/code'
pub type TypeAlias = Type;

View file

@ -0,0 +1,4 @@
<code>pub enum TypeAlias {
#[non_exhaustive]
Variant,
}</code>

View file

@ -0,0 +1,4 @@
<code>pub enum Type {
#[non_exhaustive]
Variant,
}</code>

View file

@ -0,0 +1,20 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x10000
#[allow(dead_code)]
#[repr(u32)]
enum Foo {
A,
B,
}
#[allow(dead_code)]
struct Bar {
a: u16,
b: u16,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 1 }) };
}

View file

@ -0,0 +1,27 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x5
#[allow(dead_code)]
#[repr(u16)]
enum Mix {
A,
B(u16),
}
#[allow(dead_code)]
enum Nested {
C(Mix),
D,
E,
}
#[allow(dead_code)]
struct Bar {
a: u16,
b: u16,
}
fn main() {
let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 5, b: 0 }) };
}

View file

@ -0,0 +1,29 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
#[repr(u16)]
enum Mix {
A,
B(u16),
}
#[allow(dead_code)]
enum Nested {
C(Mix),
D,
E,
}
#[allow(dead_code)]
struct Bar {
a: u16,
b: u16,
}
fn main() {
let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 0, b: 0 }) };
let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 1, b: 0 }) };
let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 2, b: 0 }) };
let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 3, b: 0 }) };
}

View file

@ -0,0 +1,20 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
#[repr(u32)]
enum Foo {
A,
B,
}
#[allow(dead_code)]
struct Bar {
a: u16,
b: u16,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 0 }) };
let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 1, b: 0 }) };
}

View file

@ -0,0 +1,20 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x3
#[allow(dead_code)]
enum Foo {
A,
B,
}
#[allow(dead_code)]
struct Bar {
a: usize,
b: usize,
}
fn main() {
let _val: Option<(usize, Foo)> =
unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 3, b: 3 }) };
}

View file

@ -0,0 +1,21 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
enum Foo {
A,
B,
}
#[allow(dead_code)]
struct Bar {
a: usize,
b: usize,
}
fn main() {
let _val: Option<(usize, Foo)> =
unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 0, b: 0 }) };
let _val: Option<(usize, Foo)> =
unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 1, b: 0 }) };
}

View file

@ -0,0 +1,13 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x3
#[allow(dead_code)]
enum Foo {
A,
B,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(3) };
}

View file

@ -0,0 +1,13 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
enum Foo {
A,
B,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(0) };
let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(1) };
}

View file

@ -0,0 +1,14 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x0
#[repr(u32)]
#[allow(dead_code)]
enum Foo {
A = 2,
B,
}
fn main() {
let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(0) };
}

View file

@ -0,0 +1,14 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[repr(u32)]
#[allow(dead_code)]
enum Foo {
A = 2,
B,
}
fn main() {
let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(2) };
let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(3) };
}

View file

@ -0,0 +1,14 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x1
#[repr(u32)]
#[allow(dead_code)]
enum Foo {
A = 2,
B,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(1) };
}

View file

@ -0,0 +1,14 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[repr(u32)]
#[allow(dead_code)]
enum Foo {
A = 2,
B,
}
fn main() {
let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(2) };
let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(3) };
}

View file

@ -0,0 +1,11 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
enum Single {
A
}
fn main() {
let _val: Single = unsafe { std::mem::transmute::<(), Single>(()) };
}

View file

@ -0,0 +1,13 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x1
#[allow(dead_code)]
#[repr(u16)]
enum Single {
A
}
fn main() {
let _val: Single = unsafe { std::mem::transmute::<u16, Single>(1) };
}

View file

@ -0,0 +1,12 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
#[repr(u16)]
enum Single {
A
}
fn main() {
let _val: Single = unsafe { std::mem::transmute::<u16, Single>(0) };
}

View file

@ -0,0 +1,21 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x4
#[allow(dead_code)]
#[repr(u16)]
enum Mix {
A,
B(u16),
}
#[allow(dead_code)]
enum Nested {
C(Mix),
D,
E,
}
fn main() {
let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(4) };
}

View file

@ -0,0 +1,23 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
#[repr(u16)]
enum Mix {
A,
B(u16),
}
#[allow(dead_code)]
enum Nested {
C(Mix),
D,
E,
}
fn main() {
let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(0) };
let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(1) };
let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(2) };
let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(3) };
}

View file

@ -0,0 +1,14 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
fn main() {
let _val = unsafe {
std::mem::transmute::<*const usize, Option<unsafe extern "C" fn()>>(std::ptr::null())
};
let _val = unsafe {
std::mem::transmute::<*const usize, Option<unsafe extern "C" fn()>>(usize::MAX as *const _)
};
let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(0) };
let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(1) };
let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(usize::MAX) };
}

View file

@ -0,0 +1,14 @@
//@ run-fail
//@ compile-flags: -C debug-assertions
//@ error-pattern: trying to construct an enum from an invalid value 0x0
#![feature(never_type)]
#![allow(invalid_value)]
#[allow(dead_code)]
enum Wrap {
A(!),
}
fn main() {
let _val: Wrap = unsafe { std::mem::transmute::<(), Wrap>(()) };
}

View file

@ -0,0 +1,12 @@
//@ run-pass
//@ compile-flags: -C debug-assertions
#[allow(dead_code)]
enum Wrap {
A(u32),
}
fn main() {
let _val: Wrap = unsafe { std::mem::transmute::<u32, Wrap>(2) };
let _val: Wrap = unsafe { std::mem::transmute::<u32, Wrap>(u32::MAX) };
}