Auto merge of #141437 - matthiaskrgr:rollup-jjagkxd, r=matthiaskrgr

Rollup of 7 pull requests

Successful merges:

 - #136400 (Improve handling of rustdoc lints when used with raw doc fragments.)
 - #140967 (Async drop poll shim for error dropee generates noop body)
 - #141019 (Update std doctests for android)
 - #141109 (discuss deadlocks in the std::io::pipe() example)
 - #141126 (rustdoc JSON: Don't apply `#[repr]` privacy heuristics)
 - #141376 (Rename `kw::Empty` as `sym::empty`.)
 - #141383 (Miri subtree update)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors
2025-05-23 15:13:45 +00:00
78 changed files with 1024 additions and 348 deletions

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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:?}"),
})
};

View File

@@ -85,7 +85,7 @@ impl From<Ident> 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

View File

@@ -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;
}

View File

@@ -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"),
};

View File

@@ -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<Span> {
/// 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<usize>,
fragments: &[DocFragment],
) -> Option<Span> {
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();

View File

@@ -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<Symbol>` 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: "<!dummy!>", // use this instead of `kw::Empty` for symbols that won't be used
dummy: "<!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<Symbol>` 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

View File

@@ -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(),
)
}

View File

@@ -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(())
/// # }
/// ```

View File

@@ -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";

View File

@@ -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<()> {

View File

@@ -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...");

View File

@@ -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<io::Stdout> for Stdio {
/// # Ok(())
/// # }
/// #
/// # if cfg!(unix) {
/// # if cfg!(all(unix, not(target_os = "android"))) {
/// # test().unwrap();
/// # }
/// ```
@@ -1724,7 +1724,7 @@ impl From<io::Stderr> 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;
///

View File

@@ -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| {

View File

@@ -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<usize>| {
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,
);
}
});
};

View File

@@ -30,7 +30,7 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // 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.
///

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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",

View File

@@ -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.

View File

@@ -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

View File

@@ -1 +1 @@
ac17c3486c6fdfbb0c3c18b99f3d8dfbff625d29
2b96ddca1272960623e41829439df8dae82d20af

View File

@@ -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(

View File

@@ -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());
}

View File

@@ -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")
});

View File

@@ -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<V> UniValMap<V> {
/// 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<V> UniValMap<V> {
/// 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<V> {
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<V> {
/// 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()] }
}
}

View File

@@ -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);
}

View File

@@ -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<VectorIdx> 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)
}
}

View File

@@ -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()
}
}

View File

@@ -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 =

View File

@@ -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,

View File

@@ -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: _,

View File

@@ -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)?,
)?;
}

View File

@@ -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<Self>,
_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<Self>,
_communicate_allowed: bool,

View File

@@ -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)?;

View File

@@ -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<io::Error> for IoError {
fn from(value: io::Error) -> Self {
IoError::HostError(value)

View File

@@ -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::<libc::Dl_info>::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))
}
}

View File

@@ -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),

View File

@@ -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] =

View File

@@ -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 {

View File

@@ -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)?,

View File

@@ -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),

View File

@@ -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);
}

View File

@@ -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 <https://github.com/rust-lang/rust/issues/141254>.
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
if success {
Scalar::from_i32(1) // return TRUE

View File

@@ -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)?;

View File

@@ -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<usize, IoError>| {
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<usize, IoError>| {
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),

View File

@@ -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::<FdNum>());
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<Self, HandleError>> {
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();

View File

@@ -133,12 +133,12 @@ fn affine_transform<'tcx>(
// This is a evaluated at compile time. Trait based conversion is not available.
/// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> 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 <https://www.corsix.org/content/galois-field-instructions-2021-cpus> 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

View File

@@ -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)?;

View File

@@ -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(())

View File

@@ -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())

View File

@@ -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]

View File

@@ -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(())
}

View File

@@ -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};

View File

@@ -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();
}

View File

@@ -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

View File

@@ -5,6 +5,8 @@ fn main() {
let bad = unsafe { std::mem::transmute::<u128, &[u8]>(42 << 64) };
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
let bad = unsafe { std::mem::transmute::<u64, &[u8]>(42) };
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
let bad = unsafe { std::mem::transmute::<u64, &[u8]>(42 << 32) };
// This created a slice with length 0, so the following will fail the bounds check.
bad[0];
}

View File

@@ -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 {

View File

@@ -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::<libc::cpuset_t>(),
&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();
}

View File

@@ -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::<IO_STATUS_BLOCK>();
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<u16> {
let mut raw_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
raw_path.extend([0, 0]);

View File

@@ -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));

View File

@@ -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();
}

View File

@@ -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

View File

@@ -0,0 +1 @@
stderr

View File

@@ -0,0 +1 @@
stdout

View File

@@ -77,3 +77,7 @@ pub enum AlignedExplicitRepr {
pub enum ReorderedAlignedExplicitRepr {
First,
}
//@ is "$.index[?(@.name=='Transparent')].attrs" '["#[repr(transparent)]"]'
#[repr(transparent)]
pub struct Transparent(i64);

View File

@@ -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 ()>, ());

View File

@@ -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`

View File

@@ -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: <https://example.com/> \n\n"]
//~^ ERROR this URL is not a hyperlink
#[doc = "bad: https://example.com/"]
pub fn duplicate_raw() {}

View File

@@ -0,0 +1,18 @@
error: this URL is not a hyperlink
--> $DIR/bare-urls-limit.rs:9:9
|
LL | #[doc = "good: <https://example.com/> \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

View File

@@ -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() {}
/// <https://somewhere.com>
/// [a](http://a.com)
/// [b]

View File

@@ -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() {}
/// <https://somewhere.com>
/// [a](http://a.com)
/// [b]

View File

@@ -207,5 +207,41 @@ help: use an automatic link instead
LL | /// hey! <https://somewhere.com/a?hello=12&bye=11#xyz>
| + +
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: <https://example.com/>"]
| + +
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 | /// <https://example.com/sugar>
| + +
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 = "<https://example.com/raw>"]
| + +
error: aborting due to 20 previous errors

View File

@@ -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: \`

View File

@@ -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);
}

View File

@@ -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`.