From 4831c4924b3a60590f43e117f1e303c0b242f798 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Sun, 15 Mar 2026 14:37:11 +0000 Subject: [PATCH 01/14] remove usages of to-be-deprecated float consts --- src/tools/miri/tests/pass/float.rs | 100 +++++++++--------- .../pass/shims/x86/intrinsics-x86-sse.rs | 3 +- .../pass/shims/x86/intrinsics-x86-sse2.rs | 3 +- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 7384c2cb0074..a051e6820669 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -7,21 +7,25 @@ #![allow(arithmetic_overflow)] #![allow(internal_features)] #![allow(unnecessary_transmutes)] +#![deny(deprecated_in_future)] #[path = "../utils/mod.rs"] mod utils; use std::any::type_name; use std::cmp::min; +use std::f16::consts as f16_consts; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; +use std::f128::consts as f128_consts; use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; -use std::{f16, f32, f64, f128}; use utils::check_nondet; /// Compare the two floats, allowing for $ulp many ULPs of error. /// /// ULP means "Units in the Last Place" or "Units of Least Precision". -/// The ULP of a float `a`` is the smallest possible change at `a`, so the ULP difference represents how +/// The ULP of a float `a` is the smallest possible change at `a`, so the ULP difference represents how /// many discrete floating-point steps are needed to reach the actual value from the expected value. /// /// Essentially ULP can be seen as a distance metric of floating-point numbers, but with @@ -1019,10 +1023,10 @@ fn mul_add() { assert_eq!(3.0f64.mul_add(2.0f64, 5.0f64), 11.0); assert_eq!(3.0f128.mul_add(2.0f128, 5.0f128), 11.0); - assert_eq!(0.0f16.mul_add(-2.0f16, f16::consts::E), f16::consts::E); - assert_eq!(0.0f32.mul_add(-2.0f32, f32::consts::E), f32::consts::E); - assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E); - assert_eq!(0.0f128.mul_add(-2.0f128, f128::consts::E), f128::consts::E); + assert_eq!(0.0f16.mul_add(-2.0f16, f16_consts::E), f16_consts::E); + assert_eq!(0.0f32.mul_add(-2.0f32, f32_consts::E), f32_consts::E); + assert_eq!(0.0f64.mul_add(-2.0f64, f64_consts::E), f64_consts::E); + assert_eq!(0.0f128.mul_add(-2.0f128, f128_consts::E), f128_consts::E); assert_eq!((-3.2f16).mul_add(2.4, f16::NEG_INFINITY), f16::NEG_INFINITY); assert_eq!((-3.2f32).mul_add(2.4, f32::NEG_INFINITY), f32::NEG_INFINITY); @@ -1125,16 +1129,16 @@ fn ldexp(a: f64, b: i32) -> f64 { assert_biteq((-0f32).powi(9), -0.0, "-0^x = -0 where x is negative"); assert_biteq((-0f64).powi(99), -0.0, "-0^x = -0 where x is negative"); - assert_approx_eq!(1f16.exp(), f16::consts::E); - assert_approx_eq!(1f32.exp(), f32::consts::E); - assert_approx_eq!(1f64.exp(), f64::consts::E); + assert_approx_eq!(1f16.exp(), f16_consts::E); + assert_approx_eq!(1f32.exp(), f32_consts::E); + assert_approx_eq!(1f64.exp(), f64_consts::E); assert_eq!(0f16.exp(), 1.0); assert_eq!(0f32.exp(), 1.0); assert_eq!(0f64.exp(), 1.0); - assert_approx_eq!(1f16.exp_m1(), f16::consts::E - 1.0); - assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0); - assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0); + assert_approx_eq!(1f16.exp_m1(), f16_consts::E - 1.0); + assert_approx_eq!(1f32.exp_m1(), f32_consts::E - 1.0); + assert_approx_eq!(1f64.exp_m1(), f64_consts::E - 1.0); assert_approx_eq!(f16::NEG_INFINITY.exp_m1(), -1.0); assert_approx_eq!(f32::NEG_INFINITY.exp_m1(), -1.0); assert_approx_eq!(f64::NEG_INFINITY.exp_m1(), -1.0); @@ -1146,9 +1150,9 @@ fn ldexp(a: f64, b: i32) -> f64 { assert_eq!(0f32.exp2(), 1.0); assert_eq!(0f64.exp2(), 1.0); - assert_approx_eq!(f16::consts::E.ln(), 1f16); - assert_approx_eq!(f32::consts::E.ln(), 1f32); - assert_approx_eq!(f64::consts::E.ln(), 1f64); + assert_approx_eq!(f16_consts::E.ln(), 1f16); + assert_approx_eq!(f32_consts::E.ln(), 1f32); + assert_approx_eq!(f64_consts::E.ln(), 1f64); assert_eq!(1f16.ln(), 0.0); assert_eq!(1f32.ln(), 0.0); assert_eq!(1f64.ln(), 0.0); @@ -1159,11 +1163,11 @@ fn ldexp(a: f64, b: i32) -> f64 { assert_approx_eq!(10f16.log10(), 1f16); assert_approx_eq!(10f32.log10(), 1f32); - assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E); + assert_approx_eq!(f64_consts::E.log10(), f64_consts::LOG10_E); assert_approx_eq!(8f16.log2(), 3f16); assert_approx_eq!(8f32.log2(), 3f32); - assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E); + assert_approx_eq!(f64_consts::E.log2(), f64_consts::LOG2_E); #[allow(deprecated)] { @@ -1189,13 +1193,13 @@ fn ldexp(a: f64, b: i32) -> f64 { assert_eq!(0f16.sin(), 0f16); assert_eq!(0f32.sin(), 0f32); assert_eq!(0f64.sin(), 0f64); - assert_approx_eq!(f16::consts::FRAC_PI_6.sin(), 0.5); - assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5); - assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f16_consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f32_consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f64_consts::FRAC_PI_6.sin(), 0.5); // Increase error tolerance to 16ULP because of the extra operation. - assert_approx_eq!(f16::consts::FRAC_PI_4.sin().asin(), f16::consts::FRAC_PI_4, 16); - assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4, 16); - assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4, 16); + assert_approx_eq!(f16_consts::FRAC_PI_4.sin().asin(), f16_consts::FRAC_PI_4, 16); + assert_approx_eq!(f32_consts::FRAC_PI_4.sin().asin(), f32_consts::FRAC_PI_4, 16); + assert_approx_eq!(f64_consts::FRAC_PI_4.sin().asin(), f64_consts::FRAC_PI_4, 16); assert_biteq(0.0f16.asin(), 0.0f16, "asin(+0) = +0"); assert_biteq((-0.0f16).asin(), -0.0, "asin(-0) = -0"); assert_biteq(0.0f32.asin(), 0.0f32, "asin(+0) = +0"); @@ -1212,12 +1216,12 @@ fn ldexp(a: f64, b: i32) -> f64 { // Ensure `sin` always returns something that is a valid input for `asin`, and same for // `cos` and `acos`. - let halve_pi_f16 = std::f16::consts::FRAC_PI_2; - let halve_pi_f32 = std::f32::consts::FRAC_PI_2; - let halve_pi_f64 = std::f64::consts::FRAC_PI_2; - let pi_f16 = std::f16::consts::PI; - let pi_f32 = std::f32::consts::PI; - let pi_f64 = std::f64::consts::PI; + let halve_pi_f16 = f16_consts::FRAC_PI_2; + let halve_pi_f32 = f32_consts::FRAC_PI_2; + let halve_pi_f64 = f64_consts::FRAC_PI_2; + let pi_f16 = f16_consts::PI; + let pi_f32 = f32_consts::PI; + let pi_f64 = f64_consts::PI; for _ in 0..64 { // sin() should be clamped to [-1, 1] so asin() can never return NaN assert!(!halve_pi_f16.sin().asin().is_nan()); @@ -1232,13 +1236,13 @@ fn ldexp(a: f64, b: i32) -> f64 { assert_eq!(0f16.cos(), 1f16); assert_eq!(0f32.cos(), 1f32); assert_eq!(0f64.cos(), 1f64); - assert_approx_eq!(f16::consts::FRAC_PI_3.cos(), 0.5); - assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5); - assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f16_consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f32_consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f64_consts::FRAC_PI_3.cos(), 0.5); // Increase error tolerance to 16ULP because of the extra operation. - assert_approx_eq!(f16::consts::FRAC_PI_4.cos().acos(), f16::consts::FRAC_PI_4, 16); - assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4, 16); - assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4, 16); + assert_approx_eq!(f16_consts::FRAC_PI_4.cos().acos(), f16_consts::FRAC_PI_4, 16); + assert_approx_eq!(f32_consts::FRAC_PI_4.cos().acos(), f32_consts::FRAC_PI_4, 16); + assert_approx_eq!(f64_consts::FRAC_PI_4.cos().acos(), f64_consts::FRAC_PI_4, 16); assert_biteq(1.0f16.acos(), 0.0, "acos(1) = 0"); assert_biteq(1.0f32.acos(), 0.0, "acos(1) = 0"); assert_biteq(1.0f64.acos(), 0.0, "acos(1) = 0"); @@ -1314,15 +1318,15 @@ macro_rules! fixed_atan2_cases{ assert_approx_eq!( 1.0f16.tanh(), - (1.0 - f16::consts::E.powi(-2)) / (1.0 + f16::consts::E.powi(-2)) + (1.0 - f16_consts::E.powi(-2)) / (1.0 + f16_consts::E.powi(-2)) ); assert_approx_eq!( 1.0f32.tanh(), - (1.0 - f32::consts::E.powi(-2)) / (1.0 + f32::consts::E.powi(-2)) + (1.0 - f32_consts::E.powi(-2)) / (1.0 + f32_consts::E.powi(-2)) ); assert_approx_eq!( 1.0f64.tanh(), - (1.0 - f64::consts::E.powi(-2)) / (1.0 + f64::consts::E.powi(-2)) + (1.0 - f64_consts::E.powi(-2)) / (1.0 + f64_consts::E.powi(-2)) ); assert_eq!(f16::INFINITY.tanh(), 1.0); assert_eq!(f16::NEG_INFINITY.tanh(), -1.0); @@ -1338,22 +1342,22 @@ macro_rules! fixed_atan2_cases{ assert_approx_eq!(5.0f16.gamma(), 24.0); assert_approx_eq!(5.0f32.gamma(), 24.0); assert_approx_eq!(5.0f64.gamma(), 24.0); - assert_approx_eq!((-0.5f16).gamma(), (-2.0) * f16::consts::PI.sqrt()); - assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt()); - assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt()); + assert_approx_eq!((-0.5f16).gamma(), (-2.0) * f16_consts::PI.sqrt()); + assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32_consts::PI.sqrt()); + assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64_consts::PI.sqrt()); assert_eq!(2.0f16.ln_gamma(), (0.0, 1)); assert_eq!(2.0f32.ln_gamma(), (0.0, 1)); assert_eq!(2.0f64.ln_gamma(), (0.0, 1)); // Gamma(-0.5) = -2*sqrt(π) let (val, sign) = (-0.5f16).ln_gamma(); - assert_approx_eq!(val, (2.0 * f16::consts::PI.sqrt()).ln()); + assert_approx_eq!(val, (2.0 * f16_consts::PI.sqrt()).ln()); assert_eq!(sign, -1); let (val, sign) = (-0.5f32).ln_gamma(); - assert_approx_eq!(val, (2.0 * f32::consts::PI.sqrt()).ln()); + assert_approx_eq!(val, (2.0 * f32_consts::PI.sqrt()).ln()); assert_eq!(sign, -1); let (val, sign) = (-0.5f64).ln_gamma(); - assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln()); + assert_approx_eq!(val, (2.0 * f64_consts::PI.sqrt()).ln()); assert_eq!(sign, -1); assert_approx_eq!(1.0f16.erf(), 0.84270079294971486934122063508260926f16); @@ -1558,7 +1562,7 @@ fn test_operations_f16(a: f16, b: f16) { check_nondet(|| a.log(b)); check_nondet(|| a.exp()); check_nondet(|| 10f16.exp2()); - check_nondet(|| f16::consts::E.ln()); + check_nondet(|| f16_consts::E.ln()); check_nondet(|| 10f16.log10()); check_nondet(|| 8f16.log2()); check_nondet(|| 1f16.sin()); @@ -1594,7 +1598,7 @@ fn test_operations_f32(a: f32, b: f32) { check_nondet(|| a.log(b)); check_nondet(|| a.exp()); check_nondet(|| 10f32.exp2()); - check_nondet(|| f32::consts::E.ln()); + check_nondet(|| f32_consts::E.ln()); check_nondet(|| 10f32.log10()); check_nondet(|| 8f32.log2()); check_nondet(|| 1f32.ln_1p()); @@ -1631,8 +1635,8 @@ fn test_operations_f64(a: f64, b: f64) { check_nondet(|| a.exp()); check_nondet(|| 50f64.exp2()); check_nondet(|| 3f64.ln()); - check_nondet(|| f64::consts::E.log10()); - check_nondet(|| f64::consts::E.log2()); + check_nondet(|| f64_consts::E.log10()); + check_nondet(|| f64_consts::E.log2()); check_nondet(|| 1f64.ln_1p()); check_nondet(|| 27.0f64.cbrt()); check_nondet(|| 3.0f64.hypot(4.0f64)); diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs index be3f961e10ff..9136b5eda387 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs @@ -6,9 +6,10 @@ use std::arch::x86::*; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; -use std::f32::NAN; use std::mem::transmute; +const NAN: f32 = f32::NAN; + fn main() { assert!(is_x86_feature_detected!("sse")); diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs index 242aa0e89f63..570da30f0b62 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs @@ -6,9 +6,10 @@ use std::arch::x86::*; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; -use std::f64::NAN; use std::mem::transmute; +const NAN: f64 = f64::NAN; + fn main() { assert!(is_x86_feature_detected!("sse2")); From 6122db7870f08a6b5ce5338906b750941370d94f Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 16 Mar 2026 05:32:13 +0000 Subject: [PATCH 02/14] Prepare for merging from rust-lang/rust This updates the rust-version file to 1e2183119f0ee19cc26df899e26b04ad0de3475d. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 998ecc08a8e1..69609cc49f27 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -d1ee5e59a964a419b84b760812a35075034f4861 +1e2183119f0ee19cc26df899e26b04ad0de3475d From e4625087f8e6c1897934c0223884450c4ca5e675 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 17 Mar 2026 13:19:22 +0100 Subject: [PATCH 03/14] update error kind mapping URLs --- src/tools/miri/src/shims/io_error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 91ae448e2165..ec3a2cfe5144 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -56,7 +56,7 @@ fn from(value: Scalar) -> Self { } // This mapping should match `decode_error_kind` in -// . +// . const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { use std::io::ErrorKind::*; &[ @@ -103,7 +103,7 @@ fn from(value: Scalar) -> Self { ] }; // This mapping should match `decode_error_kind` in -// . +// . const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { use std::io::ErrorKind::*; // It's common for multiple error codes to map to the same io::ErrorKind. We have all for the From 8ace95963961bf2d818431fd35f933ead33aa816 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 18 Mar 2026 05:22:01 +0000 Subject: [PATCH 04/14] Prepare for merging from rust-lang/rust This updates the rust-version file to 91775dbec9771aa0c1b9ebe268eb5bd271e79a7a. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 69609cc49f27..9c8f107f4235 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -1e2183119f0ee19cc26df899e26b04ad0de3475d +91775dbec9771aa0c1b9ebe268eb5bd271e79a7a From 55ba943a36398dbcb83df8c48fe525fcbecaa705 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 18 Mar 2026 08:30:42 +0100 Subject: [PATCH 05/14] clippy, and a bit of readability --- src/tools/miri/src/shims/x86/avx512.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/src/shims/x86/avx512.rs b/src/tools/miri/src/shims/x86/avx512.rs index 23538f0dea96..8e1d22d723e7 100644 --- a/src/tools/miri/src/shims/x86/avx512.rs +++ b/src/tools/miri/src/shims/x86/avx512.rs @@ -191,9 +191,9 @@ fn vpdpbusd<'tcx>( // fn vpdpbusd(src: i32x16, a: u8x64, b: i8x64) -> i32x16; // fn vpdpbusd256(src: i32x8, a: u8x32, b: i8x32) -> i32x8; // fn vpdpbusd128(src: i32x4, a: u8x16, b: i8x16) -> i32x4; - assert_eq!(dest_len, src_len); - assert_eq!(dest_len * 4, a_len); - assert_eq!(a_len, b_len); + assert_eq!(src_len, dest_len); + assert_eq!(a_len, dest_len.strict_mul(4)); + assert_eq!(b_len, a_len); for i in 0..dest_len { let src = ecx.read_scalar(&ecx.project_index(&src, i)?)?.to_i32()?; From e5e26094202d4b53f0b273c54d904c27d8eacf3d Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 19 Mar 2026 05:19:34 +0000 Subject: [PATCH 06/14] Prepare for merging from rust-lang/rust This updates the rust-version file to fd0c901b00ee1e08a250039cdb90258603497e20. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 9c8f107f4235..e281dad8ef1e 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -91775dbec9771aa0c1b9ebe268eb5bd271e79a7a +fd0c901b00ee1e08a250039cdb90258603497e20 From a3e469ea1f03936e2156b7d683aa7fc83d56a252 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 19 Mar 2026 07:48:27 +0100 Subject: [PATCH 07/14] fix some outdated comments in tests --- .../miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs | 2 +- src/tools/miri/tests/pass/packed-struct-dyn-trait.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs index 233b927dfc7e..03b716cda500 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs +++ b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs @@ -2,7 +2,7 @@ // (i.e, no EscapeToRaw happened). // We could, in principle, do EscapeToRaw lazily to allow this code, but that // would no alleviate the need for EscapeToRaw (see `ref_raw_int_raw` in -// `run-pass/stacked-borrows.rs`), and thus increase overall complexity. +// `pass/both_borrows/int-to-ptr.rs`), and thus increase overall complexity. use std::mem; fn main() { diff --git a/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs index bb73c26c18a0..2e61e77a221e 100644 --- a/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs +++ b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs @@ -1,4 +1,3 @@ -// run-pass use std::ptr::addr_of; // When the unsized tail is a `dyn Trait`, its alignments is only dynamically known. This means the From 359718ce103db5e46eece007c928e7d35b74786f Mon Sep 17 00:00:00 2001 From: Bourumir Wyngs Date: Thu, 19 Mar 2026 22:58:53 +0100 Subject: [PATCH 08/14] Adding serde-yaml-bw memory leak detection --- src/tools/miri/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 4808e53698c6..bd77d5616769 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -641,6 +641,7 @@ Definite bugs found: * [`winit` registering a global constructor with the wrong ABI on Windows](https://github.com/rust-windowing/winit/issues/4435) * [`VecDeque::splice` confusing physical and logical indices](https://github.com/rust-lang/rust/issues/151758) * [Data race in `oneshot` channel](https://github.com/faern/oneshot/issues/69) +* [Memory leak is serde-yaml-bw library](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): From 4d7a71ea9255a82d562929d4b965fa34720ef5c5 Mon Sep 17 00:00:00 2001 From: Bourumir Wyngs Date: Thu, 19 Mar 2026 23:16:05 +0100 Subject: [PATCH 09/14] Maybe to say more words what kind of the leak --- src/tools/miri/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index bd77d5616769..28cdc8f0a7f9 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -641,7 +641,7 @@ Definite bugs found: * [`winit` registering a global constructor with the wrong ABI on Windows](https://github.com/rust-windowing/winit/issues/4435) * [`VecDeque::splice` confusing physical and logical indices](https://github.com/rust-lang/rust/issues/151758) * [Data race in `oneshot` channel](https://github.com/faern/oneshot/issues/69) -* [Memory leak is serde-yaml-bw library](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197) +* [Memory leak in serde-yaml-bw caused by early return before libyaml event cleanup in Parser::next](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): From 7f7a3bd6c025fd77f3b3255a80e99a25f87e9203 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 20 Mar 2026 10:23:32 +0100 Subject: [PATCH 10/14] tweak wording --- src/tools/miri/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 28cdc8f0a7f9..97f385ad755b 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -641,7 +641,7 @@ Definite bugs found: * [`winit` registering a global constructor with the wrong ABI on Windows](https://github.com/rust-windowing/winit/issues/4435) * [`VecDeque::splice` confusing physical and logical indices](https://github.com/rust-lang/rust/issues/151758) * [Data race in `oneshot` channel](https://github.com/faern/oneshot/issues/69) -* [Memory leak in serde-yaml-bw caused by early return before libyaml event cleanup in Parser::next](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197) +* [Memory leak in serde-yaml-bw](https://github.com/bourumir-wyngs/serde-yaml-bw/issues/197) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): From 9a021da8db29d77e56eb70f9d8635fb13f4673c2 Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Mon, 22 Dec 2025 18:42:42 +0100 Subject: [PATCH 11/14] Add a shim for uname systemcall --- src/tools/miri/src/shims/unix/env.rs | 38 +++++++++++++++++++ .../miri/src/shims/unix/foreign_items.rs | 10 +++++ .../src/shims/unix/freebsd/foreign_items.rs | 17 +++++++++ .../miri/tests/pass-dep/libc/libc-uname.rs | 38 +++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-uname.rs diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs index 41bf70c34634..97d093ab3f1f 100644 --- a/src/tools/miri/src/shims/unix/env.rs +++ b/src/tools/miri/src/shims/unix/env.rs @@ -272,6 +272,44 @@ fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> { interp_ok(Scalar::from_u32(this.get_current_tid())) } + fn uname(&mut self, uname: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("uname"); + + let uname_ptr = this.read_pointer(uname)?; + if this.ptr_is_null(uname_ptr)? { + return this.set_last_error_and_return_i32(LibcError("EFAULT")); + } + + let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?; + let arch = this.machine.tcx.sess.target.arch.desc_symbol(); + // Values required by POSIX. + let values = [ + ("sysname", "Miri"), + ("nodename", "Miri"), + ("release", env!("CARGO_PKG_VERSION")), + ("version", concat!("Miri ", env!("CARGO_PKG_VERSION"))), + ("machine", arch.as_str()), + ]; + for (name, value) in values { + let field = this.project_field_named(&uname, name)?; + let size = field.layout().layout.size().bytes(); + let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?; + assert!(written); // All values should fit. + } + // The following fields are not defined on all OS/libc implementations, + // so only write them if they are defined. + let optional_values = [("domainname", "(none)")]; + for (name, value) in optional_values { + if let Some(field) = this.try_project_field_named(&uname, name)? { + let size = field.layout().layout.size().bytes(); + let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?; + assert!(written); // All values should fit. + } + } + interp_ok(Scalar::from_i32(0)) + } + /// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which /// allows querying the ID for arbitrary threads, identified by their pthread_t. /// diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 9a24cbc5048a..23345ccbcc19 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -174,6 +174,16 @@ fn emulate_foreign_item_inner( let result = this.getpid()?; this.write_scalar(result, dest)?; } + "uname" if !matches!(&this.tcx.sess.target.os, Os::FreeBsd) => { + let [uname] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _) -> i32), + link_name, + abi, + args, + )?; + let result = this.uname(uname)?; + this.write_scalar(result, dest)?; + } "sysconf" => { let [val] = this.check_shim_sig( shim_sig!(extern "C" fn(i32) -> isize), diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index f64ee3b16220..6cd4e6a939b4 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -165,6 +165,23 @@ fn emulate_foreign_item_inner( let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } + "__xuname" => { + // FreeBSD uses __xuname under the hood to implement uname, see: + // https://github.com/freebsd/freebsd-src/blob/3542d60fb8042474f66fbf2d779ed8c5a80d0f78/sys/sys/utsname.h#L64 + // https://github.com/freebsd/freebsd-src/blob/3542d60fb8042474f66fbf2d779ed8c5a80d0f78/lib/libc/gen/uname.c#L44 + let [size, uname] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *mut _) -> i32), + link_name, + abi, + args, + )?; + // The size determines the length of each field in the utsname + // structure. We don't really care about this, all we do check + // that the argument is an i32. + let _size = this.read_scalar(size)?.to_i32()?; + let result = this.uname(uname)?; + this.write_scalar(result, dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-uname.rs b/src/tools/miri/tests/pass-dep/libc/libc-uname.rs new file mode 100644 index 000000000000..3c72ef300379 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-uname.rs @@ -0,0 +1,38 @@ +//@ignore-target: windows # No libc + +use std::ffi::CStr; +use std::{io, ptr}; + +fn main() { + test_ok(); + test_null_ptr(); +} + +fn test_ok() { + // SAFETY: all zeros for `utsname` is valid. + let mut uname: libc::utsname = unsafe { std::mem::zeroed() }; + let result = unsafe { libc::uname(&mut uname) }; + if result != 0 { + panic!("failed to call uname"); + } + + assert_eq!(unsafe { CStr::from_ptr(&uname.sysname as *const _) }, c"Miri"); + assert_eq!(unsafe { CStr::from_ptr(&uname.nodename as *const _) }, c"Miri"); + assert_eq!( + unsafe { CStr::from_ptr(&uname.release as *const _) }.to_str().unwrap(), + env!("CARGO_PKG_VERSION") + ); + assert_eq!(unsafe { CStr::from_ptr(&uname.version as *const _) }, c"Miri 0.1.0"); + assert_eq!( + unsafe { CStr::from_ptr(&uname.machine as *const _) }.to_str().unwrap(), + std::env::consts::ARCH + ); + #[cfg(any(target_os = "linux", target_os = "android"))] + assert_eq!(unsafe { CStr::from_ptr(&uname.domainname as *const _) }, c"(none)"); +} + +fn test_null_ptr() { + let result = unsafe { libc::uname(ptr::null_mut()) }; + assert_eq!(result, -1); + assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EFAULT)); +} From 067558dc5c4453cd9f807affb0ccf99be2a642b4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 22 Mar 2026 11:27:36 +0100 Subject: [PATCH 12/14] check size field on freebsd and other tweaks --- src/tools/miri/src/shims/unix/env.rs | 33 ++++++++++++------- .../miri/src/shims/unix/foreign_items.rs | 9 +++-- .../src/shims/unix/freebsd/foreign_items.rs | 6 +--- .../miri/tests/pass-dep/libc/libc-time.rs | 1 + .../miri/tests/pass-dep/libc/libc-uname.rs | 14 ++++---- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs index 97d093ab3f1f..488d82f2109a 100644 --- a/src/tools/miri/src/shims/unix/env.rs +++ b/src/tools/miri/src/shims/unix/env.rs @@ -272,11 +272,21 @@ fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> { interp_ok(Scalar::from_u32(this.get_current_tid())) } - fn uname(&mut self, uname: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + /// `fields_size`, if present, says how large each field of the struct is. + fn uname( + &mut self, + uname: &OpTy<'tcx>, + fields_size: Option<&OpTy<'tcx>>, + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); this.assert_target_os_is_unix("uname"); let uname_ptr = this.read_pointer(uname)?; + let fields_size = match fields_size { + None => None, + Some(size) => Some(this.read_scalar(size)?.to_i32()?), + }; + if this.ptr_is_null(uname_ptr)? { return this.set_last_error_and_return_i32(LibcError("EFAULT")); } @@ -284,29 +294,28 @@ fn uname(&mut self, uname: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?; let arch = this.machine.tcx.sess.target.arch.desc_symbol(); // Values required by POSIX. - let values = [ + let mut values = vec![ ("sysname", "Miri"), ("nodename", "Miri"), ("release", env!("CARGO_PKG_VERSION")), ("version", concat!("Miri ", env!("CARGO_PKG_VERSION"))), ("machine", arch.as_str()), ]; + if matches!(this.machine.tcx.sess.target.os, Os::Linux | Os::Android) { + values.push(("domainname", "(none)")); + } + for (name, value) in values { let field = this.project_field_named(&uname, name)?; let size = field.layout().layout.size().bytes(); + if fields_size.is_some_and(|fields_size| u64::try_from(fields_size) != Ok(size)) { + throw_unsup_format!( + "the fields size passed to `uname` does not match the type in the libc crate" + ); + } let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?; assert!(written); // All values should fit. } - // The following fields are not defined on all OS/libc implementations, - // so only write them if they are defined. - let optional_values = [("domainname", "(none)")]; - for (name, value) in optional_values { - if let Some(field) = this.try_project_field_named(&uname, name)? { - let size = field.layout().layout.size().bytes(); - let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?; - assert!(written); // All values should fit. - } - } interp_ok(Scalar::from_i32(0)) } diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 23345ccbcc19..bf9482a6264b 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -174,14 +174,19 @@ fn emulate_foreign_item_inner( let result = this.getpid()?; this.write_scalar(result, dest)?; } - "uname" if !matches!(&this.tcx.sess.target.os, Os::FreeBsd) => { + "uname" => { + // Not all Unixes have the `uname` symbol, e.g. FreeBSD does not. + this.check_target_os( + &[Os::Linux, Os::Android, Os::MacOs, Os::Solaris, Os::Illumos], + link_name, + )?; let [uname] = this.check_shim_sig( shim_sig!(extern "C" fn(*mut _) -> i32), link_name, abi, args, )?; - let result = this.uname(uname)?; + let result = this.uname(uname, None)?; this.write_scalar(result, dest)?; } "sysconf" => { diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 6cd4e6a939b4..de08f4c6afe4 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -175,11 +175,7 @@ fn emulate_foreign_item_inner( abi, args, )?; - // The size determines the length of each field in the utsname - // structure. We don't really care about this, all we do check - // that the argument is an i32. - let _size = this.read_scalar(size)?.to_i32()?; - let result = this.uname(uname)?; + let result = this.uname(uname, Some(size))?; this.write_scalar(result, dest)?; } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index b80fb0025530..141e0009101a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -3,6 +3,7 @@ #[path = "../../utils/libc.rs"] mod libc_utils; + use std::time::{Duration, Instant}; use std::{env, mem, ptr}; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-uname.rs b/src/tools/miri/tests/pass-dep/libc/libc-uname.rs index 3c72ef300379..b071e0522582 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-uname.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-uname.rs @@ -1,8 +1,13 @@ //@ignore-target: windows # No libc +#[path = "../../utils/libc.rs"] +mod libc_utils; + use std::ffi::CStr; use std::{io, ptr}; +use libc_utils::*; + fn main() { test_ok(); test_null_ptr(); @@ -11,10 +16,7 @@ fn main() { fn test_ok() { // SAFETY: all zeros for `utsname` is valid. let mut uname: libc::utsname = unsafe { std::mem::zeroed() }; - let result = unsafe { libc::uname(&mut uname) }; - if result != 0 { - panic!("failed to call uname"); - } + errno_check(unsafe { libc::uname(&mut uname) }); assert_eq!(unsafe { CStr::from_ptr(&uname.sysname as *const _) }, c"Miri"); assert_eq!(unsafe { CStr::from_ptr(&uname.nodename as *const _) }, c"Miri"); @@ -32,7 +34,7 @@ fn test_ok() { } fn test_null_ptr() { - let result = unsafe { libc::uname(ptr::null_mut()) }; - assert_eq!(result, -1); + let err = errno_result(unsafe { libc::uname(ptr::null_mut()) }).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EFAULT)); assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EFAULT)); } From d7706e59a8b30d0dd40000ac639d0983b0816897 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:40:38 +0100 Subject: [PATCH 13/14] Add blocking I/O manager, socket `accept` and `connect` shims Integrates mio into the scheduler to block threads for external I/O events (like blocking socket operations) --- src/tools/miri/Cargo.lock | 10 +- src/tools/miri/Cargo.toml | 1 + src/tools/miri/src/concurrency/blocking_io.rs | 148 +++++++ src/tools/miri/src/concurrency/mod.rs | 1 + src/tools/miri/src/concurrency/thread.rs | 92 +++- src/tools/miri/src/diagnostics.rs | 14 - src/tools/miri/src/lib.rs | 2 + src/tools/miri/src/machine.rs | 7 + src/tools/miri/src/provenance_gc.rs | 2 +- src/tools/miri/src/shims/io_error.rs | 1 + .../miri/src/shims/unix/foreign_items.rs | 27 ++ .../miri/src/shims/unix/linux_like/syscall.rs | 8 + src/tools/miri/src/shims/unix/socket.rs | 413 ++++++++++++++++-- .../src/shims/unix/solarish/foreign_items.rs | 9 + .../miri/tests/pass-dep/libc/libc-socket.rs | 138 ++++-- .../libc/socket-unsupported-listen-backlog.rs | 27 -- .../socket-unsupported-listen-backlog.stderr | 8 - src/tools/miri/tests/pass-dep/shims/socket.rs | 17 - src/tools/miri/tests/pass/shims/socket.rs | 36 ++ 19 files changed, 806 insertions(+), 155 deletions(-) create mode 100644 src/tools/miri/src/concurrency/blocking_io.rs delete mode 100644 src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.rs delete mode 100644 src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.stderr delete mode 100644 src/tools/miri/tests/pass-dep/shims/socket.rs create mode 100644 src/tools/miri/tests/pass/shims/socket.rs diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 2d332ae98dda..630a4b5e3e0f 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -779,9 +779,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libffi" @@ -923,11 +923,12 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -950,6 +951,7 @@ dependencies = [ "libffi", "libloading", "measureme", + "mio", "nix", "rand", "regex", diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 62387848868d..3255885d95be 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -28,6 +28,7 @@ chrono-tz = "0.10" directories = "6" bitflags = "2.6" serde_json = { version = "1.0", optional = true } +mio = { version = "1.1.1", features = ["os-poll", "net"] } [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/src/tools/miri/src/concurrency/blocking_io.rs b/src/tools/miri/src/concurrency/blocking_io.rs new file mode 100644 index 000000000000..221d0495cd16 --- /dev/null +++ b/src/tools/miri/src/concurrency/blocking_io.rs @@ -0,0 +1,148 @@ +use std::io; +use std::time::Duration; + +use mio::event::Source; +use mio::{Events, Interest, Poll, Token}; +use rustc_data_structures::fx::FxHashMap; + +use crate::*; + +/// Capacity of the event queue which can be polled at a time. +/// Since we don't expect many simultaneous blocking I/O events +/// this value can be set rather low. +const IO_EVENT_CAPACITY: usize = 16; + +/// Trait for values that contain a mio [`Source`]. +pub trait WithSource { + /// Invoke `f` on the source inside `self`. + fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()>; +} + +/// Manager for managing blocking host I/O in a non-blocking manner. +/// We use [`Poll`] to poll for new I/O events from the OS for sources +/// registered using this manager. +/// +/// Since blocking host I/O is inherently non-deterministic, no method on this +/// manager should be called when isolation is enabled. The only exception is +/// the [`BlockingIoManager::new`] function to create the manager. Everywhere else, +/// we assert that isolation is disabled! +pub struct BlockingIoManager { + /// Poll instance to monitor I/O events from the OS. + /// This is only [`None`] when Miri is run with isolation enabled. + poll: Option, + /// Buffer used to store the ready I/O events when calling [`Poll::poll`]. + /// This is not part of the state and only stored to avoid allocating a + /// new buffer for every poll. + events: Events, + /// Map between threads which are currently blocked and the + /// underlying I/O source. + sources: FxHashMap>, +} + +impl BlockingIoManager { + /// Create a new blocking I/O manager instance based on the availability + /// of communication with the host. + pub fn new(communicate: bool) -> Result { + let manager = Self { + poll: communicate.then_some(Poll::new()?), + events: Events::with_capacity(IO_EVENT_CAPACITY), + sources: FxHashMap::default(), + }; + Ok(manager) + } + + /// 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. + /// + /// Returns all threads that are ready because they received an I/O event. + pub fn poll(&mut self, timeout: Option) -> Result, io::Error> { + let poll = + self.poll.as_mut().expect("Blocking I/O should not be called with isolation enabled"); + + // Poll for new I/O events from OS and store them in the events buffer. + poll.poll(&mut self.events, timeout)?; + + let ready = self + .events + .iter() + .map(|event| { + let token = event.token(); + ThreadId::new_unchecked(token.0.try_into().unwrap()) + }) + .collect::>(); + + // Deregister all ready sources as we only want to receive one event per thread. + ready.iter().for_each(|thread_id| self.deregister(*thread_id)); + + Ok(ready) + } + + /// Register a blocking I/O source for a thread together with it's poll interests. + /// + /// The source will be deregistered automatically once an event for it is received. + /// + /// As the OS can always produce spurious wake-ups, it's the callers responsibility to + /// verify the requested I/O interests are really ready and to register again if they're not. + pub fn register(&mut self, source: Box, thread: ThreadId, interests: Interest) { + let poll = + self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); + + let token = Token(thread.to_u32().to_usize()); + + // Treat errors from registering as fatal. On UNIX hosts this can only + // fail due to system resource errors (e.g. ENOMEM or ENOSPC). + source + .with_source(&mut |source| source.register(poll.registry(), token, interests)) + .unwrap(); + self.sources + .try_insert(thread, source) + .unwrap_or_else(|_| panic!("A thread cannot be registered twice at the same time")); + } + + /// Deregister the event source for a thread. Returns the kind of I/O the thread was + /// blocked on. + fn deregister(&mut self, thread: ThreadId) { + let poll = + self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); + + let Some(source) = self.sources.remove(&thread) else { + panic!("Attempt to deregister a token which isn't registered") + }; + + // Treat errors from deregistering as fatal. On UNIX hosts this can only + // fail due to system resource errors (e.g. ENOMEM or ENOSPC). + source.with_source(&mut |source| source.deregister(poll.registry())).unwrap(); + } +} + +impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { + /// Block the current thread until some interests on an I/O source + /// are fulfilled or the optional timeout exceeded. + /// The callback will be invoked when the thread gets unblocked. + /// + /// There can 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. + #[inline] + fn block_thread_for_io( + &mut self, + source: impl WithSource + 'static, + interests: Interest, + timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, + callback: DynUnblockCallback<'tcx>, + ) { + let this = self.eval_context_mut(); + this.machine.blocking_io.register( + Box::new(source), + this.machine.threads.active_thread(), + interests, + ); + this.block_thread(BlockReason::IO, timeout, callback); + } +} diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs index 421f24329df0..815ce914db57 100644 --- a/src/tools/miri/src/concurrency/mod.rs +++ b/src/tools/miri/src/concurrency/mod.rs @@ -1,3 +1,4 @@ +pub mod blocking_io; pub mod cpu_affinity; pub mod data_race; mod data_race_handler; diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 9d829bf69e5e..d6d6449df32b 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -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::seq::IteratorRandom; use rustc_abi::ExternAbi; @@ -25,10 +25,11 @@ enum SchedulingAction { /// Execute step on the active thread. ExecuteStep, - /// Execute a timeout callback. - ExecuteTimeoutCallback, - /// Wait for a bit, until there is a timeout to be called. - Sleep(Duration), + /// Wait for a bit, but at most as long as the duration specified. + /// We wake up early if an I/O event happened. + /// If the duration is [`None`], we sleep indefinitely. This is + /// only allowed when isolation is disabled and there are threads waiting for I/O! + SleepAndWaitForIo(Option), } /// What to do with TLS allocations from terminated threads @@ -111,6 +112,8 @@ pub enum BlockReason { Eventfd, /// Blocked on unnamed_socket. UnnamedSocket, + /// Blocked on an IO operation. + IO, /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only). /// Will be implicitly unblocked when GenMC schedules this thread again. Genmc, @@ -765,9 +768,7 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { } // We are not in GenMC mode, so we control the scheduling. - let thread_manager = &mut this.machine.threads; - let clock = &this.machine.monotonic_clock; - let rng = this.machine.rng.get_mut(); + let thread_manager = &this.machine.threads; // This thread and the program can keep going. if thread_manager.threads[thread_manager.active_thread].state.is_enabled() && !thread_manager.yield_active_thread @@ -775,16 +776,37 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { // The currently active thread is still enabled, just continue with it. return interp_ok(SchedulingAction::ExecuteStep); } - // The active thread yielded or got terminated. Let's see if there are any timeouts to take - // care of. We do this *before* running any other thread, to ensure that timeouts "in the - // past" fire before any other thread can take an action. This ensures that for + + // The active thread yielded or got terminated. Let's see if there are any I/O events + // 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. + // We do this before running any other threads such that the threads + // which received events are available for scheduling afterwards. + + // Perform a non-blocking poll for newly available I/O events from the OS. + this.poll_and_unblock(Some(Duration::ZERO))?; + } + + let thread_manager = &this.machine.threads; + let clock = &this.machine.monotonic_clock; + + // We also check timeouts before running any other thread, to ensure that timeouts + // "in the past" fire before any other thread can take an action. This ensures that for // `pthread_cond_timedwait`, "an error is returned if [...] the absolute time specified by // abstime has already been passed at the time of the call". // let potential_sleep_time = thread_manager.next_callback_wait_time(clock); if potential_sleep_time == Some(Duration::ZERO) { - return interp_ok(SchedulingAction::ExecuteTimeoutCallback); + // The timeout exceeded for some thread so we unblock the thread and execute its timeout callback. + this.run_timeout_callback()?; } + + let thread_manager = &mut this.machine.threads; + let rng = this.machine.rng.get_mut(); + // No callbacks immediately scheduled, pick a regular thread to execute. // The active thread blocked or yielded. So we go search for another enabled thread. // We build the list of threads by starting with the threads after the current one, followed by @@ -832,7 +854,16 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { // All threads are currently blocked, but we have unexecuted // timeout_callbacks, which may unblock some of the threads. Hence, // sleep until the first callback. - interp_ok(SchedulingAction::Sleep(sleep_time)) + interp_ok(SchedulingAction::SleepAndWaitForIo(Some(sleep_time))) + } else if thread_manager + .threads + .iter() + .any(|thread| thread.state.is_blocked_on(BlockReason::IO)) + { + // At least one thread is blocked on host I/O but doesn't + // have a timeout set. Hence, we sleep indefinitely in the + // hope that eventually an I/O event for this thread happens. + interp_ok(SchedulingAction::SleepAndWaitForIo(None)) } else { throw_machine_stop!(TerminationInfo::GlobalDeadlock); } @@ -1300,13 +1331,38 @@ fn run_threads(&mut self) -> InterpResult<'tcx, !> { } } } - SchedulingAction::ExecuteTimeoutCallback => { - this.run_timeout_callback()?; - } - SchedulingAction::Sleep(duration) => { - this.machine.monotonic_clock.sleep(duration); + SchedulingAction::SleepAndWaitForIo(duration) => { + if this.machine.communicate() { + // When we're running with isolation disabled, instead of + // strictly sleeping the duration we allow waking up + // early for I/O events from the OS. + + this.poll_and_unblock(duration)?; + } else { + let duration = duration.expect( + "Infinite sleep should not be triggered when isolation is enabled", + ); + this.machine.monotonic_clock.sleep(duration); + } } } } } + + /// 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`]. + fn poll_and_unblock(&mut self, timeout: Option) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let ready = match this.machine.blocking_io.poll(timeout) { + Ok(ready) => ready, + // We can ignore errors originating from interrupts; that's just a spurious wakeup. + Err(e) if e.kind() == io::ErrorKind::Interrupted => return 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!("{e}"), + }; + + ready.into_iter().try_for_each(|thread_id| this.unblock_thread(thread_id, BlockReason::IO)) + } } diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 787a71091ae6..9d93edcaa344 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -141,13 +141,6 @@ pub enum NonHaltingDiagnostic { effective_failure_ordering: AtomicReadOrd, }, FileInProcOpened, - SocketListenUnsupportedBacklog { - details: bool, - /// Unsupported backlog value provided the by the program. - provided: i32, - /// Supported backlog value by Miri. - supported: i32, - }, } /// Level of Miri specific diagnostics @@ -650,8 +643,6 @@ pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { | WeakMemoryOutdatedLoad { .. } => ("tracking was triggered here".to_string(), DiagLevel::Note), FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning), - SocketListenUnsupportedBacklog { .. } => - ("call to `listen` with unsupported backlog value".to_string(), DiagLevel::Warning), }; let title = match &e { @@ -700,17 +691,12 @@ pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), - SocketListenUnsupportedBacklog { provided, supported, .. } => format!("called `listen` on socket with backlog value of {provided} but only {supported} is supported"), }; let notes = match &e { ProgressReport { block_count } => { vec![note!("so far, {block_count} basic blocks have been executed")] } - SocketListenUnsupportedBacklog { details: true, supported, .. } => - vec![note!( - "the given value will be ignored and a backlog of {supported} will be used instead" - )], _ => vec![], }; diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 2f6c80772635..f41e3c20a7d5 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -16,6 +16,7 @@ #![feature(never_type)] #![feature(try_blocks)] #![feature(io_error_more)] +#![feature(io_error_inprogress)] #![feature(variant_count)] #![feature(yeet_expr)] #![feature(pointer_is_aligned_to)] @@ -132,6 +133,7 @@ pub mod native_lib { BorTag, BorrowTrackerMethod, EvalContextExt as _, TreeBorrowsParams, }; pub use crate::clock::{Instant, MonotonicClock}; +pub use crate::concurrency::blocking_io::{BlockingIoManager, EvalContextExt as _, WithSource}; pub use crate::concurrency::cpu_affinity::MAX_CPUS; pub use crate::concurrency::data_race::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index ab38828c2108..d8224f1878f0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -537,6 +537,9 @@ pub struct MiriMachine<'tcx> { /// The set of threads. pub(crate) threads: ThreadManager<'tcx>, + /// Handles blocking I/O and polling for completion. + pub(crate) blocking_io: BlockingIoManager, + /// 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` @@ -732,6 +735,8 @@ pub(crate) fn new( thread_cpu_affinity .insert(threads.active_thread(), CpuAffinityMask::new(&layout_cx, config.num_cpus)); } + let blocking_io = BlockingIoManager::new(config.isolated_op == IsolatedOp::Allow) + .expect("Couldn't create poll instance"); let alloc_addresses = RefCell::new(alloc_addresses::GlobalStateInner::new(config, stack_addr, tcx)); MiriMachine { @@ -754,6 +759,7 @@ pub(crate) fn new( layouts, threads, thread_cpu_affinity, + blocking_io, static_roots: Vec::new(), profiler, string_cache: Default::default(), @@ -1010,6 +1016,7 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) { data_race, alloc_addresses, fds, + blocking_io:_, epoll_interests:_, tcx: _, isolated_op: _, diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs index 66b0adb3c4eb..f2c750a7577f 100644 --- a/src/tools/miri/src/provenance_gc.rs +++ b/src/tools/miri/src/provenance_gc.rs @@ -19,7 +19,7 @@ fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} )+ } } -no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize ThreadId); +no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId); impl VisitProvenance for Option { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 91ae448e2165..59a121be0155 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -94,6 +94,7 @@ fn from(value: Scalar) -> Self { ("ETIMEDOUT", TimedOut), ("ETXTBSY", ExecutableFileBusy), ("EXDEV", CrossesDevices), + ("EINPROGRESS", InProgress), // The following have two valid options. We have both for the forwards mapping; only the // first one will be used for the backwards mapping. ("EPERM", PermissionDenied), diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 9a24cbc5048a..a6620b5d977f 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -570,6 +570,33 @@ fn emulate_foreign_item_inner( let result = this.listen(socket, backlog)?; this.write_scalar(result, dest)?; } + "accept" => { + let [socket, address, address_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *mut _, *mut _) -> i32), + link_name, + abi, + args, + )?; + this.accept4(socket, address, address_len, /* flags */ None, dest)?; + } + "accept4" => { + let [socket, address, address_len, flags] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *mut _, *mut _, i32) -> i32), + link_name, + abi, + args, + )?; + this.accept4(socket, address, address_len, Some(flags), dest)?; + } + "connect" => { + let [socket, address, address_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, libc::socklen_t) -> i32), + link_name, + abi, + args, + )?; + this.connect(socket, address, address_len, dest)?; + } "setsockopt" => { let [socket, level, option_name, option_value, option_len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, i32, i32, *const _, libc::socklen_t) -> i32), diff --git a/src/tools/miri/src/shims/unix/linux_like/syscall.rs b/src/tools/miri/src/shims/unix/linux_like/syscall.rs index 106e6c448d06..875fd0942838 100644 --- a/src/tools/miri/src/shims/unix/linux_like/syscall.rs +++ b/src/tools/miri/src/shims/unix/linux_like/syscall.rs @@ -7,6 +7,7 @@ use crate::shims::unix::env::EvalContextExt; use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; use crate::shims::unix::linux_like::sync::futex; +use crate::shims::unix::socket::EvalContextExt as _; use crate::*; pub fn syscall<'tcx>( @@ -26,6 +27,7 @@ pub fn syscall<'tcx>( let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?; let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?; let sys_gettid = ecx.eval_libc("SYS_gettid").to_target_usize(ecx)?; + let sys_accept4 = ecx.eval_libc("SYS_accept4").to_target_usize(ecx)?; match ecx.read_target_usize(op)? { // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` @@ -59,6 +61,12 @@ pub fn syscall<'tcx>( let result = ecx.unix_gettid("SYS_gettid")?; ecx.write_int(result.to_u32()?, dest)?; } + num if num == sys_accept4 => { + // Used on Android. + let [socket, address, address_len, flags] = + check_min_vararg_count("syscall(SYS_accept4, ...)", varargs)?; + ecx.accept4(socket, address, address_len, Some(flags), dest)?; + } num => { throw_unsup_format!("syscall: unsupported syscall number {num}"); } diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index af645f88a4c4..f9b3ca479b79 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1,19 +1,18 @@ use std::cell::{Cell, RefCell}; -use std::iter; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::{io, iter}; +use mio::Interest; +use mio::event::Source; +use mio::net::{TcpListener, TcpStream}; use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; use rustc_target::spec::Os; -use crate::diagnostics::SpanDedupDiagnostic; -use crate::shims::files::{FdId, FileDescription}; +use crate::shims::files::{FdId, FileDescription, FileDescriptionRef}; use crate::{OpTy, Scalar, *}; -/// Backlog value passed to the `listen` syscall by the standard library -const SUPPORTED_LISTEN_BACKLOG: i32 = 128; - #[derive(Debug, PartialEq)] enum SocketFamily { // IPv4 internet protocols @@ -22,6 +21,22 @@ enum SocketFamily { IPv6, } +enum SocketIoError { + /// The socket is not yet ready. Either EINPROGRESS or ENOTCONNECTED occurred. + NotReady, + /// Any other kind of I/O error. + Other(io::Error), +} + +impl From for SocketIoError { + fn from(value: io::Error) -> Self { + match value.kind() { + io::ErrorKind::InProgress | io::ErrorKind::NotConnected => Self::NotReady, + _ => Self::Other(value), + } + } +} + #[derive(Debug)] enum SocketState { /// No syscall after `socket` has been made. @@ -32,6 +47,69 @@ enum SocketState { /// The `listen` syscall has been called on the socket. /// This is only reachable from the [`SocketState::Bound`] state. Listening(TcpListener), + /// The `connect` syscall has been called and we weren't yet able + /// to ensure the connection is established. This is only reachable + /// from the [`SocketState::Initial`] state. + Connecting(TcpStream), + /// The `connect` syscall has been called on the socket and + /// we ensured that the connection is established, or + /// the socket was created by the `accept` syscall. + /// For a socket created using the `connect` syscall, this is + /// only reachable from the [`SocketState::Connecting`] state. + Connected(TcpStream), +} + +impl SocketState { + /// If the socket is currently in [`SocketState::Connecting`], try to ensure + /// that the connection is established by first checking that [`TcpStream::take_error`] + /// doesn't return an error and then by checking that [`TcpStream::peer_addr`] + /// returns the address of the connected peer. + /// + /// If the connection is established or the socket is in any other state, + /// [`Ok`] is returned. + /// + /// **Important**: On Windows hosts this function can only be used to ensure a socket is connected + /// _after_ a [`Interest::WRITABLE`] event was received. + pub fn try_set_connected(&mut self) -> Result<(), SocketIoError> { + // Further explanation of the limitation on Windows hosts: + // Windows treats sockets which are connecting as connected until either the connection timeout hits + // or an error occurs. Thus, the [`TcpStream::peer_addr`] method returns [`Ok`] with the provided peer + // address even when the connection might not yet be established. + + let SocketState::Connecting(stream) = self else { return Ok(()) }; + + if let Ok(Some(e)) = stream.take_error() { + // There was an error whilst connecting. + let e = SocketIoError::from(e); + // We won't get EINPROGRESS or ENOTCONNECTED here + // so we need to reset the state. + assert!(matches!(e, SocketIoError::Other(_))); + // Go back to initial state as the only way of getting into the + // `Connecting` state is from the `Initial` state. + *self = SocketState::Initial; + return Err(e); + } + + if let Err(e) = stream.peer_addr() { + let e = SocketIoError::from(e); + if let SocketIoError::Other(_) = &e { + // All other errors are fatal for a socket and thus the state needs to be reset. + *self = SocketState::Initial; + } + return Err(e); + }; + + // We just read the peer address without an error so we can be + // sure that the connection is established. + + // Temporarily use dummy state to take ownership of the stream. + let SocketState::Connecting(stream) = std::mem::replace(self, SocketState::Initial) else { + // At the start of the function we ensured that we're currently connecting. + unreachable!() + }; + *self = SocketState::Connected(stream); + Ok(()) + } } #[derive(Debug)] @@ -132,7 +210,7 @@ fn socket( } else { throw_unsup_format!( "socket: domain {:#x} is unsupported, only AF_INET and \ - AF_INET6 are allowed.", + AF_INET6 are allowed.", domain ); }; @@ -140,14 +218,14 @@ fn socket( if flags != this.eval_libc_i32("SOCK_STREAM") { throw_unsup_format!( "socket: type {:#x} is unsupported, only SOCK_STREAM, \ - SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", + SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", flags ); } if protocol != 0 { throw_unsup_format!( "socket: socket protocol {protocol} is unsupported, \ - only 0 is allowed" + only 0 is allowed" ); } @@ -177,12 +255,12 @@ fn bind( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return interp_ok(this.eval_libc("EBADF")); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return interp_ok(this.eval_libc("ENOTSOCK")); + return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -200,7 +278,7 @@ fn bind( // Attempted to bind an address from a family that doesn't match // the family of the socket. let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) { - // Linux man page states that `EINVAL` is used when there is an addres family mismatch. + // Linux man page states that `EINVAL` is used when there is an address family mismatch. // See LibcError("EINVAL") } else { @@ -214,10 +292,15 @@ fn bind( *state = SocketState::Bound(address); } + SocketState::Connecting(_) | SocketState::Connected(_) => + throw_unsup_format!( + "bind: socket is already connected and binding a + connected socket is unsupported" + ), SocketState::Bound(_) | SocketState::Listening(_) => throw_unsup_format!( "bind: socket is already bound and binding a socket \ - multiple times is unsupported" + multiple times is unsupported" ), } @@ -228,37 +311,24 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult< let this = self.eval_context_mut(); let socket = this.read_scalar(socket)?.to_i32()?; - let backlog = this.read_scalar(backlog)?.to_i32()?; + // Since the backlog value is just a performance hint we can ignore it. + let _backlog = this.read_scalar(backlog)?.to_i32()?; // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return interp_ok(this.eval_libc("EBADF")); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return interp_ok(this.eval_libc("ENOTSOCK")); + return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); - // Only allow the same backlog value as the standard library uses since the standard library - // doesn't provide a way to set a custom value. - if backlog != SUPPORTED_LISTEN_BACKLOG { - // The first time this happens at a particular location, print a warning. - static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); - this.dedup_diagnostic(&DEDUP, |first| { - NonHaltingDiagnostic::SocketListenUnsupportedBacklog { - details: first, - provided: backlog, - supported: SUPPORTED_LISTEN_BACKLOG, - } - }); - } - let mut state = socket.state.borrow_mut(); - match &*state { + match *state { SocketState::Bound(socket_addr) => match TcpListener::bind(socket_addr) { Ok(listener) => *state = SocketState::Listening(listener), @@ -272,11 +342,160 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult< SocketState::Listening(_) => { throw_unsup_format!("listen: listening on a socket multiple times is unsupported") } + SocketState::Connecting(_) | SocketState::Connected(_) => { + throw_unsup_format!("listen: listening on a connected socket is unsupported") + } } interp_ok(Scalar::from_i32(0)) } + /// For more information on the arguments see the accept manpage: + /// + fn accept4( + &mut self, + socket: &OpTy<'tcx>, + address: &OpTy<'tcx>, + address_len: &OpTy<'tcx>, + flags: Option<&OpTy<'tcx>>, + // Location where the output scalar is written to. + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let socket = this.read_scalar(socket)?.to_i32()?; + let address_ptr = this.read_pointer(address)?; + let address_len_ptr = this.read_pointer(address_len)?; + let mut flags = + if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 }; + + // Get the file handle + let Some(fd) = this.machine.fds.get(socket) else { + return this.set_last_error_and_return(LibcError("EBADF"), dest); + }; + + let Some(socket) = fd.downcast::() else { + // Man page specifies to return ENOTSOCK if `fd` is not a socket. + return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + }; + + assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + + if !matches!(*socket.state.borrow(), SocketState::Listening(_)) { + throw_unsup_format!( + "accept4: accepting incoming connections is only allowed when socket is listening" + ) + }; + + let mut is_client_sock_nonblock = false; + + // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so + // if there is anything left at the end, that's an unsupported flag. + if matches!( + this.tcx.sess.target.os, + Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos + ) { + // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD, + // Solaris, and Illumos targets + let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK"); + let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC"); + if flags & sock_nonblock == sock_nonblock { + is_client_sock_nonblock = true; + flags &= !sock_nonblock; + } + if flags & sock_cloexec == sock_cloexec { + // We don't support `exec` so we can ignore this. + flags &= !sock_cloexec; + } + } + + if flags != 0 { + throw_unsup_format!( + "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \ + and SOCK_NONBLOCK are allowed", + ); + } + + if socket.is_non_block.get() { + throw_unsup_format!("accept4: non-blocking accept is unsupported") + } + + // The socket is in blocking mode and thus the accept call should block + // until an incoming connection is ready. + this.block_for_accept( + address_ptr, + address_len_ptr, + is_client_sock_nonblock, + socket, + dest.clone(), + ); + interp_ok(()) + } + + fn connect( + &mut self, + socket: &OpTy<'tcx>, + address: &OpTy<'tcx>, + address_len: &OpTy<'tcx>, + // Location where the output scalar is written to. + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let socket = this.read_scalar(socket)?.to_i32()?; + let address = match this.socket_address(address, address_len, "connect")? { + Ok(address) => address, + Err(e) => return this.set_last_error_and_return(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); + }; + + let Some(socket) = fd.downcast::() else { + // Man page specifies to return ENOTSOCK if `fd` is not a socket + return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + }; + + assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + + match &*socket.state.borrow() { + 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), + // 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. + _ => + throw_unsup_format!( + "connect: connecting is only supported for sockets which are neither \ + bound, listening nor already connected" + ), + } + + // Mio returns a potentially unconnected stream. + // We can be ensured that the connection is established when + // [`TcpStream::take_err`] and [`TcpStream::peer_addr`] both + // don't return errors. + // For non-blocking sockets we need to check that for every + // [`Interest::WRITEABLE`] event on the stream. + match TcpStream::connect(address) { + Ok(stream) => *socket.state.borrow_mut() = SocketState::Connecting(stream), + Err(e) => return this.set_last_error_and_return(e, dest), + }; + + if socket.is_non_block.get() { + throw_unsup_format!("connect: non-blocking connect is unsupported"); + } + + // The socket is in blocking mode and thus the connect call should block + // until the connection with the server is established. + this.block_for_connect(socket, dest.clone()); + interp_ok(()) + } + fn setsockopt( &mut self, socket: &OpTy<'tcx>, @@ -295,12 +514,12 @@ fn setsockopt( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return interp_ok(this.eval_libc("EBADF")); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let Some(_socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return interp_ok(this.eval_libc("ENOTSOCK")); + return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); }; if level == this.eval_libc_i32("SOL_SOCKET") { @@ -380,7 +599,7 @@ fn getsockname( // random port will be and thus this is unsupported. throw_unsup_format!( "getsockname: when the port is 0, getting the socket address before \ - calling `listen` or `connect` is unsupported" + calling `listen` or `connect` is unsupported" ) } @@ -511,7 +730,7 @@ fn socket_address( // thus also no address family of another type should be supported. throw_unsup_format!( "{foreign_name}: address family {family:#x} is unsupported, \ - only AF_INET and AF_INET6 are allowed" + only AF_INET and AF_INET6 are allowed" ); }; @@ -672,4 +891,126 @@ fn write_socket_address( interp_ok(Ok(())) } + + /// 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. + fn block_for_accept( + &mut self, + address_ptr: Pointer, + address_len_ptr: Pointer, + is_client_sock_nonblock: bool, + socket: FileDescriptionRef, + dest: MPlaceTy<'tcx>, + ) { + let this = self.eval_context_mut(); + this.block_thread_for_io( + socket.clone(), + Interest::READABLE, + None, + callback!(@capture<'tcx> { + address_ptr: Pointer, + address_len_ptr: Pointer, + is_client_sock_nonblock: bool, + socket: FileDescriptionRef, + dest: MPlaceTy<'tcx>, + } |this, kind: UnblockKind| { + assert_eq!(kind, UnblockKind::Ready); + + let state = socket.state.borrow(); + + let SocketState::Listening(listener) = &*state else { + // We checked that the socket is in listening state before blocking + // and since there is no outgoing transition from that state this + // should be unreachable. + unreachable!() + }; + + let (stream, addr) = match listener.accept() { + Ok(peer) => peer, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // We need to block the thread again as it would still block. + drop(state); + this.block_for_accept(address_ptr, address_len_ptr, is_client_sock_nonblock, socket, dest); + return interp_ok(()) + }, + Err(e) => return this.set_last_error_and_return(e, &dest), + }; + + let family = match addr { + SocketAddr::V4(_) => SocketFamily::IPv4, + SocketAddr::V6(_) => SocketFamily::IPv6, + }; + + if address_ptr != Pointer::null() { + // We only attempt a write if the address pointer is not a null pointer. + // If the address pointer is a null pointer the user isn't interested in the + // address and we don't need to write anything. + if let Err(e) = this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")? { + return this.set_last_error_and_return(e, &dest); + }; + } + + let fd = this.machine.fds.new_ref(Socket { + family, + state: RefCell::new(SocketState::Connected(stream)), + is_non_block: Cell::new(is_client_sock_nonblock), + }); + let sockfd = this.machine.fds.insert(fd); + // We need to create the scalar using the destination size since + // `syscall(SYS_accept4, ...)` returns a long which doesn't match + // the int returned from the `accept`/`accept4` syscalls. + // See . + this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest) + }), + ); + } + + /// Block the thread until the stream is connected or an error occurred. + fn block_for_connect(&mut self, socket: FileDescriptionRef, dest: MPlaceTy<'tcx>) { + let this = self.eval_context_mut(); + this.block_thread_for_io( + socket.clone(), + Interest::WRITABLE, + None, + callback!(@capture<'tcx> { + socket: FileDescriptionRef, + dest: MPlaceTy<'tcx>, + } |this, kind: UnblockKind| { + assert_eq!(kind, UnblockKind::Ready); + + let mut state = socket.state.borrow_mut(); + + // We received a "writable" event so `try_set_connected` is safe to call. + match state.try_set_connected() { + Ok(_) => this.write_scalar(Scalar::from_i32(0), &dest), + Err(SocketIoError::NotReady) => { + // We need to block the thread again as the connection is still not yet ready. + drop(state); + this.block_for_connect(socket, dest); + return interp_ok(()) + }, + Err(SocketIoError::Other(e)) => return this.set_last_error_and_return(e, &dest) + } + }), + ); + } +} + +impl VisitProvenance for FileDescriptionRef { + // A socket doesn't contain any references to machine memory + // and thus we don't need to propagate the visit. + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} +} + +impl WithSource for FileDescriptionRef { + fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> { + let mut state = self.state.borrow_mut(); + match &mut *state { + SocketState::Listening(listener) => f(listener), + SocketState::Connecting(stream) | SocketState::Connected(stream) => f(stream), + // We never try adding a socket which is not backed by a real socket to the poll registry. + _ => unreachable!(), + } + } } diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 9dbfc6d21712..d4ebdeab86f1 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -133,6 +133,15 @@ fn emulate_foreign_item_inner( let result = this.bind(socket, address, address_len)?; this.write_scalar(result, dest)?; } + "__xnet_connect" => { + let [socket, address, address_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, libc::socklen_t) -> i32), + link_name, + abi, + args, + )?; + this.connect(socket, address, address_len, dest)?; + } // Miscellaneous "___errno" => { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 0a5f8c226af4..e3c14e60b25e 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -1,10 +1,14 @@ //@ignore-target: windows # No libc socket on Windows //@compile-flags: -Zmiri-disable-isolation +#![feature(io_error_inprogress)] + #[path = "../../utils/libc.rs"] mod libc_utils; use std::io::{self, ErrorKind}; -use std::mem::MaybeUninit; +use std::time::Duration; +#[allow(unused)] +use std::{mem::MaybeUninit, thread}; use libc_utils::*; @@ -28,6 +32,8 @@ fn main() { test_listen(); + test_accept_connect(); + test_getsockname_ipv4(); test_getsockname_ipv4_random_port(); test_getsockname_ipv4_unbound(); @@ -117,7 +123,7 @@ fn test_set_nosigpipe_invalid_len() { // By providing a u64 of size 8 bytes we trigger an invalid length error. let err = setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u64).unwrap_err(); assert_eq!(err.kind(), ErrorKind::InvalidInput); - // check that it is the right kind of `InvalidInput` + // Check that it is the right kind of `InvalidInput`. assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); } @@ -137,7 +143,7 @@ fn test_bind_ipv4_invalid_addr_len() { .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::InvalidInput); - // check that it is the right kind of `InvalidInput` + // Check that it is the right kind of `InvalidInput`. assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); } @@ -166,12 +172,86 @@ fn test_listen() { )); } - // Use the supported backlog value to avoid the warning. - let backlog = 128; + unsafe { + errno_check(libc::listen(sockfd, 16)); + } +} + +/// Test accepting connections by running a server in a separate thread and connecting clients +/// from the main thread. +/// This function tests both +/// - Connecting when the server is already accepting +/// - Accepting when there is already an incoming connection +fn test_accept_connect() { + // Create a new non-blocking server socket. + let server_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + server_sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } unsafe { - errno_check(libc::listen(sockfd, backlog)); + errno_check(libc::listen(server_sockfd, 16)); } + + // Retrieve actual listener address because we used a randomized port. + let (_, server_addr) = + sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap(); + + let LibcSocketAddr::V4(addr) = server_addr else { + // We bound an IPv4 address so we also expect + // an IPv4 address to be returned. + panic!() + }; + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (_peerfd, _peer_addr) = + sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap(); + + // Yield back to the client thread to test whether calling `connect` first also + // works. + thread::sleep(Duration::from_millis(10)); + + let (_peerfd, _peer_addr) = + sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap(); + }); + + // Yield to server thread to ensure `accept` is called before we try + // to connect. + thread::sleep(Duration::from_millis(10)); + + // Test connecting to an already accepting server. + unsafe { + errno_check(libc::connect( + client_sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + // Server thread should now be in its `sleep`. + // Test connecting when there is no actively ongoing `accept`. + // We need a new client socket since we cannot connect a socket multiple times. + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + unsafe { + errno_check(libc::connect( + client_sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + server_thread.join().unwrap(); } /// Test the `getsockname` syscall on an IPv4 socket which is bound. @@ -188,17 +268,15 @@ fn test_getsockname_ipv4() { size_of::() as libc::socklen_t, )); } - // Use the supported backlog value to avoid the warning. - let backlog = 128; unsafe { - errno_check(libc::listen(sockfd, backlog)); + errno_check(libc::listen(sockfd, 16)); } - let sockname = + let (_, sock_addr) = sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap(); - let LibcSocketAddr::V4(sock_addr) = sockname else { + let LibcSocketAddr::V4(sock_addr) = sock_addr else { // We bound an IPv4 address so we also expect // an IPv4 address to be returned. panic!() @@ -225,17 +303,15 @@ fn test_getsockname_ipv4_random_port() { size_of::() as libc::socklen_t, )); } - // Use the supported backlog value to avoid the warning. - let backlog = 128; unsafe { - errno_check(libc::listen(sockfd, backlog)); + errno_check(libc::listen(sockfd, 16)); } - let sockname = + let (_, sock_addr) = sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap(); - let LibcSocketAddr::V4(sock_addr) = sockname else { + let LibcSocketAddr::V4(sock_addr) = sock_addr else { // We bound an IPv4 address so we also expect // an IPv4 address to be returned. panic!() @@ -252,12 +328,12 @@ fn test_getsockname_ipv4_unbound() { let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let sockname = + let (_, sock_addr) = sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap(); // Libc representation of an unspecified IPv4 address with zero port. let addr = net::ipv4_sock_addr([0, 0, 0, 0], 0); - let LibcSocketAddr::V4(sock_addr) = sockname else { + let LibcSocketAddr::V4(sock_addr) = sock_addr else { // We bound an IPv4 address so we also expect // an IPv4 address to be returned. panic!() @@ -282,17 +358,15 @@ fn test_getsockname_ipv6() { size_of::() as libc::socklen_t, )); } - // Use the supported backlog value to avoid the warning. - let backlog = 128; unsafe { - errno_check(libc::listen(sockfd, backlog)); + errno_check(libc::listen(sockfd, 16)); } - let sockname = + let (_, sock_addr) = sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap(); - let LibcSocketAddr::V6(sock_addr) = sockname else { + let LibcSocketAddr::V6(sock_addr) = sock_addr else { // We bound an IPv6 address so we also expect // an IPv6 address to be returned. panic!() @@ -338,27 +412,31 @@ enum LibcSocketAddr { /// Wraps a call to a platform function that returns a socket address. /// This is very much the same as the function with the same name in the /// standard library implementation. -fn sockname(f: F) -> io::Result +/// Returns a tuple containing the actual return value of the performed +/// syscall and the written address of it. +fn sockname(f: F) -> io::Result<(libc::c_int, LibcSocketAddr)> where F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, { let mut storage = MaybeUninit::::zeroed(); let mut len = size_of::() as libc::socklen_t; - errno_result(f(storage.as_mut_ptr().cast(), &mut len))?; + let value = errno_result(f(storage.as_mut_ptr().cast(), &mut len))?; // SAFETY: // The caller guarantees that the storage has been successfully initialized // and its size written to `len` if `f` returns a success. - unsafe { + let address = unsafe { match (*storage.as_ptr()).ss_family as libc::c_int { libc::AF_INET => { assert!(len as usize >= size_of::()); - Ok(LibcSocketAddr::V4(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in))) + LibcSocketAddr::V4(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in)) } libc::AF_INET6 => { assert!(len as usize >= size_of::()); - Ok(LibcSocketAddr::V6(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in6))) + LibcSocketAddr::V6(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in6)) } - _ => Err(io::Error::new(ErrorKind::InvalidInput, "invalid argument")), + _ => return Err(io::Error::new(ErrorKind::InvalidInput, "invalid argument")), } - } + }; + + Ok((value, address)) } diff --git a/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.rs b/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.rs deleted file mode 100644 index 193a6c693801..000000000000 --- a/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.rs +++ /dev/null @@ -1,27 +0,0 @@ -//@ignore-target: windows # No libc socket on Windows -//@compile-flags: -Zmiri-disable-isolation - -#[path = "../../utils/libc.rs"] -mod libc_utils; -use libc_utils::*; - -fn main() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 2345); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } - - // Only supported value is 128, thus we use 127 to trigger the warning. - let backlog = 127; - - unsafe { - let errno = libc::listen(sockfd, backlog); - errno_check(errno); - } -} diff --git a/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.stderr b/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.stderr deleted file mode 100644 index 94582af4a6e2..000000000000 --- a/src/tools/miri/tests/pass-dep/libc/socket-unsupported-listen-backlog.stderr +++ /dev/null @@ -1,8 +0,0 @@ -warning: called `listen` on socket with backlog value of 127 but only 128 is supported - --> tests/pass-dep/libc/socket-unsupported-listen-backlog.rs:LL:CC - | -LL | let errno = libc::listen(sockfd, backlog); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to `listen` with unsupported backlog value - | - = note: the given value will be ignored and a backlog of 128 will be used instead - diff --git a/src/tools/miri/tests/pass-dep/shims/socket.rs b/src/tools/miri/tests/pass-dep/shims/socket.rs deleted file mode 100644 index 787dd5e27399..000000000000 --- a/src/tools/miri/tests/pass-dep/shims/socket.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ignore-target: windows # No libc socket on Windows -//@compile-flags: -Zmiri-disable-isolation - -use std::net::TcpListener; - -fn main() { - create_ipv4_listener(); - create_ipv6_listener(); -} - -fn create_ipv4_listener() { - let _listener_ipv4 = TcpListener::bind("127.0.0.1:0").unwrap(); -} - -fn create_ipv6_listener() { - let _listener_ipv6 = TcpListener::bind("[::1]:0").unwrap(); -} diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs new file mode 100644 index 000000000000..2e63e00c67d9 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -0,0 +1,36 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation + +use std::net::{TcpListener, TcpStream}; +use std::thread; + +fn main() { + test_create_ipv4_listener(); + test_create_ipv6_listener(); + test_accept_and_connect(); +} + +fn test_create_ipv4_listener() { + let _listener_ipv4 = TcpListener::bind("127.0.0.1:0").unwrap(); +} + +fn test_create_ipv6_listener() { + let _listener_ipv6 = TcpListener::bind("[::1]:0").unwrap(); +} + +/// Try to connect to a TCP listener running in a separate thread and +/// accepting connections. +fn test_accept_and_connect() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + // Get local address with randomized port to know where + // we need to connect to. + let address = listener.local_addr().unwrap(); + + let handle = thread::spawn(move || { + let (_stream, _addr) = listener.accept().unwrap(); + }); + + let _stream = TcpStream::connect(address).unwrap(); + + handle.join().unwrap(); +} From b584d4d6680b5032198a26a658d9aee6b82cdfd8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 22 Mar 2026 17:17:56 +0100 Subject: [PATCH 14/14] update lockfile --- Cargo.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98567f858e9f..f8ce74b9e2b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2175,9 +2175,9 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libdbus-sys" @@ -2454,11 +2454,12 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -2490,6 +2491,7 @@ dependencies = [ "libffi", "libloading 0.9.0", "measureme", + "mio", "nix", "rand 0.9.2", "regex",