Auto merge of #157031 - GuillaumeGomez:rollup-FbegncN, r=GuillaumeGomez

Rollup of 10 pull requests

Successful merges:

 - rust-lang/rust#156970 (coverage: Use original HIR info for synthetic by-move coroutine bodies)
 - rust-lang/rust#157022 (MIR inlining: allow backends to opt-in to inlining intrinsics)
 - rust-lang/rust#157026 (miri subtree update)
 - rust-lang/rust#156390 (Constify Iterator-related methods and functions)
 - rust-lang/rust#156845 (Clarify "infinite size" in cyclic-type diagnostic refers to the type name)
 - rust-lang/rust#156955 (Fix const-eval of shared generic reborrows)
 - rust-lang/rust#156973 (Add uwtable annotation to modules when required)
 - rust-lang/rust#156985 (Limit the additional DLL to Windows)
 - rust-lang/rust#156988 (interpret/validity: properly treat zero-variant enums so that we do not have to check layout.is_uninhabited)
 - rust-lang/rust#157002 (std: Fix thread::available_parallelism on Redox targets)
This commit is contained in:
bors
2026-05-27 19:54:13 +00:00
95 changed files with 1406 additions and 515 deletions
@@ -237,6 +237,10 @@ fn join_codegen(
) -> (CompiledModules, FxIndexMap<WorkProductId, WorkProduct>) {
ongoing_codegen.downcast::<driver::aot::OngoingCodegen>().unwrap().join(sess, outputs)
}
fn fallback_intrinsics(&self) -> Vec<Symbol> {
vec![sym::type_id_eq]
}
}
/// Determine if the Cranelift ir verifier should run.
+5 -1
View File
@@ -98,7 +98,7 @@
use rustc_middle::util::Providers;
use rustc_session::Session;
use rustc_session::config::{OptLevel, OutputFilenames};
use rustc_span::Symbol;
use rustc_span::{Symbol, sym};
use rustc_target::spec::{Arch, RelocModel};
use tempfile::TempDir;
@@ -311,6 +311,10 @@ fn join_codegen(
fn target_config(&self, sess: &Session) -> TargetConfig {
target_config(sess, &self.target_info)
}
fn fallback_intrinsics(&self) -> Vec<Symbol> {
vec![sym::type_id_eq]
}
}
fn new_context<'gcc, 'tcx>(tcx: TyCtxt<'tcx>) -> Context<'gcc> {
@@ -165,6 +165,8 @@ pub(crate) fn uwtable_attr(llcx: &llvm::Context, use_sync_unwind: Option<bool>)
// NOTE: We should determine if we even need async unwind tables, as they
// take have more overhead and if we can use sync unwind tables we
// probably should.
//
// Similar logic exists for the per-module uwtable annotation in `context.rs`.
let async_unwind = !use_sync_unwind.unwrap_or(false);
llvm::CreateUWTableAttr(llcx, async_unwind)
}
@@ -311,6 +311,25 @@ pub(crate) unsafe fn create_module<'ll>(
);
}
if sess.must_emit_unwind_tables() {
// This assertion checks that Max is the correct merge behavior.
// Async unwind tables are strictly more useful than sync uwtables.
const {
assert!((llvm::UWTableKind::None as u32) < (llvm::UWTableKind::Sync as u32));
assert!((llvm::UWTableKind::Sync as u32) < (llvm::UWTableKind::Async as u32));
}
llvm::add_module_flag_u32(
llmod,
llvm::ModuleFlagMergeBehavior::Max,
"uwtable",
match sess.opts.unstable_opts.use_sync_unwind {
Some(true) => llvm::UWTableKind::Sync as u32,
Some(false) | None => llvm::UWTableKind::Async as u32,
},
);
}
// Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.)
if sess.is_sanitizer_kcfi_enabled() {
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi", 1);
+8
View File
@@ -333,6 +333,14 @@ fn replaced_intrinsics(&self) -> Vec<Symbol> {
will_not_use_fallback
}
fn fallback_intrinsics(&self) -> Vec<Symbol> {
// `type_id_eq` is a safe choice since *all* backends use the fallback body for that.
// When adding more intrinsics, keep in mind that the distributed standard library
// is compiled with the LLVM backend but might later be included in a project built
// with cranelift or GCC.
vec![sym::type_id_eq]
}
fn target_cpu(&self, sess: &Session) -> String {
crate::llvm_util::target_cpu(sess).to_string()
}
@@ -240,6 +240,18 @@ pub(crate) enum DLLStorageClass {
DllExport = 2, // Function to be accessible from DLL.
}
/// Must match the layout of `llvm::UWTableKind`.
#[derive(Copy, Clone)]
#[repr(C)]
pub(crate) enum UWTableKind {
/// No unwind table requested
None = 0,
/// "Synchronous" unwind tables
Sync = 1,
/// "Asynchronous" unwind tables (instr precise)
Async = 2,
}
/// Must match the layout of `LLVMRustAttributeKind`.
/// Semantically a subset of the C++ enum llvm::Attribute::AttrKind,
/// though it is not ABI compatible (since it's a C++ enum)
@@ -79,6 +79,12 @@ fn replaced_intrinsics(&self) -> Vec<Symbol> {
vec![]
}
/// Returns a list of all intrinsics that this backend definitely
/// does *not* replace, which means their fallback bodies can be MIR-inlined.
fn fallback_intrinsics(&self) -> Vec<Symbol> {
vec![]
}
/// Is ThinLTO supported by this backend?
fn thin_lto_supported(&self) -> bool {
true
@@ -230,9 +230,15 @@ pub fn eval_rvalue_into_place(
})?;
}
Reborrow(_, _, place) => {
let op = self.eval_place_to_op(place, Some(dest.layout))?;
self.copy_op(&op, &dest)?;
Reborrow(_, mutability, place) => {
let op = self.eval_place_to_op(place, None)?;
if mutability.is_not() {
// Shared generic reborrows use `CoerceShared`: a bitwise copy into a
// distinct same-layout target ADT.
self.copy_op_allow_transmute(&op, &dest)?;
} else {
self.copy_op(&op, &dest)?;
}
}
RawPtr(kind, place) => {
@@ -1353,6 +1353,16 @@ fn visit_box(
interp_ok(())
}
#[inline]
fn visit_variantless(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
let ty = val.layout.ty;
assert!(ty.is_enum(), "encountered non-enum variantless type `{ty}`");
throw_validation_failure!(
self.path,
format!("encountered a value of zero-variant enum `{ty}`")
);
}
#[inline]
fn visit_value(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
trace!("visit_value: {:?}, {:?}", *val, val.layout);
@@ -1557,23 +1567,14 @@ fn visit_value(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'t
}
}
// *After* all of this, check further information stored in the layout.
// On leaf types like `!` or empty enums, this will raise the error.
// This means that for types wrapping such a type, we won't ever get here, but it's
// just the simplest way to check for this case.
//
// FIXME: We could avoid some redundant checks here. For newtypes wrapping
// scalars, we do the same check on every "level" (e.g., first we check
// the fields of MyNewtype, and then we check MyNewType again).
if val.layout.is_uninhabited() {
let ty = val.layout.ty;
throw_validation_failure!(
self.path,
format!("encountered a value of uninhabited type `{ty}`")
);
}
// Assert that we checked everything there is to check about this type.
assert!(
!val.layout.is_uninhabited(),
"a value of type `{}` passed validation but that type is uninhabited",
val.layout.ty
);
if cfg!(debug_assertions) {
// Check that we don't miss any new changes to layout computation in our checks above.
// Only run expensive checks when debug assertions are enabled.
match val.layout.backend_repr {
BackendRepr::Scalar(scalar_layout) => {
if !scalar_layout.is_uninit_valid() {
@@ -41,6 +41,11 @@ fn visit_union(&mut self, _v: &Self::V, _fields: NonZero<usize>) -> InterpResult
fn visit_box(&mut self, _box_ty: Ty<'tcx>, _v: &Self::V) -> InterpResult<'tcx> {
interp_ok(())
}
/// Visits the given type after it has been found to have no variants.
#[inline(always)]
fn visit_variantless(&mut self, _v: &Self::V) -> InterpResult<'tcx> {
interp_ok(())
}
/// Called each time we recurse down to a field of a "product-like" aggregate
/// (structs, tuples, arrays and the like, but not enums), passing in old (outer)
@@ -193,7 +198,11 @@ fn walk_value(&mut self, v: &Self::V) -> InterpResult<'tcx> {
self.visit_variant(v, idx, &inner)?;
}
// For single-variant layouts, we already did everything there is to do.
Variants::Single { .. } | Variants::Empty => {}
Variants::Single { .. } => {}
// Non-variant layouts need special treatment by the visitor.
Variants::Empty => {
self.visit_variantless(v)?;
}
}
interp_ok(())
@@ -449,6 +449,7 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
};
codegen_backend.init(&sess);
sess.replaced_intrinsics = FxHashSet::from_iter(codegen_backend.replaced_intrinsics());
sess.fallback_intrinsics = FxHashSet::from_iter(codegen_backend.fallback_intrinsics());
sess.thin_lto_supported = codegen_backend.thin_lto_supported();
let cfg = parse_cfg(sess.dcx(), config.crate_cfg);
+1 -1
View File
@@ -35,7 +35,7 @@ fn report_maybe_different(expected: &str, found: &str) -> String {
}
match self {
TypeError::CyclicTy(_) => "cyclic type of infinite size".into(),
TypeError::CyclicTy(_) => "recursive type with infinite-size name".into(),
TypeError::CyclicConst(_) => "encountered a self-referencing constant".into(),
TypeError::Mismatch => "types differ".into(),
TypeError::PolarityMismatch(values) => {
@@ -59,12 +59,12 @@ pub(super) fn is_inline_valid_on_fn<'tcx>(
return Err("cold");
}
// Intrinsic fallback bodies are automatically made cross-crate inlineable,
// but at this stage we don't know whether codegen knows the intrinsic,
// so just conservatively don't inline it. This also ensures that we do not
// accidentally inline the body of an intrinsic that *must* be overridden.
if find_attr!(tcx, def_id, RustcIntrinsic) {
return Err("callee is an intrinsic");
// Intrinsics without fallback body cannot be inlined. The logic for which intrinsics *with*
// body can be inlined is in the inlining pass.
if let Some(intrinsic) = tcx.intrinsic(def_id)
&& intrinsic.must_be_overridden
{
return Err("callee is an intrinsic without fallback body");
}
Ok(())
@@ -1,7 +1,7 @@
use rustc_hir as hir;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
@@ -24,9 +24,16 @@ pub(crate) fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> E
// FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back
// to HIR for it.
// HACK: For synthetic MIR bodies (async closures), use the def id of the HIR body.
// Synthetic by-move coroutine bodies don't have useful HIR of their own.
// Use the original coroutine body instead. These synthetic bodies are
// created with a coroutine type, so we can inspect that type as-is.
if tcx.is_synthetic_mir(def_id) {
return extract_hir_info(tcx, tcx.local_parent(def_id));
let effective_def_id =
match *tcx.type_of(def_id).instantiate_identity().skip_normalization().kind() {
ty::Coroutine(coroutine_def_id, _) => coroutine_def_id.expect_local(),
_ => tcx.local_parent(def_id),
};
return extract_hir_info(tcx, effective_def_id);
}
let hir_node = tcx.hir_node_by_def_id(def_id);
+14 -2
View File
@@ -565,12 +565,24 @@ fn resolve_callsite<'tcx, I: Inliner<'tcx>>(
let args = tcx
.try_normalize_erasing_regions(inliner.typing_env(), Unnormalized::new_wip(args))
.ok()?;
let callee =
let mut callee =
Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?;
if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def {
if let InstanceKind::Virtual(..) = callee.def {
return None;
}
if let InstanceKind::Intrinsic(..) = callee.def {
let intrinsic = tcx.intrinsic(def_id).unwrap();
if intrinsic.must_be_overridden {
return None; // intrinsic without fallback body
}
if !tcx.sess.fallback_intrinsics.contains(&intrinsic.name) {
return None; // intrinsic that the backend may want to overwrite
}
// The callee is the fallback body.
debug!("callsite is fallback body: {def_id:?}");
callee = ty::Instance { def: ty::InstanceKind::Item(def_id), args: callee.args };
}
if inliner.history().contains(&callee.def_id()) {
return None;
@@ -580,11 +580,7 @@ fn validate_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable>
self.validate_ref(*kind, place)?;
}
Rvalue::Reborrow(_, _, place) => {
// FIXME(reborrow): should probably have a place_simplified like above.
let op = &Operand::Copy(*place);
self.validate_operand(op)?
}
Rvalue::Reborrow(..) => return Err(Unpromotable),
Rvalue::Aggregate(_, operands) => {
for o in operands {
+4
View File
@@ -164,6 +164,9 @@ pub struct Session {
/// The names of intrinsics that the current codegen backend replaces
/// with its own implementations.
pub replaced_intrinsics: FxHashSet<Symbol>,
/// The names of intrinsics that the current codegen backend does *not* replace
/// with its own implementations.
pub fallback_intrinsics: FxHashSet<Symbol>,
/// Does the codegen backend support ThinLTO?
pub thin_lto_supported: bool,
@@ -1124,6 +1127,7 @@ pub fn build_session(
target_filesearch,
host_filesearch,
replaced_intrinsics: FxHashSet::default(), // filled by `run_compiler`
fallback_intrinsics: FxHashSet::default(), // filled by `run_compiler`
thin_lto_supported: true, // filled by `run_compiler`
mir_opt_bisect_eval_count: AtomicUsize::new(0),
used_features: Lock::default(),
+1 -24
View File
@@ -742,30 +742,7 @@ unsafe impl Sync for TypeId {}
impl const PartialEq for TypeId {
#[inline]
fn eq(&self, other: &Self) -> bool {
#[cfg(miri)]
return crate::intrinsics::type_id_eq(*self, *other);
#[cfg(not(miri))]
{
let this = self;
crate::intrinsics::const_eval_select!(
@capture { this: &TypeId, other: &TypeId } -> bool:
if const {
crate::intrinsics::type_id_eq(*this, *other)
} else {
// Ideally we would just invoke `type_id_eq` unconditionally here,
// but since we do not MIR inline intrinsics, because backends
// may want to override them (and miri does!), MIR opts do not
// clean up this call sufficiently for LLVM to turn repeated calls
// of `TypeId` comparisons against one specific `TypeId` into
// a lookup table.
// SAFETY: We know that at runtime none of the bits have provenance and all bits
// are initialized. So we can just convert the whole thing to a `u128` and compare that.
unsafe {
crate::mem::transmute::<_, u128>(*this) == crate::mem::transmute::<_, u128>(*other)
}
}
)
}
crate::intrinsics::type_id_eq(*self, *other)
}
}
+6 -3
View File
@@ -23,11 +23,13 @@ pub struct IntoIter<T, const N: usize> {
impl<T, const N: usize> IntoIter<T, N> {
#[inline]
fn unsize(&self) -> &InnerUnsized<T> {
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
const fn unsize(&self) -> &InnerUnsized<T> {
self.inner.deref()
}
#[inline]
fn unsize_mut(&mut self) -> &mut InnerUnsized<T> {
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
const fn unsize_mut(&mut self) -> &mut InnerUnsized<T> {
self.inner.deref_mut()
}
}
@@ -219,7 +221,8 @@ pub fn as_slice(&self) -> &[T] {
/// Returns a mutable slice of all elements that have not been yielded yet.
#[stable(feature = "array_value_iter", since = "1.51.0")]
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
pub const fn as_mut_slice(&mut self) -> &mut [T] {
self.unsize_mut().as_mut_slice()
}
}
+2 -1
View File
@@ -134,7 +134,8 @@ pub(super) fn as_slice(&self) -> &[T] {
}
#[inline]
pub(super) fn as_mut_slice(&mut self) -> &mut [T] {
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
pub(super) const fn as_mut_slice(&mut self) -> &mut [T] {
// SAFETY: We know that all elements within `alive` are properly initialized.
unsafe {
let slice = self.data.get_unchecked_mut(self.alive.clone());
+3 -1
View File
@@ -2934,7 +2934,9 @@ pub const fn type_of(_id: crate::any::TypeId) -> crate::mem::type_info::Type {
#[rustc_intrinsic]
#[rustc_do_not_const_check]
pub const fn type_id_eq(a: crate::any::TypeId, b: crate::any::TypeId) -> bool {
a.data == b.data
// SAFETY: we know `TypeId` is 16 bytes of initialized data.
// This is runtime-only code so we do not have to worry about provenance.
unsafe { crate::mem::transmute::<_, u128>(a) == crate::mem::transmute::<_, u128>(b) }
}
/// Gets the size of the type represented by this `TypeId`.
+3 -1
View File
@@ -63,9 +63,11 @@ impl<I: FusedIterator + ?Sized> FusedIterator for &mut I {}
/// of this trait must inspect [`Iterator::size_hint()`]s upper bound.
#[unstable(feature = "trusted_len", issue = "37572")]
#[rustc_unsafe_specialization_marker]
pub unsafe trait TrustedLen: Iterator {}
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
pub const unsafe trait TrustedLen: [const] Iterator {}
#[unstable(feature = "trusted_len", issue = "37572")]
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
unsafe impl<I: TrustedLen + ?Sized> TrustedLen for &mut I {}
/// An iterator that when yielding an item will have taken at least one element
+10 -2
View File
@@ -427,7 +427,11 @@ pub const fn into_value(self) -> T {
impl<R: ops::Try> ControlFlow<R, R::Output> {
/// Creates a `ControlFlow` from any type implementing `Try`.
#[inline]
pub(crate) fn from_try(r: R) -> Self {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub(crate) const fn from_try(r: R) -> Self
where
R: [const] ops::Try,
{
match R::branch(r) {
ControlFlow::Continue(v) => ControlFlow::Continue(v),
ControlFlow::Break(v) => ControlFlow::Break(R::from_residual(v)),
@@ -436,7 +440,11 @@ pub(crate) fn from_try(r: R) -> Self {
/// Converts a `ControlFlow` into any type implementing `Try`.
#[inline]
pub(crate) fn into_try(self) -> R {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub(crate) const fn into_try(self) -> R
where
R: [const] ops::Try,
{
match self {
ControlFlow::Continue(v) => R::from_output(v),
ControlFlow::Break(v) => v,
+8 -2
View File
@@ -416,8 +416,14 @@ pub(crate) const fn wrap_mut_1<A, F>(
}
#[inline]
pub(crate) fn wrap_mut_2<A, B>(mut f: impl FnMut(A, B) -> T) -> impl FnMut(A, B) -> Self {
move |a, b| NeverShortCircuit(f(a, b))
#[rustc_const_unstable(feature = "const_array", issue = "147606")]
pub(crate) const fn wrap_mut_2<A, B, F>(
mut f: F,
) -> impl [const] FnMut(A, B) -> Self + [const] Destruct
where
F: [const] FnMut(A, B) -> T + [const] Destruct,
{
const move |a, b| NeverShortCircuit(f(a, b))
}
}
+8 -3
View File
@@ -1748,8 +1748,12 @@ pub const fn insert(&mut self, value: T) -> &mut T
/// ```
#[inline]
#[stable(feature = "option_entry", since = "1.20.0")]
pub fn get_or_insert(&mut self, value: T) -> &mut T {
self.get_or_insert_with(|| value)
#[rustc_const_unstable(feature = "const_option_ops", issue = "143956")]
pub const fn get_or_insert(&mut self, value: T) -> &mut T
where
T: [const] Destruct,
{
self.get_or_insert_with(const || value)
}
/// Inserts the default value into the option if it is [`None`], then
@@ -2649,7 +2653,8 @@ impl<A> ExactSizeIterator for IntoIter<A> {}
impl<A> FusedIterator for IntoIter<A> {}
#[unstable(feature = "trusted_len", issue = "37572")]
unsafe impl<A> TrustedLen for IntoIter<A> {}
#[rustc_const_unstable(feature = "const_iter", issue = "92476")]
unsafe impl<A> const TrustedLen for IntoIter<A> {}
/// The iterator produced by [`Option::into_flat_iter`]. See its documentation for more.
#[derive(Clone, Debug)]
+6 -1
View File
@@ -1682,7 +1682,12 @@ pub const fn unwrap_or_else<F>(self, op: F) -> T
#[inline]
#[track_caller]
#[stable(feature = "option_result_unwrap_unchecked", since = "1.58.0")]
pub unsafe fn unwrap_err_unchecked(self) -> E {
#[rustc_const_unstable(feature = "const_result_unwrap_unchecked", issue = "148714")]
pub const unsafe fn unwrap_err_unchecked(self) -> E
where
T: [const] Destruct,
E: [const] Destruct,
{
match self {
// SAFETY: the safety contract must be upheld by the caller.
Ok(_) => unsafe { hint::unreachable_unchecked() },
+2 -1
View File
@@ -120,7 +120,8 @@ fn cmp(&self, other: &($($T,)+)) -> Ordering {
maybe_tuple_doc! {
$($T)+ @
#[stable(feature = "rust1", since = "1.0.0")]
impl<$($T: Default),+> Default for ($($T,)+) {
#[rustc_const_unstable(feature = "const_default", issue = "143894")]
impl<$($T: [const] Default),+> const Default for ($($T,)+) {
#[inline]
fn default() -> ($($T,)+) {
($({ let x: $T = Default::default(); x},)+)
+2 -1
View File
@@ -155,6 +155,7 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> {
target_os = "aix",
target_vendor = "apple",
target_os = "cygwin",
target_os = "redox",
target_os = "wasi",
) => {
#[allow(unused_assignments)]
@@ -316,7 +317,7 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> {
}
}
_ => {
// FIXME: implement on Redox, l4re
// FIXME: implement on l4re
Err(io::const_error!(io::ErrorKind::Unsupported, "getting the number of hardware threads is not supported on the target platform"))
}
}
+4 -2
View File
@@ -2591,8 +2591,10 @@ pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: TargetSelection
// To workaround lack of rpath on Windows, we bundle another copy of
// the LLVM DLL to make rust-lld and llvm-tools work when `sysroot/bin`
// is missing from PATH, i.e. when they not launched by rustc.
let dst_libdir = sysroot.join("lib/rustlib").join(target).join("bin");
maybe_install_llvm(builder, target, &dst_libdir, false);
if target.triple.contains("windows") {
let dst_libdir = sysroot.join("lib/rustlib").join(target).join("bin");
maybe_install_llvm(builder, target, &dst_libdir, false);
}
}
}
+3
View File
@@ -12,6 +12,9 @@ defaults:
run:
shell: bash
permissions:
contents: read
jobs:
test:
name: test (${{ matrix.host_target }})
+3
View File
@@ -8,6 +8,9 @@ defaults:
run:
shell: bash
permissions:
contents: read
jobs:
sysroots:
name: Build the sysroots
+1 -1
View File
@@ -1 +1 @@
281c97c3240a9abd984ca0c6a2cd7389115e80d5
1f8e04d34ab0c1fd9574840aa6db670e41593bfb
+3 -3
View File
@@ -11,12 +11,12 @@
const NANOSECONDS_PER_BASIC_BLOCK: u128 = 5000;
/// An instant (a fixed moment in time) in Miri's monotone clock.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Instant {
kind: InstantKind,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
enum InstantKind {
Host(StdInstant),
Virtual { nanoseconds: u128 },
@@ -134,7 +134,7 @@ pub fn now(&self) -> Instant {
}
/// A deadline for some event to occur.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum Deadline {
Monotonic(Instant),
RealTime(SystemTime),
+28 -8
View File
@@ -154,15 +154,9 @@ pub fn new(communicate: bool) -> Result<Self, io::Error> {
}
/// Poll for new I/O events from the OS or wait until the timeout expired.
///
/// - If the timeout is [`Some`] and contains [`Duration::ZERO`], the poll doesn't block and just
/// reads all events since the last poll.
/// - If the timeout is [`Some`] and contains a non-zero duration, it blocks at most for the
/// specified duration.
/// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs.
///
/// The timeout semantics are the same as described in [`Poll::poll`].
/// The events also immediately get processed: threads get unblocked, and epoll readiness gets updated.
pub fn poll<'tcx>(
fn poll<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
timeout: Option<Duration>,
) -> InterpResult<'tcx, Result<(), io::Error>> {
@@ -344,6 +338,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
/// readiness gets set for the source even when the requested interest
/// might not be fulfilled.
///
/// The callback function will immediately be executed with [`UnblockKind::Ready`]
/// when `interest` is already fulfilled for `source_fd`.
///
/// There can also be spurious wake-ups by the OS and thus it's the callers
/// responsibility to verify that the requested I/O interests are
/// really ready and to block again if they're not.
@@ -374,4 +371,27 @@ fn block_thread_for_io(
interp_ok(())
}
}
/// Poll for I/O events until either an I/O event happened or the timeout expired.
///
/// - If the timeout is [`Some`] and contains [`Duration::ZERO`], the poll doesn't block and just
/// reads all events since the last poll.
/// - If the timeout is [`Some`] and contains a non-zero duration, it blocks at most for the
/// specified duration.
/// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs.
///
/// Unblocks all threads which are blocked on I/O and whose I/O interests
/// are currently fulfilled.
fn poll_and_unblock(&mut self, timeout: Option<Duration>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
match BlockingIoManager::poll(this, timeout)? {
Ok(_) => interp_ok(()),
// We can ignore errors originating from interrupts; that's just a spurious wakeup.
Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()),
// For other errors we panic. On Linux and BSD hosts this should only be
// reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred.
Err(e) => panic!("unexpected error while polling: {e}"),
}
}
}
+11 -23
View File
@@ -1,9 +1,9 @@
//! Implements threads.
use std::mem;
use std::sync::atomic::Ordering::Relaxed;
use std::task::Poll;
use std::time::{Duration, SystemTime};
use std::{io, mem};
use rand::RngExt;
use rand::seq::IteratorRandom;
@@ -698,8 +698,11 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
// or timeouts to take care of.
if this.machine.communicate() {
// When isolation is disabled we need to check for events for
// threads which are blocked on host I/O.
// When isolation is disabled we need to check for events for threads
// which are blocked on host I/O. Unlike the `poll_and_unblock` before
// any foreign item, the call here is needed to ensure that threads which
// are blocked on host I/O are woken up even if no shimmed functions are
// executed afterwards.
// We do this before running any other threads such that the threads
// which received events are available for scheduling afterwards.
@@ -793,24 +796,6 @@ fn is_thread_blocked_on_host(&self, thread: &Thread<'tcx>) -> bool {
}
}
/// Poll for I/O events until either an I/O event happened or the timeout expired.
/// The different timeout values are described in [`BlockingIoManager::poll`].
///
/// Unblocks all threads which are blocked on I/O and whose I/O interests
/// are currently fulfilled.
fn poll_and_unblock(&mut self, timeout: Option<Duration>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
match BlockingIoManager::poll(this, timeout)? {
Ok(_) => interp_ok(()),
// We can ignore errors originating from interrupts; that's just a spurious wakeup.
Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()),
// For other errors we panic. On Linux and BSD hosts this should only be
// reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred.
Err(e) => panic!("unexpected error while polling: {e}"),
}
}
/// Find all threads with expired timeouts, unblock them and execute their timeout callbacks.
///
/// This method returns the minimum duration until the next thread deadline.
@@ -962,8 +947,11 @@ fn start_regular_thread(
let old_thread_id = this.machine.threads.set_active_thread_id(new_thread_id);
// The child inherits its parent's cpu affinity.
if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&old_thread_id).cloned() {
this.machine.thread_cpu_affinity.insert(new_thread_id, cpuset);
// Skips this if `machine.thread_cpu_affinity` is not initialized.
if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
&& let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
{
thread_cpu_affinity.insert(new_thread_id, cpuset);
}
// Perform the function pointer load in the new thread frame.
+14 -7
View File
@@ -21,6 +21,7 @@
use crate::concurrency::GenmcCtx;
use crate::concurrency::thread::TlsAllocAction;
use crate::diagnostics::report_leaks;
use crate::helpers::is_no_core;
use crate::shims::{global_ctor, tls};
use crate::*;
@@ -289,14 +290,20 @@ pub fn create_ecx<'tcx>(
MiriMachine::new(config, layout_cx, genmc_ctx),
);
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
let sentinel =
helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
tcx.dcx().fatal(
"the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore. However,
// if the current crate is #![no_core] it's fine to be missing the usual items from libcore.
if !is_no_core(tcx) {
let sentinel = helpers::try_resolve_path(
tcx,
&["core", "ascii", "escape_default"],
Namespace::ValueNS,
);
if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
tcx.dcx().fatal(
"the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
);
}
}
// Compute argc and argv from `config.args`.
+5 -26
View File
@@ -1,6 +1,5 @@
use std::num::NonZero;
use std::sync::Mutex;
use std::time::Duration;
use std::{cmp, iter};
use rand::Rng;
@@ -714,31 +713,6 @@ fn deref_pointer_and_write(
this.write_scalar(value, &value_place)
}
/// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None`
/// if the value in the `timespec` struct is invalid. Some libc functions will return
/// `EINVAL` in this case.
fn read_timespec(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<Duration>> {
let this = self.eval_context_mut();
let seconds_place = this.project_field(tp, FieldIdx::ZERO)?;
let seconds_scalar = this.read_scalar(&seconds_place)?;
let seconds = seconds_scalar.to_target_isize(this)?;
let nanoseconds_place = this.project_field(tp, FieldIdx::ONE)?;
let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?;
let nanoseconds = nanoseconds_scalar.to_target_isize(this)?;
interp_ok(try {
// tv_sec must be non-negative.
let seconds: u64 = seconds.try_into().ok()?;
// tv_nsec must be non-negative.
let nanoseconds: u32 = nanoseconds.try_into().ok()?;
if nanoseconds >= 1_000_000_000 {
// tv_nsec must not be greater than 999,999,999.
None?
}
Duration::new(seconds, nanoseconds)
})
}
/// Read bytes from a byte slice.
fn read_byte_slice<'a>(&'a self, slice: &ImmTy<'tcx>) -> InterpResult<'tcx, &'a [u8]>
where
@@ -1085,6 +1059,11 @@ pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
}
}
/// Check whether the local crate has the `#![no_core]` attribute.
pub fn is_no_core(tcx: TyCtxt<'_>) -> bool {
rustc_hir::find_attr!(tcx, crate, NoCore)
}
/// 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;
+16 -6
View File
@@ -40,6 +40,7 @@
use crate::concurrency::{
AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory,
};
use crate::helpers::is_no_core;
use crate::*;
/// First real-time signal.
@@ -546,7 +547,8 @@ pub struct MiriMachine<'tcx> {
/// Stores which thread is eligible to run on which CPUs.
/// This has no effect at all, it is just tracked to produce the correct result
/// in `sched_getaffinity`
pub(crate) thread_cpu_affinity: FxHashMap<ThreadId, CpuAffinityMask>,
/// This will be `None` when running `#![no_core]` crates.
pub(crate) thread_cpu_affinity: Option<FxHashMap<ThreadId, CpuAffinityMask>>,
/// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri.
pub(crate) layouts: PrimitiveLayouts<'tcx>,
@@ -735,11 +737,19 @@ pub(crate) fn new(
config.num_cpus
);
let threads = ThreadManager::new(config);
let mut thread_cpu_affinity = FxHashMap::default();
if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android) {
thread_cpu_affinity
.insert(threads.active_thread(), CpuAffinityMask::new(&layout_cx, config.num_cpus));
}
let thread_cpu_affinity =
if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android)
&& !is_no_core(tcx)
{
let mut affinity = FxHashMap::default();
affinity.insert(
threads.active_thread(),
CpuAffinityMask::new(&layout_cx, config.num_cpus),
);
Some(affinity)
} else {
None
};
let blocking_io = BlockingIoManager::new(config.isolated_op == IsolatedOp::Allow)
.expect("Couldn't create poll instance");
let alloc_addresses =
+1 -1
View File
@@ -19,7 +19,7 @@ fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
)+
}
}
no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId);
no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline);
impl VisitProvenance for &'static str {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
+8 -4
View File
@@ -2,7 +2,7 @@
use std::io::Write;
use std::path::Path;
use rustc_abi::{Align, CanonAbi, ExternAbi, Size};
use rustc_abi::{Align, CanonAbi, Endian, ExternAbi, Size};
use rustc_ast::expand::allocator::NO_ALLOC_SHIM_IS_UNSTABLE;
use rustc_data_structures::either::Either;
use rustc_hir::attrs::Linkage;
@@ -817,7 +817,9 @@ fn emulate_foreign_item_inner(
}
// Used to implement the x86 `_mm{,256,512}_popcnt_epi{8,16,32,64}` and wasm
// `{i,u}8x16_popcnt` functions.
name if name.starts_with("llvm.ctpop.v") => {
name if name.starts_with("llvm.ctpop.v")
&& this.tcx.sess.target.endian == Endian::Little =>
{
let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let (op, op_len) = this.project_to_simd(op)?;
@@ -840,14 +842,16 @@ fn emulate_foreign_item_inner(
// Target-specific shims
name if name.starts_with("llvm.x86.")
&& matches!(this.tcx.sess.target.arch, Arch::X86 | Arch::X86_64) =>
&& matches!(this.tcx.sess.target.arch, Arch::X86 | Arch::X86_64)
&& this.tcx.sess.target.endian == Endian::Little =>
{
return shims::x86::EvalContextExt::emulate_x86_intrinsic(
this, link_name, abi, args, dest,
);
}
name if name.starts_with("llvm.aarch64.")
&& this.tcx.sess.target.arch == Arch::AArch64 =>
&& this.tcx.sess.target.arch == Arch::AArch64
&& this.tcx.sess.target.endian == Endian::Little =>
{
return shims::aarch64::EvalContextExt::emulate_aarch64_intrinsic(
this, link_name, abi, args, dest,
+3 -3
View File
@@ -291,7 +291,7 @@ fn set_last_error(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx> {
}
/// Sets the last OS error and writes -1 to dest place.
fn set_last_error_and_return(
fn set_errno_and_return_neg1(
&mut self,
err: impl Into<IoError>,
dest: &MPlaceTy<'tcx>,
@@ -303,7 +303,7 @@ fn set_last_error_and_return(
}
/// Sets the last OS error and return `-1` as a `i32`-typed Scalar
fn set_last_error_and_return_i32(
fn set_errno_and_return_neg1_i32(
&mut self,
err: impl Into<IoError>,
) -> InterpResult<'tcx, Scalar> {
@@ -313,7 +313,7 @@ fn set_last_error_and_return_i32(
}
/// Sets the last OS error and return `-1` as a `i64`-typed Scalar
fn set_last_error_and_return_i64(
fn set_errno_and_return_neg1_i64(
&mut self,
err: impl Into<IoError>,
) -> InterpResult<'tcx, Scalar> {
+1 -1
View File
@@ -346,7 +346,7 @@ fn unix_to_windows<T>(path: &mut Vec<T>)
}
Cow::Owned(OsString::from_wide(&path))
};
#[cfg(unix)]
#[cfg(not(windows))]
return if *target_os == Os::Windows {
// Windows target, Unix host.
let mut path: Vec<u8> = os_str.into_owned().into_encoded_bytes();
+51 -4
View File
@@ -81,7 +81,7 @@ fn clock_gettime(
.now()
.duration_since(this.machine.monotonic_clock.epoch()),
None => {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
}
};
@@ -109,7 +109,7 @@ fn gettimeofday(
// Using tz is obsolete and should always be null
let tz = this.read_pointer(tz_op)?;
if !this.ptr_is_null(tz)? {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let duration = system_time_to_duration(&SystemTime::now())?;
@@ -362,7 +362,7 @@ fn nanosleep(&mut self, duration: &OpTy<'tcx>, rem: &OpTy<'tcx>) -> InterpResult
let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to.
let Some(duration) = this.read_timespec(&duration)? else {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
};
let deadline = this.machine.monotonic_clock.now().add_lossy(duration);
@@ -401,7 +401,7 @@ fn clock_nanosleep(
}
let Some(duration) = this.read_timespec(&timespec)? else {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
};
let timeout_style = if flags == 0 {
@@ -459,4 +459,51 @@ fn Sleep(&mut self, timeout: &OpTy<'tcx>) -> InterpResult<'tcx> {
);
interp_ok(())
}
/// Parse a `timespec` struct and return it as a [`Duration`]. It returns [`None`]
/// if the value in the `timespec` struct is invalid. Some libc functions will return
/// EINVAL in this case.
fn read_timespec(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<Duration>> {
let this = self.eval_context_mut();
let sec_field = this.project_field_named(tp, "tv_sec")?;
let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?;
let nsec_field = this.project_field_named(tp, "tv_nsec")?;
let nsec = this.read_scalar(&nsec_field)?.to_int(nsec_field.layout.size)?;
interp_ok(try {
// tv_sec must be non-negative.
let seconds: u64 = sec.try_into().ok()?;
// tv_nsec must be non-negative.
let nanoseconds: u32 = nsec.try_into().ok()?;
if nanoseconds >= 1_000_000_000 {
// tv_nsec must not be greater than 999,999,999.
None?
}
Duration::new(seconds, nanoseconds)
})
}
/// Parse a `timeval` struct and return it as a [`Duration`]. It returns [`None`]
/// if the value in the `timeval` struct is invalid. Some libc functions will return
/// EINVAL in this case.
fn read_timeval(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<Duration>> {
let this = self.eval_context_mut();
let sec_field = this.project_field_named(tp, "tv_sec")?;
let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?;
let usec_field = this.project_field_named(tp, "tv_usec")?;
let usec = this.read_scalar(&usec_field)?.to_int(usec_field.layout.size)?;
interp_ok(try {
// tv_sec must be non-negative.
let seconds: u64 = sec.try_into().ok()?;
// tv_usec must be non-negative.
let microseconds: u32 = usec.try_into().ok()?;
if microseconds >= 1_000_000 {
// tv_usec must not be greater than 999,999.
None?
}
Duration::new(seconds, microseconds.strict_mul(1000))
})
}
}
+4 -4
View File
@@ -164,7 +164,7 @@ fn setenv(
interp_ok(Scalar::from_i32(0)) // return zero on success
} else {
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
}
}
@@ -188,7 +188,7 @@ fn unsetenv(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
interp_ok(Scalar::from_i32(0))
} else {
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
}
}
@@ -227,7 +227,7 @@ fn chdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`chdir`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let result = env::set_current_dir(path).map(|()| 0);
@@ -288,7 +288,7 @@ fn uname(
};
if this.ptr_is_null(uname_ptr)? {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?;
+21 -18
View File
@@ -88,7 +88,7 @@ fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(old_fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
}
@@ -97,7 +97,7 @@ fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scala
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(old_fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
if new_fd_num != old_fd_num {
// Close new_fd if it is previously opened.
@@ -113,7 +113,7 @@ fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scala
fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
// We need to check that there aren't unsupported options in `op`.
@@ -159,7 +159,7 @@ fn ioctl(
let arg = varargs.first();
let Some(fd) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
// Handle common opcodes.
@@ -201,7 +201,7 @@ fn fcntl(
// always sets this flag when opening a file. However we still need to check that the
// file itself is open.
if !this.machine.fds.is_fd_num(fd_num) {
this.set_last_error_and_return_i32(LibcError("EBADF"))
this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
} else {
interp_ok(this.eval_libc("FD_CLOEXEC"))
}
@@ -223,13 +223,13 @@ fn fcntl(
if let Some(fd) = this.machine.fds.get(fd_num) {
interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
} else {
this.set_last_error_and_return_i32(LibcError("EBADF"))
this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
}
}
cmd if cmd == f_getfl => {
// Check if this is a valid open file descriptor.
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
fd.get_flags(this)
@@ -237,7 +237,7 @@ fn fcntl(
cmd if cmd == f_setfl => {
// Check if this is a valid open file descriptor.
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
@@ -263,7 +263,7 @@ fn fcntl(
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
this.ffullsync_fd(fd_num)
@@ -280,7 +280,7 @@ fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let fd_num = this.read_scalar(fd_op)?.to_i32()?;
let Some(fd) = this.machine.fds.remove(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let result = fd.close_ref(this.machine.communicate(), this)?;
// return `0` if close is successful
@@ -320,7 +320,7 @@ fn read(
// Get the FD.
let Some(fd) = this.machine.fds.get(fd_num) else {
trace!("read: FD not found");
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
trace!("read: FD mapped to {fd:?}");
@@ -346,7 +346,7 @@ fn read(
// This must fit since `count` fits.
this.write_int(u64::try_from(read_size).unwrap(), &dest)
}
Err(e) => this.set_last_error_and_return(e, &dest)
Err(e) => this.set_errno_and_return_neg1(e, &dest)
}}
),
)
@@ -376,7 +376,7 @@ fn write(
// We temporarily dup the FD to be able to retain mutable access to `this`.
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let dest = dest.clone();
@@ -397,7 +397,7 @@ fn write(
// This must fit since `count` fits.
this.write_int(u64::try_from(write_size).unwrap(), &dest)
}
Err(e) => this.set_last_error_and_return(e, &dest)
Err(e) => this.set_errno_and_return_neg1(e, &dest)
}}
),
@@ -435,7 +435,7 @@ fn readv(
// Check that the FD exists.
let Some(fd) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
@@ -488,7 +488,10 @@ fn readv(
this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
u64::try_from(size).unwrap()
},
Err(e) => return this.set_last_error_and_return(e, &dest)
Err(e) => {
this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
return this.set_errno_and_return_neg1(e, &dest)
}
};
let mut remaining_bytes = bytes_read;
@@ -555,7 +558,7 @@ fn writev(
// Check that the FD exists.
let Some(fd) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
@@ -628,7 +631,7 @@ fn writev(
this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
match result {
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
Err(e) => this.set_last_error_and_return(e, &dest)
Err(e) => this.set_errno_and_return_neg1(e, &dest)
}
}),
)
+39 -10
View File
@@ -1,5 +1,6 @@
use std::ffi::OsStr;
use std::str;
use std::time::Duration;
use rustc_abi::{CanonAbi, Size};
use rustc_middle::ty::Ty;
@@ -108,6 +109,16 @@ fn emulate_foreign_item_inner(
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
if this.machine.communicate() {
// When isolation is disabled we need to check for new host I/O events before
// running any shimmed function. This is needed to ensure that the shim we
// execute has up-to-date information about host readiness (as reflected
// e.g. by epoll) even if the current thread never yields.
// Perform a non-blocking poll for newly available I/O events from the OS.
this.poll_and_unblock(Some(Duration::ZERO))?;
}
// See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
match link_name.as_str() {
// Environment related shims
@@ -1126,12 +1137,18 @@ fn emulate_foreign_item_inner(
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`sched_getaffinity` is not supported on #![no_core] programs"
)
}
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
// On Linux/Android, pid can be a TID as returned by `gettid`.
let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else {
this.set_last_error_and_return(LibcError("ESRCH"), dest)?;
this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?;
return interp_ok(EmulateItemResult::NeedsReturn);
};
thread_id
@@ -1145,11 +1162,13 @@ fn emulate_foreign_item_inner(
let chunk_size = CpuAffinityMask::chunk_size(this);
if this.ptr_is_null(mask)? {
this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
this.set_errno_and_return_neg1(LibcError("EFAULT"), dest)?;
} else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 {
// we only copy whole chunks of size_of::<c_ulong>()
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) {
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
} else if let Some(cpuset) =
this.machine.thread_cpu_affinity.as_ref().unwrap().get(&thread_id)
{
let cpuset = cpuset.clone();
// we only copy whole chunks of size_of::<c_ulong>()
let byte_count =
@@ -1158,7 +1177,7 @@ fn emulate_foreign_item_inner(
this.write_null(dest)?;
} else {
// The thread whose ID is pid could not be found
this.set_last_error_and_return(LibcError("ESRCH"), dest)?;
this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?;
}
}
"sched_setaffinity" => {
@@ -1171,12 +1190,18 @@ fn emulate_foreign_item_inner(
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`sched_setaffinity` is not supported on #![no_core] programs"
)
}
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
// On Linux/Android, pid can be a TID as returned by `gettid`.
let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else {
this.set_last_error_and_return(LibcError("ESRCH"), dest)?;
this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?;
return interp_ok(EmulateItemResult::NeedsReturn);
};
thread_id
@@ -1187,7 +1212,7 @@ fn emulate_foreign_item_inner(
};
if this.ptr_is_null(mask)? {
this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
this.set_errno_and_return_neg1(LibcError("EFAULT"), dest)?;
} else {
// NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`.
// Any unspecified bytes are treated as zero here (none of the CPUs are configured).
@@ -1199,12 +1224,16 @@ fn emulate_foreign_item_inner(
std::array::from_fn(|i| bits_slice.get(i).copied().unwrap_or(0));
match CpuAffinityMask::from_array(this, this.machine.num_cpus, bits_array) {
Some(cpuset) => {
this.machine.thread_cpu_affinity.insert(thread_id, cpuset);
this.machine
.thread_cpu_affinity
.as_mut()
.unwrap()
.insert(thread_id, cpuset);
this.write_null(dest)?;
}
None => {
// The intersection between the mask and the available CPUs was empty.
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
}
}
}
@@ -1245,7 +1274,7 @@ fn emulate_foreign_item_inner(
// macOS: https://keith.github.io/xcode-man-pages/getentropy.2.html
// Solaris/Illumos: https://illumos.org/man/3C/getentropy
if bufsize > 256 {
this.set_last_error_and_return(LibcError("EIO"), dest)?;
this.set_errno_and_return_neg1(LibcError("EIO"), dest)?;
} else {
this.gen_random(buf, bufsize)?;
this.write_null(dest)?;
@@ -75,6 +75,12 @@ fn emulate_foreign_item_inner(
let set_size = this.read_target_usize(set_size)?; // measured in bytes
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`cpuset_getaffinity` is not supported on #![no_core] programs"
)
}
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");
@@ -96,7 +102,7 @@ fn emulate_foreign_item_inner(
};
if this.ptr_is_null(mask)? {
this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
this.set_errno_and_return_neg1(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.
@@ -104,14 +110,16 @@ fn emulate_foreign_item_inner(
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) {
} else if let Some(cpuset) =
this.machine.thread_cpu_affinity.as_ref().unwrap().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)?;
this.set_errno_and_return_neg1(LibcError("ERANGE"), dest)?;
} else {
let cpuset = cpuset.clone();
let byte_count =
@@ -102,7 +102,7 @@ fn _umtx_op(
let umtx_time_place = this.ptr_to_mplace(uaddr2, umtx_time_layout);
let Some(umtx_time) = this.read_umtx_time(&umtx_time_place)? else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
};
let style = if umtx_time.abs_time {
@@ -123,7 +123,7 @@ fn _umtx_op(
// `uaddr2` points to a `struct timespec`.
let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);
let Some(duration) = this.read_timespec(&timespec)? else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
};
// FreeBSD does not seem to document which clock is used when the timeout
@@ -137,7 +137,7 @@ fn _umtx_op(
duration,
))
} else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
}
};
@@ -158,7 +158,7 @@ fn _umtx_op(
ecx.write_int(0, &dest)
}
UnblockKind::TimedOut => {
ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
}
}
),
@@ -182,7 +182,7 @@ fn _umtx_op(
// Return an error code. (That seems nicer than silently doing something non-intuitive.)
// This means that if an address gets reused by a new allocation,
// we'll use an independent futex queue for this... that seems acceptable.
return this.set_last_error_and_return(LibcError("EFAULT"), dest);
return this.set_errno_and_return_neg1(LibcError("EFAULT"), dest);
};
let futex_ref = futex_ref.futex.clone();
+62 -57
View File
@@ -457,7 +457,7 @@ fn open(
let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
if flag & o_tmpfile == o_tmpfile {
// if the flag contains `O_TMPFILE` then we return a graceful error
return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
}
}
@@ -477,7 +477,7 @@ fn open(
// O_NOFOLLOW only fails when the trailing component is a symlink;
// the entire rest of the path can still contain symlinks.
if path.is_symlink() {
return this.set_last_error_and_return_i32(LibcError("ELOOP"));
return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
}
}
}
@@ -490,7 +490,7 @@ fn open(
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`open`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let fd = options
@@ -514,7 +514,7 @@ fn lseek(
let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
if offset < 0 {
// Negative offsets return `EINVAL`.
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
} else {
SeekFrom::Start(u64::try_from(offset).unwrap())
}
@@ -523,13 +523,13 @@ fn lseek(
} else if whence == this.eval_libc_i32("SEEK_END") {
SeekFrom::End(i64::try_from(offset).unwrap())
} else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
};
let communicate = this.machine.communicate();
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
drop(fd);
@@ -547,7 +547,7 @@ fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`unlink`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let result = fs::remove_file(path).map(|_| 0);
@@ -577,7 +577,7 @@ fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`symlink`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let result = create_link(&target, &linkpath).map(|_| 0);
@@ -600,13 +600,13 @@ fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'t
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`stat`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
// `stat` always follows symlinks.
let metadata = match FileMetadata::from_path(this, &path, true)? {
Ok(metadata) => metadata,
Err(err) => return this.set_last_error_and_return_i32(err),
Err(err) => return this.set_errno_and_return_neg1_i32(err),
};
interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
@@ -629,12 +629,12 @@ fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`lstat`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
let metadata = match FileMetadata::from_path(this, &path, false)? {
Ok(metadata) => metadata,
Err(err) => return this.set_last_error_and_return_i32(err),
Err(err) => return this.set_errno_and_return_neg1_i32(err),
};
interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
@@ -656,12 +656,12 @@ fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tc
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fstat`", reject_with)?;
// Set error code as "EBADF" (bad fd)
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
let metadata = match FileMetadata::from_fd_num(this, fd)? {
Ok(metadata) => metadata,
Err(err) => return this.set_last_error_and_return_i32(err),
Err(err) => return this.set_errno_and_return_neg1_i32(err),
};
interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
}
@@ -686,7 +686,7 @@ fn linux_statx(
// If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
@@ -727,7 +727,7 @@ fn linux_statx(
assert!(empty_path_flag);
LibcError("EBADF")
};
return this.set_last_error_and_return_i32(ecode);
return this.set_errno_and_return_neg1_i32(ecode);
}
// If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
@@ -743,7 +743,7 @@ fn linux_statx(
};
let metadata = match metadata {
Ok(metadata) => metadata,
Err(err) => return this.set_last_error_and_return_i32(err),
Err(err) => return this.set_errno_and_return_neg1_i32(err),
};
// The `_mask_op` parameter specifies the file information that the caller requested.
@@ -861,19 +861,19 @@ fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<
let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
if this.ptr_is_null(path_ptr)? {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
let path = this.read_path_from_c_str(path_ptr)?;
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`chmod`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
if let Err(err) = fs::set_permissions(path, permissions) {
return this.set_last_error_and_return_i32(err);
return this.set_errno_and_return_neg1_i32(err);
}
interp_ok(Scalar::from_i32(0))
@@ -886,7 +886,7 @@ fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'
let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(file) = fd.downcast::<FileHandle>() else {
// The docs don't talk about what happens for non-regular files...
@@ -896,12 +896,12 @@ fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fchmod`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
if let Err(err) = file.file.set_permissions(permissions) {
return this.set_last_error_and_return_i32(err);
return this.set_errno_and_return_neg1_i32(err);
}
interp_ok(Scalar::from_i32(0))
@@ -918,7 +918,7 @@ fn rename(
let newpath_ptr = this.read_pointer(newpath_op)?;
if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
@@ -927,7 +927,7 @@ fn rename(
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rename`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let result = fs::rename(oldpath, newpath).map(|_| 0);
@@ -950,7 +950,7 @@ fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`mkdir`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
#[cfg_attr(not(unix), allow(unused_mut))]
@@ -977,7 +977,7 @@ fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rmdir`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
}
let result = fs::remove_dir(path).map(|_| 0i32);
@@ -1235,11 +1235,11 @@ fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`closedir`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
if let Some(entry) = open_dir.entry.take() {
this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
@@ -1257,11 +1257,11 @@ fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scala
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`ftruncate64`", reject_with)?;
// Set error code as "EBADF" (bad fd)
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(file) = fd.downcast::<FileHandle>() else {
@@ -1276,11 +1276,11 @@ fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scala
let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
interp_ok(Scalar::from_i32(result))
} else {
this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
}
} else {
// The file is not writable
this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
}
}
@@ -1356,7 +1356,7 @@ fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fsync`", reject_with)?;
// Set error code as "EBADF" (bad fd)
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
self.ffullsync_fd(fd)
@@ -1365,7 +1365,7 @@ fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
@@ -1384,11 +1384,11 @@ fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fdatasync`", reject_with)?;
// Set error code as "EBADF" (bad fd)
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
let Some(fd) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
@@ -1413,24 +1413,24 @@ fn sync_file_range(
let flags = this.read_scalar(flags_op)?.to_i32()?;
if offset < 0 || nbytes < 0 {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
| this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
| this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
if flags & allowed_flags != flags {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`sync_file_range`", reject_with)?;
// Set error code as "EBADF" (bad fd)
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
}
let Some(fd) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
@@ -1590,7 +1590,7 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`mkstemp`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
// Get the bytes of the suffix we expect in _target_ encoding.
@@ -1606,7 +1606,7 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// If we don't find the suffix, it is an error.
if last_six_char_bytes != suffix_bytes {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
// At this point we know we have 6 ASCII 'X' characters as a suffix.
@@ -1624,18 +1624,23 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let mut fopts = OpenOptions::new();
fopts.read(true).write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
// Do not allow others to read or modify this file.
fopts.mode(0o600);
fopts.custom_flags(libc::O_EXCL);
}
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
// Do not allow others to read or modify this file.
fopts.share_mode(0);
cfg_select! {
unix =>
{
use std::os::unix::fs::OpenOptionsExt;
// Do not allow others to read or modify this file.
fopts.mode(0o600);
fopts.custom_flags(libc::O_EXCL);
}
windows =>
{
use std::os::windows::fs::OpenOptionsExt;
// Do not allow others to read or modify this file.
fopts.share_mode(0);
}
_ => {
throw_unsup_format!("`mkstemp` is not supported on this host OS");
}
}
// If the generated file already exists, we will try again `max_attempts` many times.
@@ -1667,14 +1672,14 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
_ => {
// "On error, -1 is returned, and errno is set to
// indicate the error"
return this.set_last_error_and_return_i32(e);
return this.set_errno_and_return_neg1_i32(e);
}
},
}
}
// We ran out of attempts to create the file, return an error.
this.set_last_error_and_return_i32(LibcError("EEXIST"))
this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
}
}
@@ -279,12 +279,12 @@ fn epoll_ctl(
// Throw EFAULT if epfd and fd have the same value.
if epfd_value == fd {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
// Check if epfd is a valid epoll file descriptor.
let Some(epfd) = this.machine.fds.get(epfd_value) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let epfd = epfd
.downcast::<Epoll>()
@@ -293,7 +293,7 @@ fn epoll_ctl(
let mut interest_list = epfd.interest_list.borrow_mut();
let Some(fd_ref) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let id = fd_ref.id();
@@ -356,12 +356,12 @@ fn epoll_ctl(
};
if interest_list.try_insert(epoll_key, new_interest).is_err() {
// We already had interest in this.
return this.set_last_error_and_return_i32(LibcError("EEXIST"));
return this.set_errno_and_return_neg1_i32(LibcError("EEXIST"));
}
} else {
// Modify the existing interest.
let Some(interest) = interest_list.get_mut(&epoll_key) else {
return this.set_last_error_and_return_i32(LibcError("ENOENT"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
};
interest.relevant_events = events;
interest.data = data;
@@ -389,7 +389,7 @@ fn epoll_ctl(
// Remove epoll_event_interest from interest_list and ready_set.
if interest_list.remove(&epoll_key).is_none() {
// We did not have interest in this.
return this.set_last_error_and_return_i32(LibcError("ENOENT"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
};
epfd.ready_set.borrow_mut().remove(&epoll_key);
// If this was the last interest in this FD, remove us from the global list
@@ -452,7 +452,7 @@ fn epoll_wait(
let timeout = this.read_scalar(timeout)?.to_i32()?;
if epfd_value <= 0 || maxevents <= 0 {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
}
// This needs to come after the maxevents value check, or else maxevents.try_into().unwrap()
@@ -463,10 +463,10 @@ fn epoll_wait(
)?;
let Some(epfd) = this.machine.fds.get(epfd_value) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(epfd) = epfd.downcast::<Epoll>() else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
@@ -64,7 +64,7 @@ pub fn futex<'tcx>(
};
if bitset == 0 {
return ecx.set_last_error_and_return(LibcError("EINVAL"), dest);
return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
}
let timeout = ecx.deref_pointer_as(timeout, ecx.libc_ty_layout("timespec"))?;
@@ -72,7 +72,7 @@ pub fn futex<'tcx>(
None
} else {
let Some(duration) = ecx.read_timespec(&timeout)? else {
return ecx.set_last_error_and_return(LibcError("EINVAL"), dest);
return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
};
let timeout_clock = if op & futex_realtime == futex_realtime {
ecx.check_no_isolation(
@@ -164,7 +164,7 @@ pub fn futex<'tcx>(
ecx.write_int(0, &dest)
}
UnblockKind::TimedOut => {
ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
}
}
),
@@ -172,7 +172,7 @@ pub fn futex<'tcx>(
} else {
// The futex value doesn't match the expected value, so we return failure
// right away without sleeping: -1 and errno set to EAGAIN.
return ecx.set_last_error_and_return(LibcError("EAGAIN"), dest);
return ecx.set_errno_and_return_neg1(LibcError("EAGAIN"), dest);
}
}
// FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
@@ -189,7 +189,7 @@ pub fn futex<'tcx>(
// Return an error code. (That seems nicer than silently doing something non-intuitive.)
// This means that if an address gets reused by a new allocation,
// we'll use an independent futex queue for this... that seems acceptable.
return ecx.set_last_error_and_return(LibcError("EFAULT"), dest);
return ecx.set_errno_and_return_neg1(LibcError("EFAULT"), dest);
};
let futex_ref = futex_ref.futex.clone();
@@ -205,7 +205,7 @@ pub fn futex<'tcx>(
u32::MAX
};
if bitset == 0 {
return ecx.set_last_error_and_return(LibcError("EINVAL"), dest);
return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
}
// Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
// will see the latest value on addr which could be changed by our caller
+7 -7
View File
@@ -163,7 +163,7 @@ fn os_sync_wait_on_address(
|| clock_timeout
.is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0)
{
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
return interp_ok(());
}
@@ -202,7 +202,7 @@ fn os_sync_wait_on_address(
futex.size.set(size);
futex.shared.set(is_shared);
} else if futex.size.get() != size || futex.shared.get() != is_shared {
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
return interp_ok(());
}
@@ -226,7 +226,7 @@ fn os_sync_wait_on_address(
this.write_scalar(Scalar::from_i32(remaining), &dest)
}
UnblockKind::TimedOut => {
this.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
this.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
}
}
}
@@ -264,7 +264,7 @@ fn os_sync_wake_by_address(
// Perform validation of the arguments.
let addr = ptr.addr().bytes();
if addr == 0 || !matches!(size, 4 | 8) || (flags != none && flags != shared) {
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
return interp_ok(());
}
@@ -282,19 +282,19 @@ fn os_sync_wake_by_address(
// non-intuitive.) This means that if an address gets reused by a
// new allocation, we'll use an independent futex queue for this...
// that seems acceptable.
this.set_last_error_and_return(LibcError("ENOENT"), dest)?;
this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?;
return interp_ok(());
};
if futex.futex.waiters() == 0 {
this.set_last_error_and_return(LibcError("ENOENT"), dest)?;
this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?;
return interp_ok(());
// If there are waiters in the queue, they have all used the parameters
// stored in `futex` (we check this in `os_sync_wait_on_address` above).
// Detect mismatches between "our" parameters and the parameters used by
// the waiters and return an error in that case.
} else if futex.size.get() != size || futex.shared.get() != is_shared {
this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
return interp_ok(());
}
+2 -2
View File
@@ -132,11 +132,11 @@ fn munmap(&mut self, addr: &OpTy<'tcx>, length: &OpTy<'tcx>) -> InterpResult<'tc
// addr must be a multiple of the page size, but apart from that munmap is just implemented
// as a dealloc.
if !addr.addr().bytes().is_multiple_of(this.machine.page_size) {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else {
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
};
if length > this.target_usize_max() {
this.set_last_error(LibcError("EINVAL"))?;
+218 -99
View File
@@ -3,6 +3,7 @@
use std::io::Read;
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4};
use std::sync::atomic::AtomicBool;
use std::time::Duration;
use mio::event::Source;
use mio::net::{TcpListener, TcpStream};
@@ -67,6 +68,18 @@ struct Socket {
io_readiness: RefCell<BlockingIoSourceReadiness>,
/// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.
error: RefCell<Option<io::Error>>,
/// Read timeout of the socket. [`None`] means that reads can block indefinitely.
/// The timeout is applied to the monotonic clock (the Unix specification doesn't
/// specify which clock to use, but the monotonic clock is more common for
/// relative timeouts).
/// This is ignored when the socket is non-blocking.
read_timeout: Cell<Option<Duration>>,
/// Write timeout of the socket. [`None`] means that writes can block indefinitely.
/// The timeout is applied to the monotonic clock (the Unix specification doesn't
/// specify which clock to use, but the monotonic clock is more common
/// for relative timeouts).
/// This is ignored when the socket is non-blocking.
write_timeout: Cell<Option<Duration>>,
}
impl FileDescription for Socket {
@@ -108,14 +121,16 @@ fn read<'tcx>(
assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
let socket = self;
let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.read_timeout.get());
ecx.ensure_connected(
socket.clone(),
!socket.is_non_block.get(),
deadline.clone(),
"read",
callback!(
@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
ptr: Pointer,
len: usize,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
@@ -134,8 +149,8 @@ fn read<'tcx>(
finish.call(this, result)
} else {
// The socket is in blocking mode and thus the read call should block
// until we can read some bytes from the socket.
this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish)
// until we can read some bytes from the socket or the timeout exceeded.
this.block_for_recv(socket, deadline, ptr, len, /* should_peek */ false, finish)
}
}
),
@@ -153,14 +168,16 @@ fn write<'tcx>(
assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
let socket = self;
let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.write_timeout.get());
ecx.ensure_connected(
socket.clone(),
!socket.is_non_block.get(),
deadline.clone(),
"write",
callback!(
@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
ptr: Pointer,
len: usize,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>
@@ -179,8 +196,8 @@ fn write<'tcx>(
return finish.call(this, result)
} else {
// The socket is in blocking mode and thus the write call should block
// until we can write some bytes into the socket.
this.block_for_send(socket, ptr, len, finish)
// until we can write some bytes into the socket or the timeout exceeded.
this.block_for_send(socket, deadline, ptr, len, finish)
}
}
),
@@ -295,7 +312,7 @@ fn socket(
// Reject if isolation is enabled
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`socket`", reject_with)?;
return this.set_last_error_and_return_i32(LibcError("EACCES"));
return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
}
let mut is_sock_nonblock = false;
@@ -353,6 +370,8 @@ fn socket(
is_non_block: Cell::new(is_sock_nonblock),
io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
error: RefCell::new(None),
read_timeout: Cell::new(None),
write_timeout: Cell::new(None),
});
interp_ok(Scalar::from_i32(fds.insert(fd)))
@@ -369,17 +388,17 @@ fn bind(
let socket = this.read_scalar(socket)?.to_i32()?;
let address = match this.read_socket_address(address, address_len, "bind")? {
Ok(addr) => addr,
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
};
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -407,7 +426,7 @@ fn bind(
// See <https://man7.org/linux/man-pages/man3/bind.3p.html>
LibcError("EAFNOSUPPORT")
};
return this.set_last_error_and_return_i32(err);
return this.set_errno_and_return_neg1_i32(err);
}
*state = SocketState::Bound(address);
@@ -437,12 +456,12 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -460,7 +479,7 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<
// we now have an associated host socket.
this.machine.blocking_io.register(socket);
}
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
},
SocketState::Initial => {
throw_unsup_format!(
@@ -500,12 +519,12 @@ fn accept4(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -562,11 +581,22 @@ fn accept4(
// See <https://man7.org/linux/man-pages/man2/syscall.2.html>.
this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)
}
Err(e) => this.set_last_error_and_return(e, dest),
Err(e) => this.set_errno_and_return_neg1(e, dest),
}
} else {
// The socket is in blocking mode and thus the accept call should block
// until an incoming connection is ready.
if socket.read_timeout.get().is_some() {
// Some Unixes like Linux also apply the SO_RCVTIMEO socket option
// to `accept` calls:
// <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/inet_connection_sock.c#L668-L675>
// This is currently not supported by Miri.
throw_unsup_format!(
"accept4: blocking accept is not supported when SO_RCVTIMEO is non-zero"
)
}
this.block_for_accept(
socket,
address_ptr,
@@ -590,17 +620,17 @@ fn connect(
let socket = this.read_scalar(socket)?.to_i32()?;
let address = match this.read_socket_address(address, address_len, "connect")? {
Ok(address) => address,
Err(e) => return this.set_last_error_and_return(e, dest),
Err(e) => return this.set_errno_and_return_neg1(e, dest),
};
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket
return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -610,7 +640,7 @@ fn connect(
SocketState::Initial => { /* fall-through to below */ }
// The socket is already in a connecting state.
SocketState::Connecting(_) =>
return this.set_last_error_and_return(LibcError("EALREADY"), dest),
return this.set_errno_and_return_neg1(LibcError("EALREADY"), dest),
// We don't return EISCONN for already connected sockets, for which we're
// sure that the connection is established, since TCP sockets are usually
// allowed to be connected multiple times.
@@ -630,7 +660,7 @@ fn connect(
// we now have an associated host socket.
this.machine.blocking_io.register(socket.clone());
}
Err(e) => return this.set_last_error_and_return(e, dest),
Err(e) => return this.set_errno_and_return_neg1(e, dest),
};
if socket.is_non_block.get() {
@@ -640,16 +670,25 @@ fn connect(
// Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS
// we just always return EINPROGRESS and check whether the connection succeeded
// once we want to use the connected socket.
this.set_last_error_and_return(LibcError("EINPROGRESS"), dest)
this.set_errno_and_return_neg1(LibcError("EINPROGRESS"), dest)
} else {
// The socket is in blocking mode and thus the connect call should block
// until the connection with the server is established.
let dest = dest.clone();
if socket.write_timeout.get().is_some() {
// Some Unixes like Linux also apply the SO_SNDTIMEO socket option
// to `connect` calls:
// <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/af_inet.c#L701-L710>
// This is currently not supported by Miri.
throw_unsup_format!(
"connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"
)
}
let dest = dest.clone();
this.ensure_connected(
socket.clone(),
/* should_wait */ true,
/* deadline */ None,
"connect",
callback!(
@capture<'tcx> {
@@ -661,7 +700,7 @@ fn connect(
// that it has been consumed by `ensure_connected`
// and is now stored in `socket.error`.
let err = socket.error.take().unwrap();
this.set_last_error_and_return(err, &dest)
this.set_errno_and_return_neg1(err, &dest)
} else {
this.write_scalar(Scalar::from_i32(0), &dest)
}
@@ -691,12 +730,12 @@ fn send(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket
return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
};
let mut is_op_non_block = false;
@@ -729,40 +768,41 @@ fn send(
);
}
// If either the operation or the socket is non-blocking, we don't want
// to wait until the connection is established.
let should_wait = !is_op_non_block && !socket.is_non_block.get();
let is_non_block = is_op_non_block || socket.is_non_block.get();
let deadline = this.action_deadline(is_non_block, socket.write_timeout.get());
let dest = dest.clone();
this.ensure_connected(
socket.clone(),
should_wait,
deadline.clone(),
"send",
callback!(
@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
flags: i32,
buffer_ptr: Pointer,
length: usize,
is_op_non_block: bool,
is_non_block: bool,
dest: MPlaceTy<'tcx>,
} |this, result: Result<(), ()>| {
if result.is_err() {
return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
}
if is_op_non_block || socket.is_non_block.get() {
if is_non_block {
// We have a non-blocking operation or a non-blocking socket and
// thus don't want to block until we can send.
match this.try_non_block_send(&socket, buffer_ptr, length)? {
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
Err(e) => this.set_last_error_and_return(e, &dest),
Err(e) => this.set_errno_and_return_neg1(e, &dest),
}
} else {
// The socket is in blocking mode and thus the send call should block
// until we can send some bytes into the socket.
// until we can send some bytes into the socket or the timeout exceeded.
this.block_for_send(
socket,
deadline,
buffer_ptr,
length,
callback!(@capture<'tcx> {
@@ -770,7 +810,7 @@ fn send(
} |this, result: Result<usize, IoError>| {
match result {
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
Err(e) => this.set_last_error_and_return(e, &dest)
Err(e) => this.set_errno_and_return_neg1(e, &dest)
}
}),
)
@@ -800,12 +840,12 @@ fn recv(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket
return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
};
let mut should_peek = false;
@@ -850,40 +890,41 @@ fn recv(
);
}
// If either the operation or the socket is non-blocking, we don't want
// to wait until the connection is established.
let should_wait = !is_op_non_block && !socket.is_non_block.get();
let is_non_block = is_op_non_block || socket.is_non_block.get();
let deadline = this.action_deadline(is_non_block, socket.read_timeout.get());
let dest = dest.clone();
this.ensure_connected(
socket.clone(),
should_wait,
deadline.clone(),
"recv",
callback!(
@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
buffer_ptr: Pointer,
length: usize,
should_peek: bool,
is_op_non_block: bool,
is_non_block: bool,
dest: MPlaceTy<'tcx>,
} |this, result: Result<(), ()>| {
if result.is_err() {
return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
}
if is_op_non_block || socket.is_non_block.get() {
if is_non_block {
// We have a non-blocking operation or a non-blocking socket and
// thus don't want to block until we can receive.
match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
Err(e) => this.set_last_error_and_return(e, &dest),
Err(e) => this.set_errno_and_return_neg1(e, &dest),
}
} else {
// The socket is in blocking mode and thus the receive call should block
// until we can receive some bytes from the socket.
// until we can receive some bytes from the socket or the timeout exceeded.
this.block_for_recv(
socket,
deadline,
buffer_ptr,
length,
should_peek,
@@ -892,7 +933,7 @@ fn recv(
} |this, result: Result<usize, IoError>| {
match result {
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
Err(e) => this.set_last_error_and_return(e, &dest)
Err(e) => this.set_errno_and_return_neg1(e, &dest)
}
}),
)
@@ -921,15 +962,17 @@ fn setsockopt(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
if level == this.eval_libc_i32("SOL_SOCKET") {
let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");
let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");
let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");
if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {
@@ -939,7 +982,7 @@ fn setsockopt(
if option_name == opt_so_nosigpipe {
if option_len != 4 {
// Option value should be C-int which is usually 4 bytes.
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let option_value =
this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
@@ -950,10 +993,29 @@ fn setsockopt(
}
}
if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {
let timeval_layout = this.libc_ty_layout("timeval");
let option_value = this.ptr_to_mplace(option_value_ptr, timeval_layout);
let timeout = match this.read_timeval(&option_value)? {
None => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),
Some(Duration::ZERO) => None,
Some(duration) => Some(duration),
};
if option_name == opt_so_rcvtimeo {
socket.read_timeout.set(timeout);
} else {
socket.write_timeout.set(timeout);
}
return interp_ok(Scalar::from_i32(0));
}
if option_name == opt_so_reuseaddr {
if option_len != 4 {
// Option value should be C-int which is usually 4 bytes.
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
let _val = this.read_scalar(&option_value)?.to_i32()?;
@@ -971,7 +1033,7 @@ fn setsockopt(
if option_name == opt_ip_ttl {
if option_len != 4 {
// Option value should be C-uint which is usually 4 bytes.
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32);
let ttl = this.read_scalar(&option_value)?.to_u32()?;
@@ -990,7 +1052,7 @@ fn setsockopt(
return match result {
Ok(_) => interp_ok(Scalar::from_i32(0)),
Err(e) => this.set_last_error_and_return_i32(e),
Err(e) => this.set_errno_and_return_neg1_i32(e),
};
} else {
throw_unsup_format!(
@@ -1003,7 +1065,7 @@ fn setsockopt(
if option_name == opt_tcp_nodelay {
if option_len != 4 {
// Option value should be C-int which is usually 4 bytes.
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
}
let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0;
@@ -1021,7 +1083,7 @@ fn setsockopt(
return match result {
Ok(_) => interp_ok(Scalar::from_i32(0)),
Err(e) => this.set_last_error_and_return_i32(e),
Err(e) => this.set_errno_and_return_neg1_i32(e),
};
} else {
throw_unsup_format!(
@@ -1059,18 +1121,18 @@ fn getsockopt(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() {
// This socket option returns a value and thus we need to return EFAULT
// when either the value or the length pointers are null pointers.
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
}
let socklen_layout = this.libc_ty_layout("socklen_t");
@@ -1085,6 +1147,8 @@ fn getsockopt(
// buffer, in which case we have to truncate.
let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") {
let opt_so_error = this.eval_libc_i32("SO_ERROR");
let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");
let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");
if option_name == opt_so_error {
// Reading SO_ERROR should always return the latest async error. Because our stored
@@ -1109,6 +1173,28 @@ fn getsockopt(
let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;
this.write_int(return_value, &value_buffer)?;
value_buffer
} else if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {
let timeout = if option_name == opt_so_rcvtimeo {
socket.read_timeout.get()
} else {
socket.write_timeout.get()
}
.unwrap_or_default();
let secs = timeout.as_secs();
let usecs = timeout.subsec_micros();
let timeval_layout = this.libc_ty_layout("timeval");
// Allocate new buffer on the stack with the `timeval` layout.
let timeval_buffer = this.allocate(timeval_layout, MemoryKind::Stack)?;
let sec_field = this.project_field_named(&timeval_buffer, "tv_sec")?;
this.write_int(secs, &sec_field)?;
let usec_field = this.project_field_named(&timeval_buffer, "tv_usec")?;
this.write_int(usecs, &usec_field)?;
timeval_buffer
} else {
throw_unsup_format!(
"getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",
@@ -1132,7 +1218,7 @@ fn getsockopt(
let ttl = match ttl {
Ok(ttl) => ttl,
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
};
// Allocate new buffer on the stack with the `u32` layout.
@@ -1161,7 +1247,7 @@ fn getsockopt(
let nodelay = match nodelay {
Ok(nodelay) => nodelay,
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
};
// Allocate new buffer on the stack with the `i32` layout.
@@ -1220,12 +1306,12 @@ fn getsockname(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -1250,7 +1336,7 @@ fn getsockname(
SocketState::Listening(listener) =>
match listener.local_addr() {
Ok(address) => address,
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
},
SocketState::Connecting(stream) | SocketState::Connected(stream) => {
if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {
@@ -1267,7 +1353,7 @@ fn getsockname(
}
match stream.local_addr() {
Ok(address) => address,
Err(e) => return this.set_last_error_and_return_i32(e),
Err(e) => return this.set_errno_and_return_neg1_i32(e),
}
}
// For non-bound sockets the POSIX manual says the returned address is unspecified.
@@ -1296,12 +1382,12 @@ fn getpeername(
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -1312,7 +1398,8 @@ fn getpeername(
// UNIX targets should return ENOTCONN when the connection is not yet established.
this.ensure_connected(
socket.clone(),
/* should_wait */ false,
// Check whether the socket is connected without blocking.
Some(this.machine.monotonic_clock.now().into()),
"getpeername",
callback!(
@capture<'tcx> {
@@ -1322,7 +1409,7 @@ fn getpeername(
dest: MPlaceTy<'tcx>,
} |this, result: Result<(), ()>| {
if result.is_err() {
return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
};
let SocketState::Connected(stream) = &*socket.state.borrow() else {
@@ -1331,7 +1418,7 @@ fn getpeername(
let address = match stream.peer_addr() {
Ok(address) => address,
Err(e) => return this.set_last_error_and_return(e, &dest),
Err(e) => return this.set_errno_and_return_neg1(e, &dest),
};
this.write_socket_address(
@@ -1354,12 +1441,12 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
@@ -1368,7 +1455,7 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t
let state = socket.state.borrow();
let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
return this.set_last_error_and_return_i32(LibcError("ENOTCONN"));
return this.set_errno_and_return_neg1_i32(LibcError("ENOTCONN"));
};
let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD");
@@ -1380,11 +1467,11 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t
_ if is_write_shutdown => Shutdown::Write,
_ if is_read_write_shutdown => Shutdown::Both,
// An invalid value was passed to `how`.
_ => return this.set_last_error_and_return_i32(LibcError("EINVAL")),
_ => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),
};
if let Err(e) = stream.shutdown(how) {
return this.set_last_error_and_return_i32(e);
return this.set_errno_and_return_neg1_i32(e);
};
drop(state);
@@ -1415,6 +1502,28 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t
impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Get the deadline for an action (e.g. reading or writing).
/// When `is_non_block` is [`true`], the returned deadline is "now", i.e.,
/// we wake up immediately if the action cannot be completed.
/// If `action_timeout` is `Some(duration)`, the returned deadline is in the
/// future be the specified `duration`. Otherwise, no deadline ([`None`]) is
/// returned, indicating that the action can block indefinitely.
fn action_deadline(
&self,
is_non_block: bool,
action_timeout: Option<Duration>,
) -> Option<Deadline> {
let this = self.eval_context_ref();
if is_non_block {
// Non-blocking sockets always have a zero timeout.
Some(this.machine.monotonic_clock.now().into())
} else {
action_timeout
.map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())
}
}
/// Block the thread until there's an incoming connection or an error occurred.
///
/// This recursively calls itself should the operation still block for some reason.
@@ -1433,19 +1542,23 @@ fn block_for_accept(
this.block_thread_for_io(
socket.clone(),
BlockingIoInterest::Read,
None,
/* deadline */ None,
callback!(@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
address_ptr: Pointer,
address_len_ptr: Pointer,
is_client_sock_nonblock: bool,
socket: FileDescriptionRef<Socket>,
dest: MPlaceTy<'tcx>,
} |this, kind: UnblockKind| {
assert_eq!(kind, UnblockKind::Ready);
// Remove the blocking I/O interest for unblocking this thread.
this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
match kind {
UnblockKind::Ready => { /* fall-through to below */ },
// When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
UnblockKind::TimedOut => return this.set_errno_and_return_neg1(LibcError("EWOULDBLOCK"), &dest)
}
match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {
Ok(sockfd) => {
// We need to create the scalar using the destination size since
@@ -1458,7 +1571,7 @@ fn block_for_accept(
// We need to block the thread again as it would still block.
this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest)
}
Err(e) => this.set_last_error_and_return(e, &dest),
Err(e) => this.set_errno_and_return_neg1(e, &dest),
}
}),
)
@@ -1515,6 +1628,8 @@ fn try_non_block_accept(
is_non_block: Cell::new(is_client_sock_nonblock),
io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
error: RefCell::new(None),
read_timeout: Cell::new(None),
write_timeout: Cell::new(None),
});
// Register the socket to the blocking I/O manager because
// there is an associated host socket.
@@ -1533,6 +1648,7 @@ fn try_non_block_accept(
fn block_for_send(
&mut self,
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
buffer_ptr: Pointer,
length: usize,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
@@ -1541,22 +1657,27 @@ fn block_for_send(
this.block_thread_for_io(
socket.clone(),
BlockingIoInterest::Write,
None,
deadline.clone(),
callback!(@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
buffer_ptr: Pointer,
length: usize,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
} |this, kind: UnblockKind| {
assert_eq!(kind, UnblockKind::Ready);
// Remove the blocking I/O interest for unblocking this thread.
this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
match kind {
UnblockKind::Ready => { /* fall-through to below */ },
// When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
}
match this.try_non_block_send(&socket, buffer_ptr, length)? {
Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
// We need to block the thread again as it would still block.
this.block_for_send(socket, buffer_ptr, length, finish)
this.block_for_send(socket, deadline, buffer_ptr, length, finish)
},
result => finish.call(this, result)
}
@@ -1647,6 +1768,7 @@ fn try_non_block_send(
fn block_for_recv(
&mut self,
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
buffer_ptr: Pointer,
length: usize,
should_peek: bool,
@@ -1656,23 +1778,28 @@ fn block_for_recv(
this.block_thread_for_io(
socket.clone(),
BlockingIoInterest::Read,
None,
deadline.clone(),
callback!(@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
deadline: Option<Deadline>,
buffer_ptr: Pointer,
length: usize,
should_peek: bool,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
} |this, kind: UnblockKind| {
assert_eq!(kind, UnblockKind::Ready);
// Remove the blocking I/O interest for unblocking this thread.
this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
match kind {
UnblockKind::Ready => { /* fall-through to below */ },
// When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
}
match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
// We need to block the thread again as it would still block.
this.block_for_recv(socket, buffer_ptr, length, should_peek, finish)
this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)
},
result => finish.call(this, result)
}
@@ -1777,7 +1904,7 @@ fn try_non_block_recv(
fn ensure_connected(
&mut self,
socket: FileDescriptionRef<Socket>,
should_wait: bool,
deadline: Option<Deadline>,
foreign_name: &'static str,
action: DynMachineCallback<'tcx, Result<(), ()>>,
) -> InterpResult<'tcx> {
@@ -1801,11 +1928,6 @@ fn ensure_connected(
// We're currently connecting. Since the underlying mio socket is non-blocking,
// the only way to determine whether we are done connecting is by polling.
// If we should wait until the connection is established, the timeout is `None`.
// Otherwise, we use a zero duration timeout, i.e. we return immediately
// (but we still go through the scheduler once -- which is fine).
let deadline =
if should_wait { None } else { Some(this.machine.monotonic_clock.now().into()) };
this.block_thread_for_io(
socket.clone(),
@@ -1814,7 +1936,6 @@ fn ensure_connected(
callback!(
@capture<'tcx> {
socket: FileDescriptionRef<Socket>,
should_wait: bool,
foreign_name: &'static str,
action: DynMachineCallback<'tcx, Result<(), ()>>,
} |this, kind: UnblockKind| {
@@ -1822,9 +1943,7 @@ fn ensure_connected(
this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
if UnblockKind::TimedOut == kind {
// We can only time out when `should_wait` is false.
// This then means that the socket is not yet connected.
assert!(!should_wait);
return action.call(this, Err(()))
}
@@ -1,5 +1,4 @@
// This code no longer works using implicit writes in tree borrows.
// This code tests that. The passing version is in `pass/tree_borrows/implicit_writes/as_mut_ptr.rs`.
// This code works in Tree Borrows without implicit writes, but is expected to fail with implicit writes.
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
fn main() {
@@ -1,4 +1,4 @@
error: Undefined Behavior: constructing invalid value of type main::Void: encountered a value of uninhabited type `main::Void`
error: Undefined Behavior: constructing invalid value of type main::Void: encountered a value of zero-variant enum `main::Void`
--> tests/fail/validity/match_binder_checks_validity1.rs:LL:CC
|
LL | _x => println!("hi from the void!"),
@@ -294,10 +294,17 @@ fn test_send_nonblock() {
if written as usize == fill_buf.len() {
// When we didn't have a short write we should still be able to write more.
// Ensure the socket is still writable.
assert_eq!(
current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET),
EPOLLOUT
);
let readiness = current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET);
if cfg!(miri) {
// With Miri we keep the writable readiness until EWOULDBLOCK is returned.
assert_eq!(readiness, EPOLLOUT);
} else {
// On native Linux hosts, the writable readiness is removed when the buffer
// is "almost" full. We can't emulate this with Miri.
// The buffer must not be "almost" full at the first write.
let is_not_first_write = total_written > fill_buf.len();
assert!(readiness == EPOLLOUT || (is_not_first_write && readiness == 0));
}
}
}
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
@@ -523,11 +530,6 @@ fn test_readiness_after_short_peek() {
// Write some bytes into the peer socket.
libc_utils::write_all(peerfd, TEST_BYTES).unwrap();
// FIXME: Changes in host I/O readiness are only processed when entering the scheduler.
// Ensure that we process the effects if the `write_all` by yielding the current (only) thread.
// <https://github.com/rust-lang/miri/issues/5047>
thread::yield_now();
// `buffer` is intentionally bigger than `TEST_BYTES.len()` to trigger a short peek.
let mut buffer = [0; 128];
let bytes_read = unsafe {
@@ -541,9 +543,6 @@ fn test_readiness_after_short_peek() {
} as usize;
assert_eq!(bytes_read, TEST_BYTES.len());
// FIXME(#5047): same as above.
thread::yield_now();
// Ensure that the readable readiness is still set.
assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET), EPOLLIN);
@@ -10,8 +10,8 @@
mod libc_utils;
use std::io::ErrorKind;
use std::thread;
use std::time::Duration;
use std::{ptr, thread};
use libc_utils::*;
@@ -50,6 +50,8 @@ fn main() {
))]
test_send_recv_dontwait();
test_write_read_nonblock();
test_readv_nonblock_err();
test_writev_nonblock_err();
test_getsockname_ipv4_connect_nonblock();
@@ -337,23 +339,23 @@ fn test_send_recv_nonblock() {
.unwrap()
};
if !cfg!(windows_host) {
// Keep sending data until the buffer is full and we block.
// We cannot test this on Windows since there apparently the send buffer
// never fills up, at least for localhost connections.
let fill_buf = [1u8; 5_000_000];
// This fills the socket receive buffer and thus should start blocking.
let err = unsafe {
let fill_buf = [1u8; 32_000];
// Keep sending data until the buffer is full and we would block.
loop {
let result = unsafe {
libc_utils::write_all_generic(
fill_buf.as_ptr().cast(),
fill_buf.len(),
libc_utils::NoRetry,
|buf, count| libc::send(client_sockfd, buf, count, 0),
)
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::WouldBlock)
match result {
Ok(_) => { /* continue to fill buffer */ }
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
server_thread.join().unwrap();
@@ -453,23 +455,23 @@ fn test_send_recv_dontwait() {
.unwrap()
};
if !cfg!(windows_host) {
// Keep sending data until the buffer is full and we block.
// We cannot test this on Windows since there apparently the send buffer
// never fills up, at least for localhost connections.
let fill_buf = [1u8; 5_000_000];
// This fills the socket receive buffer and thus should start blocking.
let err = unsafe {
let fill_buf = [1u8; 32_000];
// Keep sending data until the buffer is full and we would block.
loop {
let result = unsafe {
libc_utils::write_all_generic(
fill_buf.as_ptr().cast(),
fill_buf.len(),
libc_utils::NoRetry,
|buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT),
)
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::WouldBlock)
match result {
Ok(_) => { /* continue to fill buffer */ }
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
server_thread.join().unwrap();
@@ -540,28 +542,108 @@ fn test_write_read_nonblock() {
// Writing into the empty buffer should succeed without blocking.
libc_utils::write_all(client_sockfd, TEST_BYTES).unwrap();
if !cfg!(windows_host) {
// Keep sending data until the buffer is full and we block.
// We cannot test this on Windows since there apparently the send buffer
// never fills up, at least for localhost connections.
let fill_buf = [1u8; 5_000_000];
// This fills the socket receive buffer and thus should start blocking.
let err = unsafe {
let fill_buf = [1u8; 32_000];
// Keep sending data until the buffer is full and we would block.
loop {
let result = unsafe {
libc_utils::write_all_generic(
fill_buf.as_ptr().cast(),
fill_buf.len(),
libc_utils::NoRetry,
|buf, count| libc::write(client_sockfd, buf, count),
)
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::WouldBlock)
match result {
Ok(_) => { /* continue to fill buffer */ }
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
server_thread.join().unwrap();
}
/// Test that Miri's internal temporary read buffer gets correctly deallocated when the
/// vectored read produces an error due to the socket read buffer being empty.
fn test_readv_nonblock_err() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
net::connect_ipv4(client_sockfd, addr).unwrap();
net::accept_ipv4(server_sockfd).unwrap();
unsafe {
// Change client socket to be non-blocking.
errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK));
}
let mut buffer = [0u8; TEST_BYTES.len()];
let (buffer1, buffer2) = buffer.split_at_mut(2);
let iov = [
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
libc::iovec {
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
iov_len: buffer1.len() as libc::size_t,
},
libc::iovec {
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
iov_len: buffer2.len() as libc::size_t,
},
];
let err = unsafe {
errno_result(libc::readv(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int))
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::WouldBlock);
}
/// Test that Miri's internal temporary write buffer gets correctly deallocated when the
/// vectored write produces an error due to the socket write buffer being full.
fn test_writev_nonblock_err() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
net::connect_ipv4(client_sockfd, addr).unwrap();
net::accept_ipv4(server_sockfd).unwrap();
unsafe {
// Change client socket to be non-blocking.
errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK));
}
let mut write_buffer = [1u8; 32_000];
let (buffer1, buffer2) = write_buffer.split_at_mut(15_000);
let iov = [
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
libc::iovec {
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
iov_len: buffer1.len() as libc::size_t,
},
libc::iovec {
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
iov_len: buffer2.len() as libc::size_t,
},
];
loop {
let result = unsafe {
errno_result(libc::writev(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int))
};
match result {
Ok(_) => { /* continue filling buffer */ }
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
}
/// Test the `getsockname` syscall on a connecting IPv4 socket
/// which is not connected.
fn test_getsockname_ipv4_connect_nonblock() {
+166 -25
View File
@@ -7,8 +7,8 @@
mod utils;
use std::io::ErrorKind;
use std::time::Duration;
use std::{ptr, thread};
use std::time::{Duration, Instant};
use std::{ptr, slice, thread};
use libc_utils::*;
@@ -55,6 +55,9 @@ fn main() {
test_shutdown_writable_after_read_close();
test_getsockopt_truncate();
test_sockopt_sndtimeo();
test_sockopt_rcvtimeo();
}
/// Test creating a socket and then closing it afterwards.
@@ -118,11 +121,26 @@ fn test_set_reuseaddr_invalid_len() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Value should be of type `libc::c_int` which has size 4 bytes.
// By providing a u64 of size 8 bytes we trigger an invalid length error.
let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64).unwrap_err();
// By providing an u16 of size 2 bytes we trigger an invalid length error.
let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u16).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// Check that it is the right kind of `InvalidInput`.
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
// By providing an u64 of size 8 bytes the behavior differs between native hosts and Miri.
let result = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64);
match result {
Err(err) => {
// Check that this is the right error.
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
}
Ok(_) => {
// Some native hosts just ignore too large inputs and only look at a prefix.
// On Miri we require an exact size.
assert!(!cfg!(miri));
}
}
}
#[cfg(any(
@@ -171,18 +189,35 @@ fn test_bind_ipv4_invalid_addr_len() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0);
// A too small size is invalid.
let err = unsafe {
errno_result(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
// Add 1 to the address to make the size invalid.
(size_of::<libc::sockaddr_in>() + 1) as libc::socklen_t,
(size_of::<libc::sockaddr_in>() - 1) as libc::socklen_t,
))
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// Check that it is the right kind of `InvalidInput`.
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
if cfg!(miri) {
// A too big size is also invalid. Some native hosts (e.g. Linux)
// allow too big sizes, so we skip this test on native hosts:
// <https://github.com/rust-lang/miri/pull/5059#discussion_r3305439221>
let err = unsafe {
errno_result(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
(size_of::<libc::sockaddr_in>() + 1) as libc::socklen_t,
))
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// Check that it is the right kind of `InvalidInput`.
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
}
}
fn test_bind_ipv6() {
@@ -716,46 +751,152 @@ fn test_shutdown_writable_after_read_close() {
fn test_getsockopt_truncate() {
let (sockfd, _) = net::make_listener_ipv4().unwrap();
// The actual TTL with a correctly sized buffer.
let ttl = net::getsockopt::<libc::c_uint>(sockfd, libc::IPPROTO_IP, libc::IP_TTL).unwrap();
// Set the read timeout for the socket.
// We use a multiple of 4ms for the `usec` since Linux seems to do rounding.
let new_timeout = libc::timeval { tv_sec: 123, tv_usec: 40_000 };
net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, new_timeout).unwrap();
let mut option_value = std::mem::MaybeUninit::<u32>::zeroed();
// The actual length is 4 bytes.
let mut short_option_len = 2 as libc::socklen_t;
let mut option_value = std::mem::MaybeUninit::<[u8; 5]>::zeroed();
// The actual `timeval` length is more than 5 bytes.
let mut short_option_len = 5 as libc::socklen_t;
assert!(short_option_len < size_of::<libc::timeval>() as libc::socklen_t);
let timeout =
net::getsockopt::<libc::timeval>(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO).unwrap();
// Ensure that we get the same value back as we just set.
assert_eq!(timeout.tv_sec, new_timeout.tv_sec);
assert_eq!(timeout.tv_usec, new_timeout.tv_usec);
errno_result(unsafe {
libc::getsockopt(
sockfd,
libc::IPPROTO_IP,
libc::IP_TTL,
libc::SOL_SOCKET,
libc::SO_RCVTIMEO,
option_value.as_mut_ptr().cast(),
&mut short_option_len,
)
})
.unwrap();
// Ensure that the size wasn't changed.
assert_eq!(short_option_len, 2);
let short_ttl = unsafe { option_value.assume_init() };
assert_eq!(short_option_len, 5);
let truncated_timeout = unsafe { option_value.assume_init() };
// Assert that the value was silently truncated.
assert_eq!(short_ttl.to_ne_bytes()[0..2], ttl.to_ne_bytes()[0..2]);
unsafe {
let timeout_ptr = (&new_timeout) as *const libc::timeval as *const u8;
// Assert that the value was silently truncated.
assert_eq!(&truncated_timeout, slice::from_raw_parts(timeout_ptr, 5));
}
let mut option_value = std::mem::MaybeUninit::<u32>::zeroed();
// The actual length is 4 bytes.
let mut long_option_len = 6 as libc::socklen_t;
let mut option_value = std::mem::MaybeUninit::<libc::timeval>::zeroed();
// The actual length is smaller than this.
let mut long_option_len = (size_of::<libc::timeval>() + 2) as libc::socklen_t;
errno_result(unsafe {
libc::getsockopt(
sockfd,
libc::IPPROTO_IP,
libc::IP_TTL,
libc::SOL_SOCKET,
libc::SO_RCVTIMEO,
option_value.as_mut_ptr().cast(),
&mut long_option_len,
)
})
.unwrap();
// Ensure that the size was shortened to the actual length.
assert_eq!(long_option_len, 4);
let long_ttl = unsafe { option_value.assume_init() };
assert_eq!(long_ttl, ttl);
assert_eq!(long_option_len, size_of::<libc::timeval>() as libc::socklen_t);
// The returned timeout should be the same value as we just set.
let untruncated_timeout = unsafe { option_value.assume_init() };
assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec);
assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec);
}
/// Test setting and getting the SO_SNDTIMEO socket option.
/// Also test that writes don't block indefinitely when we
/// have a nonzero timeout.
fn test_sockopt_sndtimeo() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
net::connect_ipv4(client_sockfd, addr).unwrap();
net::accept_ipv4(server_sockfd).unwrap();
let timeout =
net::getsockopt::<libc::timeval>(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO)
.unwrap();
// By default, no write timeout should be set.
assert_eq!(timeout.tv_sec, 0);
assert_eq!(timeout.tv_usec, 0);
// A 40 millisecond timeout.
let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 40_000 };
net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO, short_timeout).unwrap();
let timeout =
net::getsockopt::<libc::timeval>(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO)
.unwrap();
// We should now read the same value as we wrote above.
assert_eq!(timeout.tv_sec, short_timeout.tv_sec);
assert_eq!(timeout.tv_usec, short_timeout.tv_usec);
let buffer = [1u8; 32_000];
loop {
let before = Instant::now();
let result = unsafe {
errno_result(libc::write(client_sockfd, buffer.as_ptr().cast(), buffer.len()))
};
match result {
Ok(_) => { /* continue to fill up buffer */ }
// When we get an EAGAIN/EWOULDBLOCK when writing into a blocking socket, we know
// it's because of the write timeout exceeding because the write buffer
// is full.
Err(err) if err.kind() == ErrorKind::WouldBlock => {
// The last write should return an EAGAIN/EWOULDBLOCK after ~40ms instead
// of blocking indefinitely.
assert!(Instant::now().duration_since(before) >= Duration::from_millis(40));
break;
}
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
}
/// Test setting and getting the SO_RCVTIMEO socket option.
/// Also test that reads don't block indefinitely when we
/// have a nonzero timeout.
fn test_sockopt_rcvtimeo() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
net::connect_ipv4(client_sockfd, addr).unwrap();
net::accept_ipv4(server_sockfd).unwrap();
let timeout =
net::getsockopt::<libc::timeval>(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO)
.unwrap();
// By default, no read timeout should be set.
assert_eq!(timeout.tv_sec, 0);
assert_eq!(timeout.tv_usec, 0);
// A 40 millisecond timeout.
let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 40_000 };
net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, short_timeout).unwrap();
let timeout =
net::getsockopt::<libc::timeval>(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO)
.unwrap();
// We should now read the same value as we wrote above.
assert_eq!(timeout.tv_sec, short_timeout.tv_sec);
assert_eq!(timeout.tv_usec, short_timeout.tv_usec);
let mut buffer = [0u8; 16];
// The read should return an EAGAIN/EWOULDBLOCK after ~10ms instead of blocking indefinitely.
let before = Instant::now();
let err = unsafe {
errno_result(libc::read(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len()))
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::WouldBlock);
// Ensure that we blocked for at least 40 milliseconds.
assert!(Instant::now().duration_since(before) >= Duration::from_millis(40))
}
+22
View File
@@ -0,0 +1,22 @@
//! Test that Miri is able to run no_core programs.
//! This ensures that we don't depend on any paths from core when no_core is set.
#![no_std]
#![no_core]
#![no_main]
#![feature(rustc_attrs, no_core, lang_items, intrinsics)]
#![allow(internal_features)]
#[lang = "pointee_sized"]
pub trait PointeeSized {}
#[lang = "meta_sized"]
pub trait MetaSized: PointeeSized {}
#[lang = "sized"]
pub trait Sized: MetaSized {}
#[no_mangle]
fn miri_start(_argc: isize, _argv: *const *const u8) -> isize {
0
}
@@ -0,0 +1,48 @@
// Regression test for the Miri reproducer in rust-lang/rust#156313.
//
// The issue's exact recursive example ICEd while evaluating the argument
// conversion before the recursion mattered. This keeps that same
// `CustomMut`-to-`CustomRef` call path, but terminates after one recursive
// call so it can be a pass test once the ICE is fixed.
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
#[allow(unused)]
struct CustomMut<'a, T>(&'a mut T);
impl<'a, T> Reborrow for CustomMut<'a, T> {}
impl<'a, T> CoerceShared<CustomRef<'a, T>> for CustomMut<'a, T> {}
struct CustomRef<'a, T>(&'a T);
impl<'a, T> Clone for CustomRef<'a, T> {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<'a, T> Copy for CustomRef<'a, T> {}
fn method(_a: CustomRef<'_, ()>) {}
fn recursive_method(_a: CustomRef<'_, ()>, recurse: bool) {
if recurse {
let a = CustomMut(&mut ());
recursive_method(a, false);
}
}
fn issue_156313_runtime_reproducer() {
let a = CustomMut(&mut ());
method(a);
}
fn issue_156313_recursive_call_reproducer() {
let a = CustomMut(&mut ());
recursive_method(a, true);
}
fn main() {
issue_156313_runtime_reproducer();
issue_156313_recursive_call_reproducer();
}
+56
View File
@@ -4,6 +4,7 @@
use std::io::{ErrorKind, Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
use std::thread;
use std::time::Duration;
const TEST_BYTES: &[u8] = b"these are some test bytes!";
@@ -17,6 +18,8 @@ fn main() {
test_shutdown();
test_sockopt_ttl();
test_sockopt_nodelay();
test_sockopt_read_timeout();
test_sockopt_write_timeout();
}
fn test_create_ipv4_listener() {
@@ -167,3 +170,56 @@ fn test_sockopt_nodelay() {
stream.set_nodelay(false).unwrap();
assert_eq!(stream.nodelay().unwrap(), false);
}
/// Test setting and reading the SNDTIMEO socket option.
/// This also tests that a read won't block indefinitely
/// when the read timeout is set to [`Some`] duration.
fn test_sockopt_read_timeout() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let mut stream = TcpStream::connect(address).unwrap();
let _other_end = listener.accept().unwrap();
// By default, reads on blocking sockets should block indefinitely.
assert_eq!(stream.read_timeout().unwrap(), None);
let short_read_timeout = Some(Duration::from_millis(10));
stream.set_read_timeout(short_read_timeout).unwrap();
assert_eq!(stream.read_timeout().unwrap(), short_read_timeout);
let mut buffer = [0u8; 128];
// This should not block indefinitely and instead return EAGAIN/EWOULDBLOCK.
let err = stream.read(&mut buffer).unwrap_err();
assert_eq!(err.kind(), ErrorKind::WouldBlock);
}
/// Test setting and reading the RCVTIMEO socket option.
/// This also tests that a write won't block indefinitely when
/// the write timeout is set to [`Some`] duration.
fn test_sockopt_write_timeout() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let mut stream = TcpStream::connect(address).unwrap();
let _other_end = listener.accept().unwrap();
// By default, writes on blocking sockets should block indefinitely.
assert_eq!(stream.write_timeout().unwrap(), None);
let short_write_timeout = Some(Duration::from_millis(10));
stream.set_write_timeout(short_write_timeout).unwrap();
assert_eq!(stream.write_timeout().unwrap(), short_write_timeout);
let fill_buffer = [1u8; 1024];
loop {
match stream.write_all(&fill_buffer) {
Ok(_) => { /* continue to fill up buffer */ }
// When we get an EAGAIN/EWOULDBLOCK when writing into a blocking socket,
// we know it's because of the write timeout exceeding because the write
// buffer is full.
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => panic!("unexpected error whilst filling up buffer: {err}"),
}
}
}
@@ -9,3 +9,5 @@
fn foo() {
panic!();
}
// CHECK-NOT: !"uwtable"
@@ -4,3 +4,5 @@
// CHECK: attributes #{{.*}} uwtable
pub fn foo() {}
// CHECK: !{{[0-9]+}} = !{i32 7, !"uwtable", i32 2}
+72
View File
@@ -0,0 +1,72 @@
Function name: async_closure2::call_once::<async_closure2::main::{closure#0}>
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0c, 01, 00, 2a]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 12, 1) to (start + 0, 42)
Highest counter ID seen: c0
Function name: async_closure2::call_once::<async_closure2::main::{closure#0}>::{closure#0}
Raw bytes (21): 0x[01, 01, 01, 05, 09, 03, 01, 0c, 2b, 00, 2c, 01, 01, 05, 00, 0e, 02, 01, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 1
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
Number of file 0 mappings: 3
- Code(Counter(0)) at (prev + 12, 43) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 14)
- Code(Expression(0, Sub)) at (prev + 1, 1) to (start + 0, 2)
= (c1 - c2)
Highest counter ID seen: c0
Function name: async_closure2::main
Raw bytes (54): 0x[01, 01, 00, 0a, 01, 10, 01, 00, 0e, 01, 01, 09, 00, 16, 01, 04, 05, 00, 17, 01, 00, 18, 00, 21, 01, 00, 22, 00, 2f, 01, 01, 05, 00, 0f, 01, 00, 10, 00, 15, 01, 00, 16, 00, 1a, 01, 00, 1b, 00, 2b, 05, 01, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 10
- Code(Counter(0)) at (prev + 16, 1) to (start + 0, 14)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 22)
- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 23)
- Code(Counter(0)) at (prev + 0, 24) to (start + 0, 33)
- Code(Counter(0)) at (prev + 0, 34) to (start + 0, 47)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 15)
- Code(Counter(0)) at (prev + 0, 16) to (start + 0, 21)
- Code(Counter(0)) at (prev + 0, 22) to (start + 0, 26)
- Code(Counter(0)) at (prev + 0, 27) to (start + 0, 43)
- Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2)
Highest counter ID seen: c1
Function name: async_closure2::main::{closure#0}
Raw bytes (44): 0x[01, 01, 00, 08, 01, 11, 22, 00, 23, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 05, 00, 06]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 8
- Code(Counter(0)) at (prev + 17, 34) to (start + 0, 35)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14)
- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24)
- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14)
- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24)
- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 6)
Highest counter ID seen: c0
Function name: async_closure2::main::{closure#0}::{closure#0}::<_> (unused)
Raw bytes (44): 0x[01, 01, 00, 08, 00, 11, 22, 00, 23, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 05, 00, 06]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 8
- Code(Zero) at (prev + 17, 34) to (start + 0, 35)
- Code(Zero) at (prev + 1, 9) to (start + 0, 14)
- Code(Zero) at (prev + 0, 15) to (start + 0, 24)
- Code(Zero) at (prev + 0, 28) to (start + 0, 44)
- Code(Zero) at (prev + 1, 9) to (start + 0, 14)
- Code(Zero) at (prev + 0, 15) to (start + 0, 24)
- Code(Zero) at (prev + 0, 28) to (start + 0, 44)
- Code(Zero) at (prev + 1, 5) to (start + 0, 6)
Highest counter ID seen: (none)
+33
View File
@@ -0,0 +1,33 @@
LL| |// Regression test for <https://github.com/rust-lang/rust/issues/151135>.
LL| |
LL| |//@ edition: 2021
LL| |
LL| |//@ aux-build: executor.rs
LL| |extern crate executor;
LL| |
LL| |use std::sync::atomic::{AtomicUsize, Ordering};
LL| |
LL| |static STEPS: AtomicUsize = AtomicUsize::new(0);
LL| |
LL| 1|async fn call_once(f: impl AsyncFnOnce()) {
LL| 1| f().await;
LL| 1|}
LL| |
LL| 1|pub fn main() {
LL| 1| let async_closure = async || {
LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
LL| 1| };
------------------
| Unexecuted instantiation: async_closure2::main::{closure#0}::{closure#0}::<_>
------------------
| async_closure2::main::{closure#0}:
| LL| 1| let async_closure = async || {
| LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
| LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
| LL| 1| };
------------------
LL| 1| executor::block_on(call_once(async_closure));
LL| 1| assert_eq!(STEPS.load(Ordering::SeqCst), 2);
LL| 1|}
+23
View File
@@ -0,0 +1,23 @@
// Regression test for <https://github.com/rust-lang/rust/issues/151135>.
//@ edition: 2021
//@ aux-build: executor.rs
extern crate executor;
use std::sync::atomic::{AtomicUsize, Ordering};
static STEPS: AtomicUsize = AtomicUsize::new(0);
async fn call_once(f: impl AsyncFnOnce()) {
f().await;
}
pub fn main() {
let async_closure = async || {
STEPS.fetch_add(1, Ordering::SeqCst);
STEPS.fetch_add(1, Ordering::SeqCst);
};
executor::block_on(call_once(async_closure));
assert_eq!(STEPS.load(Ordering::SeqCst), 2);
}
@@ -0,0 +1,36 @@
- // MIR for `call` before Inline
+ // MIR for `call` after Inline
fn call(_1: TypeId, _2: TypeId) -> bool {
debug a => _1;
debug b => _2;
let mut _0: bool;
let mut _3: std::any::TypeId;
let mut _4: std::any::TypeId;
+ scope 1 (inlined type_id_eq) {
+ let mut _5: u128;
+ let mut _6: u128;
+ }
bb0: {
StorageLive(_3);
_3 = copy _1;
StorageLive(_4);
_4 = copy _2;
- _0 = type_id_eq(move _3, move _4) -> [return: bb1, unwind unreachable];
- }
-
- bb1: {
+ StorageLive(_5);
+ _5 = copy _3 as u128 (Transmute);
+ StorageLive(_6);
+ _6 = copy _4 as u128 (Transmute);
+ _0 = Eq(move _5, move _6);
+ StorageDead(_6);
+ StorageDead(_5);
StorageDead(_4);
StorageDead(_3);
return;
}
}
+20
View File
@@ -0,0 +1,20 @@
#![feature(core_intrinsics)]
//@ test-mir-pass: Inline
//@ compile-flags: --crate-type=lib -C panic=abort
use std::any::{Any, TypeId};
use std::intrinsics::type_id_eq;
struct A<T: ?Sized + 'static> {
a: i32,
b: T,
}
// EMIT_MIR type_id_eq.call.Inline.diff
// CHECK-LABEL: fn call(
pub fn call(a: TypeId, b: TypeId) -> bool {
// CHECK: as u128 (Transmute)
// CHECK: as u128 (Transmute)
// CHECK: Eq
type_id_eq(a, b)
}
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/closure-referencing-itself-issue-25954.rs:16:13
|
LL | let q = || p.b.set(5i32);
| ^^^^^^^^^^^^^^^^ cyclic type of infinite size
| ^^^^^^^^^^^^^^^^ recursive type with infinite-size name
error: aborting due to 1 previous error
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/generic-typed-nested-closures-59494.rs:21:40
|
LL | let t7 = |env| |a| |b| t7p(f, g)(((env, a), b));
| ^^^ cyclic type of infinite size
| ^^^ recursive type with infinite-size name
error: aborting due to 1 previous error
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself
--> $DIR/issue-25439.rs:8:9
|
LL | fix(|_, x| x);
| ^^^^^^ cyclic type of infinite size
| ^^^^^^ recursive type with infinite-size name
|
= note: closures cannot capture themselves or take themselves as argument;
this error may be the result of a recent compiler bug-fix,
@@ -21,7 +21,7 @@ fn bind() -> (T, Self) {
fn main() {
let (mut t, foo) = Foo::bind();
//~^ ERROR mismatched types
//~| NOTE cyclic type
//~| NOTE recursive type
// `t` is `ty::Infer(TyVar(?1t))`
// `foo` contains `ty::Infer(TyVar(?1t))` in its substs
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/unused-substs-2.rs:22:24
|
LL | let (mut t, foo) = Foo::bind();
| ^^^^^^^^^^^ cyclic type of infinite size
| ^^^^^^^^^^^ recursive type with infinite-size name
error: aborting due to 1 previous error
@@ -12,7 +12,7 @@
fn main() {
let (mut t, foo) = bind();
//~^ ERROR mismatched types
//~| NOTE cyclic type
//~| NOTE recursive type
// `t` is `ty::Infer(TyVar(?1t))`
// `foo` contains `ty::Infer(TyVar(?1t))` in its substs
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/unused-substs-3.rs:13:24
|
LL | let (mut t, foo) = bind();
| ^^^^^^ cyclic type of infinite size
| ^^^^^^ recursive type with infinite-size name
error: aborting due to 1 previous error
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/unused-substs-5.rs:15:19
|
LL | x = q::<_, N>(x);
| ^ cyclic type of infinite size
| ^ recursive type with infinite-size name
error: aborting due to 1 previous error
@@ -1,4 +1,4 @@
error[E0080]: constructing invalid value of type Bar: encountered a value of uninhabited type `Bar`
error[E0080]: constructing invalid value of type Bar: encountered a value of zero-variant enum `Bar`
--> $DIR/ub-uninhabit.rs:20:35
|
LL | const BAD_BAD_BAD: Bar = unsafe { MaybeUninit { uninit: () }.init };
@@ -15,7 +15,7 @@ LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
HEX_DUMP
}
error[E0080]: constructing invalid value of type [Bar; 1]: at [0], encountered a value of uninhabited type `Bar`
error[E0080]: constructing invalid value of type [Bar; 1]: at [0], encountered a value of zero-variant enum `Bar`
--> $DIR/ub-uninhabit.rs:26:42
|
LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { MaybeUninit { uninit: () }.init };
@@ -18,7 +18,7 @@ enum Void {}
//~^ ERROR value of the never type
const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3];
//~^ ERROR value of uninhabited type
//~^ ERROR value of zero-variant enum
fn main() {
FOO;
@@ -10,7 +10,7 @@ note: inside `foo`
LL | unsafe { std::mem::transmute(()) }
| ^^^^^^^^^^^^^^^^^^^^^^^ the failure occurred here
error[E0080]: constructing invalid value of type empty::Empty: at .0, encountered a value of uninhabited type `Void`
error[E0080]: constructing invalid value of type empty::Empty: at .0, encountered a value of zero-variant enum `Void`
--> $DIR/validate_uninhabited_zsts.rs:20:42
|
LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3];
@@ -5,14 +5,7 @@ LL | assert!(a == b);
| ^^^^^^ evaluation of `_` failed inside this call
|
note: inside `<TypeId as PartialEq>::eq`
--> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL
--> $SRC_DIR/core/src/any.rs:LL:COL
::: $SRC_DIR/core/src/any.rs:LL:COL
|
= note: in this macro invocation
note: inside `<TypeId as PartialEq>::eq::compiletime`
--> $SRC_DIR/core/src/any.rs:LL:COL
= note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
@@ -5,14 +5,7 @@ LL | assert!(a == b);
| ^^^^^^ evaluation of `_` failed inside this call
|
note: inside `<TypeId as PartialEq>::eq`
--> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL
--> $SRC_DIR/core/src/any.rs:LL:COL
::: $SRC_DIR/core/src/any.rs:LL:COL
|
= note: in this macro invocation
note: inside `<TypeId as PartialEq>::eq::compiletime`
--> $SRC_DIR/core/src/any.rs:LL:COL
= note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
@@ -5,14 +5,7 @@ LL | assert!(a == b);
| ^^^^^^ evaluation of `_` failed inside this call
|
note: inside `<TypeId as PartialEq>::eq`
--> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL
--> $SRC_DIR/core/src/any.rs:LL:COL
::: $SRC_DIR/core/src/any.rs:LL:COL
|
= note: in this macro invocation
note: inside `<TypeId as PartialEq>::eq::compiletime`
--> $SRC_DIR/core/src/any.rs:LL:COL
= note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
@@ -5,14 +5,7 @@ LL | assert!(b == b);
| ^^^^^^ evaluation of `_` failed inside this call
|
note: inside `<TypeId as PartialEq>::eq`
--> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL
--> $SRC_DIR/core/src/any.rs:LL:COL
::: $SRC_DIR/core/src/any.rs:LL:COL
|
= note: in this macro invocation
note: inside `<TypeId as PartialEq>::eq::compiletime`
--> $SRC_DIR/core/src/any.rs:LL:COL
= note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
@@ -5,14 +5,7 @@ LL | id == id
| ^^^^^^^^ evaluation of `X` failed inside this call
|
note: inside `<TypeId as PartialEq>::eq`
--> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL
--> $SRC_DIR/core/src/any.rs:LL:COL
::: $SRC_DIR/core/src/any.rs:LL:COL
|
= note: in this macro invocation
note: inside `<TypeId as PartialEq>::eq::compiletime`
--> $SRC_DIR/core/src/any.rs:LL:COL
= note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
+1 -1
View File
@@ -14,7 +14,7 @@ union Foo {
b: (),
}
let x = unsafe { Foo { b: () }.a };
//~^ ERROR: value of uninhabited type
//~^ ERROR: value of zero-variant enum
let x = &x.inner;
};
+1 -1
View File
@@ -1,4 +1,4 @@
error[E0080]: constructing invalid value of type ChildStdin: at .inner, encountered a value of uninhabited type `AnonPipe`
error[E0080]: constructing invalid value of type ChildStdin: at .inner, encountered a value of zero-variant enum `AnonPipe`
--> $DIR/issue-64506.rs:16:22
|
LL | let x = unsafe { Foo { b: () }.a };
@@ -9,7 +9,7 @@ LL | |
LL | | if false { yield None.unwrap(); }
LL | | None.unwrap()
LL | | })
| |_____^ cyclic type of infinite size
| |_____^ recursive type with infinite-size name
|
= note: closures cannot capture themselves or take themselves as argument;
this error may be the result of a recent compiler bug-fix,
@@ -34,7 +34,7 @@ LL | |
LL | | if false { yield None.unwrap(); }
LL | | None.unwrap()
LL | | })
| |_____^ cyclic type of infinite size
| |_____^ recursive type with infinite-size name
|
= note: closures cannot capture themselves or take themselves as argument;
this error may be the result of a recent compiler bug-fix,
@@ -0,0 +1,28 @@
//@ run-pass
// Regression test for the const-eval reproducer in rust-lang/rust#156313.
#![feature(reborrow)]
#![allow(dead_code)]
#![allow(unused_variables)]
use std::marker::{CoerceShared, Reborrow};
pub struct MyMut<'a>(&'a u8);
impl Reborrow for MyMut<'_> {}
#[derive(Clone, Copy)]
pub struct MyRef<'a>(&'a u8);
impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
const fn consteval_reproducer() {
let value = 1;
foo(MyMut(&value));
}
const fn foo(x: MyRef<'_>) {}
fn main() {
const { consteval_reproducer(); }
}
@@ -0,0 +1,21 @@
//@ check-fail
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct MyMut<'a>(&'a u8);
impl Reborrow for MyMut<'_> {}
#[derive(Clone, Copy)]
struct MyRef<'a>(&'a u8);
impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
const fn coerce(x: MyRef<'_>) -> MyRef<'_> {
x
}
static BAD: &'static MyRef<'static> = &coerce(MyMut(&1));
//~^ ERROR temporary value dropped while borrowed
fn main() {}
@@ -0,0 +1,13 @@
error[E0716]: temporary value dropped while borrowed
--> $DIR/reborrow-promotion-rejected.rs:18:47
|
LL | static BAD: &'static MyRef<'static> = &coerce(MyMut(&1));
| --------^^^^^^^^^-
| | | |
| | | temporary value is freed at the end of this statement
| | creates a temporary value which is freed while still in use
| using this value as a static requires that borrow lasts for `'static`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0716`.
+2 -2
View File
@@ -10,9 +10,9 @@ enum Void {}
static VOID2: Void = unsafe { std::mem::transmute(()) }; //~ ERROR static of uninhabited type
//~| WARN: previously accepted
//~| ERROR value of uninhabited type `Void`
//~| ERROR value of zero-variant enum `Void`
static NEVER2: Void = unsafe { std::mem::transmute(()) }; //~ ERROR static of uninhabited type
//~| WARN: previously accepted
//~| ERROR value of uninhabited type `Void`
//~| ERROR value of zero-variant enum `Void`
fn main() {}
+2 -2
View File
@@ -39,13 +39,13 @@ LL | static NEVER: !;
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #74840 <https://github.com/rust-lang/rust/issues/74840>
error[E0080]: constructing invalid value of type Void: encountered a value of uninhabited type `Void`
error[E0080]: constructing invalid value of type Void: encountered a value of zero-variant enum `Void`
--> $DIR/uninhabited-static.rs:11:31
|
LL | static VOID2: Void = unsafe { std::mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `VOID2` failed here
error[E0080]: constructing invalid value of type Void: encountered a value of uninhabited type `Void`
error[E0080]: constructing invalid value of type Void: encountered a value of zero-variant enum `Void`
--> $DIR/uninhabited-static.rs:14:32
|
LL | static NEVER2: Void = unsafe { std::mem::transmute(()) };
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself
--> $DIR/cyclic_type_ice.rs:3:7
|
LL | f(f);
| ^ cyclic type of infinite size
| ^ recursive type with infinite-size name
|
= note: closures cannot capture themselves or take themselves as argument;
this error may be the result of a recent compiler bug-fix,
@@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself
--> $DIR/unboxed-closure-no-cyclic-sig.rs:8:7
|
LL | g(|_| { });
| ^^^ cyclic type of infinite size
| ^^^ recursive type with infinite-size name
|
= note: closures cannot capture themselves or take themselves as argument;
this error may be the result of a recent compiler bug-fix,