diff --git a/Cargo.lock b/Cargo.lock index 3e937b055e870..41c82c989becc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1318,6 +1318,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "ena" version = "0.14.4" @@ -2551,6 +2562,7 @@ dependencies = [ "chrono-tz", "colored", "directories", + "emmy_dap_types", "genmc-sys", "getrandom 0.4.3", "ipc-channel", diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index b5d8fc821ae0e..2b264a6843837 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -1,6 +1,5 @@ //! Code related to parsing literals. -use std::fmt::Write as _; use std::{ascii, fmt, str}; use rustc_literal_escaper::{ @@ -38,19 +37,8 @@ pub fn escape_string_symbol(symbol: Symbol) -> Symbol { let mut escaped = String::with_capacity(s.len() + 1); escaped.push_str(prefix); - // Don't use escape_default() here, because using it is slower than escaping manually. - for c in suffix.chars() { - match c { - '\t' => escaped.push_str("\\t"), - '\r' => escaped.push_str("\\r"), - '\n' => escaped.push_str("\\n"), - '\\' => escaped.push_str("\\\\"), - '\'' => escaped.push_str("\\'"), - '\"' => escaped.push_str("\\\""), - '\x20'..='\x7e' => escaped.push(c), - c => write!(escaped, "\\u{{{:x}}}", c as u32).unwrap(), - } - } + escaped.extend(suffix.escape_default()); + Symbol::intern(&escaped) } diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 4c3b42eaa0b22..aaaf67e34af4e 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -46,6 +46,7 @@ mod iter; mod log_syntax; mod offload; mod pattern_type; +mod rustdoc_skip_body; mod source_util; mod test; mod test_binder_constraints; @@ -125,6 +126,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { eii_shared_macro: eii::eii_shared_macro, global_allocator: global_allocator::expand, offload_kernel: offload::expand_kernel, + rustdoc_skip_body: rustdoc_skip_body::expand, test: test::expand_test, test_case: test::expand_test_case, unsafe_eii: eii::unsafe_eii, diff --git a/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs b/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs new file mode 100644 index 0000000000000..7649fc7d72fe0 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs @@ -0,0 +1,93 @@ +//! The expansion from a function when the code would not compile with rustdoc: so if `cfg(doc)`, +//! expands to a `loop {}`. + +use rustc_ast::token::{self, Delimiter, IdentKind}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::{ + AssocItemKind, AttrArgs, AttrItem, Attribute, DelimArgs, ExprKind, Fn, ItemKind, MetaItem, + Safety, +}; +use rustc_expand::base::*; +use rustc_span::{Ident, Span, sym}; +use thin_vec::ThinVec; + +pub(crate) fn expand( + cx: &mut ExtCtxt<'_>, + attr_path_sp: Span, + meta_item: &MetaItem, + mut item: Annotatable, +) -> Vec { + if !meta_item.is_word() { + cx.dcx().struct_span_err(attr_path_sp, "`rustdoc_skip_body` doesn't take arguments").emit(); + } + let mut is_expected_item = false; + match item { + Annotatable::Item(ref mut item) => { + if let ItemKind::Fn(ref mut fn_) = item.kind { + expand_macro(cx, attr_path_sp, fn_); + is_expected_item = true; + } + } + Annotatable::AssocItem(ref mut item, _) => { + if let AssocItemKind::Fn(ref mut fn_) = item.kind { + expand_macro(cx, attr_path_sp, fn_); + is_expected_item = true; + } + } + _ => {} + } + if !is_expected_item { + cx.dcx() + .struct_span_err(attr_path_sp, "`rustdoc_skip_body` can only be used on functions") + .emit(); + } + vec![item] +} + +fn cfg_not_doc(cx: &mut ExtCtxt<'_>, sp: Span) -> Attribute { + // `not(doc)` + let not_doc = TokenStream::new(vec![ + TokenTree::token_alone(token::Ident(sym::not, IdentKind::Normal), sp), + TokenTree::Delimited( + DelimSpan::from_single(sp), + DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), + Delimiter::Parenthesis, + TokenStream::token_alone(token::Ident(sym::doc, IdentKind::Normal), sp), + ), + ]); + + cx.attr_nested( + AttrItem { + unsafety: Safety::Default, + path: cx.path_ident(sp, Ident::new(sym::cfg, sp)), + args: AttrArgs::Delimited(DelimArgs { + dspan: DelimSpan::from_single(sp), + delim: Delimiter::Parenthesis, + tokens: not_doc, + }), + span: sp, + }, + sp, + ) +} + +fn expand_macro(cx: &mut ExtCtxt<'_>, sp: Span, fn_: &mut Fn) { + let Some(old_body) = fn_.body.take() else { + // No body... Well, no possibility to not compile I guess so all good. + return; + }; + let mut old_body = cx.expr_block(old_body); + // We add on the function's body `#[cfg(not(doc))]`. + old_body.attrs.push(cfg_not_doc(cx, sp)); + + // We create the `#[cfg(doc)] loop {}` "block". + let mut loop_expr = cx.expr(sp, ExprKind::Loop(cx.block(sp, ThinVec::new()), None, sp)); + loop_expr.attrs.push(cx.attr_nested_word(sym::cfg, sym::doc, sp)); + + let mut stmts = ThinVec::new(); + stmts.push(cx.stmt_expr(loop_expr)); + stmts.push(cx.stmt_expr(old_body)); + + // We create a new function body containing both blocks. + fn_.body = Some(cx.block(sp, stmts)); +} diff --git a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml index 0687bc1150907..66362e27ce4da 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml @@ -116,8 +116,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV #- name: Cache rust repository ## We only clone the rust repository for rustc tests diff --git a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml index 57d7a39de049a..0309ccbad5481 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml @@ -77,8 +77,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV - name: Prepare if: ${{ matrix.run_cargo_tests }} diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index d94ce6575ac0e..aec0ef270bf73 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cd340f3c265988de3379689cbbd103c25b1f8a3d0cc73f91a49083e899d3e1" +checksum = "ea1820165dfb376a3bbfe8b69628d9e5a851c423e0480a666f71f560e2d0a924" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2d9d74572f004908f17ec1bd0af071e5db63a12c3335c9651a6125593951c6" +checksum = "a6091291fc118448b91df5b662d20564020198fee46d8445a6aa0c25b6b435be" dependencies = [ "libc", ] diff --git a/compiler/rustc_codegen_gcc/Cargo.toml b/compiler/rustc_codegen_gcc/Cargo.toml index 95c4c991297fa..3e279961bfbe0 100644 --- a/compiler/rustc_codegen_gcc/Cargo.toml +++ b/compiler/rustc_codegen_gcc/Cargo.toml @@ -20,7 +20,7 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "7.2.0", features = ["dlopen"] } +gccjit = { version = "7.3.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index ac076836579ac..cc87c660520d8 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -6,7 +6,8 @@ use std::path::{Path, PathBuf}; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ - create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, walk_dir, + create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, rustc_version_info, + walk_dir, }; #[derive(Default)] @@ -137,17 +138,16 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); - // Record the sysroot sources under the path the `rust-src` component uses, which is where - // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests - // print the build path where they expect `$SRC_DIR`. - let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); + // Remap both source spans and embedded panic locations to the standard virtual path. + let sysroot_source_dir = start_dir.join("sysroot_src"); + let rustc_commit = rustc_version_info(env.get("RUSTC").map(String::as_str))? + .commit_hash + .ok_or_else(|| "Couldn't retrieve rustc commit hash".to_string())?; + // Cargo passes workspace-relative filenames, including those used by panic locations. + // Also: the `==` is intentional! The first `=` is "FROM" and the second `=` is for "TO". + rustflags.push_str(&format!(" --remap-path-prefix==/rustc/{rustc_commit}/library")); rustflags.push_str(&format!( - " --remap-path-prefix={library_dir}={sysroot_source_dir}", - library_dir = std::path::absolute(&library_dir) - .map_err(|error| format!( - "Failed to get the absolute path of the sysroot sources: {error:?}" - ))? - .display(), + " --remap-path-prefix={sysroot_source_dir}=/rustc/{rustc_commit}", sysroot_source_dir = std::path::absolute(&sysroot_source_dir) .map_err(|error| format!( "Failed to get the absolute path of the sysroot sources: {error:?}" diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index fcac399e46542..dbdc09499e30d 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -1,54 +1,13 @@ -# git subtree sync +# Subtree sync procedure -`rustc_codegen_gcc` is a subtree of the rust compiler. As such, it needs to be -synced from time to time to ensure changes that happened on their side are also -included on our side. +`rustc_codegen_gcc` is using [Josh] to perform subtree synces with the `rust-lang/rust` repository. -### How to install a forked git-subtree +[Josh]: https://josh-project.dev/docs/intro.html -Using git-subtree with `rustc` requires a patched git to make it work. -The PR that is needed is [here](https://github.com/gitgitgadget/git/pull/493). -Use the following instructions to install it: +The procedure for performing subtree synchronizations is documented [here][rdg-josh]. -```bash -git clone git@github.com:tqc/git.git -cd git -git checkout tqc/subtree -make -make install -cd contrib/subtree -make -cp git-subtree ~/bin -``` +[rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree -### Syncing with rust compiler +## Pushing changes to `rust-lang/rust` -Do a sync with this command: - -```bash -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -cd ../rustc_codegen_gcc -git checkout master -git pull -git checkout sync_branch_name -git merge master -``` - -To send the changes to the rust repo: - -```bash -cd ../rust -git pull origin master -git checkout -b subtree-update_cg_gcc_YYYY-MM-DD -PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master -# Don't forget to update the `gcc` submodule to the same version as the -# one in `rustc_codegen_gcc/libgccjit.version`. -git push - -# Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later. -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -``` - -FIXME: write a script that does the above. - -https://rust-lang.zulipchat.com/#narrow/stream/301329-t-devtools/topic/subtree.20madness/near/258877725 +If the used GCC commit (recorded in `libgccjit.version`) changed in `rustc_codegen_gcc`, then you will have to update the `gcc` submodule in the `rust-lang/rust` repository in the push sync PR. The CI in `rust-lang/rust` should ensure that these two commit references match. You can simply update the `gcc` submodule commit and push it to your push sync PR once it has been created. diff --git a/compiler/rustc_codegen_gcc/libgccjit.version b/compiler/rustc_codegen_gcc/libgccjit.version index 0bcc711e4696b..0c6f40148d1e5 100644 --- a/compiler/rustc_codegen_gcc/libgccjit.version +++ b/compiler/rustc_codegen_gcc/libgccjit.version @@ -1 +1 @@ -37ea7cd851f04ee6a313c5067c9fde9328f876aa +e1c6e07b095d091177376a163da44039475e05e3 diff --git a/compiler/rustc_codegen_gcc/rust-toolchain.toml b/compiler/rustc_codegen_gcc/rust-toolchain.toml index 7d0a982195aa0..5750754596ba2 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain.toml +++ b/compiler/rustc_codegen_gcc/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-19" +channel = "nightly-2026-10-24" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index b6b0352d2516c..925c600aee936 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -20,7 +20,7 @@ use crate::base; use crate::context::CodegenCx; #[cfg(feature = "master")] use crate::gcc_util::to_gcc_aarch64_extension; -use crate::gcc_util::to_gcc_features; +use crate::gcc_util::{to_gcc_features, to_gcc_target_attribute_feature}; /// Checks if the function `instance` is recursively inline. /// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive. @@ -192,20 +192,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } let target_features = function_features .iter() - .filter_map(|feature| { - // FIXME(antoyo): support soft-float. - if feature.contains("soft-float") { - return None; - } - - if feature.starts_with('-') { - Some(format!("no{}", feature)) - } else if let Some(stripped) = feature.strip_prefix('+') { - Some(stripped.to_string()) - } else { - Some(feature.to_string()) - } - }) + .filter_map(|feature| to_gcc_target_attribute_feature(feature)) .collect::>() .join(","); if !target_features.is_empty() { diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 21d92c6cc2936..895bc629d63ce 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -28,12 +28,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.const_uint(self.type_u16(), i as u64) } - fn global_string(&self, string: &str) -> LValue<'gcc> { - // FIXME(antoyo): handle non-null-terminated strings. - let string = self.context.new_string_literal(string); + fn global_const_string(&self, string: &str) -> LValue<'gcc> { + // The bytes of a Rust string are not NUL-terminated and can contain NULs. + let string = bytes_in_context(self, string.as_bytes()); let sym = self.generate_local_symbol_name("str"); let global = self.declare_private_global(&sym, self.val_ty(string)); global.global_set_initializer_rvalue(string); + #[cfg(feature = "master")] + global.global_set_readonly(); global // FIXME(antoyo): set linkage. } @@ -126,18 +128,6 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } } -/// The element type and element count of the array used to represent a run of `len` constant bytes. -/// -/// Larger integers are used where possible: this reduces the number of rvalues, which is a -/// significant memory saving on constant-heavy crates. -fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u64) { - match len % 8 { - 0 => (cx.context.new_type::(), len as u64 / 8), - 4 => (cx.context.new_type::(), len as u64 / 4), - _ => (cx.context.new_type::(), len as u64), - } -} - /// The type [`bytes_in_context`] gives a run of `len` constant bytes. /// /// Exposed separately so that the type of a constant allocation can be computed before any of its @@ -147,65 +137,30 @@ fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u6 /// Two equal-but-distinct array types would key [`CodegenCx::type_struct`] differently and so /// produce two distinct anonymous structs, and libgccjit compares struct types by identity. pub fn bytes_type_in_context<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> Type<'gcc> { - let (element_type, count) = byte_run_shape(cx, len); - if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { + if let Some(&typ) = cx.byte_array_types.borrow().get(&len) { return typ; } - let typ = new_array_type(cx.context, None, element_type, count); - cx.byte_array_types.borrow_mut().insert((element_type, count), typ); + let typ = new_array_type(cx.context, None, cx.type_u8(), len as u64); + cx.byte_array_types.borrow_mut().insert(len, typ); typ } -// FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that -// `global_set_initializer` is more memory efficient than the current solution. -// `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, -// or is it using a more efficient representation? pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { let typ = bytes_type_in_context(cx, bytes.len()); - let (element_type, _) = byte_run_shape(cx, bytes.len()); - let context = &cx.context; - // Since we are representing arbitrary byte runs as integers, we need to follow the target - // endianness. - let endian = cx.sess().target.options.endian; - let elements: Vec<_> = match bytes.len() % 8 { - 0 => { - let (arrays, remainder) = bytes.as_chunks::<8>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_long( - element_type, - match endian { - rustc_abi::Endian::Little => u64::from_le_bytes(arr) as i64, - rustc_abi::Endian::Big => u64::from_be_bytes(arr) as i64, - }, - ) - }) - .collect() - } - 4 => { - let (arrays, remainder) = bytes.as_chunks::<4>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_int( - element_type, - match endian { - rustc_abi::Endian::Little => u32::from_le_bytes(arr) as i32, - rustc_abi::Endian::Big => u32::from_be_bytes(arr) as i32, - }, - ) - }) - .collect() - } - _ => bytes + // Raw bytes take far less RAM in libgccjit than one rvalue per array element. + #[cfg(feature = "master")] + { + cx.context.new_array_from_bytes(None, typ, bytes) + } + #[cfg(not(feature = "master"))] + { + let byte_type = cx.type_u8(); + let elements: Vec<_> = bytes .iter() - .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) - .collect(), - }; - context.new_array_constructor(None, typ, &elements) + .map(|&byte| cx.context.new_rvalue_from_int(byte_type, byte as i32)) + .collect(); + cx.context.new_array_constructor(None, typ, &elements) + } } pub fn type_is_pointer(typ: Type<'_>) -> bool { @@ -292,7 +247,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) { let mut const_str_cache = self.const_str_cache.borrow_mut(); let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| { - let g = self.global_string(s); + let g = self.global_const_string(s); const_str_cache.insert(s.to_owned(), g); g }); diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 902f8ba52faaa..6cf765a9b80b7 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -64,14 +64,18 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let cv = const_alloc_to_gcc(self, alloc); let align = alloc.inner().align; - if let Some(variable) = self.const_globals.borrow().get(&cv) { - if let Some(global_variable) = self.global_lvalues.borrow().get(variable) { - let alignment = align.bits() as i32; - if alignment > global_variable.get_alignment() { - global_variable.set_alignment(alignment); - } + if let Some(&mut (variable, ref mut global_align)) = + self.const_globals.borrow_mut().get_mut(&cv) + { + // `set_alignment` replaces the alignment, which is on the global's type and thus not + // reported by `get_alignment`: only raise it, or a smaller use would lower it. + if align > *global_align + && let Some(global_variable) = self.global_lvalues.borrow().get(&variable) + { + global_variable.set_alignment(align.bytes() as i32); + *global_align = align; } - return *variable; + return variable; } let global_value = self.static_addr_of_mut(cv, align, kind); #[cfg(feature = "master")] @@ -80,7 +84,7 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { .get(&global_value) .expect("`static_addr_of_mut` did not add the global to `self.global_lvalues`") .global_set_readonly(); - self.const_globals.borrow_mut().insert(cv, global_value); + self.const_globals.borrow_mut().insert(cv, (global_value, align)); global_value } @@ -359,18 +363,30 @@ fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec next_offset { - fields.push(AllocField::Bytes { range: next_offset..offset }); + push_byte_runs(&mut fields, next_offset..offset); } fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - fields.push(AllocField::Bytes { range: next_offset..alloc.len() }); + push_byte_runs(&mut fields, next_offset..alloc.len()); } fields } +/// `gcc_jit_context_new_array_from_bytes` rejects runs of `i32::MAX` bytes or more. +const MAX_BYTE_RUN_LEN: usize = i32::MAX as usize - 1; + +fn push_byte_runs(fields: &mut Vec, range: Range) { + let mut start = range.start; + while range.end - start > MAX_BYTE_RUN_LEN { + fields.push(AllocField::Bytes { range: start..start + MAX_BYTE_RUN_LEN }); + start += MAX_BYTE_RUN_LEN; + } + fields.push(AllocField::Bytes { range: start..range.end }); +} + /// The type [`const_alloc_to_gcc`] gives `alloc`, computed without building any rvalue. /// /// This lets `predefine_static` declare a static's global with the type its initializer will have, diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index b57e0fb2cd23a..39a98ede628f2 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -97,11 +97,11 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Cache of the anonymous struct types. pub struct_types: RefCell, Type<'gcc>>>, - /// Cache of the array types used for runs of constant bytes, keyed by element type and count. + /// Cache of the array types used for runs of constant bytes, keyed by length. /// /// libgccjit mints a fresh type on every `new_array_type`, and struct types are keyed on their /// field types, so without this two equal byte runs would yield two distinct anonymous structs. - pub byte_array_types: RefCell, u64), Type<'gcc>>>, + pub byte_array_types: RefCell>>, /// Cache of the return types of the pairs returned in two registers, keyed by the types of the /// two scalars. @@ -126,8 +126,8 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Local holding the return value of the defined functions returning it in memory. pub functions_with_indirect_return: RefCell, LValue<'gcc>>>, - /// Cache of emitted const globals (value -> global) - pub const_globals: RefCell, RValue<'gcc>>>, + /// Cache of emitted const globals (value -> global and its alignment) + pub const_globals: RefCell, (RValue<'gcc>, Align)>>, /// Map from the address of a global variable (rvalue) to the global variable itself (lvalue). /// FIXME(antoyo): remove when the rustc API is fixed. diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 5ecc971830f20..b0442cdfb11cc 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -20,7 +20,10 @@ fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`, /// `--target` and similar). -pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { +/// +/// If `for_cfg` is `true`, only the features implied by `--target` are included, to configure the +/// target info that populates [`rustc_codegen_ssa::TargetConfig`]. +pub(crate) fn global_gcc_features(sess: &EarlySession, for_cfg: bool) -> Vec { // Features that come earlier are overridden by conflicting features later in the string. // Typically we'll want more explicit settings to override the implicit ones, so: // @@ -57,6 +60,12 @@ pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { // Features implied by an implicit or explicit `--target`. target_features::target_spec_to_backend_features(sess, &mut extend_backend_features); + // `internal_target_features` applies -Ctarget-features itself, and GCC has no options for the + // features implied by other flags (e.g. retpoline), which would break the target info. + if for_cfg { + return features; + } + // -Ctarget-features target_features::flag_to_backend_features(sess, extend_backend_features); @@ -267,25 +276,7 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context.add_command_line_option("-mavx"); } - if sess.target.arch == Arch::AArch64 { - // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are - // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. - let outline_atomics = sess - .global_backend_features - .iter() - .rev() - .find_map(|feature| match feature.as_str() { - "outline-atomics" | "+outline-atomics" => Some(true), - "-outline-atomics" => Some(false), - _ => None, - }) - .unwrap_or(false); - context.add_command_line_option(if outline_atomics { - "-moutline-atomics" - } else { - "-mno-outline-atomics" - }); - } + add_feature_options(&context, &sess.target.arch, &sess.global_backend_features); for arg in &sess.opts.cg.llvm_args { context.add_command_line_option(arg); @@ -387,6 +378,80 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context } +/// Add the options for the global `features` that are not applied with function attributes. +fn add_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + if *arch == Arch::AArch64 { + // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are + // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. + let outline_atomics = + feature_setting(features, "outline-atomics") == FeatureSetting::Enabled; + context.add_command_line_option(if outline_atomics { + "-moutline-atomics" + } else { + "-mno-outline-atomics" + }); + + // Rust's `neon` covers both FP and SIMD, so without it (e.g. on softfloat targets like the + // Linux kernel) GCC must not touch those registers at all. + if feature_setting(features, "neon") == FeatureSetting::Disabled { + context.add_command_line_option("-mgeneral-regs-only"); + } + } +} + +/// Configure the target info `context` with the global `features`, like codegen does. +#[cfg(feature = "master")] +pub fn add_target_info_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + add_feature_options(context, arch, features); + // Codegen applies these as function attributes, which the target info does not see. Of the + // arches using these attributes, libgccjit only has target info for x86, not PowerPC. + if matches!(arch, Arch::X86 | Arch::X86_64) { + for feature in + features.iter().filter_map(|feature| to_gcc_target_attribute_feature(feature)) + { + context.add_command_line_option(format!("-m{}", feature)); + } + } +} + +/// The spelling of a global `feature` in a GCC `target` attribute, e.g. `no-sse` for `-sse`. +pub fn to_gcc_target_attribute_feature(feature: &str) -> Option { + // FIXME(antoyo): support soft-float. + if feature.contains("soft-float") { + return None; + } + + if feature.starts_with('-') { + Some(format!("no{}", feature)) + } else if let Some(stripped) = feature.strip_prefix('+') { + Some(stripped.to_string()) + } else { + Some(feature.to_string()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum FeatureSetting { + Enabled, + Disabled, + /// No global feature mentions it. + Unspecified, +} + +/// The last setting of `feature` in `features`. +fn feature_setting(features: &[String], feature: &str) -> FeatureSetting { + features + .iter() + .rev() + .find_map(|global_feature| match global_feature.strip_prefix('-') { + Some(disabled) if disabled == feature => Some(FeatureSetting::Disabled), + Some(_) => None, + None => (global_feature.strip_prefix('+').unwrap_or(global_feature) == feature) + .then_some(FeatureSetting::Enabled), + }) + .unwrap_or(FeatureSetting::Unspecified) +} + pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { match relocation_model { rustc_target::spec::RelocModel::Static => { diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 414bff06866a8..17034d0c195e7 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -240,7 +240,7 @@ impl CodegenBackend for GccCodegenBackend { panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } } - let global_backend_features = gcc_util::global_gcc_features(sess); + let global_backend_features = gcc_util::global_gcc_features(sess, false); #[cfg(feature = "master")] { @@ -253,6 +253,11 @@ impl CodegenBackend for GccCodegenBackend { if target_cpu != "generic" { context.add_command_line_option(format!("-march={}", target_cpu)); } + gcc_util::add_target_info_feature_options( + &context, + &sess.target.arch, + &gcc_util::global_gcc_features(sess, true), + ); // libgccjit always passes -fPIC, which some targets reject (e.g. MIPS without -mabicalls). gcc_util::add_pic_option(&context, sess.relocation_model()); diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs new file mode 100644 index 0000000000000..e4dfd4bb26dd0 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs @@ -0,0 +1,44 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu + +#![crate_type = "lib"] + +// Constants with identical bytes share one global, which needs the largest alignment among +// their uses: lowering it to a later, smaller one or reading it as bits fails these checks. + +// CHECK: "A_WIDE": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES:global\.[0-9]+]]" +#[no_mangle] +pub static A_WIDE: &[u128; 4] = &[0x09090909_09090909_09090909_09090909; 4]; + +// CHECK: "B_BYTES": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES]]" +#[no_mangle] +pub static B_BYTES: &[u8; 64] = &[9; 64]; + +// CHECK: "C_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE:global\.[0-9]+]]" +#[no_mangle] +pub static C_BYTES: &[u8; 64] = &[8; 64]; + +// CHECK: "D_WIDE": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE]]" +#[no_mangle] +pub static D_WIDE: &[u128; 4] = &[0x08080808_08080808_08080808_08080808; 4]; + +// CHECK: "E_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES:global\.[0-9]+]]" +#[no_mangle] +pub static E_BYTES: &[u8; 4] = &[3; 4]; + +// CHECK: "F_HALVES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES]]" +#[no_mangle] +pub static F_HALVES: &[u16; 2] = &[0x0303; 2]; + +// CHECK: .section .rodata.[[WIDE_THEN_BYTES]],"a" +// CHECK-NEXT: .align {{16|32|64}}{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_WIDE]],"a" +// CHECK-NEXT: .align 16{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_HALVES]],"a" +// CHECK-NEXT: .align 2{{$}} diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_str.rs b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs new file mode 100644 index 0000000000000..65969a713c996 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs @@ -0,0 +1,24 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=2 + +#![crate_type = "lib"] +#![allow(invalid_value)] + +// The panic messages of the `assert_zero_valid` and `assert_inhabited` intrinsics are built with +// `const_str`, which passes the address of its global as the string data. The global must +// therefore hold the bytes themselves (not a pointer to a string literal), without a trailing +// NUL so that its size matches the length passed along, and it must be read-only. + +// CHECK-LABEL: "zeroed_ref": +// CHECK: mov esi, 57 +// CHECK: lea rdi, "[[REF_STR:str\.[0-9]+]]"[rip] +#[unsafe(no_mangle)] +pub fn zeroed_ref() -> &'static u8 { + unsafe { std::mem::zeroed() } +} + +// CHECK: .section .rodata.[[REF_STR]],"a" +// CHECK: .size "[[REF_STR]]", 57 +// CHECK-NEXT: "[[REF_STR]]": +// CHECK-NEXT: .ascii "attempted to zero-initialize type `&u8`, which is invalid" diff --git a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs index b26387ecacdb6..2ca3f4c2fec36 100644 --- a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs +++ b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs @@ -1,8 +1,8 @@ -//@ memory-limit: 2G +//@ memory-limit: 1G // Compiler: -// An 8 MiB constant must compile within 2 GiB of address space. A backend that builds one rvalue -// per byte of a constant needs more than 6 GiB for it and runs out of memory. +// An 8 MiB constant must compile within 1 GiB of address space. A backend that builds one rvalue +// per 8 bytes of a constant needs about 1.4 GiB for it and runs out of memory. #![crate_type = "lib"] diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index a18eaf272fd07..e35d9916247f1 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -1,6 +1,5 @@ tests/ui/asm/may_unwind.rs tests/ui/asm/x86_64/may_unwind.rs -tests/ui/intrinsics/panic-uninitialized-zeroed.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs tests/ui/process/println-with-broken-pipe.rs @@ -12,7 +11,6 @@ tests/ui/process/nofile-limit.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs tests/ui/statics/const_generics.rs -tests/ui/thir-print/offset_of.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs @@ -20,7 +18,6 @@ tests/ui/explicit-tail-calls/become-indirect-return.rs tests/ui/explicit-tail-calls/indirect.rs tests/ui/abi/rust-tail-cc.rs tests/ui/abi/rust-preserve-none-cc.rs -tests/ui/extern/extern-types-field-offset.rs tests/ui/scalable-vectors/debuginfo-does-not-spill.rs tests/ui/scalable-vectors/debuginfo-no-opt-llvm-ice.rs tests/ui/scalable-vectors/transmute.rs diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 385b785233a75..c2fd8a79407f0 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -2,12 +2,12 @@ //! and related traits for the LLVM codegen backend. use std::any::Any; +use std::cell::OnceCell; use std::ffi::CStr; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::path::PathBuf; use std::ptr; -use std::sync::OnceLock; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::ThinModule; @@ -451,13 +451,15 @@ pub struct ModuleLlvm { /// Saved ThinLTO buffer for embedding in the object file. /// - /// This is currently a `OnceLock` to avoid aliasing problems when writing to it + /// This is currently a `OnceCell` to avoid aliasing problems when writing to it /// from code that also wants to pass around a shared ref to `ModuleCodegen`. - pub(crate) thin_lto_buffer: OnceLock>, + pub(crate) thin_lto_buffer: OnceCell>, } +// An LLVM context can only be used by one thread at a time. But we can move the entire context and +// all objects inside it to another thread (needed for parallel codegen). This is sound because +// `ModuleLlvm` owns the context and only allows borrowing LLVM objects tied to the context. unsafe impl Send for ModuleLlvm {} -unsafe impl Sync for ModuleLlvm {} /// Shared helper to call [`llvm::LLVMContextCreate`] and then configure /// [`llvm::LLVMContextSetDiscardValueNames`]. @@ -478,7 +480,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_target_machine(tcx, mod_name)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -491,7 +493,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_informational_target_machine(tcx.sess)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -512,7 +514,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(tm), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index 4c0df97a41985..36bc0a68d882e 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -14,7 +14,7 @@ use crate::back::write::{ use crate::{CompiledModule, ModuleCodegen}; pub trait WriteBackendMethods: Clone + 'static { - type Module: Send + Sync; + type Module: Send; type TargetMachine; type ModuleBuffer: ModuleBufferMethods; type ThinData: Send + Sync; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 89deeb44cf1b2..fc46005938b69 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1913,6 +1913,7 @@ symbols! { rustdoc, rustdoc_internals, rustdoc_missing_doc_code_examples, + rustdoc_skip_body, rustfmt, rvalue_static_promotion, rvector, diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 94b3c6ee01455..51f5bc189281b 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1480,8 +1480,8 @@ impl VecDeque { // SAFETY: // * Any slice passed to `drop_in_place` is valid; the second case has - // `len <= front.len()` and returning on `len > self.len()` ensures - // `begin <= back.len()` in the first case + // `len <= front.len()` and returning on `len >= self.len()` ensures + // `begin < back.len()` in the first case // * The head of the VecDeque is moved before calling `drop_in_place`, // so no value is dropped twice if `drop_in_place` panics unsafe { diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 6c2d5d68d787c..aefe43aa13ef2 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -92,6 +92,7 @@ #![feature(allocator_ext)] #![feature(array_into_iter_constructors)] #![feature(ascii_char)] +#![feature(ascii_char_variants)] #![feature(async_fn_traits)] #![feature(async_iterator)] #![feature(borrowed_buf_init)] @@ -156,6 +157,7 @@ #![feature(pattern)] #![feature(pin_coerce_unsized_trait)] #![feature(ptr_alignment_type)] +#![feature(ptr_cast_array)] #![feature(ptr_metadata)] #![feature(random)] #![feature(raw_os_error_ty)] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index bb8301e53d34f..9961c71dc062e 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -42,11 +42,15 @@ #![stable(feature = "rust1", since = "1.0.0")] +#[cfg(not(no_global_oom_handling))] +use core::ascii; use core::error::Error; use core::iter::FusedIterator; #[cfg(not(no_global_oom_handling))] use core::iter::from_fn; #[cfg(not(no_global_oom_handling))] +use core::mem::MaybeUninit; +#[cfg(not(no_global_oom_handling))] use core::num::Saturating; #[cfg(not(no_global_oom_handling))] use core::ops::Add; @@ -1488,8 +1492,8 @@ impl String { /// Shortens this `String` to the specified length. /// - /// If `new_len` is greater than or equal to the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// Note that this method has no effect on the allocated capacity /// of the string @@ -1511,10 +1515,11 @@ impl String { #[stable(feature = "rust1", since = "1.0.0")] #[track_caller] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_char_boundary(new_len)); - self.vec.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_char_boundary(new_len)); + self.vec.truncate(new_len) } /// Removes the last character from the string buffer and returns it. @@ -2448,12 +2453,35 @@ impl Clone for String { #[stable(feature = "rust1", since = "1.0.0")] impl FromIterator for String { fn from_iter>(iter: I) -> String { - let mut buf = String::new(); - buf.extend(iter); + FromCharIterSpec::to_string_spec(iter.into_iter()) + } +} + +#[cfg(not(no_global_oom_handling))] +trait FromCharIterSpec: Iterator { + fn to_string_spec(self) -> String; +} + +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for T +where + T: Iterator, +{ + default fn to_string_spec(self) -> String { + let mut buf = String::with_capacity(self.size_hint().0); + buf.extend(self); buf } } +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for core::str::EscapeDefault<'_> { + fn to_string_spec(self) -> String { + // go through the ToString specialization + self.to_string() + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "string_from_iter_by_ref", since = "1.17.0")] impl<'a> FromIterator<&'a char> for String { @@ -2549,10 +2577,7 @@ impl<'a> FromIterator<&'a core::ascii::Char> for String { #[stable(feature = "rust1", since = "1.0.0")] impl Extend for String { fn extend>(&mut self, iter: I) { - let iterator = iter.into_iter(); - let (lower_bound, _) = iterator.size_hint(); - self.reserve(lower_bound); - iterator.for_each(move |c| self.push(c)); + SpecExtendChars::spec_extend_into(iter, self); } #[inline] @@ -2566,6 +2591,28 @@ impl Extend for String { } } +#[cfg(not(no_global_oom_handling))] +trait SpecExtendChars { + fn spec_extend_into(self, s: &mut String); +} + +#[cfg(not(no_global_oom_handling))] +impl> SpecExtendChars for T { + default fn spec_extend_into(self, target: &mut String) { + let iterator = self.into_iter(); + let (lower_bound, _) = iterator.size_hint(); + target.reserve(lower_bound); + iterator.for_each(move |c| target.push(c)); + } +} + +#[cfg(not(no_global_oom_handling))] +impl SpecExtendChars for core::str::EscapeDefault<'_> { + fn spec_extend_into(self, target: &mut String) { + target.extend_from_escape_default(self); + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "extend_ref", since = "1.2.0")] impl<'a> Extend<&'a char> for String { @@ -3160,6 +3207,126 @@ impl SpecToString for fmt::Arguments<'_> { } } +#[cfg(not(no_global_oom_handling))] +impl SpecToString for core::str::EscapeDefault<'_> { + fn spec_to_string(&self) -> String { + let mut s = String::new(); + s.extend_from_escape_default(self.clone()); + s + } +} + +#[cfg(not(no_global_oom_handling))] +impl String { + #[inline] + fn extend_from_escape_default(&mut self, escaper: core::str::EscapeDefault<'_>) { + let (front, middle, tail) = escaper.into_parts(); + let lengths = front.as_ref().map_or_default(|f| f.size_hint().0) + + middle.as_ref().map_or_default(|m| m.size_hint().0) + + tail.as_ref().map_or_default(|t| t.size_hint().0); + self.reserve(lengths); + + if let Some(front) = front { + self.extend(front); + } + if let Some(middle) = middle { + /// Ensures a spare capacity of N bytes and then passes that as MaybeUninit + /// slice to a closure that returns the bytes written, then + /// updates the length. + /// + /// # Safety: + /// + /// The caller must initialize the number of bytes returned by the closure. + #[inline] + unsafe fn with_spare( + bytes: &mut Vec, + then: impl FnOnce(&mut [MaybeUninit; N]) -> usize, + ) { + const { + assert!(core::mem::size_of::() == 1); + } + bytes.reserve(N); + let old_len = bytes.len(); + // SAFETY: We reserved enough space for the requested number of bytes. + unsafe { + let spare: &mut [MaybeUninit; N] = bytes + .spare_capacity_mut() + .as_mut_ptr() + .cast::() + .cast_uninit() + .cast_array::() + .as_mut_unchecked(); + let written = then(spare); + bytes.set_len(old_len + written); + } + } + + for c in middle { + match c { + '\'' | '\"' | '\\' => { + self.push('\\'); + self.push(c); + } + '\x20'..='\x7e' => self.push(c), + '\t' => { + self.push('\\'); + self.push('t'); + } + '\r' => { + self.push('\\'); + self.push('r'); + } + '\n' => { + self.push('\\'); + self.push('n'); + } + c => { + // SAFETY: We only let the function write ascii chars, so it remains valid utf8. + // and it returns the number of bytes written, so so with_spare ensures the rest for us. + unsafe { + with_spare::<10, ascii::Char>(self.as_mut_vec(), |spare| { + escape_unicode_into(spare, c) + }); + } + } + } + } + } + + if let Some(tail) = tail { + self.extend(tail); + } + } +} + +#[cfg(not(no_global_oom_handling))] +#[inline] +// adapted from core::escape::escape_unicode, but it's left-aligned instead of right-aligned. +fn escape_unicode_into(output: &mut [MaybeUninit; 10], c: char) -> usize { + const HEX_DIGITS: [ascii::Char; 16] = *b"0123456789abcdef".as_ascii().unwrap(); + + let c = c as u32; + // OR-ing `1` ensures that for `c == 0` the code computes that + // one digit should be printed. + let prefix_skip = (c | 1).leading_zeros() as usize / 4 - 2; + + // Always write all digits, but for shorter representations the unused + // digits get overwritten by later writes. + output[3usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 20) & 15) as usize]); + output[4usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 16) & 15) as usize]); + output[5usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 12) & 15) as usize]); + output[6usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 8) & 15) as usize]); + output[7usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 4) & 15) as usize]); + output[8usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[(c & 15) as usize]); + + output[0].write(ascii::Char::ReverseSolidus); + output[1].write(ascii::Char::SmallU); + output[2].write(ascii::Char::LeftCurlyBracket); + output[9 - prefix_skip].write(ascii::Char::RightCurlyBracket); + + 10 - prefix_skip +} + #[stable(feature = "rust1", since = "1.0.0")] impl AsRef for String { #[inline] diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index d51eb95e9eeec..03702b19f92c7 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -1701,8 +1701,8 @@ impl Vec { /// assert_eq!(vec, [1, 2]); /// ``` /// - /// No truncation occurs when `len` is greater than the vector's current - /// length: + /// No truncation occurs when `len` is greater than or equal to the + /// vector's current length: /// /// ``` /// let mut vec = vec![1, 2, 3]; @@ -1728,16 +1728,13 @@ impl Vec { // This is safe because: // - // * the slice passed to `drop_in_place` is valid; the `len > self.len` + // * the slice passed to `drop_in_place` is valid; the `len >= self.len` // case avoids creating an invalid slice, and // * the `len` of the vector is shrunk before calling `drop_in_place`, // such that no value will be dropped twice in case `drop_in_place` // were to panic once (if it panics twice, the program aborts). unsafe { - // Note: It's intentional that this is `>` and not `>=`. - // Changing it to `>=` has negative performance - // implications in some cases. See #78884 for more. - if len > self.len { + if len >= self.len { return; } let remaining_len = self.len - len; @@ -3163,7 +3160,8 @@ impl Vec { /// calling the closure `f`. The return values from `f` will end up /// in the `Vec` in the order they have been generated. /// - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method uses a closure to create new values on every push. If /// you'd rather [`Clone`] a given value, use [`Vec::resize`]. If you @@ -3517,7 +3515,8 @@ impl Vec { /// /// If `new_len` is greater than `len`, the `Vec` is extended by the /// difference, with each additional slot filled with `value`. - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method requires `T` to implement [`Clone`], /// in order to be able to clone the passed value. diff --git a/library/alloc/src/wtf8/mod.rs b/library/alloc/src/wtf8/mod.rs index 232fbba622b35..2296806d0cd42 100644 --- a/library/alloc/src/wtf8/mod.rs +++ b/library/alloc/src/wtf8/mod.rs @@ -354,18 +354,19 @@ impl Wtf8Buf { /// Shortens a string to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// /// Panics if `new_len` does not lie on a code point boundary. #[inline] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_code_point_boundary(new_len)); - self.bytes.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_code_point_boundary(new_len)); + self.bytes.truncate(new_len) } /// Consumes the WTF-8 string and tries to convert it to a vec of bytes. diff --git a/library/alloctests/benches/str.rs b/library/alloctests/benches/str.rs index 98c7c5413caef..68bdfbed8c8b5 100644 --- a/library/alloctests/benches/str.rs +++ b/library/alloctests/benches/str.rs @@ -1,5 +1,9 @@ use test::{Bencher, black_box}; +#[allow(unused)] // this is a symlink to core benches, we don't use everything here +#[path = "../../coretests/benches/str/corpora.rs"] +pub(super) mod corpora; + #[bench] fn char_iterator(b: &mut Bencher) { let s = "ศไทย中华Việt Nam; Mary had a little lamb, Little lamb"; diff --git a/library/alloctests/benches/string.rs b/library/alloctests/benches/string.rs index 0bbec12e4fdc6..75caf722d723d 100644 --- a/library/alloctests/benches/string.rs +++ b/library/alloctests/benches/string.rs @@ -159,3 +159,15 @@ fn bench_insert_str_long(b: &mut Bencher) { x }) } + +#[bench] +fn bench_from_escape_default_ascii(b: &mut Bencher) { + let s = super::str::corpora::en::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} + +#[bench] +fn bench_from_escape_default_multibyte(b: &mut Bencher) { + let s = super::str::corpora::emoji::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} diff --git a/library/alloctests/tests/string.rs b/library/alloctests/tests/string.rs index 08eb1855a4824..9c632b4e3a2f9 100644 --- a/library/alloctests/tests/string.rs +++ b/library/alloctests/tests/string.rs @@ -956,3 +956,14 @@ fn test_str_concat() { let s: String = format!("{a}{b}"); assert_eq!(s.as_bytes()[9], 'd' as u8); } + +// alloc has a specialization for EscapeDefault's ToString impl, which isn't covered by core tests. +#[test] +fn test_from_escaped_str() { + assert_eq!("abcABC012".escape_default().to_string(), "abcABC012"); + assert_eq!("\t\n\"".escape_default().to_string(), r#"\t\n\""#); + assert_eq!("\u{10FFFF}".escape_default().to_string(), "\\u{10ffff}"); + assert_eq!("\u{0}".escape_default().to_string(), "\\u{0}"); + assert_eq!("\u{1}".escape_default().to_string(), "\\u{1}"); + assert_eq!("\u{100}".escape_default().to_string(), "\\u{100}"); +} diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index c97e6f4c45395..d296d0ceb12b3 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1506,10 +1506,12 @@ macro_rules! nonzero_integer { #[inline] const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result { + // FIXME(const-hack): This should use `?` again, once it's `const` let n = match <$Int>::from_ascii_bytes_radix_impl(src, radix) { Ok(n) => n, Err(err) => return Err(err), }; + // FIXME(const-hack): This should use `Option::ok_or` again, once it's `const` if let Some(n) = Self::new(n) { Ok(n) } else { diff --git a/library/core/src/str/iter.rs b/library/core/src/str/iter.rs index 60b9e81aaeff9..657d894d38878 100644 --- a/library/core/src/str/iter.rs +++ b/library/core/src/str/iter.rs @@ -1589,6 +1589,18 @@ pub struct EscapeDefault<'a> { pub(super) inner: FlatMap, char_mod::EscapeDefault, CharEscapeDefault>, } +impl<'a> EscapeDefault<'a> { + /// Disassembles the iterator into its internal state. + /// Used by alloc for an optimized to_string impl. + #[unstable(feature = "std_internals", issue = "none")] + pub fn into_parts( + self, + ) -> (Option>, Option>, Option>) + { + self.inner.into_parts() + } +} + /// The return type of [`str::escape_unicode`]. #[stable(feature = "str_escape", since = "1.34.0")] #[derive(Clone, Debug)] diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 770cc5f834a11..7a4a7049533ce 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -569,8 +569,8 @@ impl OsString { /// Truncate the `OsString` to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// @@ -579,11 +579,12 @@ impl OsString { #[inline] #[unstable(feature = "os_string_truncate", issue = "133262")] pub fn truncate(&mut self, len: usize) { - if len <= self.len() { - self.as_os_str().inner.check_public_boundary(len); - // SAFETY: The length was just checked to be at a valid boundary. - unsafe { self.inner.truncate_unchecked(len) }; + if len >= self.len() { + return; } + self.as_os_str().inner.check_public_boundary(len); + // SAFETY: The length was just checked to be at a valid boundary. + unsafe { self.inner.truncate_unchecked(len) }; } /// Provides plumbing to `Vec::extend_from_slice` without giving full diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index ea574fc4146e7..15ff7afa45818 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -461,3 +461,17 @@ macro_rules! hash_map { map }} } + +/// Attribute macro to replace a function's body with `loop {}` under rustdoc. +/// +/// This allows target-specific functions to be documented on other targets, +/// even when their bodies reference items unavailable on the target used to +/// build the documentation. Use this with `#[cfg(any(doc, ...))]` to include +/// such functions in documentation builds. +/// +/// The function's signature must still be valid on the documentation target. +#[rustc_builtin_macro] +#[allow(unused_macros)] +pub(crate) macro rustdoc_skip_body($item:item) { + /* compiler built-in */ +} diff --git a/library/std/src/os/darwin/fs.rs b/library/std/src/os/darwin/fs.rs index b41391f19dccf..ece59e96c7819 100644 --- a/library/std/src/os/darwin/fs.rs +++ b/library/std/src/os/darwin/fs.rs @@ -4,6 +4,7 @@ #![stable(feature = "metadata_ext", since = "1.1.0")] use crate::fs::{self, Metadata}; +use crate::macros::rustdoc_skip_body; use crate::sys::{AsInner, AsInnerMut, IntoInner}; use crate::time::SystemTime; @@ -88,69 +89,91 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] #[cfg(any(doc, target_os = "macos", target_os = "ios"))] + #[rustdoc_skip_body] fn as_raw_stat(&self) -> &super::raw::stat { unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const super::raw::stat) } } + #[rustdoc_skip_body] fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 } + #[rustdoc_skip_body] fn st_ino(&self) -> u64 { self.as_inner().as_inner().st_ino as u64 } + #[rustdoc_skip_body] fn st_mode(&self) -> u32 { self.as_inner().as_inner().st_mode as u32 } + #[rustdoc_skip_body] fn st_nlink(&self) -> u64 { self.as_inner().as_inner().st_nlink as u64 } + #[rustdoc_skip_body] fn st_uid(&self) -> u32 { self.as_inner().as_inner().st_uid as u32 } + #[rustdoc_skip_body] fn st_gid(&self) -> u32 { self.as_inner().as_inner().st_gid as u32 } + #[rustdoc_skip_body] fn st_rdev(&self) -> u64 { self.as_inner().as_inner().st_rdev as u64 } + #[rustdoc_skip_body] fn st_size(&self) -> u64 { self.as_inner().as_inner().st_size as u64 } + #[rustdoc_skip_body] fn st_atime(&self) -> i64 { self.as_inner().as_inner().st_atime as i64 } + #[rustdoc_skip_body] fn st_atime_nsec(&self) -> i64 { self.as_inner().as_inner().st_atime_nsec as i64 } + #[rustdoc_skip_body] fn st_mtime(&self) -> i64 { self.as_inner().as_inner().st_mtime as i64 } + #[rustdoc_skip_body] fn st_mtime_nsec(&self) -> i64 { self.as_inner().as_inner().st_mtime_nsec as i64 } + #[rustdoc_skip_body] fn st_ctime(&self) -> i64 { self.as_inner().as_inner().st_ctime as i64 } + #[rustdoc_skip_body] fn st_ctime_nsec(&self) -> i64 { self.as_inner().as_inner().st_ctime_nsec as i64 } + #[rustdoc_skip_body] fn st_birthtime(&self) -> i64 { self.as_inner().as_inner().st_birthtime as i64 } + #[rustdoc_skip_body] fn st_birthtime_nsec(&self) -> i64 { self.as_inner().as_inner().st_birthtime_nsec as i64 } + #[rustdoc_skip_body] fn st_blksize(&self) -> u64 { self.as_inner().as_inner().st_blksize as u64 } + #[rustdoc_skip_body] fn st_blocks(&self) -> u64 { self.as_inner().as_inner().st_blocks as u64 } + #[rustdoc_skip_body] fn st_gen(&self) -> u32 { self.as_inner().as_inner().st_gen as u32 } + #[rustdoc_skip_body] fn st_flags(&self) -> u32 { self.as_inner().as_inner().st_flags as u32 } + #[rustdoc_skip_body] fn st_lspare(&self) -> u32 { self.as_inner().as_inner().st_lspare as u32 } @@ -171,6 +194,7 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { + #[rustdoc_skip_body] fn set_created(mut self, t: SystemTime) -> Self { self.as_inner_mut().set_created(t.into_inner()); self diff --git a/library/std/src/os/linux/process.rs b/library/std/src/os/linux/process.rs index e0ddaa85cb82e..26a4ab4b4c603 100644 --- a/library/std/src/os/linux/process.rs +++ b/library/std/src/os/linux/process.rs @@ -5,6 +5,7 @@ #![unstable(feature = "linux_pidfd", issue = "82971")] use crate::io::Result; +use crate::macros::rustdoc_skip_body; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use crate::process::{self, ExitStatus}; use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -61,6 +62,7 @@ impl PidFd { /// races. But doing so will return an Error. /// /// [`Child::kill`]: process::Child::kill + #[rustdoc_skip_body] pub fn kill(&self) -> Result<()> { self.inner.kill() } @@ -74,6 +76,7 @@ impl PidFd { /// will return an Error. /// /// [`Child::wait`]: process::Child::wait + #[rustdoc_skip_body] pub fn wait(&self) -> Result { self.inner.wait().map(FromInner::from_inner) } @@ -84,6 +87,7 @@ impl PidFd { /// to reap a child will return an ExitStatus, further attempts will return an Error. /// /// [`Child::try_wait`]: process::Child::try_wait + #[rustdoc_skip_body] pub fn try_wait(&self) -> Result> { Ok(self.inner.try_wait()?.map(FromInner::from_inner)) } @@ -110,36 +114,42 @@ impl IntoInner for PidFd { impl AsRawFd for PidFd { #[inline] + #[rustdoc_skip_body] fn as_raw_fd(&self) -> RawFd { self.as_inner().as_inner().as_raw_fd() } } impl FromRawFd for PidFd { + #[rustdoc_skip_body] unsafe fn from_raw_fd(fd: RawFd) -> Self { Self::from_inner(InnerPidFd::from_raw_fd(fd)) } } impl IntoRawFd for PidFd { + #[rustdoc_skip_body] fn into_raw_fd(self) -> RawFd { self.into_inner().into_inner().into_raw_fd() } } impl AsFd for PidFd { + #[rustdoc_skip_body] fn as_fd(&self) -> BorrowedFd<'_> { self.as_inner().as_inner().as_fd() } } impl From for PidFd { + #[rustdoc_skip_body] fn from(fd: OwnedFd) -> Self { Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) } } impl From for OwnedFd { + #[rustdoc_skip_body] fn from(pid_fd: PidFd) -> Self { pid_fd.into_inner().into_inner().into_inner() } diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 369e5d4b0fb9d..811ab7ea8480b 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -12,6 +12,7 @@ use io::{Read, Write}; use crate::ffi::OsStr; use crate::fs::{self, File, OpenOptions}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::macros::rustdoc_skip_body; #[cfg(target_env = "p1")] use crate::os::fd::AsRawFd; use crate::path::Path; @@ -333,12 +334,15 @@ pub trait MetadataExt { } impl MetadataExt for fs::Metadata { + #[rustdoc_skip_body] fn dev(&self) -> u64 { self.as_inner().as_inner().st_dev } + #[rustdoc_skip_body] fn ino(&self) -> u64 { self.as_inner().as_inner().st_ino } + #[rustdoc_skip_body] fn nlink(&self) -> u64 { self.as_inner().as_inner().st_nlink } @@ -358,12 +362,15 @@ pub trait FileTypeExt { } impl FileTypeExt for fs::FileType { + #[rustdoc_skip_body] fn is_block_device(&self) -> bool { self.as_inner().is(libc::S_IFBLK) } + #[rustdoc_skip_body] fn is_char_device(&self) -> bool { self.as_inner().is(libc::S_IFCHR) } + #[rustdoc_skip_body] fn is_socket(&self) -> bool { self.as_inner().is(libc::S_IFSOCK) } diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs index b27a116f60388..a4c6f1ad7df42 100644 --- a/library/std/src/os/windows/ffi.rs +++ b/library/std/src/os/windows/ffi.rs @@ -58,6 +58,7 @@ use alloc::wtf8::Wtf8Buf; use crate::ffi::{OsStr, OsString}; use crate::fmt; use crate::iter::FusedIterator; +use crate::macros::rustdoc_skip_body; use crate::sys::os_str::Buf; use crate::sys::{AsInner, FromInner}; @@ -88,6 +89,7 @@ pub impl(self) trait OsStringExt { #[stable(feature = "rust1", since = "1.0.0")] impl OsStringExt for OsString { + #[rustdoc_skip_body] fn from_wide(wide: &[u16]) -> OsString { FromInner::from_inner(Buf { inner: Wtf8Buf::from_wide(wide) }) } @@ -125,6 +127,7 @@ pub impl(self) trait OsStrExt { #[stable(feature = "rust1", since = "1.0.0")] impl OsStrExt for OsStr { #[inline] + #[rustdoc_skip_body] fn encode_wide(&self) -> EncodeWide<'_> { EncodeWide { inner: self.as_inner().inner.encode_wide() } } diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 865cc90e83156..d931dd2476039 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -6,6 +6,7 @@ use crate::fs::{self, Metadata, OpenOptions, Permissions}; use crate::io::BorrowedCursor; +use crate::macros::rustdoc_skip_body; use crate::path::Path; use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; @@ -493,26 +494,31 @@ pub trait OpenOptionsExt { #[stable(feature = "open_options_ext", since = "1.10.0")] impl OpenOptionsExt for OpenOptions { + #[rustdoc_skip_body] fn access_mode(&mut self, access: u32) -> &mut OpenOptions { self.as_inner_mut().access_mode(access); self } + #[rustdoc_skip_body] fn share_mode(&mut self, share: u32) -> &mut OpenOptions { self.as_inner_mut().share_mode(share); self } + #[rustdoc_skip_body] fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions { self.as_inner_mut().custom_flags(flags); self } + #[rustdoc_skip_body] fn attributes(&mut self, attributes: u32) -> &mut OpenOptions { self.as_inner_mut().attributes(attributes); self } + #[rustdoc_skip_body] fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions { self.as_inner_mut().security_qos_flags(flags); self @@ -536,11 +542,13 @@ pub impl(self) trait OpenOptionsExt2 { #[unstable(feature = "windows_freeze_file_times", issue = "149715")] impl OpenOptionsExt2 for OpenOptions { + #[rustdoc_skip_body] fn freeze_last_access_time(&mut self, freeze: bool) -> &mut Self { self.as_inner_mut().freeze_last_access_time(freeze); self } + #[rustdoc_skip_body] fn freeze_last_write_time(&mut self, freeze: bool) -> &mut Self { self.as_inner_mut().freeze_last_write_time(freeze); self @@ -594,6 +602,7 @@ pub impl(self) trait PermissionsExt { #[unstable(feature = "windows_permissions_ext", issue = "152956")] impl PermissionsExt for fs::Permissions { + #[rustdoc_skip_body] fn file_attributes(&self) -> u32 { self.as_inner().file_attributes() } @@ -805,30 +814,39 @@ pub trait MetadataExt { #[stable(feature = "metadata_ext", since = "1.1.0")] impl MetadataExt for Metadata { + #[rustdoc_skip_body] fn file_attributes(&self) -> u32 { self.as_inner().attrs() } + #[rustdoc_skip_body] fn creation_time(&self) -> u64 { self.as_inner().created_u64() } + #[rustdoc_skip_body] fn last_access_time(&self) -> u64 { self.as_inner().accessed_u64() } + #[rustdoc_skip_body] fn last_write_time(&self) -> u64 { self.as_inner().modified_u64() } + #[rustdoc_skip_body] fn file_size(&self) -> u64 { self.as_inner().size() } + #[rustdoc_skip_body] fn volume_serial_number(&self) -> Option { self.as_inner().volume_serial_number() } + #[rustdoc_skip_body] fn number_of_links(&self) -> Option { self.as_inner().number_of_links() } + #[rustdoc_skip_body] fn file_index(&self) -> Option { self.as_inner().file_index() } + #[rustdoc_skip_body] fn change_time(&self) -> Option { self.as_inner().changed_u64() } @@ -849,9 +867,11 @@ pub impl(self) trait FileTypeExt { #[stable(feature = "windows_file_type_ext", since = "1.64.0")] impl FileTypeExt for fs::FileType { + #[rustdoc_skip_body] fn is_symlink_dir(&self) -> bool { self.as_inner().is_symlink_dir() } + #[rustdoc_skip_body] fn is_symlink_file(&self) -> bool { self.as_inner().is_symlink_file() } @@ -867,6 +887,7 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { + #[rustdoc_skip_body] fn set_created(mut self, t: SystemTime) -> Self { self.as_inner_mut().set_created(t.into_inner()); self @@ -911,6 +932,7 @@ impl FileTimesExt for fs::FileTimes { #[doc(alias = "CreateSymbolicLink", alias = "CreateSymbolicLinkW")] #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_file")] +#[rustdoc_skip_body] pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io::Result<()> { sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) } @@ -953,6 +975,7 @@ pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io: #[doc(alias = "CreateSymbolicLink", alias = "CreateSymbolicLinkW")] #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_dir")] +#[rustdoc_skip_body] pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io::Result<()> { sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) } @@ -965,6 +988,7 @@ pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io:: /// /// If either path is not a local file path then this will fail. #[unstable(feature = "junction_point", issue = "121709")] +#[rustdoc_skip_body] pub fn junction_point, Q: AsRef>(original: P, link: Q) -> io::Result<()> { sys::fs::junction_point(original.as_ref(), link.as_ref()) } diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index 01bd36155b110..caeffaed3d381 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -4,6 +4,7 @@ use super::raw::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use crate::alloc::Allocator; +use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::ManuallyDrop; use crate::sys::{AsInner, FromInner, IntoInner, cvt}; @@ -172,6 +173,7 @@ impl TryFrom for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for HandleOrNull { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { if self.is_valid() { unsafe { @@ -194,10 +196,12 @@ impl BorrowedHandle<'_> { /// Creates a new `OwnedHandle` instance that shares the same underlying /// object as the existing `BorrowedHandle` instance. #[stable(feature = "io_safety", since = "1.63.0")] + #[rustdoc_skip_body] pub fn try_clone_to_owned(&self) -> io::Result { self.duplicate(0, false, sys::c::DUPLICATE_SAME_ACCESS) } + #[rustdoc_skip_body] pub(crate) fn duplicate( &self, access: u32, @@ -250,6 +254,7 @@ impl TryFrom for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for HandleOrInvalid { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { if self.is_valid() { unsafe { @@ -470,6 +475,7 @@ impl AsHandle for &mut T { /// ``` impl AsHandle for crate::sync::Arc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { (**self).as_handle() } @@ -478,6 +484,7 @@ impl AsHandle for crate::sync::Arc { #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for crate::rc::Rc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { (**self).as_handle() } @@ -486,6 +493,7 @@ impl AsHandle for crate::rc::Rc { #[unstable(feature = "unique_rc_arc", issue = "112566")] impl AsHandle for crate::rc::UniqueRc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { (**self).as_handle() } @@ -494,6 +502,7 @@ impl AsHandle for crate::rc::UniqueRc { #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for Box { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { (**self).as_handle() } @@ -521,6 +530,7 @@ impl AsHandle for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl AsHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { self.as_inner().as_handle() } @@ -530,6 +540,7 @@ impl AsHandle for fs::File { impl From for OwnedHandle { /// Takes ownership of a [`File`](fs::File)'s underlying file handle. #[inline] + #[rustdoc_skip_body] fn from(file: fs::File) -> OwnedHandle { file.into_inner().into_inner().into_inner() } @@ -539,6 +550,7 @@ impl From for OwnedHandle { impl From for fs::File { /// Returns a [`File`](fs::File) that takes ownership of the given handle. #[inline] + #[rustdoc_skip_body] fn from(owned: OwnedHandle) -> Self { Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) } @@ -654,6 +666,7 @@ impl AsHandle for crate::thread::JoinHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl From> for OwnedHandle { #[inline] + #[rustdoc_skip_body] fn from(join_handle: crate::thread::JoinHandle) -> OwnedHandle { join_handle.into_inner().into_handle().into_inner() } @@ -661,6 +674,7 @@ impl From> for OwnedHandle { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeReader { + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { self.0.as_handle() } @@ -668,6 +682,7 @@ impl AsHandle for io::PipeReader { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { + #[rustdoc_skip_body] fn from(pipe: io::PipeReader) -> Self { pipe.into_inner().into_inner() } @@ -675,6 +690,7 @@ impl From for OwnedHandle { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeWriter { + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { self.0.as_handle() } @@ -682,6 +698,7 @@ impl AsHandle for io::PipeWriter { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { + #[rustdoc_skip_body] fn from(pipe: io::PipeWriter) -> Self { pipe.into_inner().into_inner() } @@ -689,6 +706,7 @@ impl From for OwnedHandle { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeReader { + #[rustdoc_skip_body] fn from(owned_handle: OwnedHandle) -> Self { Self::from_inner(FromInner::from_inner(owned_handle)) } @@ -696,6 +714,7 @@ impl From for io::PipeReader { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeWriter { + #[rustdoc_skip_body] fn from(owned_handle: OwnedHandle) -> Self { Self::from_inner(FromInner::from_inner(owned_handle)) } diff --git a/library/std/src/os/windows/io/raw.rs b/library/std/src/os/windows/io/raw.rs index d050c28832517..0bd74c76490b1 100644 --- a/library/std/src/os/windows/io/raw.rs +++ b/library/std/src/os/windows/io/raw.rs @@ -2,6 +2,7 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::macros::rustdoc_skip_body; #[cfg(doc)] use crate::os::windows::io::{AsHandle, AsSocket}; use crate::os::windows::io::{OwnedHandle, OwnedSocket}; @@ -93,6 +94,7 @@ pub trait IntoRawHandle { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().as_raw_handle() as RawHandle } @@ -155,6 +157,7 @@ pub(super) fn stdio_handle(raw: RawHandle) -> RawHandle { #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_handle(handle: RawHandle) -> fs::File { unsafe { let handle = handle as sys::c::HANDLE; @@ -168,6 +171,7 @@ impl FromRawHandle for fs::File { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_raw_handle() as *mut _ } @@ -237,6 +241,7 @@ pub trait IntoRawSocket { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { self.as_inner().socket().as_raw_socket() } @@ -244,6 +249,7 @@ impl AsRawSocket for net::TcpStream { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { self.as_inner().socket().as_raw_socket() } @@ -251,6 +257,7 @@ impl AsRawSocket for net::TcpListener { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { self.as_inner().socket().as_raw_socket() } @@ -259,6 +266,7 @@ impl AsRawSocket for net::UdpSocket { #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpStream { unsafe { let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); @@ -269,6 +277,7 @@ impl FromRawSocket for net::TcpStream { #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpListener { unsafe { let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); @@ -279,6 +288,7 @@ impl FromRawSocket for net::TcpListener { #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::UdpSocket { unsafe { let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); @@ -290,6 +300,7 @@ impl FromRawSocket for net::UdpSocket { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { self.into_inner().into_socket().into_inner().into_raw_socket() } @@ -298,6 +309,7 @@ impl IntoRawSocket for net::TcpStream { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { self.into_inner().into_socket().into_inner().into_raw_socket() } @@ -306,6 +318,7 @@ impl IntoRawSocket for net::TcpListener { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { self.into_inner().into_socket().into_inner().into_raw_socket() } @@ -313,6 +326,7 @@ impl IntoRawSocket for net::UdpSocket { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeReader { + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.0.as_raw_handle() } @@ -320,6 +334,7 @@ impl AsRawHandle for io::PipeReader { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeReader { + #[rustdoc_skip_body] unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } } @@ -327,6 +342,7 @@ impl FromRawHandle for io::PipeReader { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeReader { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.0.into_raw_handle() } @@ -334,6 +350,7 @@ impl IntoRawHandle for io::PipeReader { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeWriter { + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.0.as_raw_handle() } @@ -341,6 +358,7 @@ impl AsRawHandle for io::PipeWriter { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeWriter { + #[rustdoc_skip_body] unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } } @@ -348,6 +366,7 @@ impl FromRawHandle for io::PipeWriter { #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeWriter { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.0.into_raw_handle() } diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index d625a9ac52730..24038e3dd5624 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -4,6 +4,7 @@ use super::raw::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; use crate::alloc::Allocator; +use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::{self, ManuallyDrop}; #[cfg(not(target_vendor = "uwp"))] @@ -78,6 +79,7 @@ impl OwnedSocket { // FIXME(strict_provenance_magic): we defined RawSocket to be a u64 ;-; #[allow(implicit_provenance_casts)] #[cfg(not(target_vendor = "uwp"))] + #[rustdoc_skip_body] pub(crate) fn set_no_inherit(&self) -> io::Result<()> { cvt(unsafe { sys::c::SetHandleInformation( @@ -99,6 +101,7 @@ impl BorrowedSocket<'_> { /// Creates a new `OwnedSocket` instance that shares the same underlying /// object as the existing `BorrowedSocket` instance. #[stable(feature = "io_safety", since = "1.63.0")] + #[rustdoc_skip_body] pub fn try_clone_to_owned(&self) -> io::Result { let mut info = unsafe { mem::zeroed::() }; let result = unsafe { @@ -154,6 +157,7 @@ impl BorrowedSocket<'_> { } /// Returns the last error from the Windows socket interface. +#[rustdoc_skip_body] fn last_error() -> io::Error { io::Error::from_raw_os_error(unsafe { sys::c::WSAGetLastError() }) } @@ -194,6 +198,7 @@ impl FromRawSocket for OwnedSocket { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for OwnedSocket { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { unsafe { let _ = sys::c::closesocket(self.socket.as_inner() as sys::c::SOCKET); diff --git a/library/std/src/os/windows/net/addr.rs b/library/std/src/os/windows/net/addr.rs index 5f6b93e56e723..7ab856ae6ba44 100644 --- a/library/std/src/os/windows/net/addr.rs +++ b/library/std/src/os/windows/net/addr.rs @@ -1,6 +1,7 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] use crate::byte_str::ByteStr; use crate::ffi::OsStr; +use crate::macros::rustdoc_skip_body; use crate::path::Path; #[cfg(not(doc))] use crate::sys::c::{AF_UNIX, SOCKADDR, SOCKADDR_UN}; @@ -127,12 +128,15 @@ impl SocketAddr { /// /// assert!(SocketAddr::from_pathname("/path/with/\0/bytes").is_err()); /// ``` + #[rustdoc_skip_body] pub fn from_pathname

(path: P) -> io::Result where P: AsRef, { sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) } + + #[rustdoc_skip_body] fn address(&self) -> AddressKind<'_> { let len = self.len as usize - SUN_PATH_OFFSET; let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; diff --git a/library/std/src/os/windows/net/listener.rs b/library/std/src/os/windows/net/listener.rs index 19f5254e08bf9..d3b9b50c17293 100644 --- a/library/std/src/os/windows/net/listener.rs +++ b/library/std/src/os/windows/net/listener.rs @@ -1,4 +1,6 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] + +use crate::macros::rustdoc_skip_body; use crate::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; use crate::os::windows::net::{SocketAddr, UnixStream}; use crate::path::Path; @@ -163,6 +165,7 @@ impl UnixListener { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) } @@ -325,6 +328,7 @@ impl<'a> Iterator for Incoming<'a> { impl AsRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { self.0.as_raw_socket() } @@ -332,6 +336,7 @@ impl AsRawSocket for UnixListener { impl FromRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> Self { UnixListener(unsafe { Socket::from_raw_socket(sock) }) } @@ -339,6 +344,7 @@ impl FromRawSocket for UnixListener { impl IntoRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { self.0.into_raw_socket() } diff --git a/library/std/src/os/windows/net/stream.rs b/library/std/src/os/windows/net/stream.rs index c0f32e75411e9..9a3360bfb565c 100644 --- a/library/std/src/os/windows/net/stream.rs +++ b/library/std/src/os/windows/net/stream.rs @@ -1,4 +1,6 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] + +use crate::macros::rustdoc_skip_body; use crate::net::Shutdown; use crate::os::windows::io::{ AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, RawSocket, @@ -126,6 +128,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) } @@ -145,6 +148,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) } @@ -166,6 +170,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn read_timeout(&self) -> io::Result> { self.0.timeout(SO_RCVTIMEO) } @@ -230,6 +235,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { self.0.set_timeout(dur, SO_RCVTIMEO) } @@ -276,6 +282,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { self.0.set_timeout(dur, SO_SNDTIMEO) } @@ -367,6 +374,7 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn write_timeout(&self) -> io::Result> { self.0.timeout(SO_SNDTIMEO) } @@ -412,6 +420,7 @@ impl<'a> io::Write for &'a UnixStream { impl AsSocket for UnixStream { #[inline] + #[rustdoc_skip_body] fn as_socket(&self) -> BorrowedSocket<'_> { self.0.as_socket() } @@ -419,6 +428,7 @@ impl AsSocket for UnixStream { impl AsRawSocket for UnixStream { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { self.0.as_raw_socket() } @@ -426,12 +436,14 @@ impl AsRawSocket for UnixStream { impl FromRawSocket for UnixStream { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> Self { unsafe { UnixStream(Socket::from_raw_socket(sock)) } } } impl IntoRawSocket for UnixStream { + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { self.0.into_raw_socket() } diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index f4b60f2ed8eca..5065026b21a46 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -5,6 +5,7 @@ #![stable(feature = "process_extensions", since = "1.2.0")] use crate::ffi::{OsStr, c_void}; +use crate::macros::rustdoc_skip_body; use crate::mem::MaybeUninit; use crate::os::windows::io::{ AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle, @@ -14,6 +15,7 @@ use crate::{io, marker, process, ptr, sys}; #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { + #[rustdoc_skip_body] unsafe fn from_raw_handle(handle: RawHandle) -> process::Stdio { let handle = unsafe { sys::handle::Handle::from_raw_handle(handle as *mut _) }; let io = sys::process::Stdio::Handle(handle); @@ -25,6 +27,7 @@ impl FromRawHandle for process::Stdio { impl From for process::Stdio { /// Takes ownership of a handle and returns a [`Stdio`](process::Stdio) /// that can attach a stream to it. + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::Stdio { let handle = sys::handle::Handle::from_inner(handle); let io = sys::process::Stdio::Handle(handle); @@ -35,6 +38,7 @@ impl From for process::Stdio { #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::Child { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().handle().as_raw_handle() as *mut _ } @@ -43,6 +47,7 @@ impl AsRawHandle for process::Child { #[stable(feature = "io_safety", since = "1.63.0")] impl AsHandle for process::Child { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { self.as_inner().handle().as_handle() } @@ -50,6 +55,7 @@ impl AsHandle for process::Child { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::Child { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_handle().into_raw_handle() as *mut _ } @@ -58,6 +64,7 @@ impl IntoRawHandle for process::Child { #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { /// Takes ownership of a [`Child`](process::Child)'s process handle. + #[rustdoc_skip_body] fn from(child: process::Child) -> OwnedHandle { child.into_inner().into_handle().into_inner() } @@ -66,6 +73,7 @@ impl From for OwnedHandle { #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStdin { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().handle().as_raw_handle() as *mut _ } @@ -74,6 +82,7 @@ impl AsRawHandle for process::ChildStdin { #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStdout { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().handle().as_raw_handle() as *mut _ } @@ -82,6 +91,7 @@ impl AsRawHandle for process::ChildStdout { #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStderr { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().handle().as_raw_handle() as *mut _ } @@ -89,6 +99,7 @@ impl AsRawHandle for process::ChildStderr { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdin { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_handle().into_raw_handle() as *mut _ } @@ -96,6 +107,7 @@ impl IntoRawHandle for process::ChildStdin { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdout { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_handle().into_raw_handle() as *mut _ } @@ -103,6 +115,7 @@ impl IntoRawHandle for process::ChildStdout { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStderr { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_handle().into_raw_handle() as *mut _ } @@ -114,6 +127,7 @@ impl IntoRawHandle for process::ChildStderr { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdin { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStdin { let handle = sys::handle::Handle::from_inner(handle); let pipe = sys::process::ChildPipe::from_inner(handle); @@ -127,6 +141,7 @@ impl From for process::ChildStdin { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdout { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStdout { let handle = sys::handle::Handle::from_inner(handle); let pipe = sys::process::ChildPipe::from_inner(handle); @@ -140,6 +155,7 @@ impl From for process::ChildStdout { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStderr { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStderr { let handle = sys::handle::Handle::from_inner(handle); let pipe = sys::process::ChildPipe::from_inner(handle); @@ -158,6 +174,7 @@ pub impl(self) trait ExitStatusExt { #[stable(feature = "exit_status_from", since = "1.12.0")] impl ExitStatusExt for process::ExitStatus { + #[rustdoc_skip_body] fn from_raw(raw: u32) -> Self { process::ExitStatus::from_inner(From::from(raw)) } @@ -390,26 +407,31 @@ pub impl(self) trait CommandExt { #[stable(feature = "windows_process_extensions", since = "1.16.0")] impl CommandExt for process::Command { + #[rustdoc_skip_body] fn creation_flags(&mut self, flags: u32) -> &mut process::Command { self.as_inner_mut().creation_flags(flags); self } + #[rustdoc_skip_body] fn desktop>(&mut self, desktop: S) -> &mut process::Command { self.as_inner_mut().desktop(desktop.as_ref()); self } + #[rustdoc_skip_body] fn show_window(&mut self, cmd_show: u16) -> &mut process::Command { self.as_inner_mut().show_window(Some(cmd_show)); self } + #[rustdoc_skip_body] fn force_quotes(&mut self, enabled: bool) -> &mut process::Command { self.as_inner_mut().force_quotes(enabled); self } + #[rustdoc_skip_body] fn raw_arg>(&mut self, raw_text: S) -> &mut process::Command { self.as_inner_mut().raw_arg(raw_text.as_ref()); self @@ -424,6 +446,7 @@ impl CommandExt for process::Command { self } + #[rustdoc_skip_body] fn spawn_with_attributes( &mut self, attribute_list: &ProcThreadAttributeList<'_>, @@ -433,21 +456,25 @@ impl CommandExt for process::Command { .map(process::Child::from_inner) } + #[rustdoc_skip_body] fn startupinfo_fullscreen(&mut self, enabled: bool) -> &mut process::Command { self.as_inner_mut().startupinfo_fullscreen(enabled); self } + #[rustdoc_skip_body] fn startupinfo_untrusted_source(&mut self, enabled: bool) -> &mut process::Command { self.as_inner_mut().startupinfo_untrusted_source(enabled); self } + #[rustdoc_skip_body] fn startupinfo_force_feedback(&mut self, enabled: Option) -> &mut process::Command { self.as_inner_mut().startupinfo_force_feedback(enabled); self } + #[rustdoc_skip_body] fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command { self.as_inner_mut().inherit_handles(inherit_handles); self @@ -463,6 +490,7 @@ pub impl(self) trait ChildExt { #[stable(feature = "windows_process_extensions_main_thread_handle", since = "1.100.0")] impl ChildExt for process::Child { + #[rustdoc_skip_body] fn main_thread_handle(&self) -> BorrowedHandle<'_> { self.handle.main_thread_handle() } @@ -483,6 +511,7 @@ pub impl(self) trait ExitCodeExt { #[stable(feature = "windows_process_exit_code_from", since = "CURRENT_RUSTC_VERSION")] impl ExitCodeExt for process::ExitCode { + #[rustdoc_skip_body] fn from_raw(raw: u32) -> Self { process::ExitCode::from_inner(From::from(raw)) } @@ -520,6 +549,7 @@ impl<'a> Drop for ProcThreadAttributeList<'a> { /// underlying attribute list. /// /// [1]: + #[rustdoc_skip_body] fn drop(&mut self) { let lp_attribute_list = self.attribute_list.as_mut_ptr().cast::(); @@ -666,6 +696,7 @@ impl<'a> ProcThreadAttributeListBuilder<'a> { /// /// Returns an error if the maximum number of attributes is exceeded /// or if there is an I/O error during initialization. + #[rustdoc_skip_body] pub fn finish(&self) -> io::Result> { // To initialize our ProcThreadAttributeList, we need to determine // how many bytes to allocate for it. The Windows API simplifies this diff --git a/library/std/src/os/windows/thread.rs b/library/std/src/os/windows/thread.rs index 1cdee44de4b72..b53bd72f5d3e1 100644 --- a/library/std/src/os/windows/thread.rs +++ b/library/std/src/os/windows/thread.rs @@ -4,6 +4,7 @@ #![stable(feature = "thread_extensions", since = "1.9.0")] +use crate::macros::rustdoc_skip_body; use crate::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle}; use crate::sys::{AsInner, IntoInner}; use crate::thread; @@ -11,6 +12,7 @@ use crate::thread; #[stable(feature = "thread_extensions", since = "1.9.0")] impl AsRawHandle for thread::JoinHandle { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { self.as_inner().handle().as_raw_handle() as *mut _ } @@ -19,6 +21,7 @@ impl AsRawHandle for thread::JoinHandle { #[stable(feature = "thread_extensions", since = "1.9.0")] impl IntoRawHandle for thread::JoinHandle { #[inline] + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { self.into_inner().into_handle().into_raw_handle() as *mut _ } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index aa47fcb3360c1..87835abf23fbe 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -1299,6 +1299,7 @@ impl ExitStatusError { #[cfg(target_os = "linux")] mod linux_child_ext { use crate::io::ErrorKind; + use crate::macros::rustdoc_skip_body; use crate::os::linux::process as os; use crate::sys::{FromInner, process as imp}; use crate::{io, mem}; @@ -1314,6 +1315,7 @@ mod linux_child_ext { .ok_or_else(|| io::const_error!(ErrorKind::Uncategorized, "no pidfd was created.")) } + #[rustdoc_skip_body] fn into_pidfd(mut self) -> Result { self.handle .pidfd diff --git a/library/std/tests/thread_local/tests.rs b/library/std/tests/thread_local/tests.rs index c2164a0bc2cff..35fc967e683b4 100644 --- a/library/std/tests/thread_local/tests.rs +++ b/library/std/tests/thread_local/tests.rs @@ -365,6 +365,7 @@ fn join_orders_after_tls_destructors() { // ** glibc: https://github.com/bminor/glibc/blob/e5893e6349541d871e8a25120bca014551d13ff5/nptl/nptl_deallocate_tsd.c#L59 // ** musl: https://github.com/kraj/musl/blob/1880359b54ff7dd9f5016002bfdae4b136007dde/src/thread/pthread_key_create.c#L87 #[test] +#[cfg_attr(miri, ignore)] // FIXME: fails in Miri ever since we support __cxa_thread_atexit_impl fn thread_current_in_dtor() { use std::thread::Builder; diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index aebd7fee1f231..fc7d0a386a60f 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -856,10 +856,9 @@ tool_check_step!(Clippy { path: "src/tools/clippy", mode: Mode::ToolRustcPrivate tool_check_step!(Miri { path: "src/tools/miri", mode: Mode::ToolRustcPrivate, - enable_features: ["check_only"], + enable_features: ["check_only", "priroda"], }); tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri", mode: Mode::ToolRustcPrivate }); -tool_check_step!(Priroda { path: "src/tools/miri/priroda", mode: Mode::ToolRustcPrivate }); tool_check_step!(Rustfmt { path: "src/tools/rustfmt", mode: Mode::ToolRustcPrivate }); tool_check_step!(RustAnalyzer { path: "src/tools/rust-analyzer", diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 34e9b354bddb2..ebc1a0fd3da89 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -710,7 +710,14 @@ impl CommandLineStep for Std { .expect("non-utf8 paths are unsupported"); let mut extra_args = match self.format { DocumentationFormat::Html => { - vec!["--markdown-css", "rust.css", "--markdown-no-toc", "--index-page", &index_page] + vec![ + "--markdown-css", + "rust.css", + "--markdown-no-toc", + "--index-page", + &index_page, + "--generate-link-to-definition", + ] } DocumentationFormat::Json => vec![], }; diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f329f61c100c8..70a97c8b87826 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -915,10 +915,11 @@ impl CommandLineStep for Priroda { Mode::ToolRustcPrivate, host, Kind::Test, - "src/tools/miri/priroda", + "src/tools/miri", SourceType::InTree, - &[], + &["priroda".into()], ); + cargo.args(["--test", "priroda"]); cargo.add_rustc_lib_path(builder); @@ -4155,9 +4156,6 @@ impl CommandLineStep for Bootstrap { let build_compiler = builder.compiler(0, host); let record_failed_tests = builder.ensure(SetupFailedTestsFile); - // Some tests require cargo submodule to be present. - builder.sess.require_submodule("src/tools/cargo", None); - let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap index d5754846441a2..8f9abbf0e3f5a 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap @@ -103,9 +103,6 @@ expression: check [Check] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Check] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Check] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap index cb48844356717..526aa31cc74af 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap @@ -103,9 +103,6 @@ expression: check compiletest --include-default-paths [Check] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Check] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Check] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap index 8ed386a4f219a..01f0ef3f05d67 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap @@ -103,9 +103,6 @@ expression: fix [Fix] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Fix] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Fix] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index cc0b32d7bb863..1359733089eee 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -853,7 +853,6 @@ impl<'a> Builder<'a> { check::Clippy, check::Miri, check::CargoMiri, - check::Priroda, check::MiroptTestTools, check::Rustfmt, check::RustAnalyzer, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index bebf17d84618a..ecdb057fd43b7 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1664,7 +1664,6 @@ mod snapshot { [check] rustc 1 -> Clippy 2 [check] rustc 1 -> Miri 2 [check] rustc 1 -> CargoMiri 2 - [check] rustc 1 -> Priroda 2 [check] rustc 1 -> Rustfmt 2 [check] rustc 1 -> RustAnalyzer 2 [check] rustc 1 -> TestFloatParse 2 diff --git a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt index 8784e18864b23..231e06ba0a00c 100644 --- a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt +++ b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt @@ -24,9 +24,9 @@ chardet==5.2.0 \ # via # binaryornot # python-debian -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d +Jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via reuse license-expression==30.3.0 \ --hash=sha256:1295406f736b4f395ff069aec1cebfad53c0fcb3cf57df0f5ec58fc7b905aea5 \ diff --git a/src/gcc b/src/gcc index 37ea7cd851f04..e1c6e07b095d0 160000 --- a/src/gcc +++ b/src/gcc @@ -1 +1 @@ -Subproject commit 37ea7cd851f04ee6a313c5067c9fde9328f876aa +Subproject commit e1c6e07b095d091177376a163da44039475e05e3 diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 9a432c3bad343..7c68f0091342f 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -12,6 +12,11 @@ pub(super) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) - } let is_json_output = cx.is_json_output(); - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(krate) + ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 54c8e9daedd17..e99e488ea227c 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -25,9 +25,13 @@ pub(super) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> is_json_output, tcx: cx.tcx, }; - krate = - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(stripper.fold_crate(krate)); + krate = ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(stripper.fold_crate(krate)); } // strip all impls referencing private items diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 4c6d62917ab24..45bfb519cf4f5 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -268,6 +268,7 @@ pub(crate) struct ImportStripper<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, pub(crate) is_json_output: bool, pub(crate) document_hidden: bool, + pub(crate) document_private: bool, } impl ImportStripper<'_> { @@ -290,6 +291,7 @@ impl DocFolder for ImportStripper<'_> { debug!("ImportStripper: stripping {:?}", i.name); None } + clean::ImportItem(imp) if self.document_private => Some(self.fold_item_recur(i)), // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None, clean::ExternCrateItem { .. } | clean::ImportItem(..) if i.visibility(self.tcx) != Some(Visibility::Public) => diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index df55626ffb05d..33b0334e3fd5d 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -490,7 +490,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let tcx = self.cx.tcx; let def_id = item.owner_id.to_def_id(); - let is_pub = tcx.visibility(def_id).is_public(); + // Explanations: if the item is public, nothing more to check. If not, then we check if + // we're documenting private item. If we are, then we want all items, except if it's a + // `use`. In this case, we want to check if it's a reexport. + let is_pub = tcx.visibility(def_id).is_public() + || (self.cx.document_private() + && (!matches!(item.kind, hir::ItemKind::Use(..)) + || def_id.as_local().is_some_and(|def_id| { + tcx.visibility(def_id).is_accessible_from(CRATE_DEF_ID, tcx) + }))); if is_pub { self.store_path(item.owner_id.to_def_id()); @@ -592,7 +600,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. - if is_pub && self.inside_public_path { + if is_pub && (self.inside_public_path || self.cx.document_private()) { let please_inline = if let Some(res_did) = res.opt_def_id() && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG)) { diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 21ce9f16021b8..b4be293e4ab45 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -183,30 +183,21 @@ jobs: - name: check build run: | cd ../rust # ./x does not seem to like being invoked from elsewhere - # checks every tool in that folder (including priroda) ./x check src/tools/miri - # This job is intentionally separate from `test` so that Priroda can be - # developed as a separate crate inside the Miri repository for now. + # This job is intentionally separate from `test` so that the Priroda test + # suite is exercised on its own (it is gated behind the `priroda` feature of + # the `miri` crate, and so is not part of the regular `./miri test` run). priroda: - name: Priroda + name: priroda runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/workflows/setup - - name: install Miri driver - run: ./miri install - - name: build Priroda - working-directory: priroda - run: cargo build --locked - name: clippy Priroda - working-directory: priroda - run: cargo clippy --all-targets --locked -- -D warnings + run: CARGO_EXTRA_FLAGS=--locked ./miri clippy --features priroda -- -D warnings - name: test Priroda - working-directory: priroda - run: | - sysroot="$(cargo miri setup --print-sysroot)" - MIRI_SYSROOT="$sysroot" cargo test --locked + run: CARGO_EXTRA_FLAGS=--locked ./miri test-priroda coverage: name: coverage report diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md index 4bc02217c6f7b..5726421b71690 100644 --- a/src/tools/miri/CONTRIBUTING.md +++ b/src/tools/miri/CONTRIBUTING.md @@ -173,7 +173,7 @@ For example, you can (cross-)run the driver on a particular file by doing Tests in ``pass-dep`` need to be run using ``./miri run --dep ``. For example: ```sh -./miri run --dep tests/pass-dep/shims/libc-fs.rs +./miri run --dep tests/pass-dep/libc/fs.rs ``` You can (cross-)run the entire test suite using: @@ -187,6 +187,10 @@ You can (cross-)run the entire test suite using: base directory, e.g. `./miri test fail` will run all compile-fail tests). Multiple filters are supported: `./miri test FILTER1 FILTER2` runs all tests that contain either string. +Priroda has a separate test suite. Run it with `./miri test-priroda`, or use +`./miri test-priroda --bless` to update its expected output files after an +intentional output change. + #### Fine grained logging You can get a trace of which MIR statements are being executed by setting the diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 5037ac5b2c217..86e1a0cd13fd5 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -472,6 +472,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -993,6 +1004,7 @@ dependencies = [ "chrono-tz", "colored", "directories", + "emmy_dap_types", "genmc-sys", "getrandom 0.4.3", "ipc-channel", diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 34a7a8789e4de..7c0c196c33eac 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -16,6 +16,13 @@ name = "miri" test = false # we have no unit tests doctest = false # and no doc tests +[[bin]] +name = "priroda" +path = "priroda/src/main.rs" +test = false +doctest = false +required-features = ["priroda"] + [dependencies] getrandom = { version = "0.4", features = ["std"] } rand = "0.10" @@ -30,6 +37,9 @@ serde_json = { version = "1.0", optional = true } mio = { version = "1.1.1", features = ["os-poll", "net"] } socket2 = "0.6.5" +# Only used by priroda binary. +emmy_dap_types = { version = "0.2.0", optional = true } + [target.'cfg(unix)'.dependencies] libc = "0.2" # native-lib dependencies @@ -69,12 +79,19 @@ rustc_private = true name = "ui" harness = false +[[test]] +name = "priroda" +path = "priroda/tests/ui.rs" +harness = false +required-features = ["priroda"] + [features] default = ["stack-cache", "native-lib"] genmc = ["dep:genmc-sys"] stack-cache = [] expensive-consistency-checks = ["stack-cache"] tracing = ["serde_json"] +priroda = ["dep:emmy_dap_types"] native-lib = ["dep:libffi", "dep:libffi-sys", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"] check_only = ["libffi?/check_only", "capstone?/check_only", "genmc-sys?/check_only"] diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index c65d8097e0e92..e7b0fd652f01e 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -177,7 +177,7 @@ case $HOST_TARGET in MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests - MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-netbsd run_tests_minimal hello libc-env libc-misc + MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-netbsd run_tests_minimal hello libc/env.rs libc/misc.rs ;; armv7-unknown-linux-gnueabihf) # Host diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index e9d4849bc5960..b39bc57362c56 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -52,6 +52,7 @@ Note that `cargo miri test` in GenMC mode is currently not supported. - `debug2`: Print the execution graph after every memory access. - `debug3`: Print reads-from values considered by GenMC. - `-Zmiri-genmc-verbose`: Show more information, such as estimated number of executions, and time taken for verification. +- `-Zmiri-genmc-weak-cas-spurious-failures`: Model spurious failures of `compare_exchange_weak`. This is off by default, since all retry loops around `compare_exchange_weak` then need to be bounded (see [below](#eliminating-unbounded-loops)), which is not possible for loops inside the standard library (e.g., in the runtime around `fn main`). - `-Zmiri-disable-weak-memory-emulation`: Disable any weak memory effects (effectively upgrading all atomic orderings in the program to `SeqCst`). This option may reduce the number of explored program executions, but any bugs related to weak memory effects will be missed. This option can help determine if an error is caused by weak memory effects (i.e., if it disappears with this option enabled). @@ -60,9 +61,7 @@ Note that `cargo miri test` in GenMC mode is currently not supported. There are several limitations which can make GenMC miss bugs: - GenMC does not support re-using freed memory for new allocations, so any bugs related to that will be missed. -- GenMC does not support `compare_exchange_weak`, so the consequences of spurious failures are not explored. - A warning will be emitted if this affects code you wrote (but not if it happens inside your dependencies). -- GenMC does not support the separate failure ordering of `compare_exchange`. Miri will take the maximum of the success and failure ordering and use that for the access; outcomes that rely on the real ordering being weaker will not be explored. +- By default, spurious failures of `compare_exchange_weak` are not explored (see `-Zmiri-genmc-weak-cas-spurious-failures`). A warning will be emitted if this affects code you wrote (but not if it happens inside your dependencies). - GenMC is incompatible with borrow tracking (Stacked/Tree Borrows). You need to set `-Zmiri-disable-stacked-borrows` to use GenMC. - Like all C++ memory model verification tools, GenMC has to solve the [out-of-thin-air problem](https://www.cl.cam.ac.uk/~pes20/cpp/notes42.html). diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index bde9ce7feaf5b..75906632ba6c0 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -28,7 +28,7 @@ mod downloading { /// The GenMC repository the we get our commit from. pub(crate) const GENMC_GITHUB_URL: &str = "https://github.com/MPI-SWS/genmc.git"; /// The GenMC commit we depend on. It must be available on the specified GenMC repository. - pub(crate) const GENMC_COMMIT: &str = "29b03a66402c4453fc77901ef3be90bb55707cd4"; + pub(crate) const GENMC_COMMIT: &str = "9f6c4c0772d0c42b325681581acc6a0f3eb9b5f7"; /// Ensure that a local GenMC repo is present and set to the correct commit. /// Return the path of the GenMC repo clone. diff --git a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp index 54220c17b67d7..eb29035525e6c 100644 --- a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp @@ -298,11 +298,10 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { MemOrdering fail_load_ordering, bool can_fail_spuriously ) -> CompareExchangeResult { - // NOTE: Both the store and load events should get the same `ordering`, it should not be split + // NOTE: On success, both the load and the store get `success_ordering`, it should not be split // into a load and a store component. This means we can have for example `AcqRel` loads and - // stores, but this is intended for CAS operations. - - // FIXME(GenMC): properly handle failure memory ordering. + // stores, but this is intended for CAS operations. On failure, the load gets + // `fail_load_ordering`. auto expectedVal = GenmcScalarExt::to_sval(expected_value); auto new_val = GenmcScalarExt::to_sval(new_value); @@ -312,11 +311,13 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { curr_pos(thread_id), GenmcScalarExt::try_to_sval(old_val), success_ordering, + fail_load_ordering, SAddr(address), ASize(size), expectedVal, new_val, WriteAttr(), + can_fail_spuriously, nullptr, std::nullopt, EventDeps() @@ -327,15 +328,13 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { if (std::holds_alternative(load_ret.result)) return CompareExchangeResultExt::from_invalid(); - const auto* ret_val = std::get_if(&load_ret.result); + const auto* ret_val = std::get_if(&load_ret.result); // FIXME(genmc): handle `HandleResult::Reset` return values. ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result."); - const auto read_old_val = *ret_val; - if (read_old_val != expectedVal) + const auto read_old_val = ret_val->val; + if (!ret_val->success) return CompareExchangeResultExt::failure(read_old_val); - // FIXME(GenMC): Add support for modelling spurious failures. - const auto store_ret = GenMCDriver::handleCasWrite( nullptr, curr_pos(thread_id), diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index 90fdef1da949a..9026e1f9d0a5a 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -96,6 +96,7 @@ impl Command { | Command::Build { .. } | Command::Check { .. } | Command::Test { .. } + | Command::TestPriroda { .. } | Command::Run { .. } | Command::Fmt { .. } | Command::Doc { .. } @@ -109,6 +110,7 @@ impl Command { Command::Check { features, flags } => Self::check(features, flags), Command::Test { bless, target, coverage, features, flags } => Self::test(bless, target, coverage, features, flags), + Command::TestPriroda { bless, flags } => Self::test_priroda(bless, flags), Command::Run { dep, native, quiet, target, edition, features, flags } => Self::run(dep, native, quiet, target, edition, features, flags), Command::Doc { features, flags } => Self::doc(features, flags), @@ -479,6 +481,30 @@ impl Command { Ok(()) } + fn test_priroda(bless: bool, flags: Vec) -> Result<()> { + let mut e = MiriEnv::new()?; + + // Build cargo-miri and the local sysroot. + let features = ["priroda".to_owned()]; + let quiet = false; + e.build_miri_sysroot(quiet, None::<&str>, &features)?; + + // Forward information to test harness. + if bless { + e.sh.set_var("RUSTC_BLESS", "Gesundheit"); + } + + // `priroda` is a bin/test target of the `miri` package, so test it there. + let mut args = vec!["--test".to_owned(), "priroda".to_owned()]; + // Pass flags as well. Make sure they are going to the test harness, not cargo. + args.push("--".to_owned()); + args.extend(flags); + + e.test(".", &features, &args)?; + + Ok(()) + } + fn run( dep: bool, native: bool, diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs index bcad8f148d23f..ee085d9c703fb 100644 --- a/src/tools/miri/miri-script/src/main.rs +++ b/src/tools/miri/miri-script/src/main.rs @@ -72,6 +72,15 @@ pub enum Command { #[arg(trailing_var_arg = true, allow_hyphen_values = true)] flags: Vec, }, + /// Run the Priroda test suite. + TestPriroda { + /// Update stdout reference files. + #[arg(long)] + bless: bool, + /// Flags that are passed through to the test harness. + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + flags: Vec, + }, /// Run the Miri driver. /// /// Also respects MIRIFLAGS environment variable. @@ -177,6 +186,10 @@ impl Command { flags.extend(remainder); Ok(()) } + Self::TestPriroda { flags, .. } => { + flags.extend(remainder); + Ok(()) + } Self::Bench { .. } | Self::Squash => bail!("unexpected \"--\" found in arguments"), } } diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index 01743c79f140b..391ff2bdcc974 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -203,6 +203,8 @@ impl MiriEnv { let json: serde_json::Value = serde_json::from_str(&line)?; if json["reason"] == "compiler-artifact" && !json["profile"]["test"].as_bool().unwrap() + // The "miri" crate ships a second binary ("priroda"); pick the "miri" driver. + && json["target"]["name"] == "miri" && !json["executable"].is_null() { if bin.is_some() { diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock deleted file mode 100644 index a24d764a39d3a..0000000000000 --- a/src/tools/miri/priroda/Cargo.lock +++ /dev/null @@ -1,1553 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aes" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" -dependencies = [ - "cipher", - "cpubits", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "annotate-snippets" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" -dependencies = [ - "anstyle", - "unicode-width", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - -[[package]] -name = "bstr" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "camino" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" -dependencies = [ - "serde_core", -] - -[[package]] -name = "capstone" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" -dependencies = [ - "capstone-sys", - "static_assertions", -] - -[[package]] -name = "capstone-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" -dependencies = [ - "cc", -] - -[[package]] -name = "cargo-platform" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "cc" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "num-traits", -] - -[[package]] -name = "chrono-tz" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" -dependencies = [ - "chrono", - "phf", -] - -[[package]] -name = "cipher" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.21", -] - -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "colored" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "comma" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" - -[[package]] -name = "console" -version = "0.16.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" -dependencies = [ - "encode_unicode", - "libc", - "unicode-width", - "windows-sys", -] - -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "emmy_dap_types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "eyre" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" -dependencies = [ - "autocfg", - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "typenum", -] - -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - -[[package]] -name = "indicatif" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" -dependencies = [ - "console", - "portable-atomic", - "unicode-width", - "unit-prefix", - "web-time", -] - -[[package]] -name = "inout" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ipc-channel" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347ef783e380784658248bdb0b87ca1293e3e3df3fc7dee061e129aa5f013d61" -dependencies = [ - "crossbeam-channel", - "libc", - "mio", - "postcard", - "rand 0.9.5", - "rustc-hash 2.1.3", - "serde_core", - "tempfile", - "thiserror 2.0.21", - "uuid", - "windows", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" - -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libffi" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" -dependencies = [ - "libc", - "libffi-sys", -] - -[[package]] -name = "libffi-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" -dependencies = [ - "cc", -] - -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - -[[package]] -name = "libredox" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" -dependencies = [ - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "measureme" -version = "12.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebd1ebda747ae161a4a377bf93f87e18d46faad2331cc0c7d25b84b1d445f49" -dependencies = [ - "log", - "memmap2", - "parking_lot", - "perf-event-open-sys", - "rustc-hash 1.1.0", - "smallvec", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "miri" -version = "0.1.0" -dependencies = [ - "aes", - "bitflags", - "capstone", - "chrono", - "chrono-tz", - "directories", - "getrandom 0.4.3", - "ipc-channel", - "libc", - "libffi", - "libffi-sys", - "libloading", - "measureme", - "mio", - "nix", - "rand 0.10.3", - "serde", - "smallvec", - "socket2", - "windows-sys", -] - -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "owo-colors" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "perf-event-open-sys" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b29be2ba35c12c6939f6bc73187f728bba82c3c062ecdc5fa90ea739282a1f58" -dependencies = [ - "libc", -] - -[[package]] -name = "phf" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettydiff" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" -dependencies = [ - "owo-colors", -] - -[[package]] -name = "priroda" -version = "0.1.0" -dependencies = [ - "emmy_dap_types", - "miri", - "regex", - "ui_test", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" -dependencies = [ - "libredox", - "thiserror 2.0.21", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "rustc-demangle" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustfix" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "siphasher" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "spanned" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" -dependencies = [ - "anyhow", - "bstr", - "color-eyre", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl 2.0.21", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "ui_test" -version = "0.30.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8811281d587a786747c0c49245925016c07767bc996305bdd34d5ce076786a" -dependencies = [ - "annotate-snippets", - "anyhow", - "bstr", - "cargo-platform", - "cargo_metadata", - "color-eyre", - "colored", - "comma", - "crossbeam-channel", - "indicatif", - "levenshtein", - "prettydiff", - "regex", - "rustc_version", - "rustfix", - "serde", - "serde_json", - "spanned", -] - -[[package]] -name = "unicode-ident" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unit-prefix" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" - -[[package]] -name = "uuid" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "zerocopy" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml deleted file mode 100644 index 48a20fecb0ba8..0000000000000 --- a/src/tools/miri/priroda/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -description = "Debugger for Rust MIR." -license = "MIT OR Apache-2.0" -name = "priroda" -repository = "https://github.com/rust-lang/miri" -version = "0.1.0" -edition = "2024" - -[workspace] - -[[bin]] -name = "priroda" -path = "src/main.rs" -test = false # Disable unit tests to avoid errors with --bless -doctest = false # and no doc tests - -[[test]] -name = "cli" -harness = false - -[dependencies] -emmy_dap_types = "0.2.0" -miri = { path = ".." } - -[package.metadata.rust-analyzer] -rustc_private = true - -# Same lint policy as miri/src/lib.rs. -[lints.rust] -rust_2018_idioms = "warn" - -[lints.clippy] -as_conversions = "warn" -manual_let_else = "warn" - -[dev-dependencies] -ui_test = "0.30.2" -regex = "1.5.5" diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index 25ec34dce64d8..30fec23f430a7 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -14,28 +14,28 @@ Current focus: ## Setup -From `miri/`, install the pinned toolchain and the local `cargo-miri` -command: +From `miri/`, install the pinned toolchain: ```sh ./miri toolchain -./miri install ``` -Then build the Miri sysroot and export it for Priroda: +This is sufficient for running the [tests](#test) with `./miri test-priroda`, +which builds the driver and prepares the sysroot itself. To run Priroda +manually, install it together with Miri and prepare the local Miri sysroot: ```sh -cargo +miri miri setup +./miri install --features priroda export MIRI_SYSROOT="$(cargo +miri miri setup --print-sysroot)" ``` ## Run -Priroda currently reads `MIRI_SYSROOT` directly. After setup, run Priroda -from `miri/priroda/`: +Priroda currently reads `MIRI_SYSROOT` directly. After setup, run the installed +binary from the miri toolchain: ```sh -cargo run -- ../tests/pass/empty_main.rs +rustup run miri priroda tests/pass/empty_main.rs ``` ## DAP Prototype @@ -93,14 +93,15 @@ Before running the debugger configuration, make sure: it in your shell before launching VS Code, or replace the argument with the absolute sysroot path. - The `cargo` in `command` resolves to the `miri` toolchain's cargo, so the task - builds Priroda with `rustc_private`. That is automatic when the workspace is - `miri/priroda`; when using the templates from another project, pass `+miri` as - the first `cargo` argument. + builds Priroda with `rustc_private`. When using the templates outside a Miri + checkout with its toolchain override, pass `+miri` as the first `cargo` + argument. -The task runs Priroda through `cargo run` against the Priroda crate: +The task runs the Priroda binary target from the main Miri package: ```sh -cargo run --manifest-path /path/to/miri/priroda/Cargo.toml -- \ +cargo run --manifest-path /path/to/miri/Cargo.toml \ + --bin priroda --features priroda -- \ --dap --port 4711 --sysroot "$MIRI_SYSROOT" /path/to/project/src/main.rs ``` @@ -133,22 +134,16 @@ skips unrecognized requests rather than failing, so those are ignored. ## Test -Priroda's CLI tests also need `MIRI_SYSROOT`. Run them from `miri/priroda/`: +Run Priroda's tests from the Miri repository root: ```sh -cargo test +./miri test-priroda ``` -If the CLI tests fail due to mismatched output, you can update the expected output files by running the tests with the `--bless` flag: +To update the expected output files after an intentional output change, run: ```sh -cargo test -- --bless -``` - -or - -```sh -RUSTC_BLESS=1 cargo test +./miri test-priroda --bless ``` ## Commands diff --git a/src/tools/miri/priroda/rust-toolchain.toml b/src/tools/miri/priroda/rust-toolchain.toml deleted file mode 100644 index c5d0b97ed4f01..0000000000000 --- a/src/tools/miri/priroda/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "miri" diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 73fa355e164eb..186e020562a77 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -1,4 +1,3 @@ -use std::collections::{HashMap, HashSet}; use std::ops::Range; use std::path::PathBuf; @@ -6,10 +5,11 @@ use miri::Immediate::Uninit; use miri::{InterpErrorInfo, InterpErrorKind, TerminationInfo, *}; use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; use rustc_data_structures::either::Either; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::CtorKind; use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; -use rustc_middle::ty::{self, TyKind}; +use rustc_middle::ty; use rustc_span::source_map::SourceMap; use rustc_span::{Span, Symbol}; @@ -30,7 +30,7 @@ impl SourceLocation { } /// Source-level breakpoints indexed by normalized path, then line. -type BreakpointTable = HashMap>; +type BreakpointTable = FxHashMap>; /// Owns one interpreter session and its debugger state. /// @@ -161,7 +161,7 @@ impl<'tcx> PrirodaContext<'tcx> { pub(super) fn new(ecx: MiriInterpCx<'tcx>) -> Self { Self { ecx, - breakpoints: HashMap::new(), + breakpoints: FxHashMap::default(), current_location: None, last_location: None, exit_code: None, @@ -300,11 +300,11 @@ impl<'tcx> PrirodaContext<'tcx> { // FIXME: Preserve `TerminationInfo::Exit::leak_check` and run Miri's // leak/thread-leak diagnostics once Priroda grows a proper post-exit // finalization path. For now, program exit only records the debuggee exit code. - let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + let Some(TerminationInfo::Exit { exit_code, .. }) = info.downcast_ref::() else { return None; }; - Some(*code) + Some(*exit_code) } fn stop_at_exception(&mut self, err: InterpErrorInfo<'tcx>) -> StepResult { @@ -827,14 +827,13 @@ impl<'tcx> PrirodaContext<'tcx> { match elem { ProjectionElem::Field(field_idx, field_ty) => { let rendered = match projection_ty.kind() { - TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { + ty::Adt(adt_def, _args) if adt_def.is_struct() => { let variant = adt_def.non_enum_variant(); let field = &variant.fields[*field_idx]; Symbol::intern(&format!(".{}", field.name)) } - TyKind::Tuple(_) => - Symbol::intern(&format!(".{}", field_idx.index())), + ty::Tuple(_) => Symbol::intern(&format!(".{}", field_idx.index())), _ => Symbol::intern("."), }; diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index d550cb85e83ed..eb1d3a2a7486b 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -702,9 +702,10 @@ impl DapSession { fn interp_error_outcome<'tcx>(err: InterpErrorInfo<'tcx>) -> ExecutionOutcome { let kind = err.into_kind(); if let InterpErrorKind::MachineStop(info) = &kind - && let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + && let Some(TerminationInfo::Exit { exit_code, .. }) = + info.downcast_ref::() { - return ExecutionOutcome::Terminated { code: *code }; + return ExecutionOutcome::Terminated { code: *exit_code }; } ExecutionOutcome::Failed(kind.to_string()) diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/ui.rs similarity index 69% rename from src/tools/miri/priroda/tests/cli.rs rename to src/tools/miri/priroda/tests/ui.rs index 2bf7f22bd1d98..41c98300d8ebb 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/ui.rs @@ -8,8 +8,24 @@ use ui_test::status_emitter::StatusEmitter; use ui_test::{CommandBuilder, Config, default_file_filter, run_tests_generic}; fn main() -> Result<(), Box> { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let miri_dir = manifest_dir.parent().unwrap(); + // `priroda` is a target of the `miri` package, so `CARGO_MANIFEST_DIR` is the Miri repo root. + let miri_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let priroda_dir = miri_dir.join("priroda"); + + // The fixtures assume a host build of Miri, so skip them under cross-target bootstrap runs. + // The bootstrap Priroda test step sets `MIRI_TEST_TARGET`. + if let Some(miri_test_target) = env::var_os("MIRI_TEST_TARGET") { + let host_target = Command::new("rustc").arg("--print").arg("host-tuple").output()?; + let host_target = String::from_utf8(host_target.stdout)?.trim().to_owned(); + if miri_test_target.to_str() != Some(&host_target) { + println!("skipping Priroda tests for non-host target {miri_test_target:?}"); + return Ok(()); + } + } + + // The test fixtures use paths relative to the Priroda directory; run the harness from there + // so the relative `tests/ui/...` paths resolve in the same way as a standalone crate. + std::env::set_current_dir(&priroda_dir)?; let rustc_sysroot = Command::new("rustc").arg("--print").arg("sysroot").output()?; let rustc_sysroot = String::from_utf8(rustc_sysroot.stdout)?.trim().to_owned(); @@ -29,7 +45,7 @@ fn main() -> Result<(), Box> { // Replace the dynamic paths in the actual stdout with the stable placeholders let manifest_dir_regex = - Regex::new(®ex::escape(&manifest_dir.display().to_string())).unwrap(); + Regex::new(®ex::escape(&priroda_dir.display().to_string())).unwrap(); let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); @@ -49,7 +65,7 @@ fn main() -> Result<(), Box> { (content_length_regex.into(), b"Content-Length: {CONTENT_LENGTH}".to_vec()), ]); - // Priroda CLI tests do not currently require annotation comments in the test files + // Priroda UI tests do not currently require annotation comments in the test files. config.comment_defaults.base().exit_status = Spanned::dummy(0).into(); config.comment_defaults.base().require_annotations = Spanned::dummy(false).into(); diff --git a/src/tools/miri/priroda/vscode_tasks.json b/src/tools/miri/priroda/vscode_tasks.json index 9663bb3169bae..0ed7d9ee15664 100644 --- a/src/tools/miri/priroda/vscode_tasks.json +++ b/src/tools/miri/priroda/vscode_tasks.json @@ -8,7 +8,11 @@ "args": [ "run", "--manifest-path", - "${workspaceFolder}/Cargo.toml", + "${workspaceFolder}/../Cargo.toml", + "--bin", + "priroda", + "--features", + "priroda", "--", "--dap", "--port", diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 7c934ebcea4c1..3a20250c1292e 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -b6eeb93c992027a03e1fd120b6652546396b142c +9cd159cfa5b56207cfecf70fd8bd213558243c23 diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index d6ac9390dc0eb..9a49b6ca1db14 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -929,8 +929,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn print_stacks(&mut self, alloc_id: AllocId) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn print_stacks(&self, alloc_id: AllocId) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let alloc_extra = this.get_alloc_extra(alloc_id)?; let stacks = alloc_extra.borrow_tracker_sb().borrow(); for (range, stack) in stacks.stacks.iter_all() { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index f2875548d08d5..bf38ca356df45 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -560,8 +560,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Display the tree. - fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn print_tree(&self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let alloc_extra = this.get_alloc_extra(alloc_id)?; let tree_borrows = alloc_extra.borrow_tracker_tb().borrow(); let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow(); @@ -572,12 +572,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// The name given is `name` and the pointer that receives it is the `nth_parent` /// of `ptr` (with 0 representing `ptr` itself) fn tb_give_pointer_debug_name( - &mut self, + &self, ptr: Pointer, nth_parent: u8, name: &str, ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let (tag, alloc_id) = match ptr.provenance { Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id), Some(Provenance::Wildcard) => { diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index f9b2a0eece9b8..acf45c84bcc6d 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1508,11 +1508,11 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { /// Update the data-race detector for an atomic write occurring at the /// associated memory-place and on the current thread. fn validate_atomic_store( - &mut self, + &self, place: &MPlaceTy<'tcx>, atomic: AtomicWriteOrd, ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); this.validate_atomic_op( place, atomic, @@ -1530,14 +1530,14 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { /// Update the data-race detector for an atomic read-modify-write occurring /// at the associated memory place and on the current thread. fn validate_atomic_rmw( - &mut self, + &self, place: &MPlaceTy<'tcx>, atomic: AtomicRwOrd, ) -> InterpResult<'tcx> { use AtomicRwOrd::*; let acquire = matches!(atomic, Acquire | AcqRel | SeqCst); let release = matches!(atomic, Release | AcqRel | SeqCst); - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); this.validate_atomic_op( place, atomic, diff --git a/src/tools/miri/src/concurrency/genmc/config.rs b/src/tools/miri/src/concurrency/genmc/config.rs index ea5760f7acde8..6749f8acdc52f 100644 --- a/src/tools/miri/src/concurrency/genmc/config.rs +++ b/src/tools/miri/src/concurrency/genmc/config.rs @@ -19,6 +19,8 @@ pub struct GenmcConfig { /// Enable more verbose output, such as number of executions estimate /// and time to completion of verification step. pub(super) verbose_output: bool, + /// Model spurious failures of `compare_exchange_weak`. + pub(super) weak_cas_spurious_failures: bool, } impl GenmcConfig { @@ -74,6 +76,8 @@ impl GenmcConfig { genmc_config.print_genmc_output = true; } else if trimmed_arg == "verbose" { genmc_config.verbose_output = true; + } else if trimmed_arg == "weak-cas-spurious-failures" { + genmc_config.weak_cas_spurious_failures = true; } else { return Err(format!("Invalid GenMC argument: \"-Zmiri-genmc-{trimmed_arg}\"")); } diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index 0ab369f25770a..0314da441a37b 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -3,17 +3,12 @@ use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; +use crate::eval::ExitType; use crate::{ AtomicFenceOrd, AtomicReadOrd, AtomicRmwOp, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, OpTy, Scalar, ThreadId, ThreadManager, VisitProvenance, VisitWith, }; -#[derive(Clone, Copy, Debug)] -pub enum ExitType { - MainThreadFinish, - ExitCalled, -} - #[derive(Debug)] pub struct GenmcCtx {} diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index 87daa05bd224c..d66c163220646 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -114,50 +114,7 @@ impl AtomicFenceOrd { } } -/// Since GenMC ignores the failure memory ordering and Miri should not detect bugs that don't actually exist, we upgrade the success ordering if required. -/// This means that Miri running in GenMC mode will not explore all possible executions allowed under the RC11 memory model. -/// FIXME(genmc): remove this once GenMC properly supports the failure memory ordering. -pub(super) fn maybe_upgrade_compare_exchange_success_orderings( - success: AtomicRwOrd, - failure: AtomicReadOrd, -) -> AtomicRwOrd { - use AtomicReadOrd::*; - let (success_read, success_write) = success.split_memory_orderings(); - let upgraded_success_read = match (success_read, failure) { - (_, SeqCst) | (SeqCst, _) => SeqCst, - (Acquire, _) | (_, Acquire) => Acquire, - (Relaxed, Relaxed) => Relaxed, - }; - AtomicRwOrd::from_split_memory_orderings(upgraded_success_read, success_write) -} - impl AtomicRwOrd { - /// Split up an atomic read-write memory ordering into a separate read and write ordering. - pub(super) fn split_memory_orderings(self) -> (AtomicReadOrd, AtomicWriteOrd) { - match self { - AtomicRwOrd::Relaxed => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed), - AtomicRwOrd::Acquire => (AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed), - AtomicRwOrd::Release => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Release), - AtomicRwOrd::AcqRel => (AtomicReadOrd::Acquire, AtomicWriteOrd::Release), - AtomicRwOrd::SeqCst => (AtomicReadOrd::SeqCst, AtomicWriteOrd::SeqCst), - } - } - - /// Split up an atomic read-write memory ordering into a separate read and write ordering. - fn from_split_memory_orderings( - read_ordering: AtomicReadOrd, - write_ordering: AtomicWriteOrd, - ) -> Self { - match (read_ordering, write_ordering) { - (AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Relaxed, - (AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Acquire, - (AtomicReadOrd::Relaxed, AtomicWriteOrd::Release) => AtomicRwOrd::Release, - (AtomicReadOrd::Acquire, AtomicWriteOrd::Release) => AtomicRwOrd::AcqRel, - (AtomicReadOrd::SeqCst, _) => AtomicRwOrd::SeqCst, - (_, AtomicWriteOrd::SeqCst) => AtomicRwOrd::SeqCst, - } - } - pub(super) fn to_genmc(self) -> MemOrdering { match self { AtomicRwOrd::Relaxed => MemOrdering::Relaxed, diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index a4eab95fbef5d..70336ee0ab4b5 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -14,12 +14,12 @@ use tracing::debug; use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler}; use self::helper::{ - MAX_ACCESS_SIZE, genmc_scalar_to_scalar, get_outcome, - maybe_upgrade_compare_exchange_success_orderings, scalar_to_genmc_scalar, to_genmc_rmw_op, + MAX_ACCESS_SIZE, genmc_scalar_to_scalar, get_outcome, scalar_to_genmc_scalar, to_genmc_rmw_op, }; use self::run::GenmcMode; use self::thread_id_map::ThreadIdMap; use crate::diagnostics::SpanDedupDiagnostic; +use crate::eval::ExitType; use crate::*; mod config; @@ -46,12 +46,6 @@ pub enum ExecutionEndResult { Stop, } -#[derive(Clone, Copy, Debug)] -pub enum ExitType { - MainThreadFinish, - ExitCalled, -} - /// The exit status of a program. /// GenMC must store this if a thread exits while any others can still run. /// The other threads must also be explored before the program is terminated. @@ -123,6 +117,9 @@ pub struct GenmcCtx { /// State that persists across executions. /// All `GenmcCtx` in one verification step share this state. global_state: Arc, + + /// Whether to model spurious failures of `compare_exchange_weak`. + weak_cas_spurious_failures: bool, } /// GenMC Context creation and administrative / query actions @@ -135,7 +132,12 @@ impl GenmcCtx { genmc_config.log_level, /* do_estimation: */ mode == GenmcMode::Estimation, )); - Self { genmc, exec_state: Default::default(), global_state } + Self { + genmc, + exec_state: Default::default(), + global_state, + weak_cas_spurious_failures: genmc_config.weak_cas_spurious_failures, + } } fn get_estimation_results(&self) -> EstimationResult { @@ -375,14 +377,6 @@ impl GenmcCtx { size.bytes() ); - // Upgrade the success memory ordering to equal the failure ordering, since GenMC currently ignores the failure ordering. - // FIXME(genmc): remove this once GenMC properly supports the failure memory ordering. - let upgraded_success_ordering = - maybe_upgrade_compare_exchange_success_orderings(success, fail); - - // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. - let (effective_failure_ordering, _) = upgraded_success_ordering.split_memory_orderings(); - // Return a warning if we cannot explore all behaviors of this operation. // Only emit this if the operation is "in user code": walk up across `#[track_caller]` // frames, then check if the next frame is local. @@ -393,27 +387,13 @@ impl GenmcCtx { .find(|f| !f.instance().def.requires_caller_location(*ecx.tcx)) .is_none_or(|f| ecx.machine.is_local(f.instance())) }; - if (success != upgraded_success_ordering || effective_failure_ordering != fail) - && show_warning() - { - static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); - ecx.dedup_diagnostic(&DEDUP, |_first| { - NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { - success_ordering: success, - upgraded_success_ordering, - failure_ordering: fail, - effective_failure_ordering, - } - }); - } - // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. - if can_fail_spuriously && show_warning() { + if can_fail_spuriously && !self.weak_cas_spurious_failures && show_warning() { static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); ecx.dedup_diagnostic(&DEDUP, |_first| NonHaltingDiagnostic::GenmcCompareExchangeWeak); } debug!( - "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, {success:?}, orderings: {fail:?}), can fail spuriously: {can_fail_spuriously}" + "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, orderings: {success:?}/{fail:?}), can fail spuriously: {can_fail_spuriously}" ); let cas_result = self.genmc.borrow_mut().pin_mut().handle_compare_exchange( self.active_thread_genmc_tid(&ecx.machine), @@ -422,9 +402,9 @@ impl GenmcCtx { scalar_to_genmc_scalar(ecx, self, expected_old_value)?, scalar_to_genmc_scalar(ecx, self, new_value)?, scalar_to_genmc_scalar(ecx, self, old_value)?, - upgraded_success_ordering.to_genmc(), + success.to_genmc(), fail.to_genmc(), - can_fail_spuriously, + can_fail_spuriously && self.weak_cas_spurious_failures, ); let (old_value, is_success, is_coherence_order_maximal_write) = diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index e704346ad2ea4..9e802654d4fbb 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -128,7 +128,7 @@ impl GenmcCtx { "If the execution is finished, we should have a return value from the program.", ); throw_machine_stop!(TerminationInfo::Exit { - code: exit_status.exit_code, + exit_code: exit_status.exit_code, leak_check: matches!(exit_status.exit_type, super::ExitType::MainThreadFinish), }); } diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs index 5c3541ffbe4c1..be010bb0458fb 100644 --- a/src/tools/miri/src/concurrency/init_once.rs +++ b/src/tools/miri/src/concurrency/init_once.rs @@ -140,8 +140,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Synchronize with the previous completion of an InitOnce. /// Must only be called after checking that it is complete. #[inline] - fn init_once_observe_completed(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn init_once_observe_completed(&self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let init_once = init_once_ref.0.borrow(); assert_eq!( diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs index d6d006d0dabb6..5c059e36b9731 100644 --- a/src/tools/miri/src/concurrency/mod.rs +++ b/src/tools/miri/src/concurrency/mod.rs @@ -22,5 +22,5 @@ pub mod weak_memory; mod genmc; pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler}; -pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; +pub use self::genmc::{GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; pub use self::vector_clock::VClock; diff --git a/src/tools/miri/src/concurrency/scheduler.rs b/src/tools/miri/src/concurrency/scheduler.rs index cf81db0910723..6364710fb8905 100644 --- a/src/tools/miri/src/concurrency/scheduler.rs +++ b/src/tools/miri/src/concurrency/scheduler.rs @@ -31,7 +31,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> { let this = self.eval_context_mut(); let active_thread = this.active_thread_mut(); - active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applied + active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applies let mut callback = active_thread .on_stack_empty .take() @@ -175,11 +175,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); if !this.step()? { - // See if this thread can do something else. + // The stack is empty. See if this thread can do something else. match this.run_on_stack_empty()? { Poll::Pending => {} // keep going Poll::Ready(()) => { - this.terminate_active_thread(TlsAllocAction::Deallocate)?; + this.terminate_active_thread(/* for_exit */ false)?; } } } diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index 0175e5573d686..14bdcb37c32f9 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -445,8 +445,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Lock by setting the mutex owner and increasing the lock count. - fn mutex_lock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn mutex_lock(&self, mutex_ref: &MutexRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); let mut mutex = mutex_ref.0.borrow_mut(); if let Some(current_owner) = mutex.owner { @@ -541,8 +541,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Read-lock the lock by adding the `reader` the list of threads that own /// this lock. - fn rwlock_reader_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn rwlock_reader_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); trace!("rwlock_reader_lock: now also held (one more time) by {:?}", thread); let mut rwlock = rwlock_ref.0.borrow_mut(); @@ -628,8 +628,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lock by setting the writer that owns the lock. #[inline] - fn rwlock_writer_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn rwlock_writer_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); trace!("rwlock_writer_lock: now held by {:?}", thread); diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index b2878ae5201d9..3966ea360b516 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -19,16 +19,6 @@ use crate::concurrency::GlobalDataRaceHandler; use crate::shims::tls; use crate::*; -/// What to do with TLS allocations from terminated threads -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TlsAllocAction { - /// Deallocate backing memory of thread-local statics as usual - Deallocate, - /// Skip deallocating backing memory of thread-local statics and consider all memory reachable - /// from them as "allowed to leak" (like global `static`s). - Leak, -} - /// The argument type for the "unblock" callback, indicating why the thread got unblocked. #[derive(Clone, Copy, Debug, PartialEq)] pub enum UnblockKind { @@ -749,45 +739,60 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(new_thread_id) } + /// Replace the active thread with an empty one, and a new on-empty handler. + /// The memory of the old stack is leaked. + /// Used to implement `exit`. + fn replace_active_thread_stack(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) { + let this = self.eval_context_mut(); + let thread = this.active_thread_mut(); + let span = thread.current_user_relevant_span(); + + thread.stack.clear(); + thread.origin_span = span; + thread.top_user_relevant_frame = None; + thread.on_stack_empty = Some(on_stack_empty); + } + /// Handles thread termination of the active thread: wakes up threads joining on this one, - /// and deals with the thread's thread-local statics according to `tls_alloc_action`. + /// and frees the thread's thread-local statics. `for_exit` indicates whether we are + /// about to exit the program; that changes some details. /// /// This is called by the eval loop when a thread's on_stack_empty returns `Ready`. - fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> { + fn terminate_active_thread(&mut self, for_exit: bool) -> InterpResult<'tcx> { let this = self.eval_context_mut(); // Mark thread as terminated. let thread = this.active_thread_mut(); - assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated"); thread.state = ThreadState::Terminated; + assert!( + for_exit || thread.stack.is_empty(), + "only threads with an empty stack can be terminated" + ); // Deallocate TLS. let gone_thread = this.active_thread(); { let mut free_tls_statics = Vec::new(); - this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| { + this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut ptr| { if thread != gone_thread { // A different thread, keep this static around. return true; } // Delete this static from the map and from memory. // We cannot free directly here as we cannot use `?` in this context. - free_tls_statics.push(alloc_id); + free_tls_statics.push(ptr); false }); // Now free the TLS statics. for ptr in free_tls_statics { - match tls_alloc_action { - TlsAllocAction::Deallocate => - this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?, - TlsAllocAction::Leak => - if let Some(alloc) = ptr.provenance.get_alloc_id() { - trace!( - "Thread-local static leaked and stored as static root: {:?}", - alloc - ); - this.machine.static_roots.push(alloc); - }, + if for_exit { + // Leak the thread-local statics rather than freeing them. + if let Some(alloc) = ptr.provenance.get_alloc_id() { + trace!("Thread-local static leaked and stored as static root: {:?}", alloc); + this.machine.static_roots.push(alloc); + } + } else { + this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?; } } } diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 7c5b38e9a4891..8c65a7a3a8cf6 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -14,7 +14,7 @@ use crate::*; /// Details of premature program termination. pub enum TerminationInfo { Exit { - code: i32, + exit_code: i32, leak_check: bool, }, Abort(String), @@ -71,7 +71,8 @@ impl fmt::Display for TerminationInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use TerminationInfo::*; match self { - Exit { code, .. } => write!(f, "the evaluated program completed with exit code {code}"), + Exit { exit_code, .. } => + write!(f, "the evaluated program completed with exit code {exit_code}"), Abort(msg) => write!(f, "{msg}"), Interrupted => write!(f, "interpretation was interrupted"), UnsupportedInIsolation(msg) => write!(f, "{msg}"), @@ -148,12 +149,6 @@ pub enum NonHaltingDiagnostic { }, ExternTypeReborrow, GenmcCompareExchangeWeak, - GenmcCompareExchangeOrderingMismatch { - success_ordering: AtomicRwOrd, - upgraded_success_ordering: AtomicRwOrd, - failure_ordering: AtomicReadOrd, - effective_failure_ordering: AtomicReadOrd, - }, FileInProcOpened, ConnectingSocketGetsockname, SocketAddressResolution { @@ -251,7 +246,7 @@ pub fn report_result<'tcx>( let info = info.downcast_ref::().expect("invalid MachineStop payload"); use TerminationInfo::*; let title = match info { - &Exit { code, leak_check } => return Some((code, leak_check)), + &Exit { exit_code, leak_check } => return Some((exit_code, leak_check)), Abort(_) => Some("abnormal termination"), Interrupted => None, UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) => @@ -651,7 +646,7 @@ impl<'tcx> MiriMachine<'tcx> { ("sharing memory with a native function".to_string(), DiagLevel::Warning), ExternTypeReborrow => ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning), - GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } => + GenmcCompareExchangeWeak => ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning), CreatedPointerTag(..) | PoppedPointerTag(..) @@ -697,22 +692,8 @@ impl<'tcx> MiriMachine<'tcx> { ExternTypeReborrow => format!("reborrow of a reference to `extern type` is not properly supported"), GenmcCompareExchangeWeak => - "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures." + "GenMC does not model spurious failures of `compare_exchange_weak` by default. Miri with GenMC might miss bugs related to spurious failures." .to_string(), - GenmcCompareExchangeOrderingMismatch { - success_ordering, - upgraded_success_ordering, - failure_ordering, - effective_failure_ordering, - } => { - let was_upgraded_msg = if success_ordering != upgraded_success_ordering { - format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'") - } else { - assert_ne!(failure_ordering, effective_failure_ordering); - format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'") - }; - format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") - } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts"), SocketAddressResolution { error } => format!("address resolution failed: {error}"), @@ -821,6 +802,10 @@ impl<'tcx> MiriMachine<'tcx> { ), ] } + GenmcCompareExchangeWeak => + vec![note!( + "add `-Zmiri-genmc-weak-cas-spurious-failures` to MIRIFLAGS to explore spurious failures; all retry loops around `compare_exchange_weak` then need to be bounded" + )], _ => vec![], }; diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 6e51ff42367dc..5ba03ff1bad7f 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -22,7 +22,6 @@ use rustc_session::config::EntryFnType; use rustc_target::spec::Os; use crate::concurrency::GenmcCtx; -use crate::concurrency::thread::TlsAllocAction; use crate::diagnostics::report_leaks; use crate::helpers::is_no_core; use crate::shims::{global_ctor, tls}; @@ -84,10 +83,10 @@ pub fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { } } -/// When the main thread would exit, we will yield to any other thread that is ready to execute. +/// When the program exits, we will yield to any other thread that is ready to execute. /// But we must only do that a finite number of times, or a background thread running `loop {}` /// will hang the program. -const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256; +const THREAD_YIELDS_AT_EXIT: u32 = 256; /// Configuration needed to spawn a Miri instance. #[derive(Clone)] @@ -219,6 +218,94 @@ impl Default for MiriConfig { } } +#[derive(Clone, Copy, Debug)] +pub enum ExitType { + MainThreadFinish, + ExitCalled, +} + +/// A state machine that's run when the program is exiting. +#[derive(Debug)] +pub struct ProgramExitHandler<'tcx> { + state: ProgramExitState<'tcx>, + exit_code: i32, + exit_type: ExitType, +} +#[derive(Debug)] +enum ProgramExitState<'tcx> { + TlsDtors(tls::TlsDtorsState<'tcx>), + Yield { remaining: u32 }, + Done, + Unreachable, +} + +impl<'tcx> ProgramExitHandler<'tcx> { + pub fn new(exit_code: i32, exit_type: ExitType) -> Self { + Self { + state: ProgramExitState::TlsDtors(tls::TlsDtorsState::new_for_exit()), + exit_code, + exit_type, + } + } + pub fn on_stack_empty( + &mut self, + this: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Poll<()>> { + use ProgramExitState::*; + match &mut self.state { + TlsDtors(state) => + match state.on_stack_empty(this)? { + Poll::Pending => {} // just keep going + Poll::Ready(()) => { + if this.machine.data_race.as_genmc_ref().is_some() { + // In GenMC mode, we don't yield at the end of the main thread. Instead, + // the `GenmcCtx` will ensure that unfinished threads get a chance to + // run at this point. + self.state = Done; + } else { + // Give background threads a chance to finish by yielding the main + // thread a couple of times. + self.state = Yield { remaining: THREAD_YIELDS_AT_EXIT }; + } + } + }, + Yield { remaining } => + match remaining.checked_sub(1) { + None => self.state = Done, + Some(new_remaining) => { + *remaining = new_remaining; + this.yield_active_thread(); + } + }, + Done => { + // Terminate this thread in the scheduler. + this.terminate_active_thread(/* for_exit */ true)?; + + // We cannot come back. + self.state = Unreachable; + + // In GenMC mode, we do not immediately stop execution on exit. + if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { + // If there's no error, execution will continue (on another thread). + genmc_ctx.handle_exit( + this.machine.threads.active_thread(), + self.exit_code, + self.exit_type, + )?; + } else { + // Stop interpreter loop. + throw_machine_stop!(TerminationInfo::Exit { + exit_code: self.exit_code, + leak_check: matches!(self.exit_type, ExitType::MainThreadFinish), + }); + } + } + Unreachable => unreachable!(), + } + interp_ok(Poll::Pending) + } +} + /// The state of the main thread. Implementation detail of `on_main_stack_empty`. #[derive(Debug)] enum MainThreadState<'tcx> { @@ -232,11 +319,7 @@ enum MainThreadState<'tcx> { argv: ImmTy<'tcx>, }, Running, - TlsDtors(tls::TlsDtorsState<'tcx>), - Yield { - remaining: u32, - }, - Done, + Exiting(ProgramExitHandler<'tcx>), } impl<'tcx> MainThreadState<'tcx> { @@ -256,69 +339,22 @@ impl<'tcx> MainThreadState<'tcx> { } } Running => { - *self = TlsDtors(Default::default()); - } - TlsDtors(state) => - match state.on_stack_empty(this)? { - Poll::Pending => {} // just keep going - Poll::Ready(()) => { - if this.machine.data_race.as_genmc_ref().is_some() { - // In GenMC mode, we don't yield at the end of the main thread. - // Instead, the `GenmcCtx` will ensure that unfinished threads get a chance to run at this point. - *self = Done; - } else { - // Give background threads a chance to finish by yielding the main thread a - // couple of times -- but only if we would also preempt threads randomly. - if this.machine.preemption_rate > 0.0 { - // There is a non-zero chance they will yield back to us often enough to - // make Miri terminate eventually. - *self = Yield { remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN }; - } else { - // The other threads did not get preempted, so no need to yield back to - // them. - *self = Done; - } - } - } - }, - Yield { remaining } => - match remaining.checked_sub(1) { - None => *self = Done, - Some(new_remaining) => { - *remaining = new_remaining; - this.yield_active_thread(); - } - }, - Done => { + // Main code is done, the program is exiting. + // Figure out exit code. let ret_place = this.machine.main_fn_ret_place.clone().unwrap(); let exit_code = this.read_target_isize(&ret_place)?; // Rust uses `isize` but the underlying type of an exit code is `i32`. - // Do a saturating cast. - let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 { - i32::MAX - } else { - i32::MIN - }); - // Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS - // to be like a global `static`, so that all memory reached by it is considered to "not leak". - this.terminate_active_thread(TlsAllocAction::Leak)?; - - // In GenMC mode, we do not immediately stop execution on main thread exit. - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - // If there's no error, execution will continue (on another thread). - genmc_ctx.handle_exit( - ThreadId::MAIN_THREAD, - exit_code, - crate::concurrency::ExitType::MainThreadFinish, - )?; - } else { - // Stop interpreter loop. - throw_machine_stop!(TerminationInfo::Exit { - code: exit_code, - leak_check: true - }); - } + let Ok(exit_code) = i32::try_from(exit_code) else { + throw_ub_format!( + "lang_start item return an exit code that does not fit into `i32`" + ); + }; + *self = Exiting(ProgramExitHandler::new(exit_code, ExitType::MainThreadFinish)); + } + Exiting(state) => { + // This is a sink state, we stay here forever. + return state.on_stack_empty(this); } } interp_ok(Poll::Pending) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 26f2a8a459f79..7db3f4aad641d 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -954,10 +954,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lookup an array of immediates from any linker sections matching the provided predicate, /// with the spans of where they were found. fn lookup_link_section( - &mut self, + &self, include_name: impl Fn(&str) -> bool, ) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let tcx = this.tcx.tcx; let mut array = vec![]; diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 7543617243881..507f93e144277 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -148,7 +148,7 @@ pub use crate::concurrency::sync::{ }; pub use crate::concurrency::thread::{ BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId, - ThreadManager, TlsAllocAction, UnblockKind, + ThreadManager, UnblockKind, }; pub use crate::concurrency::{GenmcConfig, GenmcCtx, run_genmc_mode}; pub use crate::data_structures::dedup_range_map::DedupRangeMap; diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 61194a1bba98f..b96acb8ecf8c0 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -1,4 +1,4 @@ -use rustc_abi::{CanonAbi, FieldIdx, Size}; +use rustc_abi::{FieldIdx, Size}; use rustc_middle::ty::{self, Instance, Ty}; use rustc_span::{BytePos, Loc, Symbol, hygiene}; use rustc_target::callconv::FnAbi; @@ -84,10 +84,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } fn resolve_frame_pointer( - &mut self, + &self, ptr: &OpTy<'tcx>, ) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let ptr = this.read_pointer(ptr)?; // Take apart the pointer, we need its pieces. The offset encodes the span. @@ -116,7 +116,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [ptr, flags] = this.check_shim_sig_deprecated(abi, CanonAbi::Rust, link_name, args)?; + + // FIXME: not nice to read the type from std but we don't have a good way to construct the + // type here and now. ADTs need a DefId. + let [ptr, flags] = &this.check_shim_sig( + shim_sig!(extern "Rust" fn(*_, u64) -> std::backtrace_rs::backtrace::miri::MiriFrame), + (link_name, abi, args), + )?; let flags = this.read_scalar(flags)?.to_u64()?; @@ -126,16 +132,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // which we pass to user code. let fn_ptr = this.fn_ptr(FnVal::Instance(fn_instance)); - let num_fields = dest.layout.fields.count(); - - if !(4..=5).contains(&num_fields) { - // Always mention 5 fields, since the 4-field struct - // is deprecated and slated for removal. - throw_ub_format!( - "bad declaration of miri_resolve_frame - should return a struct with 5 fields" - ); - } - // `u32` is not enough to fit line/colno, which can be `usize`. It seems unlikely that a // file would have more than 2^32 lines or columns, but whatever, just default to 0. let lineno: u32 = u32::try_from(lo.line).unwrap_or(0); @@ -175,12 +171,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Scalar::from_u32(colno), &this.project_field(dest, FieldIdx::from_u32(3))?, )?; - - // Support a 4-field struct for now - this is deprecated - // and slated for removal. - if num_fields == 5 { - this.write_pointer(fn_ptr, &this.project_field(dest, FieldIdx::from_u32(4))?)?; - } + this.write_pointer(fn_ptr, &this.project_field(dest, FieldIdx::from_u32(4))?)?; interp_ok(()) } diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index 45fe60599fafb..5e7a391aa8451 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -1,7 +1,7 @@ //! Provides the `extern static` that this platform expects. use rustc_span::Symbol; -use rustc_target::spec::Os; +use rustc_target::spec::{Env, Os}; use crate::*; @@ -16,11 +16,11 @@ impl<'tcx> MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, name: &str, val: ImmTy<'tcx>, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, Pointer> { let place = ecx.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; ecx.write_immediate(*val, &place)?; Self::add_extern_static(ecx, name, place.ptr()); - interp_ok(()) + interp_ok(place.ptr()) } /// Make `ptr` available as a weak symbol with the given name. @@ -61,6 +61,18 @@ impl<'tcx> MiriMachine<'tcx> { match &ecx.tcx.sess.target.os { Os::Linux => { Self::weak_fn_symbols(ecx, &["getrandom", "gettid", "statx", "strlen"])?; + if ecx.tcx.sess.target.env == Env::Gnu { + // FIXME: enabling this with genmc causes test failures because of how + // std then uses pthread keys and genmc does not know about their state. + if ecx.machine.data_race.as_genmc_ref().is_none() { + Self::weak_fn_symbols(ecx, &["__cxa_thread_atexit_impl"])?; + } + // The __dso_handle symbol just marks a unique global address. + let val = ImmTy::from_int(0, ecx.machine.layouts.u8); + let ptr = Self::alloc_extern_static(ecx, "__dso_handle", val)?; + // We also need a weak symbol for `__dso_handle`. + Self::add_weak_symbol(ecx, "__dso_handle", ptr)?; + } } Os::Android => { Self::weak_fn_symbols( diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d25744a40e242..29d0d28f804f7 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -687,12 +687,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Write data to a host `Write` type, with the bytes taken from machine memory. fn write_to_host( - &mut self, + &self, mut file: impl io::Write, len: usize, ptr: Pointer, ) -> InterpResult<'tcx, Result> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; let result = file.write(bytes); diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index f5aa85bbbd8e1..9f58c0c36a6f2 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -19,6 +19,7 @@ use rustc_target::spec::Os; use super::alloc::EvalContextExt as _; use super::backtrace::EvalContextExt as _; use crate::concurrency::GenmcEvalContextExt as _; +use crate::eval::{ExitType, ProgramExitHandler}; use crate::helpers::EvalContextExt as _; use crate::*; @@ -89,7 +90,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn is_dyn_sym(&self, name: &str) -> bool { let this = self.eval_context_ref(); match &this.tcx.sess.target.os { - os if this.target_os_is_unix() => shims::unix::foreign_items::is_dyn_sym(name, os), + _ if this.target_os_is_unix() => + shims::unix::foreign_items::is_dyn_sym(name, &this.tcx.sess.target), Os::Windows => shims::windows::foreign_items::is_dyn_sym(name), _ => false, } @@ -276,9 +278,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // When adding a new shim, you should follow the following pattern: // ``` // "shim_name" => { - // let [arg1, arg2, arg3] = this.check_shim(abi, CanonAbi::C , link_name, args)?; - // let result = this.shim_name(arg1, arg2, arg3)?; - // this.write_scalar(result, dest)?; + // let [arg1, arg2, arg3] = &this.check_shim_sig( + // shim_sig!(extern "C" fn(usize, *libc::some_type, *i32) -> u32), + // (link_name, abi, args), + // )?; + // this.shim_name(arg1, arg2, arg3, dest)?; // } // ``` // and then define `shim_name` as a helper function in an extension trait in a suitable file @@ -289,19 +293,19 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // arg1: &OpTy<'tcx>, // arg2: &OpTy<'tcx>, // arg3: &OpTy<'tcx>, - // arg4: &OpTy<'tcx>) + // dest: &MPlaceTy<'tcx>) // -> InterpResult<'tcx, Scalar> { // let this = self.eval_context_mut(); // // // First thing: load all the arguments. Details depend on the shim. // let arg1 = this.read_scalar(arg1)?.to_u32()?; // let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly - // // when you want to load/store through the pointer - // let arg3 = this.deref_pointer(arg3)?; + // let arg3 = this.deref_pointer(arg3)?; // when you want to load/store through the pointer // // // ... // - // interp_ok(Scalar::from_u32(42)) + // this.write_int(42, dest)?; + // interp_ok(()) // } // ``` // You might find existing shims not following this pattern, most @@ -608,19 +612,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Aborting the process. "exit" => { // FIXME: This does not have a direct test (#3179). - let [code] = &this + let [exit_code] = &this .check_shim_sig(shim_sig!(extern "C" fn(i32) -> ()), (link_name, abi, args))?; - let code = this.read_scalar(code)?.to_i32()?; - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - // If there is no error, execution should continue (on a different thread). - genmc_ctx.handle_exit( - this.machine.threads.active_thread(), - code, - crate::concurrency::ExitType::ExitCalled, - )?; - return interp_ok(EmulateItemResult::AlreadyJumped); - } - throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false }); + let exit_code = this.read_scalar(exit_code)?.to_i32()?; + // Initialize the at-exit state machine. + this.replace_active_thread_stack({ + let mut state_machine = + ProgramExitHandler::new(exit_code, ExitType::ExitCalled); + Box::new(move |m| state_machine.on_stack_empty(m)) + }); + // We "jumped" to the exit handling. + return interp_ok(EmulateItemResult::AlreadyJumped); } "abort" => { // FIXME: This does not have a direct test (#3179). diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 22a176c803819..4d1ffb1d9b15f 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -284,8 +284,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - fn io_error_to_errnum(&mut self, err: impl Into) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn io_error_to_errnum(&self, err: impl Into) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); interp_ok(match err.into() { HostError(err) => this.host_error_to_errnum(err)?, LibcError(name) => this.eval_libc(name), diff --git a/src/tools/miri/src/shims/math.rs b/src/tools/miri/src/shims/math.rs index 1a3228425af9a..81c303307f457 100644 --- a/src/tools/miri/src/shims/math.rs +++ b/src/tools/miri/src/shims/math.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_apfloat::Float; use rustc_middle::ty::Ty; use rustc_span::Symbol; @@ -38,7 +37,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erff" | "erfcf" => { - let [f] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32) -> f32), + (link_name, abi, args), + )?; let f = this.read_scalar(f)?.to_f32()?; let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| { @@ -81,7 +83,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2f" | "fdimf" => { - let [f1, f2] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f1, f2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32, f32) -> f32), + (link_name, abi, args), + )?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; @@ -125,7 +130,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erf" | "erfc" => { - let [f] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64) -> f64), + (link_name, abi, args), + )?; let f = this.read_scalar(f)?.to_f64()?; let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| { @@ -168,7 +176,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2" | "fdim" => { - let [f1, f2] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f1, f2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, f64) -> f64), + (link_name, abi, args), + )?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; @@ -199,7 +210,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "ldexp" | "scalbn" => { - let [x, exp] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [x, exp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, i32) -> f64), + (link_name, abi, args), + )?; // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same. let x = this.read_scalar(x)?.to_f64()?; let exp = this.read_scalar(exp)?.to_i32()?; @@ -209,8 +223,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgammaf_r" => { - let [x, signp] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [x, signp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32, *_) -> f32), + (link_name, abi, args), + )?; let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; @@ -229,8 +245,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgamma_r" => { - let [x, signp] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [x, signp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, *_) -> f64), + (link_name, abi, args), + )?; let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 359f98861ffe9..c62926369e9dd 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -1,6 +1,6 @@ //! Everything related to checking the signature of shim invocations. -use rustc_abi::{CanonAbi, ExternAbi}; +use rustc_abi::ExternAbi; use rustc_middle::ty::{Binder, FnSig, FnSigKind, Ty}; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -21,7 +21,7 @@ pub struct ShimSig<'tcx, const ARGS: usize> { /// ``` /// /// The following types are supported: -/// - primitive integer types +/// - primitive integer and floating-point types /// - `()` /// - (thin) raw pointers, written `*_` since the mutability and pointee type are irrelevant /// - `$crate::$mod::...::$ty` for a type from the given crate (most commonly that is `libc`) @@ -140,6 +140,12 @@ macro_rules! shim_sig_arg { ($this:ident, usize) => { $this.tcx.types.usize }; + ($this:ident, f32) => { + $this.tcx.types.f32 + }; + ($this:ident, f64) => { + $this.tcx.types.f64 + }; ($this:ident, ()) => { $this.tcx.types.unit }; @@ -290,38 +296,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - /// 'Lenient' signature check. Deprecated; use `check_shim_sig` instead. - fn check_shim_sig_deprecated<'a, const N: usize>( - &mut self, - abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: CanonAbi, - link_name: Symbol, - args: &'a [OpTy<'tcx>], - ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { - self.check_shim_symbol_clash(link_name)?; - - if abi.conv != exp_abi { - throw_ub_format!( - r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#, - abi.conv - ); - } - if abi.c_variadic { - throw_ub_format!( - "calling a non-variadic function with a c-variadic caller-side signature" - ); - } - - if let Ok(ops) = args.try_into() { - return interp_ok(ops); - } - throw_ub_format!( - "incorrect number of arguments for `{link_name}`: got {}, expected {}", - args.len(), - N - ) - } - /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and /// then returns the list of arguments, transmuted to the expected (shim) types. fn check_shim_sig( diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 18267a3730c34..f86780eb87b7a 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -490,8 +490,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Parse a `timeval` struct and return it as a [`Duration`]. It returns [`None`] /// if the value in the `timeval` struct is invalid. Some libc functions will return /// EINVAL in this case. - fn read_timeval(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { - let this = self.eval_context_mut(); + fn read_timeval(&self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); let sec_field = this.project_field_named(tp, "tv_sec")?; let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?; diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 0444efd0ac62a..bd670831c5372 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -4,7 +4,7 @@ use std::collections::btree_map::Entry as BTreeEntry; use std::collections::{BTreeMap, VecDeque}; use std::task::Poll; -use rustc_abi::{ExternAbi, HasDataLayout, Size}; +use rustc_abi::{ExternAbi, Size}; use rustc_middle::ty; use rustc_span::Span; use rustc_target::spec::Os; @@ -15,9 +15,9 @@ pub type TlsKey = u128; #[derive(Clone, Debug)] pub struct TlsEntry<'tcx> { - /// The data for this key. None is used to represent NULL. + /// The data for this key. Absence of data for a thread ID is used to represent NULL. /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.) - pub data: BTreeMap, + pub data: BTreeMap, pub dtor: Option<(ty::Instance<'tcx>, Span)>, } @@ -46,9 +46,8 @@ pub struct TlsData<'tcx> { /// pthreads-style thread-local storage. keys: BTreeMap>, - /// On macOS, each thread holds a list of destructor functions with their - /// respective data arguments. - macos_thread_dtors: BTreeMap, Scalar, Span)>>, + /// Linux and macOS allow registering callbacks that will be executed each time a thread exits. + thread_atexit: BTreeMap, Scalar, Span)>>, } impl<'tcx> Default for TlsData<'tcx> { @@ -56,7 +55,7 @@ impl<'tcx> Default for TlsData<'tcx> { TlsData { next_key: 1, // start with 1 as we must not use 0 on Windows keys: Default::default(), - macos_thread_dtors: Default::default(), + thread_atexit: Default::default(), } } } @@ -91,17 +90,12 @@ impl<'tcx> TlsData<'tcx> { } } - pub fn load_tls( - &self, - key: TlsKey, - thread_id: ThreadId, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, Scalar> { + pub fn load_tls(&self, key: TlsKey, thread_id: ThreadId) -> InterpResult<'tcx, Pointer> { match self.keys.get(&key) { Some(TlsEntry { data, .. }) => { let value = data.get(&thread_id).copied(); trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value); - interp_ok(value.unwrap_or_else(|| Scalar::null_ptr(cx))) + interp_ok(value.unwrap_or_else(Pointer::null)) } None => throw_ub_format!("loading from a non-existing TLS key: {}", key), } @@ -111,12 +105,11 @@ impl<'tcx> TlsData<'tcx> { &mut self, key: TlsKey, thread_id: ThreadId, - new_data: Scalar, - cx: &impl HasDataLayout, + new_data: Pointer, ) -> InterpResult<'tcx> { match self.keys.get_mut(&key) { Some(TlsEntry { data, .. }) => { - if new_data.to_target_usize(cx)? != 0 { + if new_data.addr() != Size::ZERO { trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data); data.insert(thread_id, new_data); } else { @@ -131,14 +124,14 @@ impl<'tcx> TlsData<'tcx> { /// Add a thread local storage destructor for the given thread. This function /// is used to implement the `_tlv_atexit` shim on MacOS. - pub fn add_macos_thread_dtor( + pub fn add_thread_atexit( &mut self, thread: ThreadId, dtor: ty::Instance<'tcx>, data: Scalar, span: Span, ) -> InterpResult<'tcx> { - self.macos_thread_dtors.entry(thread).or_default().push((dtor, data, span)); + self.thread_atexit.entry(thread).or_default().push((dtor, data, span)); interp_ok(()) } @@ -165,7 +158,7 @@ impl<'tcx> TlsData<'tcx> { &mut self, key: Option, thread_id: ThreadId, - ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey, Span)> { + ) -> Option<(ty::Instance<'tcx>, Pointer, TlsKey, Span)> { use std::ops::Bound::*; let thread_local = &mut self.keys; @@ -185,8 +178,8 @@ impl<'tcx> TlsData<'tcx> { BTreeEntry::Occupied(entry) => { if let Some((dtor, span)) = dtor { // Set TLS data to NULL, and call dtor with old value. - let data_scalar = entry.remove(); - return Some((*dtor, data_scalar, key, *span)); + let data = entry.remove(); + return Some((*dtor, data, key, *span)); } } BTreeEntry::Vacant(_) => {} @@ -202,15 +195,26 @@ impl<'tcx> TlsData<'tcx> { data.remove(&thread_id); } - if let Some(dtors) = self.macos_thread_dtors.remove(&thread_id) { + if let Some(dtors) = self.thread_atexit.remove(&thread_id) { assert!(dtors.is_empty(), "the destructors should have already been run"); } } + + pub fn leak_all_thread_tls(&self, thread_id: ThreadId, static_roots: &mut Vec) { + for TlsEntry { data, .. } in self.keys.values() { + if let Some(ptr) = data.get(&thread_id) { + if let Some(alloc) = ptr.provenance.and_then(|p| p.get_alloc_id()) { + trace!("Thread-local key value leaked and stored as static root: {:?}", alloc); + static_roots.push(alloc); + } + } + } + } } impl VisitProvenance for TlsData<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let TlsData { keys, macos_thread_dtors, next_key: _ } = self; + let TlsData { keys, thread_atexit: macos_thread_dtors, next_key: _ } = self; for scalar in keys.values().flat_map(|v| v.data.values()) { scalar.visit_provenance(visit); @@ -228,8 +232,10 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>); enum TlsDtorsStatePriv<'tcx> { #[default] Init, - MacOsDtors, + InitForExit, PthreadDtors(RunningPthreadDtorState), + AfterPthreadDtors, + ThreadAtexit, /// For Windows, we support two different ways dtors can be registered. /// 1. Functions that are registered via the `FlsAlloc` function, which are invoked one by one. /// 2. Functions from the magic `.CRT$XLB` linker section. @@ -239,6 +245,12 @@ enum TlsDtorsStatePriv<'tcx> { } impl<'tcx> TlsDtorsState<'tcx> { + /// Returns the state we should start running when the program is exiting. + pub fn new_for_exit() -> Self { + // Pthread destructors get skipped on `exit`. + Self(TlsDtorsStatePriv::InitForExit) + } + pub fn on_stack_empty( &mut self, this: &mut MiriInterpCx<'tcx>, @@ -247,16 +259,35 @@ impl<'tcx> TlsDtorsState<'tcx> { let new_state = 'new_state: { match &mut self.0 { Init => { + if this.target_os_is_unix() { + // We run the pthread destructors first, since they get skipped on `exit`. + break 'new_state PthreadDtors(Default::default()); + } else { + break 'new_state AfterPthreadDtors; + } + } + InitForExit => { + // We skip the pthread dtors, but we mark them as static roots so they do not + // acount as leaked. + if this.target_os_is_unix() { + this.machine.tls.leak_all_thread_tls( + this.active_thread(), + &mut this.machine.static_roots, + ); + } + break 'new_state AfterPthreadDtors; + } + PthreadDtors(state) => { + match this.schedule_next_pthread_tls_dtor(state)? { + Poll::Pending => return interp_ok(Poll::Pending), // just keep going + Poll::Ready(()) => break 'new_state AfterPthreadDtors, + } + } + AfterPthreadDtors => { + // Next, we run the platform-specific destructors match this.tcx.sess.target.os { - Os::MacOs => { - // macOS has a _tlv_atexit function that allows - // registering destructors without associated keys. - // These are run first. - break 'new_state MacOsDtors; - } - _ if this.target_os_is_unix() => { - // All other Unixes directly jump to running the pthread dtors. - break 'new_state PthreadDtors(Default::default()); + Os::MacOs | Os::Linux => { + break 'new_state ThreadAtexit; } Os::Windows => { // Determine which destructors to run. @@ -276,22 +307,15 @@ impl<'tcx> TlsDtorsState<'tcx> { ); } _ => { - // No TLS dtor support. break 'new_state Done; } } } - MacOsDtors => { - match this.schedule_macos_tls_dtor()? { + ThreadAtexit => { + match this.schedule_thread_atexit()? { Poll::Pending => return interp_ok(Poll::Pending), // After all macOS destructors are run, the system switches // to destroying the pthread destructors. - Poll::Ready(()) => break 'new_state PthreadDtors(Default::default()), - } - } - PthreadDtors(state) => { - match this.schedule_next_pthread_tls_dtor(state)? { - Poll::Pending => return interp_ok(Poll::Pending), // just keep going Poll::Ready(()) => break 'new_state Done, } } @@ -336,8 +360,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lookup all the FLS keys (which are stored as TLS keys) that have a destructor. /// See also: `schedule_next_windows_fls_dtor`. - fn lookup_windows_fls_keys_with_dtors(&mut self) -> InterpResult<'tcx, VecDeque> { - let this = self.eval_context_mut(); + fn lookup_windows_fls_keys_with_dtors(&self) -> InterpResult<'tcx, VecDeque> { + let this = self.eval_context_ref(); interp_ok( this.machine @@ -376,15 +400,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Schedule the macOS thread local storage destructors to be executed. - fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> { + fn schedule_thread_atexit(&mut self) -> InterpResult<'tcx, Poll<()>> { let this = self.eval_context_mut(); let thread_id = this.active_thread(); // macOS keeps track of TLS destructors in a stack. If a destructor // registers another destructor, it will be run next. // See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277 - let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop); + let dtor = this.machine.tls.thread_atexit.get_mut(&thread_id).and_then(Vec::pop); if let Some((instance, data, span)) = dtor { - trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id); + trace!("Running thread_atexit hook {:?} on {:?} at {:?}", instance, data, thread_id); this.call_thread_root_function( instance, @@ -418,15 +442,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let Some((instance, ptr, key, span)) = dtor { state.last_key = Some(key); trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread); - assert!( - ptr.to_target_usize(this).unwrap() != 0, - "data can't be NULL when dtor is called!" - ); + assert!(ptr.addr() != Size::ZERO, "data can't be NULL when dtor is called!"); this.call_thread_root_function( instance, ExternAbi::C { unwind: false }, - &[ImmTy::from_scalar(ptr, this.machine.layouts.unit_ptr_mut)], + &[ImmTy::from_scalar( + Scalar::from_maybe_pointer(ptr, this), + this.machine.layouts.unit_ptr_mut, + )], None, span, )?; @@ -480,7 +504,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(&ptr) = data.get(&active_thread) else { continue }; assert!( - ptr.to_target_usize(this).unwrap() != 0, + ptr.addr() != Size::ZERO, "TLS key's value can't be null (should be absent instead)" ); @@ -492,7 +516,10 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.call_thread_root_function( instance, ExternAbi::System { unwind: false }, - &[ImmTy::from_scalar(ptr, this.machine.layouts.unit_ptr_mut)], + &[ImmTy::from_scalar( + Scalar::from_maybe_pointer(ptr, this), + this.machine.layouts.unit_ptr_mut, + )], None, span, )?; diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 6af9bcb74692f..7740757ee336a 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -76,37 +75,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Miscellaneous "__errno" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "gettid" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this.check_shim_sig( + shim_sig!(extern "C" fn() -> libc::pid_t), + (link_name, abi, args), + )?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs index d8352d9c06553..1d8e5e95c40d4 100644 --- a/src/tools/miri/src/shims/unix/env.rs +++ b/src/tools/miri/src/shims/unix/env.rs @@ -126,8 +126,8 @@ fn alloc_environ_block<'tcx>( impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn getenv(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> { - let this = self.eval_context_mut(); + fn getenv(&self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> { + let this = self.eval_context_ref(); this.assert_target_os_is_unix("getenv"); let name_ptr = this.read_pointer(name_op)?; @@ -326,8 +326,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn getpid(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn getpid(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); this.assert_target_os_is_unix("getpid"); // The reason we need to do this wacky of a conversion is because diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 9491698265c88..f29b2e34488c1 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -2,11 +2,11 @@ use std::ffi::OsStr; use std::str; use std::time::Duration; -use rustc_abi::{CanonAbi, Size}; +use rustc_abi::Size; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; -use rustc_target::spec::{Env, Os}; +use rustc_target::spec::{Env, Os, Target}; use self::shims::unix::android::foreign_items as android; use self::shims::unix::freebsd::foreign_items as freebsd; @@ -19,7 +19,7 @@ use crate::shims::cpu_affinity::EvalContextExt as _; use crate::shims::unix::*; use crate::{shim_sig, *}; -pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { +pub fn is_dyn_sym(name: &str, target: &Target) -> bool { match name { // Used for (std and Miri) tests. "strlen" => true, @@ -34,13 +34,13 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { // `preadv`/`pwritev` are set up as weak symbols in `init_extern_statics` (on Android, // where std uses them via `weak!` since bionic only gained them in API level 24), so // we allow them here too. They do not exist on Solaris. - "preadv" | "pwritev" if !matches!(*target_os, Os::Solaris) => true, + "preadv" | "pwritev" if !matches!(target.os, Os::Solaris) => true, // Give specific OSes a chance to allow their symbols. _ => - match *target_os { + match target.os { Os::Android => android::is_dyn_sym(name), Os::FreeBsd => freebsd::is_dyn_sym(name), - Os::Linux => linux::is_dyn_sym(name), + Os::Linux => linux::is_dyn_sym(name, target), Os::MacOs => macos::is_dyn_sym(name), Os::Solaris | Os::Illumos => solarish::is_dyn_sym(name), Os::NetBsd => netbsd::is_dyn_sym(name), @@ -52,8 +52,8 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Querying system information - fn sysconf(&mut self, val: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn sysconf(&self, val: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let name = this.read_scalar(val)?.to_i32()?; // FIXME: Which of these are POSIX, and which are GNU/Linux? @@ -437,7 +437,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "rename" => { - // FIXME: This does not have a direct test (#3179). let [oldpath, newpath] = &this.check_shim_sig( shim_sig!(extern "C" fn(*_, *_) -> i32), (link_name, abi, args), @@ -865,7 +864,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Ok(name) = str::from_utf8(name) else { throw_unsup_format!("dlsym: non UTF-8 symbol name not supported") }; - if is_dyn_sym(name, &this.tcx.sess.target.os) { + if this.is_dyn_sym(name) { let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); this.write_pointer(ptr, dest)?; } else if let Some(&ptr) = this.machine.extern_statics.get(&Symbol::intern(name)) { @@ -902,7 +901,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_key_delete" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "C" fn(libc::pthread_key_t) -> i32), (link_name, abi, args), @@ -913,15 +911,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_getspecific" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "C" fn(libc::pthread_key_t) -> *_), (link_name, abi, args), )?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "pthread_setspecific" => { let [key, new_ptr] = &this.check_shim_sig( @@ -930,8 +927,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`0`). this.write_null(dest)?; @@ -1332,8 +1329,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_getguardsize" if this.frame_in_std() => { - let [_attr, guard_size] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_attr, guard_size] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; let guard_size_layout = this.machine.layouts.usize; let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?; this.write_scalar( @@ -1346,19 +1345,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => { - let [_] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; this.write_null(dest)?; } + "pthread_attr_setstacksize" if this.frame_in_std() => { - let [_, _] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "pthread_attr_getstack" if this.frame_in_std() => { // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here. // Hence we can mostly ignore the input `attr_place`. - let [attr_place, addr_place, size_place] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [attr_place, addr_place, size_place] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?; @@ -1377,20 +1383,34 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - "signal" | "sigaltstack" if this.frame_in_std() => { - let [_, _] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + "signal" if this.frame_in_std() => { + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::sighandler_t) -> libc::sighandler_t), + (link_name, abi, args), + )?; + this.write_null(dest)?; + } + "sigaltstack" if this.frame_in_std() => { + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "sigaction" if this.frame_in_std() => { - let [_, _, _] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_, _, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish - let [uid, pwd, buf, buflen, result] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [uid, pwd, buf, buflen, result] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::uid_t, *_, *_, usize, *_) -> i32), + (link_name, abi, args), + )?; this.check_no_isolation("`getpwuid_r`")?; let uid = this.read_scalar(uid)?.to_u32()?; diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index b2a101a0938b2..d4e66f5a5d08e 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -24,8 +23,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let max_len = u64::MAX; // FreeBSD does not seem to have a limit. let res = match this.pthread_setname_np( this.read_scalar(thread)?, @@ -39,8 +40,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value, // but always adds a null terminator (except for zero-sized buffers). // https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144 @@ -57,15 +60,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getthreadid_np" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } "cpuset_getaffinity" => { // The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically. - let [level, which, id, set_size, mask] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [level, which, id, set_size, mask] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i64, usize, *_) -> i32), + (link_name, abi, args), + )?; let level = this.read_scalar(level)?.to_i32()?; let which = this.read_scalar(which)?.to_i32()?; @@ -136,14 +142,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "_umtx_op" => { - let [obj, op, val, uaddr, uaddr2] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [obj, op, val, uaddr, uaddr2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, i32, usize, *_, *_) -> i32), + (link_name, abi, args), + )?; this._umtx_op(obj, op, val, uaddr, uaddr2, dest)?; } // Miscellaneous "__error" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } @@ -162,8 +171,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_get_np" if this.frame_in_std() => { - let [_thread, _attr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_thread, _attr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs index 28f912ab973fe..f1a9572129c99 100644 --- a/src/tools/miri/src/shims/unix/freebsd/sync.rs +++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs @@ -213,8 +213,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Parses a `_umtx_time` struct. /// Returns `None` if the underlying `timespec` struct is invalid. - fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { - let this = self.eval_context_mut(); + fn read_umtx_time(&self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); // Only flag allowed is UMTX_ABSTIME. let abs_time = this.eval_libc_u32("UMTX_ABSTIME"); diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 4622114dcacd8..fb035e3951784 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{self, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; +use std::fs::{self, DirBuilder, File, FileTimes, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{self, Path}; use std::time::SystemTime; @@ -373,39 +373,15 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(0) } - fn file_type_to_d_type(&self, file_type: std::io::Result) -> InterpResult<'tcx, i32> { - #[cfg(unix)] - use std::os::unix::fs::FileTypeExt; - + #[cfg(windows)] + fn file_type_to_d_type(&self, file_type: fs::FileType) -> InterpResult<'tcx, i32> { let this = self.eval_context_ref(); - match file_type { - Ok(file_type) => { - match () { - _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()), - _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()), - _ if file_type.is_symlink() => - interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()), - // Certain file types are only supported when the host is a Unix system. - #[cfg(unix)] - _ if file_type.is_block_device() => - interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_char_device() => - interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_fifo() => - interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_socket() => - interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()), - // Fallback - _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), - } - } - Err(_) => { - // Fallback on error - interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()) - } + match () { + _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()), + _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()), + _ if file_type.is_symlink() => interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()), + // Fallback + _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), } } @@ -416,15 +392,28 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); interp_ok(match entry { Either::Left(dir_entry) => { - DirEntry { - name: dir_entry.file_name(), - d_type: this.file_type_to_d_type(dir_entry.file_type())?, - // If the host is a Unix system, fill in the inode number with its real value. - // If not, use 0 as a fallback value. - #[cfg(unix)] - ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry), - #[cfg(not(unix))] - ino: 0u64, + cfg_select! { + unix => { + use std::os::unix::fs::DirEntryExt; + DirEntry { + name: dir_entry.file_name(), + d_type: { + // We cannot rely on `dir_entry.file_type`, it might use a separate + // syscall based on `file_name` which is racy. + // It's always okay to return DT_UNKNOWN instead. + this.eval_libc("DT_UNKNOWN").to_u8()?.into() + }, + ino: dir_entry.ino(), + } + } + windows => { + DirEntry { + name: dir_entry.file_name(), + // `DirEntry::file_type` is always race-free and never fails on Windows. + d_type: this.file_type_to_d_type(dir_entry.file_type().unwrap())?, + ino: 0u64, + } + } } } Either::Right(special) => @@ -795,7 +784,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/7045 let result = cfg_select! { unix => fs::remove_file(path), windows => {{ @@ -1331,7 +1319,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/7045 let result = cfg_select! { unix => fs::remove_dir(path), windows => {{ @@ -1683,12 +1670,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Shared logic between `posix_fallocate` and `linux_fallocate`. fn fallocate_impl( - &mut self, + &self, fd_num: i32, offset: i64, len: i64, ) -> InterpResult<'tcx, Result<(), IoError>> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); // EINVAL is returned/set when: "offset was less than 0, or len was less than or equal to 0". if offset < 0 || len <= 0 { @@ -1741,7 +1728,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd_op)?.to_i32()?; - self.ffullsync_fd(fd) + this.ffullsync_fd(fd) } fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> { diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 62ec9d2bc7ed0..2409d22868ebd 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -1,7 +1,7 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; +use rustc_target::spec::Target; use self::shims::unix::linux::mem::EvalContextExt as _; use self::shims::unix::linux_like::eventfd::EvalContextExt as _; @@ -18,8 +18,10 @@ use crate::*; // long including a null terminator. const TASK_COMM_LEN: u64 = 16; -pub fn is_dyn_sym(name: &str) -> bool { +pub fn is_dyn_sym(name: &str, target: &Target) -> bool { matches!(name, "gettid" | "statx") + || (target.env == rustc_target::spec::Env::Gnu + && matches!(name, "__cxa_thread_atexit_impl")) } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -147,14 +149,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "sync_file_range" => { - let [fd, offset, nbytes, flags] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [fd, offset, nbytes, flags] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t, u32) -> i32), + (link_name, abi, args), + )?; let result = this.sync_file_range(fd, offset, nbytes, flags)?; this.write_scalar(result, dest)?; } "statx" => { - let [dirfd, pathname, flags, mask, statxbuf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirfd, pathname, flags, mask, statxbuf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, u32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } @@ -167,38 +173,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "readdir64" => { - let [dirp] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirp] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; this.readdir(dirp, dest, "dirent64")?; } // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let res = match this.pthread_setname_np( this.read_scalar(thread)?, this.read_scalar(name)?, @@ -211,8 +227,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // The function's behavior isn't portable between platforms. // In case of glibc, the length of the output buffer must // be not shorter than TASK_COMM_LEN. @@ -233,11 +251,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "gettid" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this.check_shim_sig( + shim_sig!(extern "C" fn() -> libc::pid_t), + (link_name, abi, args), + )?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } "prctl" => prctl(this, link_name, abi, args, dest)?, + "__cxa_thread_atexit_impl" + if this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => + { + let [dtor, arg, dso] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; + let dtor = this.read_pointer(dtor)?; + let arg = this.read_scalar(arg)?; + let dso = this.read_pointer(dso)?; + + let dso_handle = Symbol::intern("__dso_handle"); + if dso.addr() != Pointer::from(this.machine.extern_statics[&dso_handle]).addr() { + throw_unsup_format!( + "__cxa_thread_atexit_impl: a DSO value other than the address of `__dso_handle` is not supported" + ); + } + + let dtor = this.get_ptr_fn(dtor)?.as_instance()?; + this.machine.tls.add_thread_atexit( + this.active_thread(), + dtor, + arg, + this.machine.current_user_relevant_span(), + )?; + + this.write_null(dest)?; + } // Dynamically invoked syscalls "syscall" => { @@ -246,8 +295,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "mmap64" => { - let [addr, length, prot, flags, fd, offset] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr, length, prot, flags, fd, offset] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, i32, i32, i32, libc::off64_t) -> *_), + (link_name, abi, args), + )?; let offset = this.read_scalar(offset)?.to_i64()?; let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?; this.write_scalar(ptr, dest)?; @@ -261,23 +312,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(ptr, dest)?; } "__xpg_strerror_r" => { - let [errnum, buf, buflen] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [errnum, buf, buflen] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, usize) -> i32), + (link_name, abi, args), + )?; let result = this.strerror_r(errnum, buf, buflen)?; this.write_scalar(result, dest)?; } "__errno_location" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "__libc_current_sigrtmin" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; this.write_int(SIGRTMIN, dest)?; } "__libc_current_sigrtmax" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; this.write_int(SIGRTMAX, dest)?; } @@ -285,15 +341,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_getattr_np" if this.frame_in_std() => { - let [_thread, _attr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_thread, _attr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "gnu_get_libc_version" if this.frame_in_std() && this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; // We have to be at least version 2.26 so that std does not call `res_init`. // This returns a C string, so we have to add a null terminator. let version = "2.26\0"; diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 2b5428e36f298..bd60bc69f4c48 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -35,27 +34,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // errno "__error" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } // File related shims "close$NOCANCEL" => { - let [fd] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [fd] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let fd = this.read_scalar(fd)?.to_i32()?; let result = this.close(fd)?; this.write_scalar(result, dest)?; } "stat$INODE64" => { - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, buf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *libc::stat) -> i32), + (link_name, abi, args), + )?; let result = this.stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat$INODE64" => { - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, buf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *libc::stat) -> i32), + (link_name, abi, args), + )?; let result = this.lstat(path, buf)?; this.write_scalar(result, dest)?; } @@ -76,17 +81,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "opendir$INODE64" => { - let [name] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [name] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; let result = this.opendir(name)?; this.write_scalar(result, dest)?; } "readdir$INODE64" => { - let [dirp] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirp] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; this.readdir(dirp, dest, "dirent")?; } "realpath$DARWIN_EXTSN" => { - let [path, resolved_path] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, resolved_path] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> *_), + (link_name, abi, args), + )?; let result = this.realpath(path, resolved_path)?; this.write_scalar(result, dest)?; } @@ -94,15 +103,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Environment related shims "_NSGetEnviron" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let environ = this.machine.env_vars.unix().environ(); this.write_pointer(environ, dest)?; } // Random data generation "CCRandomGenerateBytes" => { - let [bytes, count] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [bytes, count] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize) -> i32), + (link_name, abi, args), + )?; let bytes = this.read_pointer(bytes)?; let count = this.read_target_usize(count)?; let success = this.eval_libc_i32("kCCSuccess"); @@ -112,22 +124,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Time related shims "mach_absolute_time" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> u64), (link_name, abi, args))?; let result = this.mach_absolute_time()?; this.write_scalar(result, dest)?; } "mach_timebase_info" => { // FIXME: This does not have a direct test (#3179). - let [info] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [info] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; let result = this.mach_timebase_info(info)?; this.write_scalar(result, dest)?; } "mach_wait_until" => { // FIXME: This does not have a direct test (#3179). - let [deadline] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [deadline] = &this + .check_shim_sig(shim_sig!(extern "C" fn(u64) -> i32), (link_name, abi, args))?; let result = this.mach_wait_until(deadline)?; this.write_scalar(result, dest)?; } @@ -135,18 +149,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Access to command-line arguments "_NSGetArgc" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?; } "_NSGetArgv" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?; } "_NSGetExecutablePath" => { // FIXME: This does not have a direct test (#3179). - let [buf, bufsize] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [buf, bufsize] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; this.check_no_isolation("`_NSGetExecutablePath`")?; let buf_ptr = this.read_pointer(buf)?; @@ -173,14 +191,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "_tlv_atexit" => { - let [dtor, data] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dtor, data] = &this.check_shim_sig( + shim_sig!(extern "C" fn(fn(..) -> _, *_) -> ()), + (link_name, abi, args), + )?; let dtor = this.read_pointer(dtor)?; let dtor = this.get_ptr_fn(dtor)?.as_instance()?; let data = this.read_scalar(data)?; - let active_thread = this.active_thread(); - this.machine.tls.add_macos_thread_dtor( - active_thread, + this.machine.tls.add_thread_atexit( + this.active_thread(), dtor, data, this.machine.current_user_relevant_span(), @@ -190,14 +209,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Querying system information "pthread_get_stackaddr_np" => { // FIXME: This does not have a direct test (#3179). - let [thread] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t) -> *_), + (link_name, abi, args), + )?; this.read_target_usize(thread)?; let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size()); this.write_scalar(stack_addr, dest)?; } "pthread_get_stacksize_np" => { // FIXME: This does not have a direct test (#3179). - let [thread] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t) -> usize), + (link_name, abi, args), + )?; this.read_target_usize(thread)?; let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size()); this.write_scalar(stack_size, dest)?; @@ -205,7 +230,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Threading "pthread_setname_np" => { - let [name] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [name] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; // The real implementation has logic in two places: // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200, @@ -234,8 +260,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // The function's behavior isn't portable between platforms. // In case of macOS, a truncated name (due to a too small buffer) @@ -258,16 +286,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_threadid_np" => { - let [thread, tid_ptr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, tid_ptr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let res = this.apple_pthread_threadid_np(thread, tid_ptr)?; this.write_scalar(res, dest)?; } // Synchronization primitives "os_sync_wait_on_address" => { - let [addr_op, value_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -278,8 +310,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wait_on_address_with_deadline" => { - let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this + .check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -290,8 +325,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wait_on_address_with_timeout" => { - let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this + .check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -302,42 +340,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wake_by_address_any" => { - let [addr_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ false, dest, )?; } "os_sync_wake_by_address_all" => { - let [addr_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ true, dest, )?; } "os_unfair_lock_lock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_lock(lock_op)?; } "os_unfair_lock_trylock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> bool), (link_name, abi, args))?; this.os_unfair_lock_trylock(lock_op, dest)?; } "os_unfair_lock_unlock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_unlock(lock_op)?; } "os_unfair_lock_assert_owner" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_assert_owner(lock_op)?; } "os_unfair_lock_assert_not_owner" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_assert_not_owner(lock_op)?; } "dispatch_semaphore_create" => { @@ -375,8 +417,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pthread_cond_timedwait_relative_np" => { - let [cond, mutex, reltime] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [cond, mutex, reltime] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; this.pthread_cond_timedwait( cond, mutex, reltime, dest, /* macos_relative_np */ true, )?; @@ -385,8 +429,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "confstr" => { - let [_key, _buf, _buflen] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + // FIXME: This does not have a direct test (#3179). + let [_key, _buf, _buflen] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, usize) -> usize), + (link_name, abi, args), + )?; // We just pretend that no configuration key exists, and return EINVAL. this.set_last_error(LibcError("EINVAL"))?; this.write_null(dest)?; diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 8a3ae89ae1dee..e03cbecd34154 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -10,6 +10,7 @@ mod socket_address; mod sync; mod tcp_socket; mod thread; +mod udp_socket; mod virtual_socket; mod android; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index a32ae29541493..e237617b296d4 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -5,11 +5,12 @@ use std::time::Duration; use rustc_abi::Size; use rustc_target::spec::Os; -use crate::shims::FileDescriptionRef; use crate::shims::files::FdNum; use crate::shims::unix::UnixFileDescription; use crate::shims::unix::socket_address::EvalContextExt as _; use crate::shims::unix::tcp_socket::TcpSocket; +use crate::shims::unix::udp_socket::UdpSocket; +use crate::shims::{FileDescription, FileDescriptionRef}; use crate::*; /// Represents unix-specific socket file descriptions. @@ -266,28 +267,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); }; - if flags != this.eval_libc_i32("SOCK_STREAM") { + let fd_result = if flags == this.eval_libc_i32("SOCK_STREAM") { + if ![0, this.eval_libc_i32("IPPROTO_TCP")].contains(&protocol) { + throw_unsup_format!( + "socket: socket protocol {protocol} is unsupported for \ + streaming socket, only IPPROTO_TCP and 0 are allowed" + ); + } + + TcpSocket::new(family, is_non_block, this).map(|fd| fd.as_unix()) + } else if flags == this.eval_libc_i32("SOCK_DGRAM") { + if ![0, this.eval_libc_i32("IPPROTO_UDP")].contains(&protocol) { + throw_unsup_format!( + "socket: socket protocol {protocol} is unsupported for \ + datagram socket, only IPPROTO_UDP and 0 are allowed" + ); + } + + UdpSocket::new(family, is_non_block, this).map(|fd| fd.as_unix()) + } else { throw_unsup_format!( "socket: type {:#x} is unsupported, only SOCK_STREAM, \ - SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", + SOCK_DGRAM, SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", flags ); - } - if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") { - throw_unsup_format!( - "socket: socket protocol {protocol} is unsupported, \ - only IPPROTO_TCP and 0 are allowed" - ); - } - - let socket = match TcpSocket::new(family, is_non_block) { - Ok(socket) => socket, - Err(e) => return this.set_errno_and_return_neg1_i32(e), }; - let fds = &mut this.machine.fds; - let fd = fds.new_ref(socket); - interp_ok(Scalar::from_i32(fds.insert(fd))) + match fd_result { + Ok(fd) => interp_ok(Scalar::from_i32(this.machine.fds.insert(fd))), + Err(e) => this.set_errno_and_return_neg1_i32(e), + } } fn bind( diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 35c9144235b6c..2e7c6a83b4d81 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -28,35 +27,44 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // epoll, eventfd (NOT available on Solaris!) "epoll_create1" => { this.assert_target_os(Os::Illumos, "epoll_create1"); - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { this.assert_target_os(Os::Illumos, "epoll_ctl"); - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { this.assert_target_os(Os::Illumos, "epoll_wait"); - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { this.assert_target_os(Os::Illumos, "eventfd"); - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; // THREAD_NAME_MAX allows a thread name of 31+1 length // https://github.com/illumos/illumos-gate/blob/7671517e13b8123748eda4ef1ee165c6d9dba7fe/usr/src/uts/common/sys/thread.h#L613 let max_len = 32; @@ -73,8 +81,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // See https://illumos.org/man/3C/pthread_getname_np for the error codes. let res = match this.pthread_getname_np( this.read_scalar(thread)?, @@ -88,26 +98,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } - // File related shims - "stat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; - let result = this.stat(path, buf)?; - this.write_scalar(result, dest)?; - } - "lstat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; - let result = this.lstat(path, buf)?; - this.write_scalar(result, dest)?; - } - // Sockets and pipes "__xnet_socketpair" => { - let [domain, type_, protocol, sv] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [domain, type_, protocol, sv] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.socketpair(domain, type_, protocol, sv)?; this.write_scalar(result, dest)?; } @@ -156,14 +152,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "___errno" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "stack_getbounds" => { // FIXME: This does not have a direct test (#3179). - let [stack] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [stack] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?; this.write_int_fields_named( @@ -182,8 +180,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "pset_info" => { // FIXME: This does not have a direct test (#3179). - let [pset, tpe, cpus, list] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [pset, tpe, cpus, list] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, *_, *_) -> i32), + (link_name, abi, args), + )?; // We do not need to handle the current process cpu mask, available_parallelism // implementation pass null anyway. We only care for the number of // cpus. @@ -212,7 +212,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "__sysconf_xpg7" => { - let [val] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32) -> isize), + (link_name, abi, args), + )?; let result = this.sysconf(val)?; this.write_scalar(result, dest)?; } diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 3cd462d31db4a..239569c469478 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -117,7 +117,11 @@ pub(super) struct TcpSocket { } impl TcpSocket { - pub fn new(family: socket2::Domain, is_non_block: bool) -> io::Result { + pub fn new<'tcx>( + family: socket2::Domain, + is_non_block: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> io::Result> { let socket = socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP))?; // The underlying host socket needs to be non-blocking. The actual @@ -131,7 +135,7 @@ impl TcpSocket { // does have a static initial readiness until `connect` or `listen` is // invoked on it. - Ok(TcpSocket { + let tcp_socket = TcpSocket { family, state: RefCell::new(SocketState::Initial(socket)), is_non_block: Cell::new(is_non_block), @@ -141,13 +145,15 @@ impl TcpSocket { read_timeout: Cell::new(None), write_timeout: Cell::new(None), watched: ReadinessWatched::default(), - }) + }; + + Ok(ecx.machine.fds.new_ref(tcp_socket)) } } impl FileDescription for TcpSocket { fn name(&self) -> &'static str { - "socket" + "tcp socket" } fn read<'tcx>( @@ -401,29 +407,19 @@ impl UnixSocketFileDescription for TcpSocket { unreachable!() }; - // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside - // a `cfg_select!` block. See . - #[rustfmt::skip] let listener = cfg_select! { // Turn the `socket2::Socket` into a `mio::TcpListener`. // We follow Tokio's TCP socket implementation: // See - // SAFETY: mio specifies that it is safe to use `from_raw_fd` and - // `from_raw_socket` as long as it's ensured that the socket is non-blocking. - // Because we immediately make the socket non-blocking after creation, that - // is always guaranteed. - unix => {{ - use std::os::fd::{IntoRawFd, FromRawFd}; - let raw_fd = socket.into_raw_fd(); - unsafe { TcpListener::from_raw_fd(raw_fd) } + use std::os::fd::OwnedFd; + TcpListener::from(OwnedFd::from(socket)) }}, windows => {{ - use std::os::windows::io::{IntoRawSocket, FromRawSocket}; - let raw_socket = socket.into_raw_socket(); - unsafe { TcpListener::from_raw_socket(raw_socket) } + use std::os::windows::io::OwnedSocket; + TcpListener::from(OwnedSocket::from(socket)) }}, - _ => unreachable!("unsupported host platform") + _ => unreachable!("unsupported host platform"), }; *state = SocketState::Listening(listener); @@ -539,30 +535,20 @@ impl UnixSocketFileDescription for TcpSocket { unreachable!() }; - // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside - // a `cfg_select!` block. See . - #[rustfmt::skip] let stream = cfg_select! { // Turn the `socket2::Socket` into a `mio::TcpStream`. // We follow Tokio's TCP socket implementation: // See - // SAFETY: mio specifies that it is safe to use `from_raw_fd` and - // `from_raw_socket` as long as it's ensured that the socket is non-blocking. - // Because we immediately make the socket non-blocking after creation, that - // is always guaranteed. - - unix => {{ - use std::os::fd::{IntoRawFd, FromRawFd}; - let raw_fd = socket.into_raw_fd(); - unsafe { TcpStream::from_raw_fd(raw_fd) } - }}, - windows => {{ - use std::os::windows::io::{IntoRawSocket, FromRawSocket}; - let raw_socket = socket.into_raw_socket(); - unsafe { TcpStream::from_raw_socket(raw_socket) } - }}, - _ => unreachable!("unsupported host platform") - }; + unix => {{ + use std::os::fd::OwnedFd; + TcpStream::from(OwnedFd::from(socket)) + }}, + windows => {{ + use std::os::windows::io::OwnedSocket; + TcpStream::from(OwnedSocket::from(socket)) + }}, + _ => unreachable!("unsupported host platform"), + }; *state = SocketState::Connecting(stream); drop(state); diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs index c2322e38633e3..6564849bb7675 100644 --- a/src/tools/miri/src/shims/unix/thread.rs +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -80,8 +80,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_u32(0)) } - fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn pthread_self(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let thread_id = this.active_thread(); interp_ok(Scalar::from_uint(thread_id.to_u32(), this.libc_ty_layout("pthread_t").size)) diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs new file mode 100644 index 0000000000000..13a1e3a38f1df --- /dev/null +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -0,0 +1,346 @@ +use std::cell::{Cell, RefCell, RefMut}; +use std::io; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::time::Duration; + +use mio::event::Source; +use rustc_target::spec::Os; + +use crate::shims::files::{FileDescription, FileDescriptionRef}; +use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::socket::{EvalContextExt, UnixSocketFileDescription}; +use crate::*; + +#[derive(Debug)] +pub(super) struct UdpSocket { + /// Family of the socket, used to ensure that the socket only binds/connects to addresses + /// of the same family. + family: socket2::Domain, + /// The underlying host socket. + socket: RefCell, + /// Whether this fd is non-blocking or not. + is_non_block: Cell, + /// Whether the socket is implicitly or explicitly bound to an address. + is_bound: Cell, + /// The current blocking I/O readiness of the file description. + io_readiness: RefCell, + /// Read timeout of the socket. [`None`] means that reads can block indefinitely. + /// The timeout is applied to the monotonic clock (the Unix specification doesn't + /// specify which clock to use, but the monotonic clock is more common for + /// relative timeouts). + /// This is ignored when the socket is non-blocking. + read_timeout: Cell>, + /// Write timeout of the socket. [`None`] means that writes can block indefinitely. + /// The timeout is applied to the monotonic clock (the Unix specification doesn't + /// specify which clock to use, but the monotonic clock is more common + /// for relative timeouts). + /// This is ignored when the socket is non-blocking. + write_timeout: Cell>, + /// State for being watched by epoll. + watched: ReadinessWatched, +} + +impl UdpSocket { + pub fn new<'tcx>( + family: socket2::Domain, + is_non_block: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> io::Result> { + let socket = + socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))?; + // The underlying host socket needs to be non-blocking. The actual + // blocking mode of the socket is stored in `is_non_block`. + socket.set_nonblocking(true)?; + + let socket = cfg_select! { + // Turn the `socket2::Socket` into a `mio::UdpSocket`. We cannot construct the + // mio socket directly, since its only constructor is `UdpSocket::bind`. + unix => {{ + use std::os::fd::OwnedFd; + mio::net::UdpSocket::from(OwnedFd::from(socket)) + }}, + windows => {{ + use std::os::windows::io::OwnedSocket; + mio::net::UdpSocket::from(OwnedSocket::from(socket)) + }}, + _ => unreachable!("unsupported host platform"), + }; + + let udp_socket = UdpSocket { + family, + socket: RefCell::new(socket), + is_non_block: Cell::new(is_non_block), + is_bound: Cell::new(false), + // We can set the default readiness to the empty readiness + // since the socket will be registered to the blocking I/O + // directly after its creation. + io_readiness: RefCell::new(Readiness::EMPTY), + read_timeout: Cell::new(None), + write_timeout: Cell::new(None), + watched: ReadinessWatched::default(), + }; + + let fd = ecx.machine.fds.new_ref(udp_socket); + + // Add the UDP socket to the blocking I/O manager as it's always + // backed by a mio UdpSocket. + ecx.machine.blocking_io.register(fd.clone()); + + Ok(fd) + } +} + +impl FileDescription for UdpSocket { + fn name(&self) -> &'static str { + "udp socket" + } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) + } + + fn readiness(&self) -> Readiness { + *self.io_readiness.borrow() + } +} + +impl UnixFileDescription for UdpSocket { + fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + let mut flags = ecx.eval_libc_i32("O_RDWR"); + + if self.is_non_block.get() { + flags |= ecx.eval_libc_i32("O_NONBLOCK"); + } + + interp_ok(Scalar::from_i32(flags)) + } + + fn set_flags<'tcx>( + &self, + mut flag: i32, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK"); + + // O_NONBLOCK flag can be set / unset by user. + if flag & o_nonblock == o_nonblock { + self.is_non_block.set(true); + flag &= !o_nonblock; + } else { + self.is_non_block.set(false); + } + + // Throw error if there is any unsupported flag. + if flag != 0 { + throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets") + } + + interp_ok(Scalar::from_i32(0)) + } + + fn as_socket( + self: FileDescriptionRef, + ) -> Option> { + Some(self) + } +} + +impl UnixSocketFileDescription for UdpSocket { + fn bind<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + address: SocketAddr, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result<(), IoError>> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + let address_family = match &address { + SocketAddr::V4(_) => socket2::Domain::IPV4, + SocketAddr::V6(_) => socket2::Domain::IPV6, + }; + + if self.family != address_family { + // Attempted to bind an address from a family that doesn't match + // the family of the socket. + let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs) { + // Linux man page states that `EINVAL` is used when there is an address family mismatch. + // See + // macOS also returns `EINVAL` but their man page does not specify this. + LibcError("EINVAL") + } else { + // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address + // family mismatch. + // See + LibcError("EAFNOSUPPORT") + }; + return interp_ok(Err(err)); + } + + // `bind` is a non-blocking operation for UDP sockets. + if let Err(e) = self.with_socket_ref(|s| s.bind(&socket2::SockAddr::from(address))).unwrap() + { + return interp_ok(Err(IoError::HostError(e))); + } + + // The socket has been explicitly bound to a local address. + self.is_bound.set(true); + + interp_ok(Ok(())) + } + + fn connect<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + address: SocketAddr, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result<(), IoError>>, + ) -> InterpResult<'tcx> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + if let Err(e) = self.socket.borrow().connect(address) { + return finish.call(ecx, Err(IoError::HostError(e))); + } + + // POSIX specifies that unbound sockets get bound to an unassigned + // local address after `connect` has been invoked on them. + // We thus treat the socket as implicitly bound. + self.is_bound.set(true); + + finish.call(ecx, Ok(())) + } + + fn setsockopt<'tcx>( + self: FileDescriptionRef, + level: i32, + option: i32, + value_ptr: Pointer, + value_len: u64, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result<(), IoError>> { + if level == ecx.eval_libc_i32("SOL_SOCKET") { + return ecx.set_generic_socket_option(option, value_ptr, value_len, self); + } else if level == ecx.eval_libc_i32("IPPROTO_IP") { + let socket = self.socket.borrow(); + return ecx.set_ip_socket_option( + option, + value_ptr, + value_len, + socket2::SockRef::from(&*socket), + ); + } + + throw_unsup_format!( + "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \ + are allowed" + ); + } + + fn getsockopt<'tcx>( + self: FileDescriptionRef, + level: i32, + option: i32, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result, IoError>> { + if level == ecx.eval_libc_i32("SOL_SOCKET") { + ecx.get_generic_socket_option(option, self) + } else if level == ecx.eval_libc_i32("IPPROTO_IP") { + let socket = self.socket.borrow(); + ecx.get_ip_socket_option(option, socket2::SockRef::from(&*socket)) + } else { + throw_unsup_format!( + "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \ + are allowed" + ) + } + } + + fn getsockname<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + if !self.is_bound.get() { + // Since Windows returns EINVAL when invoking `getsockname` on + // a socket which hasn't been bound yet, we need to manually + // return an unspecified address here. + + let address = if self.family == socket2::Domain::IPV4 { + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, /* port */ 0)) + } else { + SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::UNSPECIFIED, + /* port */ 0, + /* flowinfo */ 0, + /* scope_id */ 0, + )) + }; + return interp_ok(Ok(address)); + } + + interp_ok(self.socket.borrow().local_addr().map_err(IoError::HostError)) + } + + fn getpeername<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + finish.call(ecx, self.socket.borrow().peer_addr().map_err(IoError::HostError)) + } + + fn set_read_timeout<'tcx>( + self: FileDescriptionRef, + timeout: Option, + ) -> InterpResult<'tcx> { + self.read_timeout.set(timeout); + interp_ok(()) + } + + fn get_read_timeout<'tcx>( + self: FileDescriptionRef, + ) -> InterpResult<'tcx, Option> { + interp_ok(self.read_timeout.get()) + } + + fn set_write_timeout<'tcx>( + self: FileDescriptionRef, + timeout: Option, + ) -> InterpResult<'tcx> { + self.write_timeout.set(timeout); + interp_ok(()) + } + + fn get_write_timeout<'tcx>( + self: FileDescriptionRef, + ) -> InterpResult<'tcx, Option> { + interp_ok(self.write_timeout.get()) + } + + fn with_socket_ref(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result + where + Self: Sized, + { + let socket = self.socket.borrow(); + Ok(cb(socket2::SockRef::from(&*socket))) + } +} + +impl SourceFileDescription for UdpSocket { + fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> { + let mut socket = self.socket.borrow_mut(); + f(&mut *socket) + } + + fn get_readiness_mut(&self) -> RefMut<'_, Readiness> { + self.io_readiness.borrow_mut() + } +} diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs index f203a6833a424..f8c9bed395e7f 100644 --- a/src/tools/miri/src/shims/windows/env.rs +++ b/src/tools/miri/src/shims/windows/env.rs @@ -198,8 +198,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } #[allow(non_snake_case)] - fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn GetCurrentProcessId(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); this.assert_target_os(Os::Windows, "GetCurrentProcessId"); interp_ok(Scalar::from_u32(this.get_pid())) diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index df7a6a0cbd6d5..9051522a030fb 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -9,6 +9,7 @@ use rustc_target::callconv::FnAbi; use rustc_target::spec::Env; use self::shims::windows::handle::{Handle, PseudoHandle}; +use crate::eval::{ExitType, ProgramExitHandler}; use crate::shims::os_str::bytes_to_os_str; use crate::shims::windows::*; use crate::*; @@ -504,7 +505,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(new_ptr, dest)?; } "LocalFree" => { - // FIXME: This does not have a direct test (#3179). let [ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(winapi::HLOCAL) -> winapi::HLOCAL), (link_name, abi, args), @@ -585,7 +585,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "TlsAlloc" => { - // FIXME: This does not have a direct test (#3179). // This just creates a key; Windows does not natively support TLS destructors. // Create key and return it. @@ -597,32 +596,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "TlsGetValue" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *_), (link_name, abi, args), )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "TlsSetValue" => { - // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> winapi::BOOL), (link_name, abi, args), )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`1`). this.write_int(1, dest)?; } "TlsFree" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), (link_name, abi, args), @@ -636,7 +632,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Fiber-local storage - similar to TLS but supports destructors. "FlsAlloc" => { - // FIXME: This does not have a direct test (#3179). // Create key and return it. let [dtor] = &this.check_shim_sig( shim_sig!(extern "system" fn(winapi::PFLS_CALLBACK_FUNCTION) -> u32), @@ -658,32 +653,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "FlsGetValue" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *_), (link_name, abi, args), )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "FlsSetValue" => { - // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> winapi::BOOL), (link_name, abi, args), )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`1`). this.write_int(1, dest)?; } "FlsFree" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), (link_name, abi, args), @@ -702,7 +694,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "IsThreadAFiber" => { - // FIXME: This does not have a direct test (#3179). let [] = &this.check_shim_sig( shim_sig!(extern "system" fn() -> winapi::BOOL), (link_name, abi, args), @@ -966,8 +957,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { (link_name, abi, args), )?; // Windows technically uses u32, but we unify everything to a Unix-style i32. - let code = this.read_scalar(code)?.to_i32()?; - throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false }); + let exit_code = this.read_scalar(code)?.to_i32()?; + // Initialize the at-exit state machine. + this.replace_active_thread_stack({ + let mut state_machine = + ProgramExitHandler::new(exit_code, ExitType::ExitCalled); + Box::new(move |m| state_machine.on_stack_empty(m)) + }); + // We "jumped" to the exit handling. + return interp_ok(EmulateItemResult::AlreadyJumped); } "SystemFunction036" => { // used by getrandom 0.1 @@ -983,7 +981,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_bool(true), dest)?; } "ProcessPrng" => { - // FIXME: This does not have a direct test (#3179). // used by `std` let [ptr, len] = &this.check_shim_sig( shim_sig!(extern "system" fn(*_, usize) -> winapi::BOOL), @@ -1039,10 +1036,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { (link_name, abi, args), )?; this.read_target_isize(console)?; - // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std - this.deref_pointer(buffer_info)?; + this.read_pointer(buffer_info)?; // Indicate an error. - // FIXME: we should set last_error, but to what? + this.set_last_error(io::ErrorKind::Unsupported)?; this.write_null(dest)?; } "GetStdHandle" => { diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index da9247c844c4b..4655b2dac2bf3 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -231,8 +231,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ))) } - fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn GetStdHandle(&self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let which = this.read_scalar(which)?.to_i32()?; let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?; diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs similarity index 64% rename from src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs rename to src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs index b5eec2db201cb..2b258c2970a59 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs @@ -1,6 +1,6 @@ //@ignore-target: windows # No pthreads on Windows -use std::{mem, ptr}; +use std::{mem, ptr, thread}; pub type Key = libc::pthread_key_t; @@ -16,6 +16,10 @@ unsafe fn dtor(_ptr: *mut u8) {} fn main() { unsafe { let key = create(dtor); - libc::pthread_setspecific(key, ptr::without_provenance(1)); + + // Main thread never calls these dtors, so set the key in another thread. + thread::spawn(move || { + libc::pthread_setspecific(key, ptr::without_provenance(1)); + }); } } diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr similarity index 88% rename from src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr rename to src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr index 32e92319cdf35..08ec4a6b67b31 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr @@ -1,11 +1,12 @@ error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C" - --> tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs:LL:CC + --> tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs:LL:CC | LL | assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` = note: this error occurred while pushing a call frame onto an empty stack = note: the span indicates which code caused the function to be called, but may not be the literal call site diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs b/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs new file mode 100644 index 0000000000000..e293961d50097 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs @@ -0,0 +1,12 @@ +//! Regression test for https://github.com/rust-lang/miri/issues/2629: +//! If we fail to yield from the main thread, we miss the UB here. +//@compile-flags: -Zmiri-fixed-schedule +//@revisions: with_exit without_exit + +fn main() { + std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + //~^ERROR: Undefined Behavior + if cfg!(with_exit) { + std::process::exit(0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr b/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr new file mode 100644 index 0000000000000..4608f64203cf3 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: entering unreachable code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` +note: the current function got called indirectly due to this code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr b/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr new file mode 100644 index 0000000000000..4608f64203cf3 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: entering unreachable code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` +note: the current function got called indirectly due to this code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs index c25684af50902..12ce5538055b3 100644 --- a/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs +++ b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs @@ -19,5 +19,7 @@ thread_local! { } fn main() { - NOISY.with(|_| ()); + std::thread::spawn(|| { + NOISY.with(|_| ()); + }); } diff --git a/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs index 32fd8318f7963..b6a0526987802 100644 --- a/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs +++ b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs @@ -17,5 +17,7 @@ thread_local! { } fn main() { - NOISY.with(|_| ()); + std::thread::spawn(|| { + NOISY.with(|_| ()); + }); } diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs deleted file mode 100644 index c557c35c9dea8..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64); -} - -fn main() { - let size = unsafe { miri_backtrace_size(0) }; - let mut frames = vec![std::ptr::null_mut(); size]; - unsafe { miri_get_backtrace(1, frames.as_mut_ptr()) }; - for frame in frames.iter() { - unsafe { - miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields - } - } -} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr deleted file mode 100644 index e50fd41617a81..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields - --> tests/fail/shims/backtrace/bad-backtrace-decl.rs:LL:CC - | -LL | ... miri_resolve_frame(*frame, 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs deleted file mode 100644 index a398eb1ae0c22..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs +++ /dev/null @@ -1,9 +0,0 @@ -extern "Rust" { - fn miri_resolve_frame(ptr: *mut (), flags: u64); -} - -fn main() { - unsafe { - miri_resolve_frame(std::ptr::null_mut(), 0); //~ ERROR: null pointer - } -} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr deleted file mode 100644 index bb4615dd14b50..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: Undefined Behavior: pointer not dereferenceable: pointer must point to some allocation, but got null pointer - --> tests/fail/shims/backtrace/bad-backtrace-ptr.rs:LL:CC - | -LL | miri_resolve_frame(std::ptr::null_mut(), 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs index 31e3915f3d64f..a33c1f191f530 100644 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs @@ -1,25 +1,13 @@ -#[repr(C)] -struct MiriFrame { - name_len: usize, - filename_len: usize, - lineno: u32, - colno: u32, - fn_ptr: *mut (), -} - -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; -} +#[path = "../../../utils/mod.no_std.rs"] +mod utils; fn main() { unsafe { - let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)]; + let mut buf = vec![std::ptr::null_mut(); utils::miri_backtrace_size(0)]; - miri_get_backtrace(1, buf.as_mut_ptr()); + utils::miri_get_backtrace(1, buf.as_mut_ptr()); // miri_resolve_frame will error from an invalid backtrace before it will from invalid flags - miri_resolve_frame(buf[0], 2); //~ ERROR: unsupported operation: unknown `miri_resolve_frame` flags 2 + utils::miri_resolve_frame(buf[0], 2); //~ ERROR: unsupported operation: unknown `miri_resolve_frame` flags 2 } } diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr index 947c42397309d..85c9ea4f55bb2 100644 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr @@ -1,8 +1,8 @@ error: unsupported operation: unknown `miri_resolve_frame` flags 2 --> tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs:LL:CC | -LL | miri_resolve_frame(buf[0], 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here +LL | utils::miri_resolve_frame(buf[0], 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here | = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support diff --git a/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs new file mode 100644 index 0000000000000..5907fb547dbe3 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs @@ -0,0 +1,34 @@ +// Translated from GenMC's test `wrong/racy/cas-failord-mp`. +// The race on `DATA` only manifests if the CAS fails, and its failure ordering is properly modeled. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicI32 = AtomicI32::new(0); +static mut DATA: i32 = 0; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + DATA = 42; + X.store(1, Release); + }), + spawn_pthread_closure(|| { + if X.compare_exchange(2, 3, Acquire, Relaxed) == Err(1) { + assert!(DATA == 42); //~ ERROR: Undefined Behavior: Non-atomic race + } + }), + ]; + join_pthreads(ids); + } + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr new file mode 100644 index 0000000000000..353ab85f17325 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr @@ -0,0 +1,32 @@ +Running GenMC Verification... +error: Undefined Behavior: Non-atomic race + --> tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC + | +LL | assert!(DATA == 42); + | ^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` + = note: stack backtrace: + 0: miri_start::{closure#1} + at tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC + 1: as std::ops::FnOnce<()>>::call_once + at RUSTLIB/alloc/src/boxed.rs:LL:CC + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC}> + at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC +note: the last function in that backtrace got called indirectly due to this code + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | / libc::pthread_create( +LL | | &raw mut thread_id, +LL | | attr, +LL | | thread_func::, +LL | | Box::into_raw(f) as *mut c_void, +LL | | ) + | |_________^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs index cf20482092ede..073a85d560379 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs @@ -4,10 +4,6 @@ // Two threads race to initialize a key, which is just an index into an array in this test. // The (Acquire, Release) orderings on the compare_exchange prevent any data races for reading from `VALUES[key]`. -// FIXME(genmc): GenMC does not model the failure ordering of compare_exchange currently. -// Miri thus upgrades the success ordering to prevent showing any false data races in cases like this one. -// Once GenMC supports the failure ordering, this test should work without the upgrading. - #![no_main] #[path = "../../../utils/genmc.rs"] diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr index f0c3377623058..bde951866d013 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr @@ -1,31 +1,2 @@ Running GenMC Verification... -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Success ordering 'Release' was upgraded to 'AcqRel' to match failure ordering 'Acquire'. Miri with GenMC might miss bugs related to this memory access. - --> tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - | -LL | match KEY.compare_exchange(KEY_SENTVAL, key, Release, Acquire) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - | - = note: this is on thread `unnamed-ID` - = note: stack backtrace: - 0: get_or_init - at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - 1: miri_start::{closure#0} - at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - 2: as std::ops::FnOnce<()>>::call_once - at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC}> - at tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC -note: the last function in that backtrace got called indirectly due to this code - --> tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC - | -LL | / libc::pthread_create( -LL | | &raw mut thread_id, -LL | | attr, -LL | | thread_func::, -LL | | Box::into_raw(f) as *mut c_void, -LL | | ) - | |_________^ - Verification complete with 2 executions. No errors found. -warning: 1 warning emitted - diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs index 5ef1dd2352d68..33222ed6679c5 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs @@ -30,7 +30,6 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { } // Check that we emit warnings for cases that are not fully supported. - let _ = VALUE.compare_exchange(99, 99, SeqCst, Relaxed); let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); 0 diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr index 59785a2f19d7b..0203748beab7c 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr @@ -1,22 +1,12 @@ Running GenMC Verification... -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'SeqCst', the failure ordering 'Relaxed' is treated like 'SeqCst'. Miri with GenMC might miss bugs related to this memory access. - --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC - | -LL | let _ = VALUE.compare_exchange(99, 99, SeqCst, Relaxed); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Success ordering 'Relaxed' was upgraded to 'SeqCst' to match failure ordering 'SeqCst'. Miri with GenMC might miss bugs related to this memory access. +warning: GenMC does not model spurious failures of `compare_exchange_weak` by default. Miri with GenMC might miss bugs related to spurious failures. --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC | LL | let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - -warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. - --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC | -LL | let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + = help: add `-Zmiri-genmc-weak-cas-spurious-failures` to MIRIFLAGS to explore spurious failures; all retry loops around `compare_exchange_weak` then need to be bounded Verification complete with 1 executions. No errors found. -warning: 3 warnings emitted +warning: 1 warning emitted diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs new file mode 100644 index 0000000000000..010236dbfb2b4 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-genmc-weak-cas-spurious-failures + +// Translated from GenMC's test `correct/synthetic/weak-cas-spurious`. +// Observable spurious failure of a weak CAS (not used in a loop). + +#![no_main] + +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::*; + +static X: AtomicI32 = AtomicI32::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // Either the CAS succeeds, or it fails spuriously and leaves `X` unchanged. + match X.compare_exchange_weak(0, 1, Relaxed, Relaxed) { + Ok(0) => assert!(X.load(Relaxed) == 1), + Err(0) => assert!(X.load(Relaxed) == 0), + _ => std::process::abort(), + } + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr new file mode 100644 index 0000000000000..bde951866d013 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. diff --git a/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs b/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs deleted file mode 100644 index f28e43696f709..0000000000000 --- a/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Regression test for https://github.com/rust-lang/miri/issues/2629 -use std::thread; - -fn main() { - thread::scope(|s| { - s.spawn(|| {}); - }); -} diff --git a/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs new file mode 100644 index 0000000000000..0fa42b4f26926 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs @@ -0,0 +1,63 @@ +//@ignore-target: windows # No pthreads on Windows +//@run-native + +use std::{process, ptr, thread}; + +static mut STATE: i32 = 0; + +unsafe extern "C" fn dtor(ptr: *mut libc::c_void) { + assert_eq!(ptr.addr(), 43); + + // We expect to be called exactly once. + if STATE == 0 { + STATE = 1; + } else { + process::abort(); + } +} + +fn main() { + unsafe { + let dtor = dtor as unsafe extern "C" fn(*mut libc::c_void); + + let mut key: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key, Some(dtor)), 0); + let mut key2: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key2, Some(dtor)), 0); + + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(42)); + assert_eq!(r, 0); + let ptr = libc::pthread_getspecific(key); + assert_eq!(ptr.addr(), 42); + + let t = thread::spawn(move || { + // Set the value the dtor will see. + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(43)); + assert_eq!(r, 0); + }); + t.join().unwrap(); + // This called the dtor. + assert_eq!({ STATE }, 1); + + // A thread that does not set the key, does not call the dtor. + thread::spawn(move || {}).join().unwrap(); + + // Destroying the key means the dtor does not get called. + let t = thread::spawn(move || { + let r = libc::pthread_setspecific(key2, ptr::without_provenance_mut(44)); + assert_eq!(r, 0); + let r = libc::pthread_key_delete(key2); + assert_eq!(r, 0); + }); + t.join().unwrap(); + + // And when we exit, that also does not call the dtor. + let t = thread::spawn(move || { + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(45)); + assert_eq!(r, 0); + + std::process::exit(0); + }); + t.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs new file mode 100644 index 0000000000000..07becf6e28855 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs @@ -0,0 +1,22 @@ +//@ignore-target: windows # No pthreads on Windows +//@run-native + +use std::{process, ptr}; + +unsafe extern "C" fn dtor(_ptr: *mut libc::c_void) { + process::abort(); +} + +// Returning from `main` does not call the destructor (consistent with the behavior on `exit`, +// which is tested in `pthread_key.rs`). +fn main() { + unsafe { + let dtor = dtor as unsafe extern "C" fn(*mut libc::c_void); + + let mut key: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key, Some(dtor)), 0); + + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(42)); + assert_eq!(r, 0); + } +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs similarity index 89% rename from src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs rename to src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs index 6ec4b6e48bc3f..f4090f22654a0 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs @@ -1,9 +1,6 @@ //@ignore-target: windows # No pthreads on Windows +//@run-native //! Test that pthread_key destructors are run in the right order. -//! Note that these are *not* used by actual `thread_local!` on Linux! Those use -//! `destructors::register` from the stdlib instead. In Miri this ends up hitting -//! the fallback path in `guard::key::enable`, which uses a *single* pthread_key -//! to manage a thread-local list of dtors to call. // FIXME(static_mut_refs): use raw pointers instead of references #![allow(static_mut_refs)] diff --git a/src/tools/miri/tests/pass-dep/gettid.rs b/src/tools/miri/tests/pass-dep/gettid.rs index 1466770694117..5dc479e241ce5 100644 --- a/src/tools/miri/tests/pass-dep/gettid.rs +++ b/src/tools/miri/tests/pass-dep/gettid.rs @@ -7,6 +7,7 @@ #![allow(unused_features)] // only used on some targets fn gettid() -> u64 { + #[rustfmt::skip] // FIXME rustfmt is eating comments cfg_select! { any(target_os = "android", target_os = "linux") => gettid_linux_like(), any(target_os = "nto", target_os = "qnx") => unsafe { libc::gettid() as u64 }, diff --git a/src/tools/miri/tests/pass-dep/libc/fs.rs b/src/tools/miri/tests/pass-dep/libc/fs.rs index a5a60de75f6b0..3862fb42bd489 100644 --- a/src/tools/miri/tests/pass-dep/libc/fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/fs.rs @@ -1596,11 +1596,11 @@ fn test_readdir() { let dir_path = utils::prepare("miri_test_libc_readdir"); fs::create_dir(&dir_path).ok(); - // Create test files - let file1 = dir_path.join("file1.txt"); - let file2 = dir_path.join("file2.txt"); - fs::write(&file1, b"content1").unwrap(); - fs::write(&file2, b"content2").unwrap(); + // Create test files & dir + fs::write(dir_path.join("file.txt"), b"content").unwrap(); + fs::create_dir(dir_path.join("dir")).unwrap(); + fs::symlink("file.txt", dir_path.join("link1")).unwrap(); + fs::symlink("dir", dir_path.join("link2")).unwrap(); let c_path = utils::into_c_string(&dir_path); @@ -1616,11 +1616,26 @@ fn test_readdir() { let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; let name = CStr::from_ptr(name_ptr); let name_str = name.to_string_lossy(); + + // Check d_type field, if it exists. + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + let type_ = (*entry_ptr).d_type; + if type_ != libc::DT_UNKNOWN { + match &*name_str { + "." | ".." | "dir" => assert_eq!(type_, libc::DT_DIR), + "file.txt" => assert_eq!(type_, libc::DT_REG), + "link1" | "link2" => assert_eq!(type_, libc::DT_LNK), + _ => panic!("unexpected filename {name_str:?}"), + } + } + } + entries.push(name_str.into_owned()); } errno_check(libc::closedir(dirp)); entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); + assert_eq!(&entries, &[".", "..", "dir", "file.txt", "link1", "link2"]); } // On Linux, also test `readdir64`. @@ -1639,16 +1654,29 @@ fn test_readdir() { let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; let name = CStr::from_ptr(name_ptr); let name_str = name.to_string_lossy(); + + // Check d_type field, if it exists. + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + let type_ = (*entry_ptr).d_type; + if type_ != libc::DT_UNKNOWN { + match &*name_str { + "." | ".." | "dir" => assert_eq!(type_, libc::DT_DIR), + "file.txt" => assert_eq!(type_, libc::DT_REG), + "link1" | "link2" => assert_eq!(type_, libc::DT_LNK), + _ => panic!("unexpected filename {name_str:?}"), + } + } + } + entries.push(name_str.into_owned()); } errno_check(libc::closedir(dirp)); entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); + assert_eq!(&entries, &[".", "..", "dir", "file.txt", "link1", "link2"]); } - fs::remove_file(&file1).unwrap(); - fs::remove_file(&file2).unwrap(); - fs::remove_dir(&dir_path).unwrap(); + fs::remove_dir_all(&dir_path).unwrap(); } fn test_dirfd() { diff --git a/src/tools/miri/tests/pass-dep/libc/random.rs b/src/tools/miri/tests/pass-dep/libc/random.rs index 7c4010f6c0ac6..135eafa1e01ef 100644 --- a/src/tools/miri/tests/pass-dep/libc/random.rs +++ b/src/tools/miri/tests/pass-dep/libc/random.rs @@ -67,11 +67,7 @@ fn test_getrandom() { #[cfg(any(target_os = "freebsd", target_os = "illumos", target_os = "solaris"))] fn test_arc4random_buf() { - // FIXME: Use declaration from libc once lands. - extern "C" { - fn arc4random_buf(buf: *mut libc::c_void, size: libc::size_t); - } let mut buf = [0u8; 5]; - unsafe { arc4random_buf(buf.as_mut_ptr() as _, buf.len()) }; + unsafe { libc::arc4random_buf(buf.as_mut_ptr() as _, buf.len()) }; assert!(buf != [0u8; 5]); } diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs index 628f7f9493567..3032578c62449 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs @@ -300,16 +300,12 @@ fn test_send_nonblock() { // When we didn't have a short write we should still be able to write more. // Ensure the socket is still writable. let readiness = current_epoll_readiness(client_sockfd, EPOLLOUT | EPOLLET); - if cfg!(miri) { - // With Miri we keep the writable readiness until EWOULDBLOCK is returned. - assert_eq!(readiness, EPOLLOUT); - } else { - // On native Linux hosts, the writable readiness is removed when the buffer - // is "almost" full. We can't emulate this with Miri. - // The buffer must not be "almost" full at the first write. - let is_not_first_write = total_written > fill_buf.len(); - assert!(readiness == EPOLLOUT || (is_not_first_write && readiness == 0)); - } + // When a buffer is fairly full, Linux can clear EPOLLOUT even if there is some + // space left. Similarly, Linux can also produce short writes (causing Miri + // to clear EPOLLOUT) even if the next write would work again. + // So, except after the first write, we cannot expect the socket to be writable here. + let is_not_first_write = total_written > fill_buf.len(); + assert!(readiness == EPOLLOUT || (is_not_first_write && readiness == 0)); } } Err(err) if err.kind() == ErrorKind::WouldBlock => break, diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index 4e53a5497e14b..566420ac0d66d 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -9,30 +9,13 @@ mod utils; use std::io::ErrorKind; use std::time::{Duration, Instant}; -use std::{ptr, slice, thread}; +use std::{ptr, thread}; use libc_utils::*; const TEST_BYTES: &[u8] = b"these are some test bytes!"; fn main() { - test_create_close(); - test_create_close_tcp(); - test_bind_ipv4(); - test_bind_ipv4_reuseaddr(); - test_set_reuseaddr_invalid_len(); - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" - ))] - { - test_bind_ipv4_nosigpipe(); - test_set_nosigpipe_invalid_len(); - } - test_bind_ipv4_invalid_addr_len(); - test_bind_ipv6(); test_bind_twice(); test_bind_connected(); test_bind_listening(); @@ -49,12 +32,7 @@ fn main() { test_readv(); test_writev(); - test_getsockname_ipv4(); - test_getsockname_ipv4_random_port(); - test_getsockname_ipv4_unbound(); test_getsockname_ipv4_connect(); - test_getsockname_ipv6(); - test_getsockname_ipv6_unbound(); test_getpeername_ipv4(); test_getpeername_ipv6(); @@ -63,8 +41,6 @@ fn main() { test_shutdown_readable_after_write_close(); test_shutdown_writable_after_read_close(); - test_getsockopt_truncate(); - test_sockopt_sndtimeo(); test_sockopt_rcvtimeo(); test_sockopt_reuseaddr(); @@ -72,170 +48,6 @@ fn main() { test_unblock_after_socket_close(); } -/// Test creating a socket and then closing it afterwards. -fn test_create_close() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; - - // Ensure that socket is initially blocking. - assert_eq!(flags & libc::O_NONBLOCK, 0); - - unsafe { errno_check(libc::close(sockfd)) }; -} - -/// Test creating a socket and then closing it afterwards but we explicitly -/// specify that the TCP protocol should be used. -fn test_create_close_tcp() { - let sockfd = unsafe { - errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, libc::IPPROTO_TCP)).unwrap() - }; - - let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; - - // Ensure that socket is initially blocking. - assert_eq!(flags & libc::O_NONBLOCK, 0); - - unsafe { errno_check(libc::close(sockfd)) }; -} - -fn test_bind_ipv4() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - -/// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket. -fn test_bind_ipv4_reuseaddr() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int).unwrap(); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - -/// Tests setting the `SO_REUSEADDR` socket option but with an invalid length. -fn test_set_reuseaddr_invalid_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Value should be of type `libc::c_int` which has size 4 bytes. - // By providing an u16 of size 2 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u16).unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - - // By providing an u64 of size 8 bytes the behavior differs between native hosts and Miri. - let result = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64); - match result { - Err(err) => { - // Check that this is the right error. - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - } - Ok(_) => { - // Some native hosts just ignore too large inputs and only look at a prefix. - // On Miri we require an exact size. - assert!(!cfg!(miri)); - } - } -} - -#[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" -))] -/// Tests binding after the `SO_NOSIGPIPE` socket option has been set on the newly created socket. -/// That flag only exists on BSD-like OSes. -fn test_bind_ipv4_nosigpipe() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1 as libc::c_int).unwrap(); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - -#[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" -))] -/// Tests setting the `SO_NOSIGPIPE` socket option but with an invalid length. -fn test_set_nosigpipe_invalid_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Value should be of type `libc::c_int` which has size 4 bytes. - // By providing a u16 of size 2 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u16).unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); -} - -/// Tests binding an IPv4 socket with an IPv4 address but the addrlen argument -/// has the wrong size. -fn test_bind_ipv4_invalid_addr_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - // A too small size is invalid. - let err = unsafe { - errno_result(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - (size_of::() - 1) as libc::socklen_t, - )) - .unwrap_err() - }; - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - - if cfg!(miri) { - // A too big size is also invalid. Some native hosts (e.g. Linux) - // allow too big sizes, so we skip this test on native hosts: - // - let err = unsafe { - errno_result(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - (size_of::() + 1) as libc::socklen_t, - )) - .unwrap_err() - }; - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - } -} - -fn test_bind_ipv6() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in6).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - /// Test that invoking `bind` on an already bound TCP socket /// returns EINVAL. fn test_bind_twice() { @@ -617,83 +429,6 @@ fn test_writev() { assert_eq!(&buffer, TEST_BYTES); } -/// Test the `getsockname` syscall on an IPv4 socket which is bound. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to. -fn test_getsockname_ipv4() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 6789); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - assert_eq!(addr.sin_port, sock_addr.sin_port); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - -/// Test the `getsockname` syscall on an IPv4 socket which is bound -/// but the port was zero. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to but the port should be non-zero. -fn test_getsockname_ipv4_random_port() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Use zero-port to let the OS choose a free port to bind to. - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - // The bound port must not be the zero port. - assert!(sock_addr.sin_port > 0); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - -/// Test the `getsockname` syscall on an IPv4 socket which is not bound. -/// The `getsockname` syscall should return 0.0.0.0:0 -fn test_getsockname_ipv4_unbound() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - // Libc representation of an unspecified IPv4 address with zero port. - let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - assert_eq!(addr.sin_port, sock_addr.sin_port); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - /// Test the `getsockname` syscall on a connected IPv4 socket. fn test_getsockname_ipv4_connect() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); @@ -718,62 +453,6 @@ fn test_getsockname_ipv4_connect() { assert!(sock_addr.sin_port > 0); } -/// Test the `getsockname` syscall on an IPv6 socket which is bound. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to. -fn test_getsockname_ipv6() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in6).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin6_family, sock_addr.sin6_family); - assert_eq!(addr.sin6_port, sock_addr.sin6_port); - assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); - assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); - assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); -} - -/// Test the `getsockname` syscall on an IPv6 socket which is not bound. -/// The `getsockname` syscall should return [::]:0 with flowinfo and scope id -/// of zero. -fn test_getsockname_ipv6_unbound() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - - let (_, sock_addr) = - net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - // Libc representation of an unspecified IPv6 address with zero port, flowinfo and scope id. - let addr = net::sock_addr_full_ipv6( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - /* port */ 0, - /* flowinfo */ 0, - /* scope_id */ 0, - ); - - assert_eq!(addr.sin6_family, sock_addr.sin6_family); - assert_eq!(addr.sin6_port, sock_addr.sin6_port); - assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); - assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); - assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); -} - /// Test the `getpeername` syscall on an IPv4 socket. /// For a connected socket, the `getpeername` syscall should /// return the same address as the socket was connected to. @@ -916,70 +595,6 @@ fn test_shutdown_writable_after_read_close() { server_thread.join().unwrap(); } -/// Test that the value gets silently truncated when a too small -/// length is provided and that the length gets reduced when the value -/// is smaller than the provided length. -fn test_getsockopt_truncate() { - let (sockfd, _) = net::make_listener_ipv4().unwrap(); - - // Set the read timeout for the socket. - // We use a multiple of 4ms for the `usec` since Linux seems to do rounding. - let new_timeout = libc::timeval { tv_sec: 123, tv_usec: 40_000 }; - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, new_timeout).unwrap(); - - let mut option_value = std::mem::MaybeUninit::<[u8; 5]>::zeroed(); - // The actual `timeval` length is more than 5 bytes. - let mut short_option_len = 5 as libc::socklen_t; - assert!(short_option_len < size_of::() as libc::socklen_t); - - let timeout = - net::getsockopt::(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO).unwrap(); - // Ensure that we get the same value back as we just set. - assert_eq!(timeout.tv_sec, new_timeout.tv_sec); - assert_eq!(timeout.tv_usec, new_timeout.tv_usec); - - errno_result(unsafe { - libc::getsockopt( - sockfd, - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - option_value.as_mut_ptr().cast(), - &mut short_option_len, - ) - }) - .unwrap(); - // Ensure that the size wasn't changed. - assert_eq!(short_option_len, 5); - let truncated_timeout = unsafe { option_value.assume_init() }; - - unsafe { - let timeout_ptr = (&new_timeout) as *const libc::timeval as *const u8; - // Assert that the value was silently truncated. - assert_eq!(&truncated_timeout, slice::from_raw_parts(timeout_ptr, 5)); - } - - let mut option_value = std::mem::MaybeUninit::::zeroed(); - // The actual length is smaller than this. - let mut long_option_len = (size_of::() + 2) as libc::socklen_t; - - errno_result(unsafe { - libc::getsockopt( - sockfd, - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - option_value.as_mut_ptr().cast(), - &mut long_option_len, - ) - }) - .unwrap(); - // Ensure that the size was shortened to the actual length. - assert_eq!(long_option_len, size_of::() as libc::socklen_t); - // The returned timeout should be the same value as we just set. - let untruncated_timeout = unsafe { option_value.assume_init() }; - assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec); - assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec); -} - /// Test setting and getting the SO_SNDTIMEO socket option. /// Also test that writes don't block indefinitely when we /// have a nonzero timeout. diff --git a/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs new file mode 100644 index 0000000000000..24cb05d9ffaf5 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs @@ -0,0 +1,31 @@ +//@only-target: linux android illumos +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; + +use libc_utils::epoll::*; +use libc_utils::*; + +fn main() { + test_epoll_udp_socket(); +} + +/// Test creating a non-blocking UDP socket and adding it to an epoll instance. +/// The socket should be writable. +fn test_epoll_udp_socket() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change socket to be non-blocking. + errno_check(libc::fcntl(sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Add socket with READABLE and WRITABLE interest to epoll. + epoll_ctl_add(epfd, sockfd, EPOLLIN | EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + // The socket should always be writable. + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: sockfd }]); +} diff --git a/src/tools/miri/tests/pass-dep/libc/socket-udp.rs b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs new file mode 100644 index 0000000000000..6bdfb2e72b3cc --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs @@ -0,0 +1,100 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use libc_utils::*; + +fn main() { + test_getsockname_ipv4_connect(); + + test_getpeername_ipv4(); + test_getpeername_ipv6(); +} + +/// Test the `getsockname` syscall on a IPv4 UDP socket for which the default +/// destination address has been set using `connect` but which has not been +/// explicitly bound to a local address. +/// The socket should be implicitly bound, resulting in a non-unspecified address +/// with a non-zero port being returned. +fn test_getsockname_ipv4_connect() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + + // Destiniation address of the UDP socket. + // There is no deeper meaning behind choice of the address and the port. + let addr = net::sock_addr_ipv4([127, 0, 1, 3], 7564); + + net::connect_ipv4(sockfd, addr).unwrap(); + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // We want to ensure that the local address is not the unspecified address. + // Because the bound address could be of any local interface, we cannot + // test for localhost. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + assert!(sock_addr.sin_port > 0); +} + +/// Test `getpeername` on an IPv4 socket. +/// For a socket whose default destination has been set using `connect`, +/// this should return the address of the specified default destination. +/// Otherwise, errno should be set to ENOTCONN. +fn test_getpeername_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + + // When no default destination has been set using `connect`, + // `getpeername` should fail and set errno to ENOTCONN. + let err = net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::ENOTCONN)); + + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 1234); + // Explicitly set default destination address. + net::connect_ipv4(sockfd, addr).unwrap(); + + let (_, dest_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, dest_addr.sin_family); + assert_eq!(addr.sin_port, dest_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, dest_addr.sin_addr.s_addr); +} + +/// Test `getpeername` on an IPv6 socket. +/// For a socket whose default destination has been set using `connect`, +/// this should return the address of the specified default destination. +/// Otherwise, errno should be set to ENOTCONN. +fn test_getpeername_ipv6() { + let sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0)).unwrap() }; + + // When no default destination has been set using `connect`, + // `getpeername` should fail and set errno to ENOTCONN. + let err = net::sockname_ipv6(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::ENOTCONN)); + + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); + // Explicitly set default destination address. + net::connect_ipv6(sockfd, addr).unwrap(); + + let (_, dest_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin6_family, dest_addr.sin6_family); + assert_eq!(addr.sin6_port, dest_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, dest_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, dest_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, dest_addr.sin6_addr.s6_addr); +} diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs index 0826db7e94a19..034236297651a 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No libc socket on Windows //@compile-flags: -Zmiri-isolation-error=warn-nobacktrace +//@revisions: tcp udp use std::io::ErrorKind; @@ -7,9 +8,14 @@ use std::io::ErrorKind; mod libc_utils; use libc_utils::*; +const SOCKET_TYPE: libc::c_int = cfg_select! { + tcp => libc::SOCK_STREAM, + udp => libc::SOCK_DGRAM, +}; + fn main() { unsafe { - let err = errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap_err(); + let err = errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap_err(); assert_eq!(err.kind(), ErrorKind::PermissionDenied); // check that it is the right kind of `PermissionDenied` assert_eq!(err.raw_os_error(), Some(libc::EACCES)); diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.stderr b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.tcp.stderr similarity index 100% rename from src/tools/miri/tests/pass-dep/libc/socket-with-isolation.stderr rename to src/tools/miri/tests/pass-dep/libc/socket-with-isolation.tcp.stderr diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr new file mode 100644 index 0000000000000..f7cfb6138cdcc --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr @@ -0,0 +1,4 @@ +warning: `socket` was made to return an error due to isolation + +warning: 1 warning emitted + diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs new file mode 100644 index 0000000000000..a8ed32066d2b7 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -0,0 +1,420 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@revisions: tcp udp +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use std::slice; + +use libc_utils::*; + +// This file contains tests which are executed with different socket types (e.g., TCP, UDP, ...). +// Every socket type has its own revision and based on that the `SOCKET_TYPE` and `SOCKET_PROTOCOL` +// constants get set. + +cfg_select! { + tcp => { + const SOCKET_TYPE: libc::c_int = libc::SOCK_STREAM; + const SOCKET_PROTOCOL: libc::c_int = libc::IPPROTO_TCP; + } + udp => { + const SOCKET_TYPE: libc::c_int = libc::SOCK_DGRAM; + const SOCKET_PROTOCOL: libc::c_int = libc::IPPROTO_UDP; + } +} + +fn main() { + test_create_close(); + test_create_close_with_protocol(); + + test_bind_ipv4(); + test_bind_ipv4_invalid_addr_len(); + test_bind_ipv4_reuseaddr(); + test_set_reuseaddr_invalid_len(); + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" + ))] + { + test_bind_ipv4_nosigpipe(); + test_set_nosigpipe_invalid_len(); + } + test_bind_ipv6(); + test_bind_wrong_address_family(); + + test_getsockopt_truncate(); + + test_getsockname_ipv4(); + test_getsockname_ipv4_random_port(); + test_getsockname_ipv4_unbound(); + test_getsockname_ipv6(); + test_getsockname_ipv6_unbound(); +} + +/// Test creating a socket and then closing it afterwards. +fn test_create_close() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; + + // Ensure that socket is initially blocking. + assert_eq!(flags & libc::O_NONBLOCK, 0); + + unsafe { errno_check(libc::close(sockfd)) }; +} + +/// Test creating a socket and then closing it afterwards but we explicitly +/// specify which protocol the socket should use. +fn test_create_close_with_protocol() { + let sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, SOCKET_PROTOCOL)).unwrap() }; + + let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; + + // Ensure that socket is initially blocking. + assert_eq!(flags & libc::O_NONBLOCK, 0); + + unsafe { errno_check(libc::close(sockfd)) }; +} + +/// Test binding a newly created socket to an IPv4 address. +fn test_bind_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Tests binding an IPv4 socket with an IPv4 address but the addrlen argument +/// has the wrong size. +fn test_bind_ipv4_invalid_addr_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + // A too small size is invalid. + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + (size_of::() - 1) as libc::socklen_t, + )) + .unwrap_err() + }; + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + + if cfg!(miri) { + // A too big size is also invalid. Some native hosts (e.g. Linux) + // allow too big sizes, so we skip this test on native hosts: + // + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + (size_of::() + 1) as libc::socklen_t, + )) + .unwrap_err() + }; + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + } +} + +/// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket. +fn test_bind_ipv4_reuseaddr() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int).unwrap(); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Tests setting the `SO_REUSEADDR` socket option but with an invalid length. +fn test_set_reuseaddr_invalid_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Value should be of type `libc::c_int` which has size 4 bytes. + // By providing an u16 of size 2 bytes we trigger an invalid length error. + let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u16).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + + // By providing an u64 of size 8 bytes the behavior differs between native hosts and Miri. + let result = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64); + match result { + Err(err) => { + // Check that this is the right error. + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + } + Ok(_) => { + // Some native hosts just ignore too large inputs and only look at a prefix. + // On Miri we require an exact size. + assert!(!cfg!(miri)); + } + } +} + +#[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] +/// Tests binding after the `SO_NOSIGPIPE` socket option has been set on the newly created socket. +/// That flag only exists on BSD-like OSes. +fn test_bind_ipv4_nosigpipe() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1 as libc::c_int).unwrap(); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +#[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] +/// Tests setting the `SO_NOSIGPIPE` socket option but with an invalid length. +fn test_set_nosigpipe_invalid_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Value should be of type `libc::c_int` which has size 4 bytes. + // By providing a u16 of size 2 bytes we trigger an invalid length error. + let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u16).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); +} + +/// Test binding a newly created socket to an IPv6 address. +fn test_bind_ipv6() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in6).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Test binding a newly created IPv6 UDP socket to an IPv4 address. +fn test_bind_wrong_address_family() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )) + .unwrap_err() + }; + + if cfg!(any(target_os = "linux", target_os = "android")) || cfg!(all(udp, target_os = "macos")) + { + // For both UDP and TCP sockets Linux and Android targets return EINVAL + // instead of EAFNOSUPPORT. However, for UDP sockets macOS also returns + // EINVAL while for TCP sockets macOS returns EAFNOSUPPORT. + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)) + } else { + assert_eq!(err.raw_os_error(), Some(libc::EAFNOSUPPORT)) + } +} + +/// Test that the value gets silently truncated when a too small +/// length is provided and that the length gets reduced when the value +/// is smaller than the provided length. +fn test_getsockopt_truncate() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + // Set the read timeout for the socket. + // We use a multiple of 4ms for the `usec` since Linux seems to do rounding. + let new_timeout = libc::timeval { tv_sec: 123, tv_usec: 40_000 }; + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, new_timeout).unwrap(); + + let mut option_value = std::mem::MaybeUninit::<[u8; 5]>::zeroed(); + // The actual `timeval` length is more than 5 bytes. + let mut short_option_len = 5 as libc::socklen_t; + assert!(short_option_len < size_of::() as libc::socklen_t); + + let timeout = + net::getsockopt::(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO).unwrap(); + // Ensure that we get the same value back as we just set. + assert_eq!(timeout.tv_sec, new_timeout.tv_sec); + assert_eq!(timeout.tv_usec, new_timeout.tv_usec); + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + option_value.as_mut_ptr().cast(), + &mut short_option_len, + ) + }) + .unwrap(); + // Ensure that the size wasn't changed. + assert_eq!(short_option_len, 5); + let truncated_timeout = unsafe { option_value.assume_init() }; + + unsafe { + let timeout_ptr = (&new_timeout) as *const libc::timeval as *const u8; + // Assert that the value was silently truncated. + assert_eq!(&truncated_timeout, slice::from_raw_parts(timeout_ptr, 5)); + } + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is smaller than this. + let mut long_option_len = (size_of::() + 2) as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + option_value.as_mut_ptr().cast(), + &mut long_option_len, + ) + }) + .unwrap(); + // Ensure that the size was shortened to the actual length. + assert_eq!(long_option_len, size_of::() as libc::socklen_t); + // The returned timeout should be the same value as we just set. + let untruncated_timeout = unsafe { option_value.assume_init() }; + assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec); + assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec); +} + +/// Test the `getsockname` syscall on an IPv4 socket which is bound. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to. +fn test_getsockname_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 6789); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_eq!(addr.sin_port, sock_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv4 socket which is bound +/// but the port was zero. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to but the port should be non-zero. +fn test_getsockname_ipv4_random_port() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Use zero-port to let the OS choose a free port to bind to. + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + // The bound port must not be the zero port. + assert!(sock_addr.sin_port > 0); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv4 socket which is not bound. +/// The `getsockname` syscall should return 0.0.0.0:0 +fn test_getsockname_ipv4_unbound() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // Libc representation of an unspecified IPv4 address with zero port. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_eq!(addr.sin_port, sock_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv6 socket which is bound. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to. +fn test_getsockname_ipv6() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in6).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin6_family, sock_addr.sin6_family); + assert_eq!(addr.sin6_port, sock_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); +} + +/// Test the `getsockname` syscall on an IPv6 socket which is not bound. +/// The `getsockname` syscall should return [::]:0 with flowinfo and scope id +/// of zero. +fn test_getsockname_ipv6_unbound() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + + let (_, sock_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // Libc representation of an unspecified IPv6 address with zero port, flowinfo and scope id. + let addr = net::sock_addr_full_ipv6( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + /* port */ 0, + /* flowinfo */ 0, + /* scope_id */ 0, + ); + + assert_eq!(addr.sin6_family, sock_addr.sin6_family); + assert_eq!(addr.sin6_port, sock_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs index a3060abc39402..23e84b67308ef 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs @@ -22,9 +22,9 @@ fn func_c() -> Box<[*mut ()]> { #[inline(never)] fn func_d() -> Box<[*mut ()]> { unsafe { - let count = miri_backtrace_size(0); + let count = utils::miri_backtrace_size(0); let mut buf = vec![std::ptr::null_mut(); count]; - miri_get_backtrace(1, buf.as_mut_ptr()); + utils::miri_get_backtrace(1, buf.as_mut_ptr()); buf.into() } } @@ -33,20 +33,20 @@ fn main() { let mut seen_main = false; let frames = func_a(); for frame in frames.iter() { - let miri_frame = unsafe { miri_resolve_frame(*frame, 1) }; + let miri_frame = unsafe { utils::miri_resolve_frame(*frame, 1) }; - let mut name = vec![0; miri_frame.name_len]; - let mut filename = vec![0; miri_frame.filename_len]; + let mut name = vec![0u8; miri_frame.name_len]; + let mut filename = vec![0u8; miri_frame.filename_len]; unsafe { - miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr()); + utils::miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr()); } let name = String::from_utf8(name).unwrap(); let filename = String::from_utf8(filename).unwrap(); if name == "func_a" { - assert_eq!(func_a as *mut (), miri_frame.fn_ptr); + assert_eq!(func_a as *mut std::ffi::c_void, miri_frame.fn_ptr); } // Print every frame to stderr. @@ -66,19 +66,5 @@ fn main() { // This goes at the bottom of the file so that we can change it // without disturbing line numbers of the functions in the backtrace. -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; - fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8); -} - -#[derive(Debug)] -#[repr(C)] -struct MiriFrame { - name_len: usize, - filename_len: usize, - lineno: u32, - colno: u32, - fn_ptr: *mut (), -} +#[path = "../../utils/mod.no_std.rs"] +mod utils; diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index 70509b2827270..e78c49457280f 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -98,16 +98,7 @@ fn test_abi_cfi_encoding() { pub x: i32, pub y: i32, } - test_abi_compat( - CfiEncoded1 { - x: 2, - y: 6, - }, - CfiEncoded2 { - x: 2, - y: 6, - }, - ); + test_abi_compat(CfiEncoded1 { x: 2, y: 6 }, CfiEncoded2 { x: 2, y: 6 }); } fn main() { diff --git a/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs index 21c47e73e8c1b..877016ab41704 100644 --- a/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs +++ b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics, portable_simd)] -use std::intrinsics::simd::simd_relaxed_fma; use std::intrinsics::fmuladd; +use std::intrinsics::simd::simd_relaxed_fma; use std::simd::prelude::*; #[path = "../../utils/mod.rs"] diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 49e382cc29567..cab0e4c00ba1e 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,6 +1,6 @@ //@ignore-target: freebsd # we don't have the shims this needs here //@compile-flags: -Zmiri-disable-isolation -//@run-native +//@run-native: ignore-output use std::env; fn main() { diff --git a/src/tools/miri/tests/pass/shims/socket-udp.rs b/src/tools/miri/tests/pass/shims/socket-udp.rs new file mode 100644 index 0000000000000..bd4e8aee28321 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-udp.rs @@ -0,0 +1,89 @@ +//@ignore-target: windows # No socket support on Windows +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +use std::net::UdpSocket; +use std::time::Duration; + +fn main() { + test_create_ipv4(); + test_create_ipv6(); + test_connect(); + test_sockopt_ttl(); + test_sockopt_read_timeout(); + test_sockopt_write_timeout(); +} + +fn test_create_ipv4() { + let _socket_ipv4 = UdpSocket::bind("127.0.0.1:0").unwrap(); +} + +fn test_create_ipv6() { + let _socket_ipv6 = UdpSocket::bind("[::1]:0").unwrap(); +} + +/// Test setting the default destination address using `connect`. +fn test_connect() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + socket.connect("127.0.1.1:8080").unwrap() +} + +// Test setting and reading the TTL socket option. +fn test_sockopt_ttl() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + socket.set_ttl(16).unwrap(); + assert_eq!(socket.ttl().unwrap(), 16); +} + +/// Test setting and reading the SNDTIMEO socket option. +/// This also tests that a read won't block indefinitely +/// when the read timeout is set to [`Some`] duration. +fn test_sockopt_read_timeout() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + + // By default, reads on blocking sockets should block indefinitely. + assert_eq!(socket.read_timeout().unwrap(), None); + + let short_read_timeout = Some(Duration::from_millis(40)); + socket.set_read_timeout(short_read_timeout).unwrap(); + assert_eq!(socket.read_timeout().unwrap(), short_read_timeout); + + /* + TODO: Once UDP sockets support reading. + + let mut buffer = [0u8; 128]; + // This should not block indefinitely and instead return EAGAIN/EWOULDBLOCK. + let err = socket.read(&mut buffer).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::WouldBlock); + */ +} + +/// Test setting and reading the RCVTIMEO socket option. +/// This also tests that a write won't block indefinitely when +/// the write timeout is set to [`Some`] duration. +fn test_sockopt_write_timeout() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + + // By default, writes on blocking sockets should block indefinitely. + assert_eq!(socket.write_timeout().unwrap(), None); + + let short_write_timeout = Some(Duration::from_millis(40)); + socket.set_write_timeout(short_write_timeout).unwrap(); + assert_eq!(socket.write_timeout().unwrap(), short_write_timeout); + + /* + TODO: Once UDP sockets support writing. + + let fill_buffer = [1u8; 1024]; + loop { + match socket.write_all(&fill_buffer) { + Ok(_) => { /* continue to fill up buffer */ } + // When we get an EAGAIN/EWOULDBLOCK when writing into a blocking socket, + // we know it's because of the write timeout exceeding because the write + // buffer is full. + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } + */ +} diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs index 11fa75962a954..9462a7abca89b 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+aes,+vaes,+avx512f -//@run-native +//@run-native: ignore-output use core::mem::transmute; #[cfg(target_arch = "x86")] diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index e96845ff09163..89f882a44167e 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512bitalg,+avx512vpopcntdq,+avx512vnni,+avx512vbmi -//@run-native +//@run-native: ignore-output #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs index ef0fd62da5f3f..e259814ff582d 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+gfni,+avx512f -//@run-native +//@run-native: ignore-output // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs index 22bd697cfd0a1..596a8f5a45d54 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs @@ -3,7 +3,7 @@ //@only-target: x86_64 i686 //@[avx512]compile-flags: -C target-feature=+vpclmulqdq,+avx512f //@[avx]compile-flags: -C target-feature=+vpclmulqdq,+avx2 -//@run-native +//@run-native: ignore-output // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but diff --git a/src/tools/miri/tests/pass/threadleak_ignored.rs b/src/tools/miri/tests/pass/threadleak_ignored.rs index e43b58cdbd158..96547625b8bdb 100644 --- a/src/tools/miri/tests/pass/threadleak_ignored.rs +++ b/src/tools/miri/tests/pass/threadleak_ignored.rs @@ -10,7 +10,7 @@ use std::cell::RefCell; struct LoudDrop(i32); impl Drop for LoudDrop { fn drop(&mut self) { - eprintln!("Dropping {}", self.0); + panic!("Dropping {}", self.0); } } @@ -19,8 +19,6 @@ thread_local! { } fn main() { - X.with(|x| *x.borrow_mut() = Some(LoudDrop(0))); - // Set up a channel so that we can learn when the other thread initialized `X` // (so that we are sure there is something to drop). let (send, recv) = std::sync::mpsc::channel::<()>(); diff --git a/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr b/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr b/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs b/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs deleted file mode 100644 index 4fc1e2c7b678b..0000000000000 --- a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs +++ /dev/null @@ -1,43 +0,0 @@ -//@only-target: darwin - -use std::thread; - -extern "C" { - fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8); -} - -fn register(f: F) -where - F: FnOnce() + 'static, -{ - // This will receive the pointer passed into `_tlv_atexit`, which is the - // original `f` but boxed up. - unsafe extern "C" fn run(ptr: *mut u8) - where - F: FnOnce() + 'static, - { - let f = unsafe { Box::from_raw(ptr as *mut F) }; - f() - } - - unsafe { - _tlv_atexit(run::, Box::into_raw(Box::new(f)) as *mut u8); - } -} - -fn main() { - thread::spawn(|| { - register(|| println!("dtor 2")); - register(|| println!("dtor 1")); - println!("exiting thread"); - }) - .join() - .unwrap(); - - println!("exiting main"); - register(|| println!("dtor 5")); - register(|| { - println!("registering dtor in dtor 3"); - register(|| println!("dtor 4")); - }); -} diff --git a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.in_thread.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/macos_tlv_atexit.stdout rename to src/tools/miri/tests/pass/tls/thread_atexit.in_thread.stdout diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.rs b/src/tools/miri/tests/pass/tls/thread_atexit.rs new file mode 100644 index 0000000000000..942938fcdfd62 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.rs @@ -0,0 +1,85 @@ +//! Test the Linux and macOS APIs for thread exit callbacks. +//@only-target: linux darwin +//@ignore-target: android +//@run-native +//@revisions: in_thread with_exit without_exit + +use std::{ffi, thread}; + +fn register(f: F) +where + F: FnOnce() + 'static, +{ + // This will receive the pointer passed into `_tlv_atexit`, which is the + // original `f` but boxed up. + unsafe extern "C" fn run(ptr: *mut ffi::c_void) + where + F: FnOnce() + 'static, + { + let f = unsafe { Box::from_raw(ptr as *mut F) }; + f() + } + + cfg_select! { + target_os = "linux" => { + extern "C" { + #[allow(improper_ctypes)] + static __dso_handle: (); + + fn __cxa_thread_atexit_impl( + dtor: unsafe extern "C" fn(*mut ffi::c_void), + arg: *mut ffi::c_void, + dso: *const ffi::c_void, + ) -> ffi::c_int; + } + + let ret = unsafe { + __cxa_thread_atexit_impl( + run::, + Box::into_raw(Box::new(f)) as *mut ffi::c_void, + &raw const __dso_handle as *const ffi::c_void, + ) + }; + assert_eq!(ret, 0); + } + target_vendor = "apple" => { + extern "C" { + fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut ffi::c_void), arg: *mut ffi::c_void); + } + unsafe { _tlv_atexit(run::, Box::into_raw(Box::new(f)) as *mut ffi::c_void) }; + } + } +} + +fn the_test() { + thread::spawn(|| { + register(|| println!("dtor 2")); + register(|| println!("dtor 1")); + println!("exiting thread"); + }) + .join() + .unwrap(); + + register(|| println!("dtor 5")); + register(|| { + println!("registering dtor in dtor 3"); + register(|| println!("dtor 4")); + }); + + println!("exiting main"); + if cfg!(with_exit) { + std::process::exit(0); + } +} + +fn main() { + if cfg!(in_thread) { + return std::thread::spawn(the_test).join().unwrap(); + } + + the_test(); + + if cfg!(with_exit) { + std::process::exit(0); + } +} diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout new file mode 100644 index 0000000000000..89d6ca2593547 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout @@ -0,0 +1,7 @@ +exiting thread +dtor 1 +dtor 2 +exiting main +registering dtor in dtor 3 +dtor 4 +dtor 5 diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout new file mode 100644 index 0000000000000..89d6ca2593547 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout @@ -0,0 +1,7 @@ +exiting thread +dtor 1 +dtor 2 +exiting main +registering dtor in dtor 3 +dtor 4 +dtor 5 diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs index 0c0b9b7efee50..057935f5ec168 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs @@ -1,6 +1,7 @@ //@revisions: stack tree tree_implicit_writes //@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes //@[tree]compile-flags: -Zmiri-tree-borrows +//@[stack]run-native use std::cell::RefCell; use std::thread; @@ -27,10 +28,7 @@ fn check_destructors() { static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } }; } - // We use the same value for both of them, since destructor order differs between Miri on Linux - // (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a - // thread-local linked list of dtors to call), real Linux rustc (which uses - // `__cxa_thread_atexit_impl`), and Miri on Windows. + // We use the same value for both of them, since destructor order differs between platforms. thread::spawn(|| { A.with(|f| { assert_eq!(*f.value.borrow(), 0); diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs similarity index 68% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs index 082a6f17838d5..eb99923508d9f 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs @@ -1,4 +1,8 @@ -//! Check that destructors of main thread thread locals are executed. +//! Check that destructors of main thread thread locals are executed when main returns. +//! Only works on tagets that use something other than pthread keys for the destructors. +//@ignore-target: freebsd illumos solaris android +//@run-native +//@revisions: with_exit without_exit struct Bar; @@ -24,4 +28,7 @@ thread_local!(static FOO: Foo = Foo); fn main() { FOO.with(|_| {}); + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.with_exit.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stdout rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.with_exit.stdout diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout new file mode 100644 index 0000000000000..6160f2726492d --- /dev/null +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout @@ -0,0 +1,2 @@ +Foo dtor +Bar dtor diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs new file mode 100644 index 0000000000000..65fd24d65b7b9 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs @@ -0,0 +1,42 @@ +//! Ensure that we call Windows TLS callbacks in the local crate. +//@only-target: windows +//@revisions: with_exit without_exit +// run-native does not work properly. It randomly sometimes calls the hook 3 times. I guess there's +// sometimes another thread appearing from somewhere. + +// Printing without touching the Rust runtime. +fn print_safe(msg: &str) { + use std::io::Write; + use std::os::windows::io::FromRawHandle; + use std::{ffi, fs, mem}; + + pub const STD_OUTPUT_HANDLE: u32 = 4294967285; + + #[link(name = "kernel32")] + unsafe extern "system" { + fn GetStdHandle(which: u32) -> *mut ffi::c_void; + } + + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + let mut handle = mem::ManuallyDrop::new(fs::File::from_raw_handle(handle)); + handle.write_all(msg.as_bytes()).unwrap(); + } +} + +#[link_section = ".CRT$XLB"] +#[used] // Miri only considers explicitly `#[used]` statics for `lookup_link_section` +pub static CALLBACK: unsafe extern "system" fn(*const (), u32, *const ()) = tls_callback; + +unsafe extern "system" fn tls_callback(_h: *const (), _dw_reason: u32, _pv: *const ()) { + // Do not call into std as it has already been shut down. That means we cannot run natively. + print_safe("in tls_callback\n"); +} + +fn main() { + // Spawn a thread so that we see the hook called twice. + std::thread::spawn(|| {}); + if cfg!(with_exit) { + std::process::exit(0); + } +} diff --git a/src/tools/miri/tests/pass/tls/win_tls_callback.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stdout similarity index 50% rename from src/tools/miri/tests/pass/tls/win_tls_callback.stderr rename to src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stdout index 8479558954456..b415132c9c854 100644 --- a/src/tools/miri/tests/pass/tls/win_tls_callback.stderr +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stdout @@ -1 +1,2 @@ in tls_callback +in tls_callback diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stdout b/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stdout new file mode 100644 index 0000000000000..b415132c9c854 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stdout @@ -0,0 +1,2 @@ +in tls_callback +in tls_callback diff --git a/src/tools/miri/tests/pass/tls/win_tls_callback.rs b/src/tools/miri/tests/pass/tls/win_tls_callback.rs deleted file mode 100644 index a91556829234f..0000000000000 --- a/src/tools/miri/tests/pass/tls/win_tls_callback.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Ensure that we call Windows TLS callbacks in the local crate. -//@only-target: windows -// Calling eprintln in the callback seems to (re-)initialize some thread-local storage -// and then leak the memory allocated for that. Let's just ignore these leaks, -// that's not what this test is about. -//@compile-flags: -Zmiri-ignore-leaks - -#[link_section = ".CRT$XLB"] -#[used] // Miri only considers explicitly `#[used]` statics for `lookup_link_section` -pub static CALLBACK: unsafe extern "system" fn(*const (), u32, *const ()) = tls_callback; - -unsafe extern "system" fn tls_callback(_h: *const (), _dw_reason: u32, _pv: *const ()) { - eprintln!("in tls_callback"); -} - -fn main() {} diff --git a/src/tools/miri/tests/pass/tls/windows-tls.stdout b/src/tools/miri/tests/pass/tls/windows-tls.in_thread.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/windows-tls.stdout rename to src/tools/miri/tests/pass/tls/windows-tls.in_thread.stdout diff --git a/src/tools/miri/tests/pass/tls/windows-tls.rs b/src/tools/miri/tests/pass/tls/windows-tls.rs index 32dd5399a08d0..13e479d2d0fd6 100644 --- a/src/tools/miri/tests/pass/tls/windows-tls.rs +++ b/src/tools/miri/tests/pass/tls/windows-tls.rs @@ -1,7 +1,9 @@ //@only-target: windows # this directly tests windows-only functions +//@revisions: with_exit without_exit in_thread +//@run-native use std::ffi::c_void; -use std::{ptr, thread}; +use std::ptr; pub type BOOL = i32; pub const FALSE: BOOL = 0i32; @@ -25,6 +27,26 @@ extern "system" fn dtor_unreachable(_val: *mut c_void) { unreachable!() } +// Printing without touching the Rust runtime. +fn print_safe(msg: &str) { + use std::io::Write; + use std::os::windows::io::FromRawHandle; + use std::{ffi, fs, mem}; + + pub const STD_OUTPUT_HANDLE: u32 = 4294967285; + + #[link(name = "kernel32")] + unsafe extern "system" { + fn GetStdHandle(which: u32) -> *mut ffi::c_void; + } + + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + let mut handle = mem::ManuallyDrop::new(fs::File::from_raw_handle(handle)); + handle.write_all(msg.as_bytes()).unwrap(); + } +} + fn fls_0_zero_value_does_not_run() { let key = unsafe { FlsAlloc(Some(dtor_unreachable)) }; @@ -36,7 +58,7 @@ fn fls_0_zero_value_does_not_run() { fn fls_1_dtor_simple() { extern "system" fn dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_1_dtor_simple"); + print_safe("fls_1_dtor_simple\n"); // Keys are freed in-order. Without a dtor, the early key's value is not zeroed out. let early_key = val as u32; @@ -60,11 +82,11 @@ fn fls_1_dtor_simple() { fn fls_2_dtor_update_value_ignored() { extern "system" fn early_dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_2.1_early_dtor"); + print_safe("fls_2.1_early_dtor\n"); } extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_2.2_later_dtor"); + print_safe("fls_2.2_later_dtor\n"); // Updating a different fls slot's value doesn't cause their dtor to run, if it already did. let early_key = val as u32; @@ -74,9 +96,12 @@ fn fls_2_dtor_update_value_ignored() { assert_eq!(unsafe { FlsSetValue(early_key, ptr::without_provenance_mut(1)) }, TRUE); - // Registering new fls slots doesn't cause their dtor to run. - let a_new_key_in_dtor = unsafe { FlsAlloc(Some(dtor_unreachable)) }; + // Registering new fls slots in a dtor is always possible, + // but their dtor might not get called. + extern "system" fn new_in_dtor(_val: *mut c_void) {} + let a_new_key_in_dtor = unsafe { FlsAlloc(Some(new_in_dtor)) }; assert_eq!(unsafe { FlsSetValue(a_new_key_in_dtor, ptr::without_provenance_mut(1)) }, TRUE); + assert_eq!(unsafe { FlsGetValue(a_new_key_in_dtor).addr() }, 1); } let early_key = unsafe { FlsAlloc(Some(early_dtor)) }; @@ -90,7 +115,7 @@ fn fls_2_dtor_update_value_ignored() { fn fls_3_dtor_update_value_skipped_ignored() { extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_3_later_dtor"); + print_safe("fls_3_later_dtor\n"); // Updating a different fls slot's value doesn't cause their dtor to run, if it was already skipped. let early_key = val as u32; @@ -108,7 +133,7 @@ fn fls_3_dtor_update_value_skipped_ignored() { fn fls_4_dtor_update_value_used() { extern "system" fn early_dtor(val: *mut c_void) { - println!("fls_4.1_early_dtor"); + print_safe("fls_4.1_early_dtor\n"); // Updating a different fls slot's value affect their dtor, if it hasn't yet run. let later_key = val as u32; @@ -116,7 +141,7 @@ fn fls_4_dtor_update_value_used() { } extern "system" fn later_dtor(_val: *mut c_void) { - println!("fls_4.2_later_dtor"); + print_safe("fls_4.2_later_dtor\n"); } let early_key = unsafe { FlsAlloc(Some(early_dtor)) }; @@ -133,7 +158,7 @@ fn fls_4_dtor_update_value_used() { fn fls_5_dtor_value() { extern "system" fn dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_5_dtor_value"); + print_safe("fls_5_dtor_value\n"); // When the key's dtor run, the key's value equals the destructor argument. let key = val as u32; @@ -147,7 +172,7 @@ fn fls_5_dtor_value() { fn fls_6_no_dtor_does_not_zero_value() { extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_6_later_dtor"); + print_safe("fls_6_later_dtor\n"); // If an earlier fls slot doesn't have a dtor, the key's value is _not_ zeroed out. let early_key = val as u32; @@ -166,7 +191,7 @@ fn fls_6_no_dtor_does_not_zero_value() { fn fls_7_free() { // This dtor is not expected to be called. extern "system" fn dtor_for_freed_key(_val: *mut c_void) { - println!("dtor_for_freed_key"); + print_safe("dtor_for_freed_key\n"); } let key_with_dtor = unsafe { FlsAlloc(Some(dtor_for_freed_key)) }; assert_eq!(unsafe { FlsSetValue(key_with_dtor, ptr::without_provenance_mut(0)) }, TRUE); @@ -190,16 +215,24 @@ fn fls() { fls_7_free(); } -fn main() { +fn the_test() { let key = unsafe { TlsAlloc() }; assert_eq!(unsafe { TlsSetValue(key, ptr::without_provenance_mut(1)) }, TRUE); assert_eq!(unsafe { TlsGetValue(key).addr() }, 1); assert_eq!(unsafe { TlsFree(key) }, TRUE); - thread::spawn(|| { - fls(); - println!("exiting thread"); - }) - .join() - .unwrap(); + fls(); + print_safe("exiting thread\n"); +} + +fn main() { + if cfg!(in_thread) { + return std::thread::spawn(the_test).join().unwrap(); + } + + the_test(); + + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout b/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout new file mode 100644 index 0000000000000..f3034813f224a --- /dev/null +++ b/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout @@ -0,0 +1,9 @@ +exiting thread +fls_1_dtor_simple +fls_2.1_early_dtor +fls_2.2_later_dtor +fls_3_later_dtor +fls_4.1_early_dtor +fls_4.2_later_dtor +fls_5_dtor_value +fls_6_later_dtor diff --git a/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout b/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout new file mode 100644 index 0000000000000..f3034813f224a --- /dev/null +++ b/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout @@ -0,0 +1,9 @@ +exiting thread +fls_1_dtor_simple +fls_2.1_early_dtor +fls_2.2_later_dtor +fls_3_later_dtor +fls_4.1_early_dtor +fls_4.2_later_dtor +fls_5_dtor_value +fls_6_later_dtor diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 33aa92b51af40..2933ac78708f6 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -138,10 +138,24 @@ fn miri_config( }; // Register custom comments. - config.custom_comments.insert("run-native", |parser, _args, span| { - // Just remember that this is present. - parser.set_custom_once("run-native", (), span); - }); + if matches!(mode, Mode::Pass { .. }) { + config.custom_comments.insert("run-native", |parser, args, span| { + let mut ignore_output = false; + for arg in args.split_whitespace() { + match arg { + "ignore-output" => ignore_output = true, + _ => parser.error(span.clone(), format!("invalid flag on `run-native`: {arg}")), + } + } + // Remember this data. We use the presence/absence of keys in the map to communicate + // with later passes, since there is apparently no way to get custom data out of + // that map again. + parser.set_custom_once("run-native", (), span.clone()); + if ignore_output { + parser.set_custom_once("run-native-ignore-output", (), span); + } + }); + } // Adjust comment defaults. config.comment_defaults.base().exit_status = match mode { @@ -225,9 +239,8 @@ fn miri_config( comments: &ui_test::Comments, revision: &str, ) -> bool { - let should_run = comments - .for_revision(revision) - .any(|r| r.custom.iter().any(|(k, _v)| *k == "run-native")); + let should_run = + comments.for_revision(revision).any(|r| r.custom.contains_key("run-native")); // We return `true` when the test should be ignored. let ignore = !should_run; ignore @@ -242,7 +255,27 @@ fn miri_config( // Delegate to the native run support. use ui_test::custom_flags::run::Run; Run::post_test_action( - &Run { exit_code: 0, output_conflict_handling: None }, + &Run { + exit_code: 0, + output_conflict_handling: Some(|path, output, errors, config| { + let ignore_output = config + .comments() + .any(|r| r.custom.contains_key("run-native-ignore-output")); + if ignore_output { + return; + } + + // We have to patch the filename. Something is adding a `.run`. + let extension = path.extension().unwrap(); + // Remove that extension from the path. + let path = path.with_extension(""); + // Now the extension is `.run`. Replace it with the final extension. + assert_eq!(path.extension().unwrap().to_str(), Some("run")); + let path = path.with_extension(extension); + + ui_test::error_on_output_conflict(&path, output, errors, config) + }), + }, config, output, build_manager, diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index fa5c540e4ca97..d54639714a278 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -13,7 +13,7 @@ pub struct MiriFrame { // The function pointer to the function currently being executed. // This can be compared against function pointers obtained by // casting a function (e.g. `my_fn as *mut ()`) - pub fn_ptr: *mut (), + pub fn_ptr: *mut core::ffi::c_void, } #[cfg(miri)] @@ -48,8 +48,8 @@ extern "Rust" { pub fn miri_resolve_frame_names( ptr: *mut (), flags: u64, - name_buf: *mut (), - filename_buf: *mut (), + name_buf: *mut u8, + filename_buf: *mut u8, ); /// Miri-provided extern function to begin unwinding with the given payload. diff --git a/tests/rustdoc-html/inline_local/document-private-reexports.rs b/tests/rustdoc-html/inline_local/document-private-reexports.rs new file mode 100644 index 0000000000000..c0953e7678716 --- /dev/null +++ b/tests/rustdoc-html/inline_local/document-private-reexports.rs @@ -0,0 +1,52 @@ +// Test ensures that when we use the `--document-private-items` option, reexports +// work the same as for "public" items. +// Regression test for . + +//@ compile-flags: --document-private-items + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +// The four reexports, the `not_hidden` and `foofoo` (private) modules. +//@ count - '//*[@class="item-table"]/dt' 6 +//@ has - '//*[@class="item-table"]/dt/a[@href="not_hidden/index.html"]' 'not_hidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Top.html"]' 'Top' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.NotHidden.html"]' 'NotHidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Hidden2.html"]' 'Hidden2' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.X.html"]' 'X' +//@ has - '//*[@class="item-table"]/dt/a[@href="foofoo/index.html"]' 'foofoo' + +#[doc(hidden)] +pub(crate) struct TopHidden; + +#[doc(inline)] +pub(crate) use TopHidden as Top; + +#[doc(hidden)] +mod hidden { + pub(crate) struct NotHidden; +} + +pub(crate) use self::hidden::NotHidden; +// Even though there is no `pub` of any kind, we display it because we're at the +// crate root. +use self::hidden::NotHidden as X; + +mod not_hidden { + #[doc(hidden)] + pub(crate) struct Hidden; +} + +#[doc(inline)] +pub(crate) use self::not_hidden::Hidden as Hidden2; + +//@ has 'foo/foofoo/index.html' +//@ count - '//*[@class="item-table"]/dt' 1 +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Something.html"]' 'Something' +mod foofoo { + // Since there is no `pub` of any kind, we don't display it, even if there is `doc(inline)`. + #[doc(inline)] + use crate::not_hidden::Hidden; + #[doc(inline)] + pub(crate) use crate::not_hidden::Hidden as Something; +} diff --git a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs index 4ad36da843f80..a85ed41fa5ddf 100644 --- a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs +++ b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs @@ -17,25 +17,25 @@ pub use mod1::*; //@ has foo/index.html //@ hasraw - "mod1" //@ hasraw - "Mod1Public" -//@ !hasraw - "Mod1Private" -//@ !hasraw - "mod2" +//@ hasraw - "Mod1Private" +//@ hasraw - "mod2" //@ hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Private" //@ has foo/struct.Mod1Public.html -//@ !has foo/struct.Mod1Private.html +//@ has foo/struct.Mod1Private.html //@ has foo/struct.Mod2Public.html -//@ !has foo/struct.Mod2Private.html +//@ has foo/struct.Mod2Private.html //@ has foo/mod1/index.html //@ hasraw - "mod2" //@ hasraw - "Mod1Public" //@ hasraw - "Mod1Private" -//@ !hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Public" +//@ hasraw - "Mod2Private" //@ has foo/mod1/struct.Mod1Public.html //@ has foo/mod1/struct.Mod1Private.html -//@ !has foo/mod1/struct.Mod2Public.html -//@ !has foo/mod1/struct.Mod2Private.html +//@ has foo/mod1/struct.Mod2Public.html +//@ has foo/mod1/struct.Mod2Private.html //@ has foo/mod1/mod2/index.html //@ hasraw - "Mod2Public" @@ -43,6 +43,6 @@ pub use mod1::*; //@ has foo/mod1/mod2/struct.Mod2Public.html //@ has foo/mod1/mod2/struct.Mod2Private.html -//@ !has foo/mod2/index.html -//@ !has foo/mod2/struct.Mod2Public.html -//@ !has foo/mod2/struct.Mod2Private.html +//@ has foo/mod2/index.html +//@ has foo/mod2/struct.Mod2Public.html +//@ has foo/mod2/struct.Mod2Private.html diff --git a/triagebot.toml b/triagebot.toml index 60786ced590f0..35891af78fbdb 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1104,6 +1104,10 @@ cc = ["@BoxyUwU"] message = "Some changes occurred in `rustc_ty_utils::consts.rs`" cc = ["@BoxyUwU"] +[mentions."compiler/rustc_ty_utils/src/layout/invariant.rs"] +message = "Layout invariants are being changed" +cc = ["@RalfJung"] + [mentions."compiler/rustc_trait_selection/src/solve/"] message = "Some changes occurred to the core trait solver" cc = ["@rust-lang/initiative-trait-system-refactor"]