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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:?}"),
|
||||
})
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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;
|
||||
///
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ac17c3486c6fdfbb0c3c18b99f3d8dfbff625d29
|
||||
2b96ddca1272960623e41829439df8dae82d20af
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
});
|
||||
|
||||
@@ -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()] }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: _,
|
||||
|
||||
@@ -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)?,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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] =
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
|
||||
12
src/tools/miri/tests/fail/shims/isolated_stdin.rs
Normal file
12
src/tools/miri/tests/fail/shims/isolated_stdin.rs
Normal 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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
1
src/tools/miri/tests/pass/shims/io.stderr
Normal file
1
src/tools/miri/tests/pass/shims/io.stderr
Normal file
@@ -0,0 +1 @@
|
||||
stderr
|
||||
1
src/tools/miri/tests/pass/shims/io.stdout
Normal file
1
src/tools/miri/tests/pass/shims/io.stdout
Normal file
@@ -0,0 +1 @@
|
||||
stdout
|
||||
@@ -77,3 +77,7 @@ pub enum AlignedExplicitRepr {
|
||||
pub enum ReorderedAlignedExplicitRepr {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='Transparent')].attrs" '["#[repr(transparent)]"]'
|
||||
#[repr(transparent)]
|
||||
pub struct Transparent(i64);
|
||||
|
||||
@@ -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 ()>, ());
|
||||
@@ -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`
|
||||
|
||||
12
tests/rustdoc-ui/lints/bare-urls-limit.rs
Normal file
12
tests/rustdoc-ui/lints/bare-urls-limit.rs
Normal 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() {}
|
||||
18
tests/rustdoc-ui/lints/bare-urls-limit.stderr
Normal file
18
tests/rustdoc-ui/lints/bare-urls-limit.stderr
Normal 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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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: \`
|
||||
|
||||
21
tests/ui/async-await/async-drop/open-drop-error2.rs
Normal file
21
tests/ui/async-await/async-drop/open-drop-error2.rs
Normal 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);
|
||||
}
|
||||
19
tests/ui/async-await/async-drop/open-drop-error2.stderr
Normal file
19
tests/ui/async-await/async-drop/open-drop-error2.stderr
Normal 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`.
|
||||
Reference in New Issue
Block a user