diff --git a/Cargo.lock b/Cargo.lock index 57157b32b9782..4e5040349e022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3116,9 +3116,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb332121f7845c6bd016f9655cf22f03c2999df936694b624a88669a78667d98" +checksum = "10edc2e4393515193bd766e2f6c050b0536a68e56f2b6d56c07ababfdc114ff0" dependencies = [ "anyhow", "rustc_version", diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 0de0319c66763..17b443b8ecc2c 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -6,7 +6,7 @@ use rustc_ast::*; use rustc_data_structures::fx::FxIndexMap; use rustc_hir as hir; use rustc_session::config::FmtDebug; -use rustc_span::{Ident, Span, Symbol, kw, sym}; +use rustc_span::{Ident, Span, Symbol, sym}; use super::LoweringContext; @@ -418,7 +418,7 @@ fn expand_format_args<'hir>( &FormatArgsPiece::Placeholder(_) => { // Inject empty string before placeholders when not already preceded by a literal piece. if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) { - Some(ctx.expr_str(fmt.span, kw::Empty)) + Some(ctx.expr_str(fmt.span, sym::empty)) } else { None } diff --git a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs index 5924c8991ad64..f731613d67e8a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs +++ b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs @@ -10,7 +10,7 @@ use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{Instance, Ty}; use rustc_middle::{bug, mir, ty}; use rustc_session::config::DebugInfo; -use rustc_span::{BytePos, Span, Symbol, hygiene, kw}; +use rustc_span::{BytePos, Span, Symbol, hygiene, sym}; use super::operand::{OperandRef, OperandValue}; use super::place::{PlaceRef, PlaceValue}; @@ -283,7 +283,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // (after #67586 gets fixed). None } else { - let name = kw::Empty; + let name = sym::empty; let decl = &self.mir.local_decls[local]; let dbg_var = if full_debug_info { self.adjusted_span_and_dbg_scope(decl.source_info).map( @@ -318,7 +318,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { None } else { Some(match whole_local_var.or(fallback_var.clone()) { - Some(var) if var.name != kw::Empty => var.name.to_string(), + Some(var) if var.name != sym::empty => var.name.to_string(), _ => format!("{local:?}"), }) }; diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index fa1d1ec0a8601..f63ab30368914 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -85,7 +85,7 @@ impl From for LifetimeSyntax { fn from(ident: Ident) -> Self { let name = ident.name; - if name == kw::Empty { + if name == sym::empty { unreachable!("A lifetime name should never be empty"); } else if name == kw::UnderscoreLifetime { LifetimeSyntax::Anonymous diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index 7976b65aae7b0..fbc8ee9b06c03 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -6,7 +6,7 @@ use rustc_middle::mir::{ BasicBlock, BasicBlockData, Body, Local, LocalDecl, MirSource, Operand, Place, Rvalue, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, }; -use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt}; +use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt, TypeVisitableExt}; use super::*; use crate::patch::MirPatch; @@ -121,9 +121,10 @@ pub(super) fn build_async_drop_shim<'tcx>( parent_args.as_coroutine().resume_ty(), ))); body.phase = MirPhase::Runtime(RuntimePhase::Initial); - if !needs_async_drop { + if !needs_async_drop || drop_ty.references_error() { // Returning noop body for types without `need async drop` - // (or sync Drop in case of !`need async drop` && `need drop`) + // (or sync Drop in case of !`need async drop` && `need drop`). + // And also for error types. return body; } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 5c0d0cf47969a..1d0246940499f 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -35,7 +35,7 @@ use rustc_session::lint::builtin::{ UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES, }; use rustc_session::parse::feature_err; -use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, edition, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, edition, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs}; use rustc_trait_selection::traits::ObligationCtxt; @@ -936,7 +936,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span()); let attr_str = &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" }); - if doc_alias == kw::Empty { + if doc_alias == sym::empty { tcx.dcx().emit_err(errors::DocAliasEmpty { span, attr_str }); return; } @@ -1068,7 +1068,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } let doc_keyword = match meta.value_str() { - Some(value) if value != kw::Empty => value, + Some(value) if value != sym::empty => value, _ => return self.doc_attr_str_error(meta, "keyword"), }; diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index a32fe6990160c..01bb1324645be 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -12,7 +12,7 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; -use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, kw, sym}; +use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, sym}; use thin_vec::ThinVec; use tracing::{debug, trace}; @@ -157,7 +157,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { }; for fragment in docs { - if fragment.doc == kw::Empty { + if fragment.doc == sym::empty { continue; } @@ -177,7 +177,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { /// /// Note: remove the trailing newline where appropriate pub fn add_doc_fragment(out: &mut String, frag: &DocFragment) { - if frag.doc == kw::Empty { + if frag.doc == sym::empty { out.push('\n'); return; } @@ -514,20 +514,30 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option { /// This method does not always work, because markdown bytes don't necessarily match source bytes, /// like if escapes are used in the string. In this case, it returns `None`. /// -/// This method will return `Some` only if: +/// `markdown` is typically the entire documentation for an item, +/// after combining fragments. +/// +/// This method will return `Some` only if one of the following is true: /// /// - The doc is made entirely from sugared doc comments, which cannot contain escapes -/// - The doc is entirely from a single doc fragment, with a string literal, exactly equal +/// - The doc is entirely from a single doc fragment with a string literal exactly equal to `markdown`. /// - The doc comes from `include_str!` +/// - The doc includes exactly one substring matching `markdown[md_range]` which is contained in a single doc fragment. +/// +/// This function is defined in the compiler so it can be used by +/// both `rustdoc` and `clippy`. pub fn source_span_for_markdown_range( tcx: TyCtxt<'_>, markdown: &str, md_range: &Range, fragments: &[DocFragment], ) -> Option { + use rustc_span::BytePos; + + let map = tcx.sess.source_map(); if let &[fragment] = &fragments && fragment.kind == DocFragmentKind::RawDoc - && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(fragment.span) + && let Ok(snippet) = map.span_to_snippet(fragment.span) && snippet.trim_end() == markdown.trim_end() && let Ok(md_range_lo) = u32::try_from(md_range.start) && let Ok(md_range_hi) = u32::try_from(md_range.end) @@ -544,10 +554,43 @@ pub fn source_span_for_markdown_range( let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc); if !is_all_sugared_doc { + // This case ignores the markdown outside of the range so that it can + // work in cases where the markdown is made from several different + // doc fragments, but the target range does not span across multiple + // fragments. + let mut match_data = None; + let pat = &markdown[md_range.clone()]; + // This heirustic doesn't make sense with a zero-sized range. + if pat.is_empty() { + return None; + } + for (i, fragment) in fragments.iter().enumerate() { + if let Ok(snippet) = map.span_to_snippet(fragment.span) + && let Some(match_start) = snippet.find(pat) + { + // If there is either a match in a previous fragment, or + // multiple matches in this fragment, there is ambiguity. + if match_data.is_none() && !snippet[match_start + 1..].contains(pat) { + match_data = Some((i, match_start)); + } else { + // Heirustic produced ambiguity, return nothing. + return None; + } + } + } + if let Some((i, match_start)) = match_data { + let sp = fragments[i].span; + // we need to calculate the span start, + // then use that in our calulations for the span end + let lo = sp.lo() + BytePos(match_start as u32); + return Some( + sp.with_lo(lo).with_hi(lo + BytePos((md_range.end - md_range.start) as u32)), + ); + } return None; } - let snippet = tcx.sess.source_map().span_to_snippet(span_of_fragments(fragments)?).ok()?; + let snippet = map.span_to_snippet(span_of_fragments(fragments)?).ok()?; let starting_line = markdown[..md_range.start].matches('\n').count(); let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count(); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 830cbafb1c5a4..6b10d2728b294 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -34,17 +34,8 @@ symbols! { // unnamed method parameters, crate root module, error recovery etc. // Matching predicates: `is_special`/`is_reserved` // - // Notes about `kw::Empty`: - // - Its use can blur the lines between "empty symbol" and "no symbol". - // Using `Option` is preferable, where possible, because that - // is unambiguous. - // - For dummy symbols that are never used and absolutely must be - // present, it's better to use `sym::dummy` than `kw::Empty`, because - // it's clearer that it's intended as a dummy value, and more likely - // to be detected if it accidentally does get used. // tidy-alphabetical-start DollarCrate: "$crate", - Empty: "", PathRoot: "{{root}}", Underscore: "_", // tidy-alphabetical-end @@ -863,7 +854,7 @@ symbols! { drop_types_in_const, dropck_eyepatch, dropck_parametricity, - dummy: "", // use this instead of `kw::Empty` for symbols that won't be used + dummy: "", // use this instead of `sym::empty` for symbols that won't be used dummy_cgu_name, dylib, dyn_compatible_for_dispatch, @@ -882,6 +873,14 @@ symbols! { emit_enum_variant_arg, emit_struct, emit_struct_field, + // Notes about `sym::empty`: + // - It should only be used when it genuinely means "empty symbol". Use + // `Option` when "no symbol" is a possibility. + // - For dummy symbols that are never used and absolutely must be + // present, it's better to use `sym::dummy` than `sym::empty`, because + // it's clearer that it's intended as a dummy value, and more likely + // to be detected if it accidentally does get used. + empty: "", emscripten_wasm_eh, enable, encode, @@ -2361,7 +2360,7 @@ impl Ident { #[inline] /// Constructs a new identifier from a symbol and a span. pub fn new(name: Symbol, span: Span) -> Ident { - debug_assert_ne!(name, kw::Empty); + debug_assert_ne!(name, sym::empty); Ident { name, span } } @@ -2583,7 +2582,7 @@ impl Symbol { } pub fn is_empty(self) -> bool { - self == kw::Empty + self == sym::empty } /// This method is supposed to be used in error messages, so it's expected to be @@ -2592,7 +2591,7 @@ impl Symbol { /// or edition, so we have to guess the rawness using the global edition. pub fn to_ident_string(self) -> String { // Avoid creating an empty identifier, because that asserts in debug builds. - if self == kw::Empty { String::new() } else { Ident::with_dummy_span(self).to_string() } + if self == sym::empty { String::new() } else { Ident::with_dummy_span(self).to_string() } } } @@ -2772,7 +2771,7 @@ impl Symbol { /// Returns `true` if this symbol can be a raw identifier. pub fn can_be_raw(self) -> bool { - self != kw::Empty && self != kw::Underscore && !self.is_path_segment_keyword() + self != sym::empty && self != kw::Underscore && !self.is_path_segment_keyword() } /// Was this symbol predefined in the compiler's `symbols!` macro diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 4a99ce09b39ab..644031af8597a 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -20,7 +20,7 @@ use rustc_middle::ty::{ self, FloatTy, GenericArg, GenericArgKind, Instance, IntTy, ReifyReason, Ty, TyCtxt, TypeVisitable, TypeVisitableExt, UintTy, }; -use rustc_span::kw; +use rustc_span::sym; pub(super) fn mangle<'tcx>( tcx: TyCtxt<'tcx>, @@ -902,7 +902,7 @@ impl<'tcx> Printer<'tcx> for SymbolMangler<'tcx> { print_prefix, ns, disambiguated_data.disambiguator as u64, - name.unwrap_or(kw::Empty).as_str(), + name.unwrap_or(sym::empty).as_str(), ) } diff --git a/library/std/src/io/pipe.rs b/library/std/src/io/pipe.rs index 47243806cd2d9..16727d445416c 100644 --- a/library/std/src/io/pipe.rs +++ b/library/std/src/io/pipe.rs @@ -38,30 +38,44 @@ use crate::sys_common::{FromInner, IntoInner}; /// > not rely on a particular capacity: an application should be designed so that a reading process /// > consumes data as soon as it is available, so that a writing process does not remain blocked. /// -/// # Examples +/// # Example /// /// ```no_run /// # #[cfg(miri)] fn main() {} /// # #[cfg(not(miri))] /// # fn main() -> std::io::Result<()> { +/// use std::io::{Read, Write, pipe}; /// use std::process::Command; -/// use std::io::{pipe, Read, Write}; -/// let (ping_rx, mut ping_tx) = pipe()?; -/// let (mut pong_rx, pong_tx) = pipe()?; +/// let (ping_reader, mut ping_writer) = pipe()?; +/// let (mut pong_reader, pong_writer) = pipe()?; /// -/// // Spawn a process that echoes its input. -/// let mut echo_server = Command::new("cat").stdin(ping_rx).stdout(pong_tx).spawn()?; +/// // Spawn a child process that echoes its input. +/// let mut echo_command = Command::new("cat"); +/// echo_command.stdin(ping_reader); +/// echo_command.stdout(pong_writer); +/// let mut echo_child = echo_command.spawn()?; /// -/// ping_tx.write_all(b"hello")?; -/// // Close to unblock echo_server's reader. -/// drop(ping_tx); +/// // Send input to the child process. Note that because we're writing all the input before we +/// // read any output, this could deadlock if the child's input and output pipe buffers both +/// // filled up. Those buffers are usually at least a few KB, so "hello" is fine, but for longer +/// // inputs we'd need to read and write at the same time, e.g. using threads. +/// ping_writer.write_all(b"hello")?; +/// +/// // `cat` exits when it reads EOF from stdin, but that can't happen while any ping writer +/// // remains open. We need to drop our ping writer, or read_to_string will deadlock below. +/// drop(ping_writer); +/// +/// // The pong reader can't report EOF while any pong writer remains open. Our Command object is +/// // holding a pong writer, and again read_to_string will deadlock if we don't drop it. +/// drop(echo_command); /// /// let mut buf = String::new(); -/// // Block until echo_server's writer is closed. -/// pong_rx.read_to_string(&mut buf)?; +/// // Block until `cat` closes its stdout (a pong writer). +/// pong_reader.read_to_string(&mut buf)?; /// assert_eq!(&buf, "hello"); /// -/// echo_server.wait()?; +/// // At this point we know `cat` has exited, but we still need to wait to clean up the "zombie". +/// echo_child.wait()?; /// # Ok(()) /// # } /// ``` diff --git a/library/std/src/os/net/linux_ext/addr.rs b/library/std/src/os/net/linux_ext/addr.rs index aed772056e1b3..41009c0e28457 100644 --- a/library/std/src/os/net/linux_ext/addr.rs +++ b/library/std/src/os/net/linux_ext/addr.rs @@ -23,7 +23,10 @@ pub trait SocketAddrExt: Sealed { /// /// ```no_run /// use std::os::unix::net::{UnixListener, SocketAddr}; + /// #[cfg(target_os = "linux")] /// use std::os::linux::net::SocketAddrExt; + /// #[cfg(target_os = "android")] + /// use std::os::android::net::SocketAddrExt; /// /// fn main() -> std::io::Result<()> { /// let addr = SocketAddr::from_abstract_name(b"hidden")?; @@ -48,7 +51,10 @@ pub trait SocketAddrExt: Sealed { /// /// ```no_run /// use std::os::unix::net::{UnixListener, SocketAddr}; + /// #[cfg(target_os = "linux")] /// use std::os::linux::net::SocketAddrExt; + /// #[cfg(target_os = "android")] + /// use std::os::android::net::SocketAddrExt; /// /// fn main() -> std::io::Result<()> { /// let name = b"hidden"; diff --git a/library/std/src/os/net/linux_ext/socket.rs b/library/std/src/os/net/linux_ext/socket.rs index 4e4168f693c34..a15feb6bd9f28 100644 --- a/library/std/src/os/net/linux_ext/socket.rs +++ b/library/std/src/os/net/linux_ext/socket.rs @@ -27,7 +27,10 @@ pub trait UnixSocketExt: Sealed { /// /// ```no_run /// #![feature(unix_socket_ancillary_data)] + /// #[cfg(target_os = "linux")] /// use std::os::linux::net::UnixSocketExt; + /// #[cfg(target_os = "android")] + /// use std::os::android::net::UnixSocketExt; /// use std::os::unix::net::UnixDatagram; /// /// fn main() -> std::io::Result<()> { diff --git a/library/std/src/os/net/linux_ext/tcp.rs b/library/std/src/os/net/linux_ext/tcp.rs index c8d012962d45a..95dffb3bc4347 100644 --- a/library/std/src/os/net/linux_ext/tcp.rs +++ b/library/std/src/os/net/linux_ext/tcp.rs @@ -25,7 +25,10 @@ pub trait TcpStreamExt: Sealed { /// ```no_run /// #![feature(tcp_quickack)] /// use std::net::TcpStream; + /// #[cfg(target_os = "linux")] /// use std::os::linux::net::TcpStreamExt; + /// #[cfg(target_os = "android")] + /// use std::os::android::net::TcpStreamExt; /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); @@ -43,7 +46,10 @@ pub trait TcpStreamExt: Sealed { /// ```no_run /// #![feature(tcp_quickack)] /// use std::net::TcpStream; + /// #[cfg(target_os = "linux")] /// use std::os::linux::net::TcpStreamExt; + /// #[cfg(target_os = "android")] + /// use std::os::android::net::TcpStreamExt; /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 359e208207252..373584d0117ce 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -1348,7 +1348,7 @@ impl Output { /// /// ``` /// #![feature(exit_status_error)] - /// # #[cfg(unix)] { + /// # #[cfg(all(unix, not(target_os = "android")))] { /// use std::process::Command; /// assert!(Command::new("false").output().unwrap().exit_ok().is_err()); /// # } @@ -1695,7 +1695,7 @@ impl From for Stdio { /// # Ok(()) /// # } /// # - /// # if cfg!(unix) { + /// # if cfg!(all(unix, not(target_os = "android"))) { /// # test().unwrap(); /// # } /// ``` @@ -1724,7 +1724,7 @@ impl From for Stdio { /// # Ok(()) /// # } /// # - /// # if cfg!(unix) { + /// # if cfg!(all(unix, not(target_os = "android"))) { /// # test().unwrap(); /// # } /// ``` @@ -1907,7 +1907,7 @@ impl crate::sealed::Sealed for ExitStatusError {} /// /// ``` /// #![feature(exit_status_error)] -/// # if cfg!(unix) { +/// # if cfg!(all(unix, not(target_os = "android"))) { /// use std::process::{Command, ExitStatusError}; /// /// fn run(cmd: &str) -> Result<(), ExitStatusError> { @@ -1950,7 +1950,7 @@ impl ExitStatusError { /// /// ``` /// #![feature(exit_status_error)] - /// # #[cfg(unix)] { + /// # #[cfg(all(unix, not(target_os = "android")))] { /// use std::process::Command; /// /// let bad = Command::new("false").status().unwrap().exit_ok().unwrap_err(); @@ -1975,7 +1975,7 @@ impl ExitStatusError { /// ``` /// #![feature(exit_status_error)] /// - /// # if cfg!(unix) { + /// # if cfg!(all(unix, not(target_os = "android"))) { /// use std::num::NonZero; /// use std::process::Command; /// diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index e45f28444fe0d..07ecd98f77596 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -774,20 +774,11 @@ impl Item { .filter_map(|attr| { if is_json { match attr { - hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => { - // rustdoc-json stores this in `Item::deprecation`, so we - // don't want it it `Item::attrs`. - None - } - rustc_hir::Attribute::Parsed( - rustc_attr_data_structures::AttributeKind::Repr(..), - ) => { - // We have separate pretty-printing logic for `#[repr(..)]` attributes. - // For example, there are circumstances where `#[repr(transparent)]` - // is applied but should not be publicly shown in rustdoc - // because it isn't public API. - None - } + // rustdoc-json stores this in `Item::deprecation`, so we + // don't want it it `Item::attrs`. + hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None, + // We have separate pretty-printing logic for `#[repr(..)]` attributes. + hir::Attribute::Parsed(AttributeKind::Repr(..)) => None, _ => Some({ let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr); assert_eq!(s.pop(), Some('\n')); @@ -820,7 +811,8 @@ impl Item { if repr.transparent() { // Render `repr(transparent)` iff the non-1-ZST field is public or at least one // field is public in case all fields are 1-ZST fields. - let render_transparent = cache.document_private + let render_transparent = is_json + || cache.document_private || adt .all_fields() .find(|field| { diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index 1e07277d38e5c..3b3ce3e92202a 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -18,12 +18,15 @@ use crate::html::markdown::main_body_opts; pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) { let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range| { - let sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings) - .unwrap_or_else(|| item.attr_span(cx.tcx)); + let maybe_sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings); + let sp = maybe_sp.unwrap_or_else(|| item.attr_span(cx.tcx)); cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| { lint.primary_message(msg) - .note("bare URLs are not automatically turned into clickable links") - .multipart_suggestion( + .note("bare URLs are not automatically turned into clickable links"); + // The fallback of using the attribute span is suitable for + // highlighting where the error is, but not for placing the < and > + if let Some(sp) = maybe_sp { + lint.multipart_suggestion( "use an automatic link instead", vec![ (sp.shrink_to_lo(), "<".to_string()), @@ -31,6 +34,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & ], Applicability::MachineApplicable, ); + } }); }; diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 0b8a906529466..c091c955ed5eb 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -30,7 +30,7 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc /// This integer is incremented with every breaking change to the API, /// and is returned along with the JSON blob as [`Crate::format_version`]. /// Consuming code should assert that this value matches the format version(s) that it supports. -pub const FORMAT_VERSION: u32 = 45; +pub const FORMAT_VERSION: u32 = 46; /// The root of the emitted JSON blob. /// diff --git a/src/tools/clippy/clippy_lints/src/manual_string_new.rs b/src/tools/clippy/clippy_lints/src/manual_string_new.rs index 7ca3b71206671..73ee1c3c78abc 100644 --- a/src/tools/clippy/clippy_lints/src/manual_string_new.rs +++ b/src/tools/clippy/clippy_lints/src/manual_string_new.rs @@ -5,7 +5,7 @@ use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym, symbol}; +use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -67,7 +67,7 @@ impl LateLintPass<'_> for ManualStringNew { fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool { if let ExprKind::Lit(lit) = expr_kind && let LitKind::Str(value, _) = lit.node - && value == symbol::kw::Empty + && value == sym::empty { return true; } diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs index c74c42e9e5bd5..38cb4d51ca0f4 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs @@ -12,7 +12,7 @@ use rustc_errors::Applicability; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; -use rustc_span::symbol::{self, Symbol}; +use rustc_span::Symbol; use {rustc_ast as ast, rustc_hir as hir}; use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; @@ -265,7 +265,7 @@ fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) && ident.name == sym::to_string && let hir::Expr { kind, .. } = self_arg && let hir::ExprKind::Lit(lit) = kind - && let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node + && let ast::LitKind::Str(rustc_span::sym::empty, _) = lit.node { return true; } diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 122438a2509b3..de521393cd0ab 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -218,7 +218,7 @@ degree documented below): make no promises and we don't run tests for such targets. - We have unofficial support (not maintained by the Miri team itself) for some further operating systems. - `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite. - - `freebsd`: maintained by @YohDeadfall. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`. + - `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite. - `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works. - `wasi`: **maintainer wanted**. Support very incomplete, not even standard output works, but an empty `main` function works. - For targets on other operating systems, Miri might fail before even reaching the `main` function. diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock index bd4ca2860f35a..c1915ae617e87 100644 --- a/src/tools/miri/cargo-miri/Cargo.lock +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -208,9 +208,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d984a9db43148467059309bd1e5ad577085162f695d9fe2cf3543aeb25cd38" +checksum = "10edc2e4393515193bd766e2f6c050b0536a68e56f2b6d56c07ababfdc114ff0" dependencies = [ "anyhow", "rustc_version", diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index 23048914af18e..5c579b2a77d62 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -18,7 +18,7 @@ directories = "6" rustc_version = "0.4" serde_json = "1.0.40" cargo_metadata = "0.19" -rustc-build-sysroot = "0.5.4" +rustc-build-sysroot = "0.5.7" # Enable some feature flags that dev-dependencies need but dependencies # do not. This makes `./miri install` after `./miri build` faster. diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 755e02d02eca1..9ae15739dcbe1 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -156,16 +156,17 @@ case $HOST_TARGET in MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests # Extra tier 2 - TEST_TARGET=arm-unknown-linux-gnueabi run_tests - TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice + MANY_SEEDS=16 TEST_TARGET=arm-unknown-linux-gnueabi run_tests + MANY_SEEDS=16 TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice # Not officially supported tier 2 - TEST_TARGET=x86_64-unknown-illumos run_tests - TEST_TARGET=x86_64-pc-solaris run_tests + MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics + MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests + MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests + MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests + MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests # Partially supported targets (tier 2) BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there - TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe - TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency epoll eventfd TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 8b98fe3c4fc3e..4698969530292 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -ac17c3486c6fdfbb0c3c18b99f3d8dfbff625d29 +2b96ddca1272960623e41829439df8dae82d20af diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index dd389d97cdce0..21bd7fb54c6cd 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -168,7 +168,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { AllocKind::Dead => unreachable!(), }; // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. - return interp_ok(base_ptr.addr().try_into().unwrap()); + return interp_ok(base_ptr.addr().to_u64()); } // We are not in native lib mode, so we control the addresses ourselves. if let Some((reuse_addr, clock)) = global_state.reuse.take_addr( diff --git a/src/tools/miri/src/alloc_addresses/reuse_pool.rs b/src/tools/miri/src/alloc_addresses/reuse_pool.rs index 29d4f2bb7b0f3..ab6aaed5e3e1c 100644 --- a/src/tools/miri/src/alloc_addresses/reuse_pool.rs +++ b/src/tools/miri/src/alloc_addresses/reuse_pool.rs @@ -4,6 +4,7 @@ use rand::Rng; use rustc_abi::{Align, Size}; use crate::concurrency::VClock; +use crate::helpers::ToUsize as _; use crate::{MemoryKind, MiriConfig, ThreadId}; const MAX_POOL_SIZE: usize = 64; @@ -46,7 +47,7 @@ impl ReusePool { } fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, ThreadId, VClock)> { - let pool_idx: usize = align.bytes().trailing_zeros().try_into().unwrap(); + let pool_idx: usize = align.bytes().trailing_zeros().to_usize(); if self.pool.len() <= pool_idx { self.pool.resize(pool_idx + 1, Vec::new()); } diff --git a/src/tools/miri/src/alloc_bytes.rs b/src/tools/miri/src/alloc_bytes.rs index 69ede279aa9b1..6788494c01cce 100644 --- a/src/tools/miri/src/alloc_bytes.rs +++ b/src/tools/miri/src/alloc_bytes.rs @@ -5,6 +5,8 @@ use std::{alloc, slice}; use rustc_abi::{Align, Size}; use rustc_middle::mir::interpret::AllocBytes; +use crate::helpers::ToU64 as _; + /// Allocation bytes that explicitly handle the layout of the data they're storing. /// This is necessary to interface with native code that accesses the program store in Miri. #[derive(Debug)] @@ -21,7 +23,7 @@ pub struct MiriAllocBytes { impl Clone for MiriAllocBytes { fn clone(&self) -> Self { let bytes: Cow<'_, [u8]> = Cow::Borrowed(self); - let align = Align::from_bytes(self.layout.align().try_into().unwrap()).unwrap(); + let align = Align::from_bytes(self.layout.align().to_u64()).unwrap(); MiriAllocBytes::from_bytes(bytes, align) } } @@ -90,7 +92,7 @@ impl AllocBytes for MiriAllocBytes { let align = align.bytes(); // SAFETY: `alloc_fn` will only be used with `size != 0`. let alloc_fn = |layout| unsafe { alloc::alloc(layout) }; - let alloc_bytes = MiriAllocBytes::alloc_with(size.try_into().unwrap(), align, alloc_fn) + let alloc_bytes = MiriAllocBytes::alloc_with(size.to_u64(), align, alloc_fn) .unwrap_or_else(|()| { panic!("Miri ran out of memory: cannot create allocation of {size} bytes") }); diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs index 7874721c0ac37..dcd5a6cb02305 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs @@ -17,6 +17,8 @@ use std::mem; use rustc_data_structures::fx::FxHashMap; +use crate::helpers::ToUsize; + /// Intermediate key between a UniKeyMap and a UniValMap. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct UniIndex { @@ -158,7 +160,7 @@ where impl UniValMap { /// Whether this index has an associated value. pub fn contains_idx(&self, idx: UniIndex) -> bool { - self.data.get(idx.idx as usize).and_then(Option::as_ref).is_some() + self.data.get(idx.idx.to_usize()).and_then(Option::as_ref).is_some() } /// Reserve enough space to insert the value at the right index. @@ -174,29 +176,29 @@ impl UniValMap { /// Assign a value to the index. Permanently overwrites any previous value. pub fn insert(&mut self, idx: UniIndex, val: V) { - self.extend_to_length(idx.idx as usize + 1); - self.data[idx.idx as usize] = Some(val) + self.extend_to_length(idx.idx.to_usize() + 1); + self.data[idx.idx.to_usize()] = Some(val) } /// Get the value at this index, if it exists. pub fn get(&self, idx: UniIndex) -> Option<&V> { - self.data.get(idx.idx as usize).and_then(Option::as_ref) + self.data.get(idx.idx.to_usize()).and_then(Option::as_ref) } /// Get the value at this index mutably, if it exists. pub fn get_mut(&mut self, idx: UniIndex) -> Option<&mut V> { - self.data.get_mut(idx.idx as usize).and_then(Option::as_mut) + self.data.get_mut(idx.idx.to_usize()).and_then(Option::as_mut) } /// Delete any value associated with this index. /// Returns None if the value was not present, otherwise /// returns the previously stored value. pub fn remove(&mut self, idx: UniIndex) -> Option { - if idx.idx as usize >= self.data.len() { + if idx.idx.to_usize() >= self.data.len() { return None; } let mut res = None; - mem::swap(&mut res, &mut self.data[idx.idx as usize]); + mem::swap(&mut res, &mut self.data[idx.idx.to_usize()]); res } } @@ -209,8 +211,8 @@ pub struct UniEntry<'a, V> { impl<'a, V> UniValMap { /// Get a wrapper around a mutable access to the value corresponding to `idx`. pub fn entry(&'a mut self, idx: UniIndex) -> UniEntry<'a, V> { - self.extend_to_length(idx.idx as usize + 1); - UniEntry { inner: &mut self.data[idx.idx as usize] } + self.extend_to_length(idx.idx.to_usize() + 1); + UniEntry { inner: &mut self.data[idx.idx.to_usize()] } } } diff --git a/src/tools/miri/src/concurrency/cpu_affinity.rs b/src/tools/miri/src/concurrency/cpu_affinity.rs index b47b614cf5f0e..9583de5a4838c 100644 --- a/src/tools/miri/src/concurrency/cpu_affinity.rs +++ b/src/tools/miri/src/concurrency/cpu_affinity.rs @@ -25,7 +25,7 @@ impl CpuAffinityMask { let mut this = Self([0; Self::CPU_MASK_BYTES]); // the default affinity mask includes only the available CPUs - for i in 0..cpu_count as usize { + for i in 0..cpu_count.to_usize() { this.set(cx, i); } diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs index 78858fcedaec7..494e7922d2b2b 100644 --- a/src/tools/miri/src/concurrency/vector_clock.rs +++ b/src/tools/miri/src/concurrency/vector_clock.rs @@ -7,6 +7,7 @@ use rustc_span::{DUMMY_SP, Span, SpanData}; use smallvec::SmallVec; use super::data_race::NaReadType; +use crate::helpers::ToUsize; /// A vector clock index, this is associated with a thread id /// but in some cases one vector index may be shared with @@ -157,7 +158,7 @@ impl VClock { #[inline] pub(super) fn index_mut(&mut self, index: VectorIdx) -> &mut VTimestamp { - self.0.as_mut_slice().get_mut(index.to_u32() as usize).unwrap() + self.0.as_mut_slice().get_mut(index.to_u32().to_usize()).unwrap() } /// Get a mutable slice to the internal vector with minimum `min_len` @@ -420,7 +421,7 @@ impl Index for VClock { #[inline] fn index(&self, index: VectorIdx) -> &VTimestamp { - self.as_slice().get(index.to_u32() as usize).unwrap_or(&VTimestamp::ZERO) + self.as_slice().get(index.to_u32().to_usize()).unwrap_or(&VTimestamp::ZERO) } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 8e7c9edfcc078..ff2ec1b3e60a8 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1412,3 +1412,26 @@ pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { u32::try_from(len).unwrap() } } + +/// We don't support 16-bit systems, so let's have ergonomic conversion from `u32` to `usize`. +pub trait ToUsize { + fn to_usize(self) -> usize; +} + +impl ToUsize for u32 { + fn to_usize(self) -> usize { + self.try_into().unwrap() + } +} + +/// Similarly, a maximum address size of `u64` is assumed widely here, so let's have ergonomic +/// converion from `usize` to `u64`. +pub trait ToU64 { + fn to_u64(self) -> u64; +} + +impl ToU64 for usize { + fn to_u64(self) -> u64 { + self.try_into().unwrap() + } +} diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index c9250ba1b818d..b17fd4fb7f932 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -634,7 +634,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let index_len = index.len(); assert_eq!(left_len, right_len); - assert_eq!(index_len as u64, dest_len); + assert_eq!(u64::try_from(index_len).unwrap(), dest_len); for i in 0..dest_len { let src_index: u64 = diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index b87682a7caf7e..9d663ca9edf1f 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -41,14 +41,7 @@ rustc::potential_query_instability, rustc::untranslatable_diagnostic, )] -#![warn( - rust_2018_idioms, - unqualified_local_imports, - clippy::cast_possible_wrap, // unsigned -> signed - clippy::cast_sign_loss, // signed -> unsigned - clippy::cast_lossless, - clippy::cast_possible_truncation, -)] +#![warn(rust_2018_idioms, unqualified_local_imports, clippy::as_conversions)] // Needed for rustdoc from bootstrap (with `-Znormalize-docs`). #![recursion_limit = "256"] @@ -140,7 +133,7 @@ pub use crate::eval::{ AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, MiriEntryFnType, RejectOpWith, ValidationMode, create_ecx, eval_entry, }; -pub use crate::helpers::{AccessKind, EvalContextExt as _}; +pub use crate::helpers::{AccessKind, EvalContextExt as _, ToU64 as _, ToUsize as _}; pub use crate::intrinsics::EvalContextExt as _; pub use crate::machine::{ AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 1c6c7894cb456..f75adffd9508e 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -544,9 +544,6 @@ pub struct MiriMachine<'tcx> { /// Failure rate of compare_exchange_weak, between 0.0 and 1.0 pub(crate) cmpxchg_weak_failure_rate: f64, - /// Corresponds to -Zmiri-mute-stdout-stderr and doesn't write the output but acts as if it succeeded. - pub(crate) mute_stdout_stderr: bool, - /// The probability of the active thread being preempted at the end of each basic block. pub(crate) preemption_rate: f64, @@ -722,7 +719,6 @@ impl<'tcx> MiriMachine<'tcx> { track_alloc_accesses: config.track_alloc_accesses, check_alignment: config.check_alignment, cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate, - mute_stdout_stderr: config.mute_stdout_stderr, preemption_rate: config.preemption_rate, report_progress: config.report_progress, basic_block_count: 0, @@ -925,7 +921,6 @@ impl VisitProvenance for MiriMachine<'_> { track_alloc_accesses: _, check_alignment: _, cmpxchg_weak_failure_rate: _, - mute_stdout_stderr: _, preemption_rate: _, report_progress: _, basic_block_count: _, diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 7e667e70a1721..9f3bc06771f55 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -25,7 +25,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let frame_count = this.active_thread_stack().len(); - this.write_scalar(Scalar::from_target_usize(frame_count.try_into().unwrap(), this), dest) + this.write_scalar(Scalar::from_target_usize(frame_count.to_u64(), this), dest) } fn handle_miri_get_backtrace( @@ -70,7 +70,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } 1 => for (i, ptr) in ptrs.into_iter().enumerate() { - let offset = ptr_layout.size.checked_mul(i.try_into().unwrap(), this).unwrap(); + let offset = ptr_layout.size.checked_mul(i.to_u64(), this).unwrap(); let op_place = buf_place.offset(offset, ptr_layout, this)?; @@ -158,11 +158,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } 1 => { this.write_scalar( - Scalar::from_target_usize(name.len().try_into().unwrap(), this), + Scalar::from_target_usize(name.len().to_u64(), this), &this.project_field(dest, 0)?, )?; this.write_scalar( - Scalar::from_target_usize(filename.len().try_into().unwrap(), this), + Scalar::from_target_usize(filename.len().to_u64(), this), &this.project_field(dest, 1)?, )?; } diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 42603e784bbd7..31142431247c4 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -135,7 +135,10 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { /// Reads as much as possible into the given buffer `ptr`. /// `len` indicates how many bytes we should try to read. - /// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error. + /// + /// When the read is done, `finish` will be called. Note that `read` itself may return before + /// that happens! Everything that should happen "after" the `read` needs to happen inside + /// `finish`. fn read<'tcx>( self: FileDescriptionRef, _communicate_allowed: bool, @@ -149,7 +152,10 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { /// Writes as much as possible from the given buffer `ptr`. /// `len` indicates how many bytes we should try to write. - /// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error. + /// + /// When the write is done, `finish` will be called. Note that `write` itself may return before + /// that happens! Everything that should happen "after" the `write` needs to happen inside + /// `finish`. fn write<'tcx>( self: FileDescriptionRef, _communicate_allowed: bool, diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 52c16a0c2e2dc..a81f459e5e1ae 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -639,7 +639,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let val = this.read_scalar(val)?.to_i32()?; let num = this.read_target_usize(num)?; // The docs say val is "interpreted as unsigned char". - #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + #[expect(clippy::as_conversions)] let val = val as u8; // C requires that this must always be a valid pointer (C18 §7.1.4). @@ -665,7 +665,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let val = this.read_scalar(val)?.to_i32()?; let num = this.read_target_usize(num)?; // The docs say val is "interpreted as unsigned char". - #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + #[expect(clippy::as_conversions)] let val = val as u8; // C requires that this must always be a valid pointer (C18 §7.1.4). @@ -676,7 +676,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { .iter() .position(|&c| c == val); if let Some(idx) = idx { - let new_ptr = ptr.wrapping_offset(Size::from_bytes(idx as u64), this); + let new_ptr = ptr.wrapping_offset(Size::from_bytes(idx), this); this.write_pointer(new_ptr, dest)?; } else { this.write_null(dest)?; diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index acf3f74a93d8b..e597b527cb7a8 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -1,4 +1,5 @@ use std::io; +use std::io::ErrorKind; use crate::*; @@ -13,6 +14,29 @@ pub enum IoError { } pub use self::IoError::*; +impl IoError { + pub(crate) fn into_ntstatus(self) -> i32 { + let raw = match self { + HostError(e) => + match e.kind() { + // STATUS_MEDIA_WRITE_PROTECTED + ErrorKind::ReadOnlyFilesystem => 0xC00000A2u32, + // STATUS_FILE_INVALID + ErrorKind::InvalidInput => 0xC0000098, + // STATUS_DISK_FULL + ErrorKind::QuotaExceeded => 0xC000007F, + // STATUS_ACCESS_DENIED + ErrorKind::PermissionDenied => 0xC0000022, + // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. + _ => 0xC0000185, + }, + // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. + _ => 0xC0000185, + }; + raw.cast_signed() + } +} + impl From for IoError { fn from(value: io::Error) -> Self { IoError::HostError(value) diff --git a/src/tools/miri/src/shims/native_lib.rs b/src/tools/miri/src/shims/native_lib.rs index 1a43e2297255e..1e6c93333c157 100644 --- a/src/tools/miri/src/shims/native_lib.rs +++ b/src/tools/miri/src/shims/native_lib.rs @@ -93,14 +93,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // Try getting the function from the shared library. let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap(); - let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe { - match lib.get(link_name.as_str().as_bytes()) { - Ok(x) => x, - Err(_) => { - return None; - } - } - }; + let func: libloading::Symbol<'_, unsafe extern "C" fn()> = + unsafe { lib.get(link_name.as_str().as_bytes()).ok()? }; + #[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`. + let fn_ptr = *func.deref() as *mut std::ffi::c_void; // FIXME: this is a hack! // The `libloading` crate will automatically load system libraries like `libc`. @@ -115,7 +111,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // using the `libc` crate where this interface is public. let mut info = std::mem::MaybeUninit::::zeroed(); unsafe { - if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 { + if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 { let info = info.assume_init(); #[cfg(target_os = "cygwin")] let fname_ptr = info.dli_fname.as_ptr(); @@ -129,8 +125,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } + // Return a pointer to the function. - Some(CodePtr(*func.deref() as *mut _)) + Some(CodePtr(fn_ptr)) } } diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs index 30ec0aefcbf1a..aa3a05ead85b4 100644 --- a/src/tools/miri/src/shims/unix/android/thread.rs +++ b/src/tools/miri/src/shims/unix/android/thread.rs @@ -7,7 +7,7 @@ use crate::helpers::check_min_vararg_count; use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult}; use crate::*; -const TASK_COMM_LEN: usize = 16; +const TASK_COMM_LEN: u64 = 16; pub fn prctl<'tcx>( ecx: &mut MiriInterpCx<'tcx>, @@ -38,7 +38,7 @@ pub fn prctl<'tcx>( let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?; let name = ecx.read_scalar(name)?; let thread = ecx.pthread_self()?; - let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx); + let len = Scalar::from_target_usize(TASK_COMM_LEN, ecx); ecx.check_ptr_access( name.to_pointer(ecx)?, Size::from_bytes(TASK_COMM_LEN), 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 21a386b29272a..533a741fea3f2 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -24,7 +24,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Threading "pthread_setname_np" => { let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; - let max_len = usize::MAX; // FreeBSD does not seem to have a limit. + let max_len = u64::MAX; // FreeBSD does not seem to have a limit. let res = match this.pthread_setname_np( this.read_scalar(thread)?, this.read_scalar(name)?, @@ -56,6 +56,70 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, 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(abi, Conv::C, link_name, args)?; + + let level = this.read_scalar(level)?.to_i32()?; + let which = this.read_scalar(which)?.to_i32()?; + let id = this.read_scalar(id)?.to_i64()?; + let set_size = this.read_target_usize(set_size)?; // measured in bytes + let mask = this.read_pointer(mask)?; + + let _level_root = this.eval_libc_i32("CPU_LEVEL_ROOT"); + let _level_cpuset = this.eval_libc_i32("CPU_LEVEL_CPUSET"); + let level_which = this.eval_libc_i32("CPU_LEVEL_WHICH"); + + let _which_tid = this.eval_libc_i32("CPU_WHICH_TID"); + let which_pid = this.eval_libc_i32("CPU_WHICH_PID"); + let _which_jail = this.eval_libc_i32("CPU_WHICH_JAIL"); + let _which_cpuset = this.eval_libc_i32("CPU_WHICH_CPUSET"); + let _which_irq = this.eval_libc_i32("CPU_WHICH_IRQ"); + + // For sched_getaffinity, the current process is identified by -1. + // TODO: Use gettid? I'm (LorrensP-2158466) not that familiar with this api . + let id = match id { + -1 => this.active_thread(), + _ => + throw_unsup_format!( + "`cpuset_getaffinity` is only supported with a pid of -1 (indicating the current thread)" + ), + }; + + if this.ptr_is_null(mask)? { + this.set_last_error_and_return(LibcError("EFAULT"), dest)?; + } + // We only support CPU_LEVEL_WHICH and CPU_WHICH_PID for now. + // This is the bare minimum to make the tests pass. + else if level != level_which || which != which_pid { + throw_unsup_format!( + "`cpuset_getaffinity` is only supported with `level` set to CPU_LEVEL_WHICH and `which` set to CPU_WHICH_PID." + ); + } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&id) { + // `cpusetsize` must be large enough to contain the entire CPU mask. + // FreeBSD only uses `cpusetsize` to verify that it's sufficient for the kernel's CPU mask. + // If it's too small, the syscall returns ERANGE. + // If it's large enough, copying the kernel mask to user space is safe, regardless of the actual size. + // See https://github.com/freebsd/freebsd-src/blob/909aa6781340f8c0b4ae01c6366bf1556ee2d1be/sys/kern/kern_cpuset.c#L1985 + if set_size < u64::from(this.machine.num_cpus).div_ceil(8) { + this.set_last_error_and_return(LibcError("ERANGE"), dest)?; + } else { + let cpuset = cpuset.clone(); + let byte_count = + Ord::min(cpuset.as_slice().len(), set_size.try_into().unwrap()); + this.write_bytes_ptr( + mask, + cpuset.as_slice()[..byte_count].iter().copied(), + )?; + this.write_null(dest)?; + } + } else { + // `id` is always that of the active thread, so this is currently unreachable. + unreachable!(); + } + } + // Synchronization primitives "_umtx_op" => { let [obj, op, val, uaddr, uaddr2] = diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 3bcfe3f1e4dc9..347930c52f23d 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -121,13 +121,13 @@ impl UnixFileDescription for FileHandle { use std::os::windows::io::AsRawHandle; use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE, + ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE, }; use windows_sys::Win32::Storage::FileSystem::{ LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile, }; - let fh = self.file.as_raw_handle() as HANDLE; + let fh = self.file.as_raw_handle(); use FlockOp::*; let (ret, lock_nb) = match op { 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 f5da7b0170b6a..51c2434d68ac8 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -14,7 +14,7 @@ use crate::*; // The documentation of glibc complains that the kernel never exposes // TASK_COMM_LEN through the headers, so it's assumed to always be 16 bytes // long including a null terminator. -const TASK_COMM_LEN: usize = 16; +const TASK_COMM_LEN: u64 = 16; pub fn is_dyn_sym(name: &str) -> bool { matches!(name, "statx") @@ -96,7 +96,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // In case of glibc, the length of the output buffer must // be not shorter than TASK_COMM_LEN. let len = this.read_scalar(len)?; - let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64 { + let res = if len.to_target_usize(this)? >= TASK_COMM_LEN { match this.pthread_getname_np( this.read_scalar(thread)?, this.read_scalar(name)?, 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 5046e96508227..0281bb9f71dfc 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -186,7 +186,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = match this.pthread_setname_np( thread, this.read_scalar(name)?, - this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(), + this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?, /* truncate */ false, )? { ThreadNameResult::Ok => Scalar::from_u32(0), diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs index 3d990a1a04201..4b6615b3ea82c 100644 --- a/src/tools/miri/src/shims/unix/thread.rs +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -86,7 +86,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, thread: Scalar, name: Scalar, - name_max_len: usize, + name_max_len: u64, truncate: bool, ) -> InterpResult<'tcx, ThreadNameResult> { let this = self.eval_context_mut(); @@ -99,9 +99,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut name = this.read_c_str(name)?.to_owned(); // Comparing with `>=` to account for null terminator. - if name.len() >= name_max_len { + if name.len().to_u64() >= name_max_len { if truncate { - name.truncate(name_max_len.saturating_sub(1)); + name.truncate(name_max_len.saturating_sub(1).try_into().unwrap()); } else { return interp_ok(ThreadNameResult::NameTooLong); } diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs index 1b2ccd99ef9f4..0cbabc52d2a5d 100644 --- a/src/tools/miri/src/shims/windows/env.rs +++ b/src/tools/miri/src/shims/windows/env.rs @@ -238,8 +238,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Of course we cannot use `windows_check_buffer_size` here since this uses // a different method for dealing with a too-small buffer than the other functions... let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?; - // The Windows docs just say that this is written on failure. But std - // seems to rely on it always being written. + // The Windows docs just say that this is written on failure, but std relies on it + // always being written. Also see . this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?; if success { Scalar::from_i32(1) // return TRUE diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index c80858c63639a..d822dd07fcd7b 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -195,69 +195,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // File related shims "NtWriteFile" => { - if !this.frame_in_std() { - throw_unsup_format!( - "`NtWriteFile` support is crude and just enough for stdout to work" - ); - } - let [ handle, - _event, - _apc_routine, - _apc_context, + event, + apc_routine, + apc_context, io_status_block, buf, n, byte_offset, - _key, + key, ] = this.check_shim(abi, sys_conv, link_name, args)?; - let handle = this.read_target_isize(handle)?; - let buf = this.read_pointer(buf)?; - let n = this.read_scalar(n)?.to_u32()?; - let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer - let io_status_block = this - .deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; - - if byte_offset != 0 { - throw_unsup_format!( - "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported" - ); - } - - let written = if handle == -11 || handle == -12 { - // stdout/stderr - use io::Write; - - let buf_cont = - this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?; - let res = if this.machine.mute_stdout_stderr { - Ok(buf_cont.len()) - } else if handle == -11 { - io::stdout().write(buf_cont) - } else { - io::stderr().write(buf_cont) - }; - // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that. - res.ok().map(|n| u32::try_from(n).unwrap()) - } else { - throw_unsup_format!( - "on Windows, writing to anything except stdout/stderr is not supported" - ) - }; - // We have to put the result into io_status_block. - if let Some(n) = written { - let io_status_information = - this.project_field_named(&io_status_block, "Information")?; - this.write_scalar( - Scalar::from_target_usize(n.into(), this), - &io_status_information, - )?; - } - // Return whether this was a success. >= 0 is success. - // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. - this.write_scalar( - Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }), + this.NtWriteFile( + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, + dest, + )?; + } + "NtReadFile" => { + let [ + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, + ] = this.check_shim(abi, sys_conv, link_name, args)?; + this.NtReadFile( + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, dest, )?; } @@ -322,6 +305,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = this.DeleteFileW(file_name)?; this.write_scalar(res, dest)?; } + "SetFilePointerEx" => { + let [file, distance_to_move, new_file_pointer, move_method] = + this.check_shim(abi, sys_conv, link_name, args)?; + let res = + this.SetFilePointerEx(file, distance_to_move, new_file_pointer, move_method)?; + this.write_scalar(res, dest)?; + } // Allocation "HeapAlloc" => { @@ -700,12 +690,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "GetStdHandle" => { let [which] = this.check_shim(abi, sys_conv, link_name, args)?; - let which = this.read_scalar(which)?.to_i32()?; - // We just make this the identity function, so we know later in `NtWriteFile` which - // one it is. This is very fake, but libtest needs it so we cannot make it a - // std-only shim. - // FIXME: this should return real HANDLEs when io support is added - this.write_scalar(Scalar::from_target_isize(which.into(), this), dest)?; + let res = this.GetStdHandle(which)?; + this.write_scalar(res, dest)?; } "CloseHandle" => { let [handle] = this.check_shim(abi, sys_conv, link_name, args)?; diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 7561bf45219ba..72e016c12e944 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -1,5 +1,6 @@ use std::fs::{Metadata, OpenOptions}; use std::io; +use std::io::SeekFrom; use std::path::PathBuf; use std::time::SystemTime; @@ -390,6 +391,267 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } + + fn NtWriteFile( + &mut self, + handle: &OpTy<'tcx>, // HANDLE + event: &OpTy<'tcx>, // HANDLE + apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE + apc_ctx: &OpTy<'tcx>, // PVOID + io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK + buf: &OpTy<'tcx>, // PVOID + n: &OpTy<'tcx>, // ULONG + byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER + key: &OpTy<'tcx>, // PULONG + dest: &MPlaceTy<'tcx>, // return type: NTSTATUS + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + let handle = this.read_handle(handle, "NtWriteFile")?; + let event = this.read_handle(event, "NtWriteFile")?; + let apc_routine = this.read_pointer(apc_routine)?; + let apc_ctx = this.read_pointer(apc_ctx)?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(n)?.to_u32()?; + let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null + let key = this.read_pointer(key)?; + let io_status_block = + this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; + + if event != Handle::Null { + throw_unsup_format!( + "`NtWriteFile` `Event` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_routine)? { + throw_unsup_format!( + "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_ctx)? { + throw_unsup_format!( + "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported" + ); + } + + if byte_offset != 0 { + throw_unsup_format!( + "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(key)? { + throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported"); + } + + let fd = match handle { + Handle::File(fd) => fd, + _ => this.invalid_handle("NtWriteFile")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? }; + + // Windows writes the output code to IO_STATUS_BLOCK.Status, and number of bytes written + // to IO_STATUS_BLOCK.Information. + // The status block value and the returned value don't need to match - but + // for the cases implemented by miri so far, we can choose to decide that they do. + let io_status = { + let anon = this.project_field_named(&io_status_block, "Anonymous")?; + this.project_field_named(&anon, "Status")? + }; + let io_status_info = this.project_field_named(&io_status_block, "Information")?; + + let finish = { + let io_status = io_status.clone(); + let io_status_info = io_status_info.clone(); + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: u32, + io_status: MPlaceTy<'tcx>, + io_status_info: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(read_size) => { + assert!(read_size <= count.try_into().unwrap()); + // This must fit since `count` fits. + this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?; + this.write_int(0, &io_status)?; + this.write_int(0, &dest) + } + Err(e) => { + this.write_int(0, &io_status_info)?; + let status = e.into_ntstatus(); + this.write_int(status, &io_status)?; + this.write_int(status, &dest) + } + }} + ) + }; + + desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?; + + // Return status is written to `dest` and `io_status_block` on callback completion. + interp_ok(()) + } + + fn NtReadFile( + &mut self, + handle: &OpTy<'tcx>, // HANDLE + event: &OpTy<'tcx>, // HANDLE + apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE + apc_ctx: &OpTy<'tcx>, // PVOID + io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK + buf: &OpTy<'tcx>, // PVOID + n: &OpTy<'tcx>, // ULONG + byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER + key: &OpTy<'tcx>, // PULONG + dest: &MPlaceTy<'tcx>, // return type: NTSTATUS + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + let handle = this.read_handle(handle, "NtReadFile")?; + let event = this.read_handle(event, "NtReadFile")?; + let apc_routine = this.read_pointer(apc_routine)?; + let apc_ctx = this.read_pointer(apc_ctx)?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(n)?.to_u32()?; + let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null + let key = this.read_pointer(key)?; + let io_status_block = + this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; + + if event != Handle::Null { + throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported"); + } + + if !this.ptr_is_null(apc_routine)? { + throw_unsup_format!( + "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_ctx)? { + throw_unsup_format!( + "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported" + ); + } + + if byte_offset != 0 { + throw_unsup_format!( + "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(key)? { + throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported"); + } + + // See NtWriteFile above for commentary on this + let io_status = { + let anon = this.project_field_named(&io_status_block, "Anonymous")?; + this.project_field_named(&anon, "Status")? + }; + let io_status_info = this.project_field_named(&io_status_block, "Information")?; + + let finish = { + let io_status = io_status.clone(); + let io_status_info = io_status_info.clone(); + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: u32, + io_status: MPlaceTy<'tcx>, + io_status_info: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(read_size) => { + assert!(read_size <= count.try_into().unwrap()); + // This must fit since `count` fits. + this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?; + this.write_int(0, &io_status)?; + this.write_int(0, &dest) + } + Err(e) => { + this.write_int(0, &io_status_info)?; + let status = e.into_ntstatus(); + this.write_int(status, &io_status)?; + this.write_int(status, &dest) + } + }} + ) + }; + + let fd = match handle { + Handle::File(fd) => fd, + _ => this.invalid_handle("NtWriteFile")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? }; + + desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?; + + // See NtWriteFile for commentary on this + interp_ok(()) + } + + fn SetFilePointerEx( + &mut self, + file: &OpTy<'tcx>, // HANDLE + dist_to_move: &OpTy<'tcx>, // LARGE_INTEGER + new_fp: &OpTy<'tcx>, // PLARGE_INTEGER + move_method: &OpTy<'tcx>, // DWORD + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns BOOL (i32 on Windows) + let this = self.eval_context_mut(); + let file = this.read_handle(file, "SetFilePointerEx")?; + let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?; + let new_fp_ptr = this.read_pointer(new_fp)?; + let move_method = this.read_scalar(move_method)?.to_u32()?; + + let fd = match file { + Handle::File(fd) => fd, + _ => this.invalid_handle("SetFilePointerEx")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { + throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles"); + }; + + let file_begin = this.eval_windows_u32("c", "FILE_BEGIN"); + let file_current = this.eval_windows_u32("c", "FILE_CURRENT"); + let file_end = this.eval_windows_u32("c", "FILE_END"); + + let seek = if move_method == file_begin { + SeekFrom::Start(dist_to_move.try_into().unwrap()) + } else if move_method == file_current { + SeekFrom::Current(dist_to_move) + } else if move_method == file_end { + SeekFrom::End(dist_to_move) + } else { + throw_unsup_format!("Invalid move method: {move_method}") + }; + + match desc.seek(this.machine.communicate(), seek)? { + Ok(n) => { + if !this.ptr_is_null(new_fp_ptr)? { + this.write_scalar( + Scalar::from_i64(n.try_into().unwrap()), + &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?, + )?; + } + interp_ok(this.eval_windows("c", "TRUE")) + } + Err(e) => { + this.set_last_error(e)?; + interp_ok(this.eval_windows("c", "FALSE")) + } + } + } } /// Windows FILETIME is measured in 100-nanosecs since 1601 @@ -401,7 +663,7 @@ fn extract_windows_epoch<'tcx>( Some(time) => { let duration = ecx.system_time_since_windows_epoch(&time)?; let duration_ticks = ecx.windows_ticks_for(duration)?; - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::as_conversions)] interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32))) } None => interp_ok(None), diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index eec6c62bebc73..5c04271fac590 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -70,8 +70,7 @@ impl Handle { Self::Null => 0, Self::Pseudo(pseudo_handle) => pseudo_handle.value(), Self::Thread(thread) => thread.to_u32(), - #[expect(clippy::cast_sign_loss)] - Self::File(fd) => fd as u32, + Self::File(fd) => fd.cast_unsigned(), // INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several // pages of Windows documentation. // 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0 @@ -124,11 +123,10 @@ impl Handle { Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null), Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)), Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))), - #[expect(clippy::cast_possible_wrap)] Self::FILE_DISCRIMINANT => { // This cast preserves all bits. assert_eq!(size_of_val(&data), size_of::()); - Some(Self::File(data as FdNum)) + Some(Self::File(data.cast_signed())) } Self::INVALID_DISCRIMINANT => Some(Self::Invalid), _ => None, @@ -156,8 +154,7 @@ impl Handle { pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar { // 64-bit handles are sign extended 32-bit handles // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication - #[expect(clippy::cast_possible_wrap)] // we want it to wrap - let signed_handle = self.to_packed() as i32; + let signed_handle = self.to_packed().cast_signed(); Scalar::from_target_isize(signed_handle.into(), cx) } @@ -171,9 +168,8 @@ impl Handle { ) -> InterpResult<'tcx, Result> { let sign_extended_handle = handle.to_target_isize(cx)?; - #[expect(clippy::cast_sign_loss)] // we want to lose the sign let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) { - signed_handle as u32 + signed_handle.cast_unsigned() } else { // if a handle doesn't fit in an i32, it isn't valid. return interp_ok(Err(HandleError::InvalidHandle)); @@ -224,6 +220,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ))) } + fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + let which = this.read_scalar(which)?.to_i32()?; + + let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?; + let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?; + let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?; + + // These values don't mean anything on Windows, but Miri unconditionally sets them up to the + // unix in/out/err descriptors. So we take advantage of that. + // Due to the `Handle` encoding, these values will not be directly exposed to the user. + let fd_num = if which == stdin { + 0 + } else if which == stdout { + 1 + } else if which == stderr { + 2 + } else { + throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}") + }; + let handle = Handle::File(fd_num); + interp_ok(handle.to_scalar(this)) + } + fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/x86/gfni.rs b/src/tools/miri/src/shims/x86/gfni.rs index 4774ec9f9d8f5..a91c74283fd85 100644 --- a/src/tools/miri/src/shims/x86/gfni.rs +++ b/src/tools/miri/src/shims/x86/gfni.rs @@ -133,12 +133,12 @@ fn affine_transform<'tcx>( // This is a evaluated at compile time. Trait based conversion is not available. /// See for the /// definition of `gf_inv` which was used for the creation of this table. -#[expect(clippy::cast_possible_truncation)] static TABLE: [u8; 256] = { let mut array = [0; 256]; let mut i = 1; while i < 256 { + #[expect(clippy::as_conversions)] // no `try_from` in const... let mut x = i as u8; let mut y = gf2p8_mul(x, x); x = y; @@ -160,7 +160,7 @@ static TABLE: [u8; 256] = { /// polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1. /// See for details. // This is a const function. Trait based conversion is not available. -#[expect(clippy::cast_possible_truncation)] +#[expect(clippy::as_conversions)] const fn gf2p8_mul(left: u8, right: u8) -> u8 { // This implementation is based on the `gf2p8mul_byte` definition found inside the Intel intrinsics guide. // See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index e57217dc6f299..ac59cc2dfeb39 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1110,7 +1110,7 @@ fn pmulhrsw<'tcx>( // The result of this operation can overflow a signed 16-bit integer. // When `left` and `right` are -0x8000, the result is 0x8000. - #[expect(clippy::cast_possible_truncation)] + #[expect(clippy::as_conversions)] let res = res as i16; ecx.write_scalar(Scalar::from_i16(res), &dest)?; diff --git a/src/tools/miri/src/shims/x86/sha.rs b/src/tools/miri/src/shims/x86/sha.rs index 6d2c151243ca1..23c83553f3b32 100644 --- a/src/tools/miri/src/shims/x86/sha.rs +++ b/src/tools/miri/src/shims/x86/sha.rs @@ -43,7 +43,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We reverse the order because x86 is little endian but the copied implementation uses // big endian. for (i, part) in val.into_iter().rev().enumerate() { - let projected = &ecx.project_index(dest, i.try_into().unwrap())?; + let projected = &ecx.project_index(dest, i.to_u64())?; ecx.write_scalar(Scalar::from_u32(part), projected)?; } interp_ok(()) diff --git a/src/tools/miri/src/shims/x86/sse42.rs b/src/tools/miri/src/shims/x86/sse42.rs index 02336a722f7ed..66bff328626a1 100644 --- a/src/tools/miri/src/shims/x86/sse42.rs +++ b/src/tools/miri/src/shims/x86/sse42.rs @@ -440,7 +440,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let crc = if bit_size == 64 { // The 64-bit version will only consider the lower 32 bits, // while the upper 32 bits get discarded. - #[expect(clippy::cast_possible_truncation)] + #[expect(clippy::as_conversions)] u128::from((left.to_u64()? as u32).reverse_bits()) } else { u128::from(left.to_u32()?.reverse_bits()) diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml index 653228a5e3db6..fa833b51fa31b 100644 --- a/src/tools/miri/test_dependencies/Cargo.toml +++ b/src/tools/miri/test_dependencies/Cargo.toml @@ -25,6 +25,13 @@ page_size = "0.6" tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", "fs", "sync", "signal", "io-util"] } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", "Win32_Security"] } +windows-sys = { version = "0.59", features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_Storage_FileSystem", + "Win32_Security", + "Win32_System_IO", + "Wdk_Storage_FileSystem", +] } [workspace] diff --git a/src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.rs b/src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.rs deleted file mode 100644 index 3ef194c5c7172..0000000000000 --- a/src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@ignore-target: windows # No libc IO on Windows - -fn main() -> std::io::Result<()> { - let mut bytes = [0u8; 512]; - unsafe { - libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: `read` from stdin not available when isolation is enabled - } - Ok(()) -} diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs index 42c3a9619d464..2c01b0132b824 100644 --- a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs @@ -2,6 +2,7 @@ //@compile-flags: -Zmiri-disable-stacked-borrows // Needs atomic accesses larger than the pointer size //@ignore-bitwidth: 64 +//@ignore-target: mips- use std::sync::atomic::{AtomicI64, Ordering}; diff --git a/src/tools/miri/tests/fail/shims/isolated_stdin.rs b/src/tools/miri/tests/fail/shims/isolated_stdin.rs new file mode 100644 index 0000000000000..9f809039adad7 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/isolated_stdin.rs @@ -0,0 +1,12 @@ +//@error-in-other-file: `read` from stdin not available when isolation is enabled +//@normalize-stderr-test: "src/sys/.*\.rs" -> "$$FILE" +//@normalize-stderr-test: "\nLL \| .*" -> "" +//@normalize-stderr-test: "\n... .*" -> "" +//@normalize-stderr-test: "\| +[|_^]+" -> "| ^" +//@normalize-stderr-test: "\n *= note:.*" -> "" +use std::io::{self, Read}; + +fn main() { + let mut bytes = [0u8; 512]; + io::stdin().read(&mut bytes).unwrap(); +} diff --git a/src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.stderr b/src/tools/miri/tests/fail/shims/isolated_stdin.stderr similarity index 58% rename from src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.stderr rename to src/tools/miri/tests/fail/shims/isolated_stdin.stderr index bb7e8cef5dcd2..1a4d7e963298e 100644 --- a/src/tools/miri/tests/fail-dep/libc/fs/isolated_stdin.stderr +++ b/src/tools/miri/tests/fail/shims/isolated_stdin.stderr @@ -1,13 +1,14 @@ error: unsupported operation: `read` from stdin not available when isolation is enabled - --> tests/fail-dep/libc/fs/isolated_stdin.rs:LL:CC + --> RUSTLIB/std/$FILE:LL:CC | -LL | libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `read` from stdin not available when isolation is enabled + | ^ `read` from stdin not available when isolation is enabled | = help: set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation; = help: or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning - = note: BACKTRACE: - = note: inside `main` at tests/fail-dep/libc/fs/isolated_stdin.rs:LL:CC +note: inside `main` + --> tests/fail/shims/isolated_stdin.rs:LL:CC + | + | ^ note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/panic/transmute_fat2.rs b/src/tools/miri/tests/panic/transmute_fat2.rs index 0205433ad9fb9..e695ff2d57ba5 100644 --- a/src/tools/miri/tests/panic/transmute_fat2.rs +++ b/src/tools/miri/tests/panic/transmute_fat2.rs @@ -5,6 +5,8 @@ fn main() { let bad = unsafe { std::mem::transmute::(42 << 64) }; #[cfg(all(target_endian = "little", target_pointer_width = "32"))] let bad = unsafe { std::mem::transmute::(42) }; + #[cfg(all(target_endian = "big", target_pointer_width = "32"))] + let bad = unsafe { std::mem::transmute::(42 << 32) }; // This created a slice with length 0, so the following will fail the bounds check. bad[0]; } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs index cffcf4a867fee..06a8cc7f4879a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs @@ -11,6 +11,10 @@ fn main() { assert!(libc::fcntl(1, libc::F_DUPFD, 0) >= 0); } + // Although `readlink` and `stat` require disable-isolation mode + // to properly run, they are tested with isolation mode on to check the error emitted + // with `-Zmiri-isolation-error=warn-nobacktrace`. + // test `readlink` let mut buf = vec![0; "foo_link.txt".len() + 1]; unsafe { diff --git a/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs b/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs new file mode 100644 index 0000000000000..9a868128d2716 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs @@ -0,0 +1,51 @@ +//@only-target: freebsd +//@compile-flags: -Zmiri-num-cpus=256 + +use std::mem; + +fn getaffinity() { + let mut set: libc::cpuset_t = unsafe { mem::zeroed() }; + unsafe { + if libc::cpuset_getaffinity( + libc::CPU_LEVEL_WHICH, + libc::CPU_WHICH_PID, + -1, + size_of::(), + &mut set, + ) == 0 + { + assert!(libc::CPU_COUNT(&set) == 256); + } + } +} + +fn get_small_cpu_mask() { + let mut set: libc::cpuset_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() }; + + // 256 CPUs so we need 32 bytes to represent this mask. + // According to Freebsd only when `cpusetsize` is smaller than this value, does it return with ERANGE + + let err = unsafe { + libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 32, &mut set) + }; + assert_eq!(err, 0, "Success Expected"); + + // 31 is not enough, so it should fail. + let err = unsafe { + libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 31, &mut set) + }; + assert_eq!(err, -1, "Expected Failure"); + assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE); + + // Zero should fail as well. + let err = unsafe { + libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 0, &mut set) + }; + assert_eq!(err, -1, "Expected Failure"); + assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE); +} + +fn main() { + getaffinity(); + get_small_cpu_mask(); +} diff --git a/src/tools/miri/tests/pass-dep/shims/windows-fs.rs b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs index 698ca4e0b4ba6..4ca19046b676b 100644 --- a/src/tools/miri/tests/pass-dep/shims/windows-fs.rs +++ b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs @@ -2,25 +2,28 @@ //@compile-flags: -Zmiri-disable-isolation #![allow(nonstandard_style)] -use std::io::ErrorKind; +use std::io::{ErrorKind, Read, Write}; use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::AsRawHandle; use std::path::Path; -use std::ptr; +use std::{fs, ptr}; #[path = "../../utils/mod.rs"] mod utils; +use windows_sys::Wdk::Storage::FileSystem::{NtReadFile, NtWriteFile}; use windows_sys::Win32::Foundation::{ CloseHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_IO_DEVICE, GENERIC_READ, GENERIC_WRITE, GetLastError, RtlNtStatusToDosError, STATUS_ACCESS_DENIED, - STATUS_IO_DEVICE_ERROR, + STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError, }; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW, - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, - GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, FILE_CURRENT, + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, + FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFilePointerEx, }; +use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; fn main() { unsafe { @@ -31,6 +34,8 @@ fn main() { test_open_dir_reparse(); test_delete_file(); test_ntstatus_to_dos(); + test_file_read_write(); + test_file_seek(); } } @@ -199,13 +204,13 @@ unsafe fn test_open_dir_reparse() { unsafe fn test_delete_file() { let temp = utils::tmp().join("test_delete_file.txt"); let raw_path = to_wide_cstr(&temp); - let _ = std::fs::File::create(&temp).unwrap(); + let _ = fs::File::create(&temp).unwrap(); if DeleteFileW(raw_path.as_ptr()) == 0 { panic!("Failed to delete file"); } - match std::fs::File::open(temp) { + match fs::File::open(temp) { Ok(_) => panic!("File not deleted"), Err(e) => assert!(e.kind() == ErrorKind::NotFound, "File not deleted"), } @@ -217,6 +222,82 @@ unsafe fn test_ntstatus_to_dos() { assert_eq!(RtlNtStatusToDosError(STATUS_ACCESS_DENIED), ERROR_ACCESS_DENIED); } +unsafe fn test_file_read_write() { + let temp = utils::tmp().join("test_file_read_write.txt"); + let file = fs::File::create(&temp).unwrap(); + let handle = file.as_raw_handle(); + + // Testing NtWriteFile doesn't clobber the error + SetLastError(1234); + + let text = b"Example text!"; + let mut status = std::mem::zeroed::(); + let out = NtWriteFile( + handle, + ptr::null_mut(), + None, + ptr::null_mut(), + &mut status, + text.as_ptr().cast(), + text.len() as u32, + ptr::null_mut(), + ptr::null_mut(), + ); + + assert_eq!(out, status.Anonymous.Status); + assert_eq!(out, STATUS_SUCCESS); + assert_eq!(GetLastError(), 1234); + + let file = fs::File::open(&temp).unwrap(); + let handle = file.as_raw_handle(); + + // Testing NtReadFile doesn't clobber the error + SetLastError(1234); + + let mut buffer = vec![0; 13]; + let out = NtReadFile( + handle, + ptr::null_mut(), + None, + ptr::null_mut(), + &mut status, + buffer.as_mut_ptr().cast(), + buffer.len() as u32, + ptr::null_mut(), + ptr::null_mut(), + ); + + assert_eq!(out, status.Anonymous.Status); + assert_eq!(out, STATUS_SUCCESS); + assert_eq!(buffer, text); + assert_eq!(GetLastError(), 1234); +} + +unsafe fn test_file_seek() { + let temp = utils::tmp().join("test_file_seek.txt"); + let mut file = fs::File::options().create(true).write(true).read(true).open(&temp).unwrap(); + file.write_all(b"Hello, World!\n").unwrap(); + + let handle = file.as_raw_handle(); + + if SetFilePointerEx(handle, 7, ptr::null_mut(), FILE_BEGIN) == 0 { + panic!("Failed to seek"); + } + + let mut buf = vec![0; 5]; + file.read(&mut buf).unwrap(); + assert_eq!(buf, b"World"); + + let mut pos = 0; + if SetFilePointerEx(handle, -7, &mut pos, FILE_CURRENT) == 0 { + panic!("Failed to seek"); + } + buf.truncate(2); + file.read_exact(&mut buf).unwrap(); + assert_eq!(buf, b", "); + assert_eq!(pos, 5); +} + fn to_wide_cstr(path: &Path) -> Vec { let mut raw_path = path.as_os_str().encode_wide().collect::>(); raw_path.extend([0, 0]); diff --git a/src/tools/miri/tests/pass/atomic.rs b/src/tools/miri/tests/pass/atomic.rs index 2b2e89e6d70fc..3de34e570c7ee 100644 --- a/src/tools/miri/tests/pass/atomic.rs +++ b/src/tools/miri/tests/pass/atomic.rs @@ -7,15 +7,17 @@ #![allow(static_mut_refs)] use std::sync::atomic::Ordering::*; -use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicPtr, AtomicU64, compiler_fence, fence}; +use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicPtr, AtomicUsize, compiler_fence, fence}; fn main() { atomic_bool(); atomic_all_ops(); - atomic_u64(); atomic_fences(); atomic_ptr(); weak_sometimes_fails(); + + #[cfg(target_has_atomic = "64")] + atomic_u64(); } fn atomic_bool() { @@ -36,25 +38,10 @@ fn atomic_bool() { } } -// There isn't a trait to use to make this generic, so just use a macro -macro_rules! compare_exchange_weak_loop { - ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => { - loop { - match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) { - Ok(n) => { - assert_eq!(n, $from); - break; - } - Err(n) => assert_eq!(n, $from), - } - } - }; -} - /// Make sure we can handle all the intrinsics fn atomic_all_ops() { static ATOMIC: AtomicIsize = AtomicIsize::new(0); - static ATOMIC_UNSIGNED: AtomicU64 = AtomicU64::new(0); + static ATOMIC_UNSIGNED: AtomicUsize = AtomicUsize::new(0); let load_orders = [Relaxed, Acquire, SeqCst]; let stored_orders = [Relaxed, Release, SeqCst]; @@ -94,9 +81,26 @@ fn atomic_all_ops() { } } +#[cfg(target_has_atomic = "64")] fn atomic_u64() { + use std::sync::atomic::AtomicU64; static ATOMIC: AtomicU64 = AtomicU64::new(0); + // There isn't a trait to use to make this generic, so just use a macro + macro_rules! compare_exchange_weak_loop { + ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => { + loop { + match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) { + Ok(n) => { + assert_eq!(n, $from); + break; + } + Err(n) => assert_eq!(n, $from), + } + } + }; + } + ATOMIC.store(1, SeqCst); assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1)); assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1)); diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index d0a7f245ee0ff..315637ff7ec73 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -17,20 +17,20 @@ mod utils; fn main() { test_path_conversion(); + test_file(); test_file_create_new(); + test_metadata(); + test_seek(); + test_errors(); + test_from_raw_os_error(); // Windows file handling is very incomplete. if cfg!(not(windows)) { - test_file(); - test_seek(); test_file_clone(); - test_metadata(); test_file_set_len(); test_file_sync(); - test_errors(); test_rename(); test_directory(); test_canonicalize(); - test_from_raw_os_error(); #[cfg(unix)] test_pread_pwrite(); } diff --git a/src/tools/miri/tests/pass/shims/io.rs b/src/tools/miri/tests/pass/shims/io.rs index 420ef95a0cbf4..a96b98c022746 100644 --- a/src/tools/miri/tests/pass/shims/io.rs +++ b/src/tools/miri/tests/pass/shims/io.rs @@ -1,8 +1,10 @@ -use std::io::{self, IsTerminal}; +use std::io::{self, IsTerminal, Write}; fn main() { - // We can't really assume that this is truly a terminal, and anyway on Windows Miri will always - // return `false` here, but we can check that the call succeeds. + io::stdout().write_all(b"stdout\n").unwrap(); + io::stderr().write_all(b"stderr\n").unwrap(); + + // We can't assume that this is truly a terminal, but we can check that the call succeeds. io::stdout().is_terminal(); // Ensure we can format `io::Error` created from OS errors diff --git a/src/tools/miri/tests/pass/shims/io.stderr b/src/tools/miri/tests/pass/shims/io.stderr new file mode 100644 index 0000000000000..af6415db3c724 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/io.stderr @@ -0,0 +1 @@ +stderr diff --git a/src/tools/miri/tests/pass/shims/io.stdout b/src/tools/miri/tests/pass/shims/io.stdout new file mode 100644 index 0000000000000..faa3a15c184b5 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/io.stdout @@ -0,0 +1 @@ +stdout diff --git a/tests/rustdoc-json/attrs/repr_combination.rs b/tests/rustdoc-json/attrs/repr_combination.rs index 0e8e2ef0d83e9..6fe29c5eac093 100644 --- a/tests/rustdoc-json/attrs/repr_combination.rs +++ b/tests/rustdoc-json/attrs/repr_combination.rs @@ -77,3 +77,7 @@ pub enum AlignedExplicitRepr { pub enum ReorderedAlignedExplicitRepr { First, } + +//@ is "$.index[?(@.name=='Transparent')].attrs" '["#[repr(transparent)]"]' +#[repr(transparent)] +pub struct Transparent(i64); diff --git a/tests/rustdoc-json/attrs/repr_transparent.rs b/tests/rustdoc-json/attrs/repr_transparent.rs deleted file mode 100644 index 1e634ca901dc4..0000000000000 --- a/tests/rustdoc-json/attrs/repr_transparent.rs +++ /dev/null @@ -1,37 +0,0 @@ -#![no_std] - -// Rustdoc JSON *only* includes `#[repr(transparent)]` -// if the transparency is public API: -// - if a non-1-ZST field exists, it has to be public -// - otherwise, all fields are 1-ZST and at least one of them is public -// -// More info: https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent - -// Here, the non-1-ZST field is public. -// We expect `#[repr(transparent)]` in the attributes. -// -//@ is "$.index[?(@.name=='Transparent')].attrs" '["#[repr(transparent)]"]' -#[repr(transparent)] -pub struct Transparent(pub i64); - -// Here the non-1-ZST field isn't public, so the attribute isn't included. -// -//@ has "$.index[?(@.name=='TransparentNonPub')]" -//@ is "$.index[?(@.name=='TransparentNonPub')].attrs" '[]' -#[repr(transparent)] -pub struct TransparentNonPub(i64); - -// Only 1-ZST fields here, and one of them is public. -// We expect `#[repr(transparent)]` in the attributes. -// -//@ is "$.index[?(@.name=='AllZst')].attrs" '["#[repr(transparent)]"]' -#[repr(transparent)] -pub struct AllZst<'a>(pub core::marker::PhantomData<&'a ()>, ()); - -// Only 1-ZST fields here but none of them are public. -// The attribute isn't included. -// -//@ has "$.index[?(@.name=='AllZstNotPublic')]" -//@ is "$.index[?(@.name=='AllZstNotPublic')].attrs" '[]' -#[repr(transparent)] -pub struct AllZstNotPublic<'a>(core::marker::PhantomData<&'a ()>, ()); diff --git a/tests/rustdoc-ui/intra-doc/warning.stderr b/tests/rustdoc-ui/intra-doc/warning.stderr index 3a06f1787e093..9b3f6f822d75b 100644 --- a/tests/rustdoc-ui/intra-doc/warning.stderr +++ b/tests/rustdoc-ui/intra-doc/warning.stderr @@ -69,29 +69,19 @@ LL | bar [BarC] bar = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `BarD` - --> $DIR/warning.rs:45:9 + --> $DIR/warning.rs:45:20 | LL | #[doc = "Foo\nbar [BarD] bar\nbaz"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^ no item named `BarD` in scope | - = note: the link appears in this line: - - bar [BarD] bar - ^^^^ - = note: no item named `BarD` in scope = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `BarF` - --> $DIR/warning.rs:54:4 + --> $DIR/warning.rs:54:15 | LL | f!("Foo\nbar [BarF] bar\nbaz"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^ no item named `BarF` in scope | - = note: the link appears in this line: - - bar [BarF] bar - ^^^^ - = note: no item named `BarF` in scope = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` = note: this warning originates in the macro `f` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -112,29 +102,19 @@ LL | * time to introduce a link [error] = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `error` - --> $DIR/warning.rs:68:9 + --> $DIR/warning.rs:68:23 | LL | #[doc = "single line [error]"] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ no item named `error` in scope | - = note: the link appears in this line: - - single line [error] - ^^^^^ - = note: no item named `error` in scope = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `error` - --> $DIR/warning.rs:71:9 + --> $DIR/warning.rs:71:41 | LL | #[doc = "single line with \"escaping\" [error]"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ no item named `error` in scope | - = note: the link appears in this line: - - single line with "escaping" [error] - ^^^^^ - = note: no item named `error` in scope = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` warning: unresolved link to `error` diff --git a/tests/rustdoc-ui/lints/bare-urls-limit.rs b/tests/rustdoc-ui/lints/bare-urls-limit.rs new file mode 100644 index 0000000000000..f64154b049698 --- /dev/null +++ b/tests/rustdoc-ui/lints/bare-urls-limit.rs @@ -0,0 +1,12 @@ +//@ check-fail + +#![deny(rustdoc::bare_urls)] + +// examples of bare urls that are beyond our ability to generate suggestions for + +// this falls through every heuristic in `source_span_for_markdown_range`, +// and thus does not get any suggestion. +#[doc = "good: \n\n"] +//~^ ERROR this URL is not a hyperlink +#[doc = "bad: https://example.com/"] +pub fn duplicate_raw() {} diff --git a/tests/rustdoc-ui/lints/bare-urls-limit.stderr b/tests/rustdoc-ui/lints/bare-urls-limit.stderr new file mode 100644 index 0000000000000..9573665cb1311 --- /dev/null +++ b/tests/rustdoc-ui/lints/bare-urls-limit.stderr @@ -0,0 +1,18 @@ +error: this URL is not a hyperlink + --> $DIR/bare-urls-limit.rs:9:9 + | +LL | #[doc = "good: \n\n"] + | _________^ +LL | | +LL | | #[doc = "bad: https://example.com/"] + | |___________________________________^ + | + = note: bare URLs are not automatically turned into clickable links +note: the lint level is defined here + --> $DIR/bare-urls-limit.rs:3:9 + | +LL | #![deny(rustdoc::bare_urls)] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/lints/bare-urls.fixed b/tests/rustdoc-ui/lints/bare-urls.fixed index 7938d715199de..a91573146b823 100644 --- a/tests/rustdoc-ui/lints/bare-urls.fixed +++ b/tests/rustdoc-ui/lints/bare-urls.fixed @@ -38,6 +38,16 @@ //~^ ERROR this URL is not a hyperlink pub fn c() {} +#[doc = "here's a thing: "] +//~^ ERROR this URL is not a hyperlink +pub fn f() {} + +/// +//~^ ERROR this URL is not a hyperlink +#[doc = ""] +//~^ ERROR this URL is not a hyperlink +pub fn mixed() {} + /// /// [a](http://a.com) /// [b] diff --git a/tests/rustdoc-ui/lints/bare-urls.rs b/tests/rustdoc-ui/lints/bare-urls.rs index 75f42b78ffbbe..5b008cdafa232 100644 --- a/tests/rustdoc-ui/lints/bare-urls.rs +++ b/tests/rustdoc-ui/lints/bare-urls.rs @@ -38,6 +38,16 @@ //~^ ERROR this URL is not a hyperlink pub fn c() {} +#[doc = "here's a thing: https://example.com/"] +//~^ ERROR this URL is not a hyperlink +pub fn f() {} + +/// https://example.com/sugar +//~^ ERROR this URL is not a hyperlink +#[doc = "https://example.com/raw"] +//~^ ERROR this URL is not a hyperlink +pub fn mixed() {} + /// /// [a](http://a.com) /// [b] diff --git a/tests/rustdoc-ui/lints/bare-urls.stderr b/tests/rustdoc-ui/lints/bare-urls.stderr index ddfc387eaf66e..e1108c7e7f84e 100644 --- a/tests/rustdoc-ui/lints/bare-urls.stderr +++ b/tests/rustdoc-ui/lints/bare-urls.stderr @@ -207,5 +207,41 @@ help: use an automatic link instead LL | /// hey! | + + -error: aborting due to 17 previous errors +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:41:26 + | +LL | #[doc = "here's a thing: https://example.com/"] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | #[doc = "here's a thing: "] + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:45:5 + | +LL | /// https://example.com/sugar + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:47:10 + | +LL | #[doc = "https://example.com/raw"] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | #[doc = ""] + | + + + +error: aborting due to 20 previous errors diff --git a/tests/rustdoc-ui/unescaped_backticks.stderr b/tests/rustdoc-ui/unescaped_backticks.stderr index d93aaf5f3ca94..1bcb88e108f20 100644 --- a/tests/rustdoc-ui/unescaped_backticks.stderr +++ b/tests/rustdoc-ui/unescaped_backticks.stderr @@ -628,10 +628,10 @@ LL | /// or even to add a number `n` to 42 (`add(42, n)\`)! | + error: unescaped backtick - --> $DIR/unescaped_backticks.rs:108:9 + --> $DIR/unescaped_backticks.rs:108:10 | LL | #[doc = "`"] - | ^^^ + | ^ | = help: the opening or closing backtick of an inline code may be missing = help: if you meant to use a literal backtick, escape it @@ -639,10 +639,10 @@ LL | #[doc = "`"] to this: \` error: unescaped backtick - --> $DIR/unescaped_backticks.rs:115:9 + --> $DIR/unescaped_backticks.rs:115:26 | LL | #[doc = concat!("\\", "`")] - | ^^^^^^^^^^^^^^^^^^^^ + | ^ | = help: the opening backtick of an inline code may be missing change: \` diff --git a/tests/ui/async-await/async-drop/open-drop-error2.rs b/tests/ui/async-await/async-drop/open-drop-error2.rs new file mode 100644 index 0000000000000..b2a7b68190ec1 --- /dev/null +++ b/tests/ui/async-await/async-drop/open-drop-error2.rs @@ -0,0 +1,21 @@ +//@compile-flags: -Zvalidate-mir -Zinline-mir=yes --crate-type=lib + +#![feature(async_drop)] +#![allow(incomplete_features)] + +use std::{ + future::{Future, async_drop_in_place}, + pin::pin, + task::Context, +}; + +fn wrong() -> impl Sized { + //~^ ERROR: the size for values of type `str` cannot be known at compilation time + *"abc" // Doesn't implement Sized +} +fn weird(context: &mut Context<'_>) { + let mut e = wrong(); + let h = unsafe { async_drop_in_place(&raw mut e) }; + let i = pin!(h); + i.poll(context); +} diff --git a/tests/ui/async-await/async-drop/open-drop-error2.stderr b/tests/ui/async-await/async-drop/open-drop-error2.stderr new file mode 100644 index 0000000000000..e849829b1c78b --- /dev/null +++ b/tests/ui/async-await/async-drop/open-drop-error2.stderr @@ -0,0 +1,19 @@ +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/open-drop-error2.rs:12:15 + | +LL | fn wrong() -> impl Sized { + | ^^^^^^^^^^ doesn't have a size known at compile-time +LL | +LL | *"abc" // Doesn't implement Sized + | ------ return type was inferred to be `str` here + | + = help: the trait `Sized` is not implemented for `str` +help: references are always `Sized`, even if they point to unsized data; consider not dereferencing the expression + | +LL - *"abc" // Doesn't implement Sized +LL + "abc" // Doesn't implement Sized + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.