mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-29 20:46:07 +03:00
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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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() },
|
||||
|
||||
@@ -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},)+)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -12,6 +12,9 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test (${{ matrix.host_target }})
|
||||
|
||||
@@ -8,6 +8,9 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sysroots:
|
||||
name: Build the sysroots
|
||||
|
||||
@@ -1 +1 @@
|
||||
281c97c3240a9abd984ca0c6a2cd7389115e80d5
|
||||
1f8e04d34ab0c1fd9574840aa6db670e41593bfb
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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<'_>) {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(×pec)? 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))?;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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(×pec)? 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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(());
|
||||
}
|
||||
|
||||
|
||||
@@ -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"))?;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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|}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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`.
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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(()) };
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user