diff --git a/build.rs b/build.rs index 4a0b8500..36a90e6b 100644 --- a/build.rs +++ b/build.rs @@ -53,7 +53,7 @@ fn main() { // Custom cfgs set by build script. Not public API. // grep -F 'cargo:rustc-cfg=' build.rs | grep -Ev '^ *//' | sed -E 's/^.*cargo:rustc-cfg=//; s/(=\\)?".*$//' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/' println!( - "cargo:rustc-check-cfg=cfg(portable_atomic_disable_fiq,portable_atomic_force_amo,portable_atomic_ll_sc_rmw,portable_atomic_new_atomic_intrinsics,portable_atomic_no_asm,portable_atomic_no_asm_maybe_uninit,portable_atomic_no_atomic_64,portable_atomic_no_atomic_cas,portable_atomic_no_atomic_load_store,portable_atomic_no_atomic_min_max,portable_atomic_no_cfg_target_has_atomic,portable_atomic_no_cmpxchg16b_intrinsic,portable_atomic_no_cmpxchg16b_target_feature,portable_atomic_no_const_mut_refs,portable_atomic_no_const_raw_ptr_deref,portable_atomic_no_const_transmute,portable_atomic_no_core_unwind_safe,portable_atomic_no_diagnostic_namespace,portable_atomic_no_offset_of,portable_atomic_no_strict_provenance,portable_atomic_no_stronger_failure_ordering,portable_atomic_no_track_caller,portable_atomic_no_unsafe_op_in_unsafe_fn,portable_atomic_pre_llvm_15,portable_atomic_pre_llvm_16,portable_atomic_pre_llvm_18,portable_atomic_s_mode,portable_atomic_sanitize_thread,portable_atomic_target_feature,portable_atomic_unsafe_assume_single_core,portable_atomic_unstable_asm,portable_atomic_unstable_asm_experimental_arch,portable_atomic_unstable_cfg_target_has_atomic,portable_atomic_unstable_isa_attribute)" + "cargo:rustc-check-cfg=cfg(portable_atomic_disable_fiq,portable_atomic_force_amo,portable_atomic_ll_sc_rmw,portable_atomic_new_atomic_intrinsics,portable_atomic_no_asm,portable_atomic_no_asm_maybe_uninit,portable_atomic_no_atomic_64,portable_atomic_no_atomic_cas,portable_atomic_no_atomic_load_store,portable_atomic_no_atomic_min_max,portable_atomic_no_cfg_target_has_atomic,portable_atomic_no_cmpxchg16b_intrinsic,portable_atomic_no_cmpxchg16b_target_feature,portable_atomic_no_const_if_match_loop,portable_atomic_no_const_mut_refs,portable_atomic_no_const_raw_ptr_deref,portable_atomic_no_const_transmute,portable_atomic_no_core_unwind_safe,portable_atomic_no_diagnostic_namespace,portable_atomic_no_offset_of,portable_atomic_no_strict_provenance,portable_atomic_no_stronger_failure_ordering,portable_atomic_no_track_caller,portable_atomic_no_unsafe_op_in_unsafe_fn,portable_atomic_pre_llvm_15,portable_atomic_pre_llvm_16,portable_atomic_pre_llvm_18,portable_atomic_s_mode,portable_atomic_sanitize_thread,portable_atomic_target_feature,portable_atomic_unsafe_assume_single_core,portable_atomic_unstable_asm,portable_atomic_unstable_asm_experimental_arch,portable_atomic_unstable_cfg_target_has_atomic,portable_atomic_unstable_isa_attribute)" ); // TODO: handle multi-line target_feature_fallback // grep -F 'target_feature_fallback("' build.rs | grep -Ev '^ *//' | sed -E 's/^.*target_feature_fallback\(//; s/",.*$/"/' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/' @@ -86,6 +86,10 @@ fn main() { if !version.probe(45, 2020, 5, 29) { println!("cargo:rustc-cfg=portable_atomic_no_atomic_min_max"); } + // const_if_match/const_loop stabilized in Rust 1.46 (nightly-2020-06-30): https://github.com/rust-lang/rust/pull/72437 + if !version.probe(46, 2020, 6, 29) { + println!("cargo:rustc-cfg=portable_atomic_no_const_if_match_loop"); + } // track_caller stabilized in Rust 1.46 (nightly-2020-07-02): https://github.com/rust-lang/rust/pull/72445 if !version.probe(46, 2020, 7, 1) { println!("cargo:rustc-cfg=portable_atomic_no_track_caller"); diff --git a/src/imp/detect/aarch64_aa64reg.rs b/src/imp/detect/aarch64_aa64reg.rs index 5c4b329f..127d95d5 100644 --- a/src/imp/detect/aarch64_aa64reg.rs +++ b/src/imp/detect/aarch64_aa64reg.rs @@ -125,7 +125,7 @@ mod imp { // core::ffi::c_* (except c_void) requires Rust 1.64, libc requires Rust 1.63 #[allow(non_camel_case_types)] pub(super) mod ffi { - pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void}; + pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void, CStr}; sys_struct!({ // Defined in machine/armreg.h. @@ -173,24 +173,21 @@ mod imp { }); } - pub(super) unsafe fn sysctl_cpu_id(name: &[u8]) -> Option { + pub(super) fn sysctl_cpu_id(name: &ffi::CStr) -> Option { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - // SAFETY: all fields of aarch64_sysctl_cpu_id are zero-able and we use // the result when machdep.cpuN.cpu_id sysctl was successful. let mut buf: ffi::aarch64_sysctl_cpu_id = unsafe { mem::zeroed() }; let mut out_len = OUT_LEN; // SAFETY: - // - the caller must guarantee that `name` is ` machdep.cpuN.cpu_id` in a C string. + // - `name` a valid C string. // - `out_len` does not exceed the size of the value at `buf`. // - `sysctlbyname` is thread-safe. let res = unsafe { ffi::sysctlbyname( - name.as_ptr().cast::(), + name.as_ptr(), (&mut buf as *mut ffi::aarch64_sysctl_cpu_id).cast::(), &mut out_len, ptr::null_mut(), @@ -211,11 +208,10 @@ mod imp { // Get system registers for cpu0. // If failed, returns default because machdep.cpuN.cpu_id sysctl is not available. // machdep.cpuN.cpu_id sysctl was added in NetBSD 9.0 so it is not available on older versions. - // SAFETY: we passed a valid name in a C string. // It is ok to check only cpu0, even if there are more CPUs. // https://github.com/NetBSD/src/commit/bd9707e06ea7d21b5c24df6dfc14cb37c2819416 // https://github.com/golang/sys/commit/ef9fd89ba245e184bdd308f7f2b4f3c551fa5b0f - match unsafe { sysctl_cpu_id(b"machdep.cpu0.cpu_id\0") } { + match sysctl_cpu_id(c!("machdep.cpu0.cpu_id")) { Some(cpu_id) => cpu_id, None => AA64Reg { aa64isar0: 0, aa64isar1: 0, aa64mmfr2: 0 }, } @@ -389,7 +385,7 @@ mod tests { // much as Linux does (It may actually be stable enough, though: https://lists.llvm.org/pipermail/llvm-dev/2019-June/133393.html). // // This is currently used only for testing. - unsafe fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result { + fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result { // https://github.com/golang/go/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/src/syscall/asm_netbsd_arm64.s #[inline] unsafe fn sysctl( @@ -504,11 +500,9 @@ mod tests { }) } - unsafe { - assert_eq!( - imp::sysctl_cpu_id(b"machdep.cpu0.cpu_id\0").unwrap(), - sysctl_cpu_id_no_libc(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() - ); - } + assert_eq!( + imp::sysctl_cpu_id(c!("machdep.cpu0.cpu_id")).unwrap(), + sysctl_cpu_id_no_libc(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() + ); } } diff --git a/src/imp/detect/aarch64_apple.rs b/src/imp/detect/aarch64_apple.rs index b2cec8b7..117d4b8a 100644 --- a/src/imp/detect/aarch64_apple.rs +++ b/src/imp/detect/aarch64_apple.rs @@ -25,7 +25,7 @@ use core::{mem, ptr}; // core::ffi::c_* (except c_void) requires Rust 1.64, libc requires Rust 1.63 #[allow(non_camel_case_types)] mod ffi { - pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void}; + pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void, CStr}; sys_fn!({ extern "C" { @@ -42,21 +42,18 @@ mod ffi { }); } -unsafe fn sysctlbyname32(name: &[u8]) -> Option { +fn sysctlbyname32(name: &ffi::CStr) -> Option { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - let mut out = 0_u32; let mut out_len = OUT_LEN; // SAFETY: - // - the caller must guarantee that `name` a valid C string. + // - `name` a valid C string. // - `out_len` does not exceed the size of `out`. // - `sysctlbyname` is thread-safe. let res = unsafe { ffi::sysctlbyname( - name.as_ptr().cast::(), + name.as_ptr(), (&mut out as *mut u32).cast::(), &mut out_len, ptr::null_mut(), @@ -77,23 +74,18 @@ fn _detect(info: &mut CpuInfo) { // Query both names in case future versions of macOS remove the old name. // https://github.com/golang/go/commit/c15593197453b8bf90fc3a9080ba2afeaf7934ea // https://github.com/google/boringssl/commit/91e0b11eba517d83b910b20fe3740eeb39ecb37e - // SAFETY: we passed a valid C string. - if unsafe { - sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0").unwrap_or(0) != 0 - || sysctlbyname32(b"hw.optional.armv8_1_atomics\0").unwrap_or(0) != 0 - } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE")).unwrap_or(0) != 0 + || sysctlbyname32(c!("hw.optional.armv8_1_atomics")).unwrap_or(0) != 0 + { info.set(CpuInfo::HAS_LSE); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE2")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_LSE2); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE128")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_LSE128); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC3")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_RCPC3); } } @@ -111,17 +103,15 @@ mod tests { #[test] fn test_macos() { - unsafe { - assert_eq!(sysctlbyname32(b"hw.optional.armv8_1_atomics\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0"), None); - assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC2\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0"), None); - assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); - } + assert_eq!(sysctlbyname32(c!("hw.optional.armv8_1_atomics")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE2")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE128")), None); + assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC2")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC3")), None); + assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); } #[cfg(target_pointer_width = "64")] @@ -137,7 +127,7 @@ mod tests { // (And they actually changed it: https://go-review.googlesource.com/c/go/+/25495) // // This is currently used only for testing. - unsafe fn sysctlbyname32_no_libc(name: &[u8]) -> Result { + fn sysctlbyname32_no_libc(name: &CStr) -> Result { // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/syscalls.master#L298 #[inline] unsafe fn sysctl( @@ -187,7 +177,7 @@ mod tests { } // https://github.com/apple-oss-distributions/Libc/blob/af11da5ca9d527ea2f48bb7efbd0f0f2a4ea4812/gen/FreeBSD/sysctlbyname.c unsafe fn sysctlbyname( - name: &[u8], + name: &CStr, old_p: *mut c_void, old_len_p: *mut c_size_t, new_p: *mut c_void, @@ -207,7 +197,7 @@ mod tests { real_oid.as_mut_ptr().cast::(), &mut oid_len, name.as_ptr().cast::() as *mut c_void, - name.len() - 1, + name.to_bytes_with_nul().len() - 1, )? }; oid_len /= mem::size_of::(); @@ -219,9 +209,6 @@ mod tests { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - let mut out = 0_u32; let mut out_len = OUT_LEN; // SAFETY: @@ -243,20 +230,18 @@ mod tests { } for name in [ - &b"hw.optional.armv8_1_atomics\0"[..], - b"hw.optional.arm.FEAT_LSE\0", - b"hw.optional.arm.FEAT_LSE2\0", - b"hw.optional.arm.FEAT_LSE128\0", - b"hw.optional.arm.FEAT_LRCPC\0", - b"hw.optional.arm.FEAT_LRCPC2\0", - b"hw.optional.arm.FEAT_LRCPC3\0", + c!("hw.optional.armv8_1_atomics"), + c!("hw.optional.arm.FEAT_LSE"), + c!("hw.optional.arm.FEAT_LSE2"), + c!("hw.optional.arm.FEAT_LSE128"), + c!("hw.optional.arm.FEAT_LRCPC"), + c!("hw.optional.arm.FEAT_LRCPC2"), + c!("hw.optional.arm.FEAT_LRCPC3"), ] { - unsafe { - if let Some(res) = sysctlbyname32(name) { - assert_eq!(res, sysctlbyname32_no_libc(name).unwrap()); - } else { - assert_eq!(sysctlbyname32_no_libc(name).unwrap_err(), libc::ENOENT); - } + if let Some(res) = sysctlbyname32(name) { + assert_eq!(res, sysctlbyname32_no_libc(name).unwrap()); + } else { + assert_eq!(sysctlbyname32_no_libc(name).unwrap_err(), libc::ENOENT); } } } diff --git a/src/imp/detect/auxv.rs b/src/imp/detect/auxv.rs index f74e5245..4939be93 100644 --- a/src/imp/detect/auxv.rs +++ b/src/imp/detect/auxv.rs @@ -276,7 +276,7 @@ mod os { // SAFETY: we passed a valid C string to dlsym, and a pointer returned by dlsym // is a valid pointer to the function if it is non-null. let getauxval: GetauxvalTy = unsafe { - let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, "getauxval\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr()); if ptr.is_null() { return 0; } @@ -385,8 +385,7 @@ mod os { // SAFETY: we passed a valid C string to dlsym, and a pointer returned by dlsym // is a valid pointer to the function if it is non-null. let elf_aux_info: ElfAuxInfoTy = unsafe { - let ptr = - ffi::dlsym(ffi::RTLD_DEFAULT, "elf_aux_info\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("elf_aux_info").as_ptr()); if ptr.is_null() { return 0; } @@ -464,7 +463,7 @@ mod arch { // SAFETY: we've passed a valid C string and a buffer with max length. let len = unsafe { ffi::__system_property_get( - b"ro.arch\0".as_ptr().cast::(), + c!("ro.arch").as_ptr(), arch.as_mut_ptr().cast::(), ) }; @@ -589,7 +588,7 @@ mod tests { unsafe { let mut arch = [1; ffi::PROP_VALUE_MAX as usize]; let len = ffi::__system_property_get( - b"ro.arch\0".as_ptr().cast::(), + c!("ro.arch").as_ptr(), arch.as_mut_ptr().cast::(), ); assert!(len >= 0); @@ -606,7 +605,7 @@ mod tests { #[test] fn test_dlsym_getauxval() { unsafe { - let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, "getauxval\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr()); if cfg!(any( all( target_os = "linux", @@ -636,8 +635,7 @@ mod tests { #[test] fn test_dlsym_elf_aux_info() { unsafe { - let ptr = - ffi::dlsym(ffi::RTLD_DEFAULT, "elf_aux_info\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("elf_aux_info").as_ptr()); if cfg!(target_os = "freebsd") || option_env!("CI").is_some() { assert!(!ptr.is_null()); } diff --git a/src/utils.rs b/src/utils.rs index d6f33947..b4bf5fb5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -601,4 +601,136 @@ pub(crate) mod ffi { let _: c_size_t = 0 as libc::size_t; // std::os::raw::c_size_t is unstable let _: c_char = 0 as std::os::raw::c_char; }; + + #[repr(transparent)] + pub(crate) struct CStr([c_char]); + impl CStr { + #[inline] + #[must_use] + pub(crate) const fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() + } + /// # Safety + /// + /// The provided slice **must** be nul-terminated and not contain any interior + /// nul bytes. + #[inline] + #[must_use] + pub(crate) unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr { + // SAFETY: Casting to CStr is safe because *our* CStr is #[repr(transparent)] + // and its internal representation is a [u8] too. (Note that std's CStr + // is not #[repr(transparent)].) + // Dereferencing the obtained pointer is safe because it comes from a + // reference. Making a reference is then safe because its lifetime + // is bound by the lifetime of the given `bytes`. + unsafe { &*(bytes as *const [u8] as *const CStr) } + } + #[cfg(test)] + #[inline] + #[must_use] + pub(crate) fn to_bytes_with_nul(&self) -> &[u8] { + // SAFETY: Transmuting a slice of `c_char`s to a slice of `u8`s + // is safe on all supported targets. + #[allow(clippy::unnecessary_cast)] // triggered for targets that c_char is u8 + unsafe { + &*(&self.0 as *const [c_char] as *const [u8]) + } + } + } +} +// Provide safe abstraction (c! macro) for creating static C strings without runtime checks. +// (c"..." requires Rust 1.77) +#[cfg(any(test, not(any(windows, target_arch = "x86", target_arch = "x86_64"))))] +#[cfg(any(not(portable_atomic_no_asm), portable_atomic_unstable_asm))] +#[allow(dead_code, unused_macros)] +#[macro_use] +pub(crate) mod c_str { + macro_rules! c { + ($s:expr) => {{ + const BYTES: &[u8] = concat!($s, "\0").as_bytes(); + const _: () = static_assert!(crate::utils::c_str::_const_is_c_str(BYTES)); + #[allow(unused_unsafe)] + // SAFETY: we've checked `BYTES` is a valid C string + unsafe { + crate::utils::ffi::CStr::from_bytes_with_nul_unchecked(BYTES) + } + }}; + } + + #[must_use] + pub(crate) const fn _const_is_c_str(bytes: &[u8]) -> bool { + #[cfg(portable_atomic_no_const_if_match_loop)] + { + // const_if_match/const_loop was stabilized 9 days after the oldest nightly version + // that uses this module, and is included in the same 1.46 stable release. + !bytes.is_empty() + } + #[cfg(not(portable_atomic_no_const_if_match_loop))] + { + // Based on https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/c_str.rs#L434 + // - bytes must be nul-terminated. + // - bytes must not contain any interior nul bytes. + if bytes.is_empty() { + return false; + } + let mut i = bytes.len() - 1; + if bytes[i] != 0 { + return false; + } + // Ending null byte exists, skip to the rest. + while i != 0 { + i -= 1; + if bytes[i] == 0 { + return false; + } + } + true + } + } + + #[allow( + clippy::alloc_instead_of_core, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core, + clippy::undocumented_unsafe_blocks, + clippy::wildcard_imports + )] + #[cfg(test)] + mod tests { + #[test] + fn test_c_macro() { + #[track_caller] + fn t(s: &crate::utils::ffi::CStr, raw: &[u8]) { + assert_eq!(s.to_bytes_with_nul(), raw); + } + t(c!(""), b"\0"); + t(c!("a"), b"a\0"); + t(c!("abc"), b"abc\0"); + t(c!(concat!("abc", "d")), b"abcd\0"); + } + + #[test] + fn test_is_c_str() { + #[track_caller] + fn t(bytes: &[u8]) { + assert_eq!( + super::_const_is_c_str(bytes), + std::ffi::CStr::from_bytes_with_nul(bytes).is_ok() + ); + } + t(b"\0"); + t(b"a\0"); + t(b"abc\0"); + t(b""); + #[cfg(not(portable_atomic_no_const_if_match_loop))] // see _is_c_str + { + t(b"a"); + t(b"abc"); + t(b"\0a"); + t(b"\0a\0"); + t(b"ab\0c\0"); + t(b"\0\0"); + } + } + } }