Rollup merge of #127763 - ChrisDenton:safe-unsafe-unsafe, r=tgross35

Make more Windows functions `#![deny(unsafe_op_in_unsafe_fn)]`

As part of #127747, I've evaluated some more Windows functions and added `unsafe` blocks where necessary. Some are just trivial wrappers that "inherit" the full unsafety of their function, but for others I've added some safety comments. A few functions weren't actually unsafe at all. I think they were just using `unsafe fn` to avoid an `unsafe {}` block.

I'm not touching `c.rs` yet because that is partially being addressed by another PR and also I have plans to further reduce the number of wrapper functions we have in there.

r? libs
This commit is contained in:
Trevor Gross 2024-07-17 04:05:59 -05:00 committed by GitHub
commit 56f95559da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 111 additions and 90 deletions

View File

@ -1,3 +1,5 @@
#![forbid(unsafe_op_in_unsafe_fn)]
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(any( if #[cfg(any(
target_os = "windows", target_os = "windows",

View File

@ -1,6 +1,5 @@
//! The underlying OsString/OsStr implementation on Windows is a //! The underlying OsString/OsStr implementation on Windows is a
//! wrapper around the "WTF-8" encoding; see the `wtf8` module for more. //! wrapper around the "WTF-8" encoding; see the `wtf8` module for more.
use crate::borrow::Cow; use crate::borrow::Cow;
use crate::collections::TryReserveError; use crate::collections::TryReserveError;
use crate::fmt; use crate::fmt;
@ -71,7 +70,7 @@ pub fn into_encoded_bytes(self) -> Vec<u8> {
#[inline] #[inline]
pub unsafe fn from_encoded_bytes_unchecked(s: Vec<u8>) -> Self { pub unsafe fn from_encoded_bytes_unchecked(s: Vec<u8>) -> Self {
Self { inner: Wtf8Buf::from_bytes_unchecked(s) } unsafe { Self { inner: Wtf8Buf::from_bytes_unchecked(s) } }
} }
pub fn with_capacity(capacity: usize) -> Buf { pub fn with_capacity(capacity: usize) -> Buf {
@ -190,7 +189,7 @@ pub fn as_encoded_bytes(&self) -> &[u8] {
#[inline] #[inline]
pub unsafe fn from_encoded_bytes_unchecked(s: &[u8]) -> &Slice { pub unsafe fn from_encoded_bytes_unchecked(s: &[u8]) -> &Slice {
mem::transmute(Wtf8::from_bytes_unchecked(s)) unsafe { mem::transmute(Wtf8::from_bytes_unchecked(s)) }
} }
#[track_caller] #[track_caller]

View File

@ -1,5 +1,3 @@
#![deny(unsafe_op_in_unsafe_fn)]
use crate::alloc::{GlobalAlloc, Layout, System}; use crate::alloc::{GlobalAlloc, Layout, System};
use crate::ffi::c_void; use crate::ffi::c_void;
use crate::ptr; use crate::ptr;

View File

@ -1,4 +1,3 @@
#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::addr_of; use core::ptr::addr_of;
use crate::os::windows::prelude::*; use crate::os::windows::prelude::*;
@ -795,12 +794,14 @@ fn next(&mut self) -> Option<Self::Item> {
} }
unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]> { unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]> {
unsafe {
if p.is_aligned() { if p.is_aligned() {
Cow::Borrowed(crate::slice::from_raw_parts(p, len)) Cow::Borrowed(crate::slice::from_raw_parts(p, len))
} else { } else {
Cow::Owned((0..len).map(|i| p.add(i).read_unaligned()).collect()) Cow::Owned((0..len).map(|i| p.add(i).read_unaligned()).collect())
} }
} }
}
/// Open a link relative to the parent directory, ensure no symlinks are followed. /// Open a link relative to the parent directory, ensure no symlinks are followed.
fn open_link_no_reparse(parent: &File, name: &[u16], access: u32) -> io::Result<File> { fn open_link_no_reparse(parent: &File, name: &[u16], access: u32) -> io::Result<File> {
@ -897,9 +898,11 @@ fn into_raw_handle(self) -> RawHandle {
impl FromRawHandle for File { impl FromRawHandle for File {
unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
unsafe {
Self { handle: FromInner::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } Self { handle: FromInner::from_inner(FromRawHandle::from_raw_handle(raw_handle)) }
} }
} }
}
impl fmt::Debug for File { impl fmt::Debug for File {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -1427,11 +1430,13 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
_hDestinationFile: c::HANDLE, _hDestinationFile: c::HANDLE,
lpData: *const c_void, lpData: *const c_void,
) -> u32 { ) -> u32 {
unsafe {
if dwStreamNumber == 1 { if dwStreamNumber == 1 {
*(lpData as *mut i64) = StreamBytesTransferred; *(lpData as *mut i64) = StreamBytesTransferred;
} }
c::PROGRESS_CONTINUE c::PROGRESS_CONTINUE
} }
}
let pfrom = maybe_verbatim(from)?; let pfrom = maybe_verbatim(from)?;
let pto = maybe_verbatim(to)?; let pto = maybe_verbatim(to)?;
let mut size = 0i64; let mut size = 0i64;

View File

@ -1,5 +1,4 @@
#![unstable(issue = "none", feature = "windows_handle")] #![unstable(issue = "none", feature = "windows_handle")]
#![allow(unsafe_op_in_unsafe_fn)]
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -73,7 +72,7 @@ fn into_raw_handle(self) -> RawHandle {
impl FromRawHandle for Handle { impl FromRawHandle for Handle {
unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
Self(FromRawHandle::from_raw_handle(raw_handle)) unsafe { Self(FromRawHandle::from_raw_handle(raw_handle)) }
} }
} }
@ -139,13 +138,23 @@ pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
pub unsafe fn read_overlapped( pub unsafe fn read_overlapped(
&self, &self,
buf: &mut [u8], buf: &mut [mem::MaybeUninit<u8>],
overlapped: *mut c::OVERLAPPED, overlapped: *mut c::OVERLAPPED,
) -> io::Result<Option<usize>> { ) -> io::Result<Option<usize>> {
// SAFETY: We have exclusive access to the buffer and it's up to the caller to
// ensure the OVERLAPPED pointer is valid for the lifetime of this function.
let (res, amt) = unsafe {
let len = cmp::min(buf.len(), u32::MAX as usize) as u32; let len = cmp::min(buf.len(), u32::MAX as usize) as u32;
let mut amt = 0; let mut amt = 0;
let res = let res = cvt(c::ReadFile(
cvt(c::ReadFile(self.as_raw_handle(), buf.as_mut_ptr(), len, &mut amt, overlapped)); self.as_raw_handle(),
buf.as_mut_ptr().cast::<u8>(),
len,
&mut amt,
overlapped,
));
(res, amt)
};
match res { match res {
Ok(_) => Ok(Some(amt as usize)), Ok(_) => Ok(Some(amt as usize)),
Err(e) => { Err(e) => {
@ -230,7 +239,10 @@ unsafe fn synchronous_read(
// The length is clamped at u32::MAX. // The length is clamped at u32::MAX.
let len = cmp::min(len, u32::MAX as usize) as u32; let len = cmp::min(len, u32::MAX as usize) as u32;
let status = c::NtReadFile( // SAFETY: It's up to the caller to ensure `buf` is writeable up to
// the provided `len`.
let status = unsafe {
c::NtReadFile(
self.as_handle(), self.as_handle(),
ptr::null_mut(), ptr::null_mut(),
None, None,
@ -240,10 +252,11 @@ unsafe fn synchronous_read(
len, len,
offset.map(|n| n as _).as_ref(), offset.map(|n| n as _).as_ref(),
None, None,
); )
};
let status = if status == c::STATUS_PENDING { let status = if status == c::STATUS_PENDING {
c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE); unsafe { c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE) };
io_status.status() io_status.status()
} else { } else {
status status
@ -261,7 +274,7 @@ unsafe fn synchronous_read(
status if c::nt_success(status) => Ok(io_status.Information), status if c::nt_success(status) => Ok(io_status.Information),
status => { status => {
let error = c::RtlNtStatusToDosError(status); let error = unsafe { c::RtlNtStatusToDosError(status) };
Err(io::Error::from_raw_os_error(error as _)) Err(io::Error::from_raw_os_error(error as _))
} }
} }

View File

@ -1,4 +1,3 @@
#![allow(unsafe_op_in_unsafe_fn)]
use crate::marker::PhantomData; use crate::marker::PhantomData;
use crate::mem::size_of; use crate::mem::size_of;
use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
@ -81,19 +80,17 @@ pub fn as_mut_slice(&mut self) -> &mut [u8] {
} }
pub fn is_terminal(h: &impl AsHandle) -> bool { pub fn is_terminal(h: &impl AsHandle) -> bool {
unsafe { handle_is_console(h.as_handle()) } handle_is_console(h.as_handle())
} }
unsafe fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { fn handle_is_console(handle: BorrowedHandle<'_>) -> bool {
let handle = handle.as_raw_handle();
// A null handle means the process has no console. // A null handle means the process has no console.
if handle.is_null() { if handle.as_raw_handle().is_null() {
return false; return false;
} }
let mut out = 0; let mut out = 0;
if c::GetConsoleMode(handle, &mut out) != 0 { if unsafe { c::GetConsoleMode(handle.as_raw_handle(), &mut out) != 0 } {
// False positives aren't possible. If we got a console then we definitely have a console. // False positives aren't possible. If we got a console then we definitely have a console.
return true; return true;
} }
@ -102,9 +99,9 @@ unsafe fn handle_is_console(handle: BorrowedHandle<'_>) -> bool {
msys_tty_on(handle) msys_tty_on(handle)
} }
unsafe fn msys_tty_on(handle: c::HANDLE) -> bool { fn msys_tty_on(handle: BorrowedHandle<'_>) -> bool {
// Early return if the handle is not a pipe. // Early return if the handle is not a pipe.
if c::GetFileType(handle) != c::FILE_TYPE_PIPE { if unsafe { c::GetFileType(handle.as_raw_handle()) != c::FILE_TYPE_PIPE } {
return false; return false;
} }
@ -120,12 +117,14 @@ struct FILE_NAME_INFO {
} }
let mut name_info = FILE_NAME_INFO { FileNameLength: 0, FileName: [0; c::MAX_PATH as usize] }; let mut name_info = FILE_NAME_INFO { FileNameLength: 0, FileName: [0; c::MAX_PATH as usize] };
// Safety: buffer length is fixed. // Safety: buffer length is fixed.
let res = c::GetFileInformationByHandleEx( let res = unsafe {
handle, c::GetFileInformationByHandleEx(
handle.as_raw_handle(),
c::FileNameInfo, c::FileNameInfo,
core::ptr::addr_of_mut!(name_info) as *mut c_void, core::ptr::addr_of_mut!(name_info) as *mut c_void,
size_of::<FILE_NAME_INFO>() as u32, size_of::<FILE_NAME_INFO>() as u32,
); )
};
if res == 0 { if res == 0 {
return false; return false;
} }

View File

@ -1,7 +1,6 @@
//! Implementation of `std::os` functionality for Windows. //! Implementation of `std::os` functionality for Windows.
#![allow(nonstandard_style)] #![allow(nonstandard_style)]
#![allow(unsafe_op_in_unsafe_fn)]
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -305,16 +304,22 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
} }
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
// SAFETY: We ensure that k and v are null-terminated wide strings.
unsafe {
let k = to_u16s(k)?; let k = to_u16s(k)?;
let v = to_u16s(v)?; let v = to_u16s(v)?;
cvt(c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())).map(drop) cvt(c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())).map(drop)
} }
}
pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> { pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
// SAFETY: We ensure that v is a null-terminated wide strings.
unsafe {
let v = to_u16s(n)?; let v = to_u16s(n)?;
cvt(c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())).map(drop) cvt(c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())).map(drop)
} }
}
pub fn temp_dir() -> PathBuf { pub fn temp_dir() -> PathBuf {
super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, super::os2path).unwrap() super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, super::os2path).unwrap()

View File

@ -1,4 +1,3 @@
#![allow(unsafe_op_in_unsafe_fn)]
use crate::os::windows::prelude::*; use crate::os::windows::prelude::*;
use crate::ffi::OsStr; use crate::ffi::OsStr;
@ -6,7 +5,6 @@
use crate::mem; use crate::mem;
use crate::path::Path; use crate::path::Path;
use crate::ptr; use crate::ptr;
use crate::slice;
use crate::sync::atomic::AtomicUsize; use crate::sync::atomic::AtomicUsize;
use crate::sync::atomic::Ordering::Relaxed; use crate::sync::atomic::Ordering::Relaxed;
use crate::sys::c; use crate::sys::c;
@ -325,6 +323,7 @@ pub fn is_write_vectored(&self) -> bool {
/// [`ReadFileEx`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfileex /// [`ReadFileEx`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfileex
/// [`WriteFileEx`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefileex /// [`WriteFileEx`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefileex
/// [Asynchronous Procedure Call]: https://docs.microsoft.com/en-us/windows/win32/sync/asynchronous-procedure-calls /// [Asynchronous Procedure Call]: https://docs.microsoft.com/en-us/windows/win32/sync/asynchronous-procedure-calls
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn alertable_io_internal( unsafe fn alertable_io_internal(
&self, &self,
io: AlertableIoFn, io: AlertableIoFn,
@ -479,8 +478,11 @@ fn new(pipe: Handle, dst: &'a mut Vec<u8>) -> io::Result<AsyncPipe<'a>> {
fn schedule_read(&mut self) -> io::Result<bool> { fn schedule_read(&mut self) -> io::Result<bool> {
assert_eq!(self.state, State::NotReading); assert_eq!(self.state, State::NotReading);
let amt = unsafe { let amt = unsafe {
let slice = slice_to_end(self.dst); if self.dst.capacity() == self.dst.len() {
self.pipe.read_overlapped(slice, &mut *self.overlapped)? let additional = if self.dst.capacity() == 0 { 16 } else { 1 };
self.dst.reserve(additional);
}
self.pipe.read_overlapped(self.dst.spare_capacity_mut(), &mut *self.overlapped)?
}; };
// If this read finished immediately then our overlapped event will // If this read finished immediately then our overlapped event will
@ -560,13 +562,3 @@ fn drop(&mut self) {
} }
} }
} }
unsafe fn slice_to_end(v: &mut Vec<u8>) -> &mut [u8] {
if v.capacity() == 0 {
v.reserve(16);
}
if v.capacity() == v.len() {
v.reserve(1);
}
slice::from_raw_parts_mut(v.as_mut_ptr().add(v.len()), v.capacity() - v.len())
}

View File

@ -1,18 +1,18 @@
#![cfg_attr(test, allow(dead_code))] #![cfg_attr(test, allow(dead_code))]
#![allow(unsafe_op_in_unsafe_fn)]
use crate::sys::c; use crate::sys::c;
use crate::thread; use crate::thread;
/// Reserve stack space for use in stack overflow exceptions. /// Reserve stack space for use in stack overflow exceptions.
pub unsafe fn reserve_stack() { pub fn reserve_stack() {
let result = c::SetThreadStackGuarantee(&mut 0x5000); let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
// Reserving stack space is not critical so we allow it to fail in the released build of libstd. // Reserving stack space is not critical so we allow it to fail in the released build of libstd.
// We still use debug assert here so that CI will test that we haven't made a mistake calling the function. // We still use debug assert here so that CI will test that we haven't made a mistake calling the function.
debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
} }
unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
// SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid.
unsafe { unsafe {
let rec = &(*(*ExceptionInfo).ExceptionRecord); let rec = &(*(*ExceptionInfo).ExceptionRecord);
let code = rec.ExceptionCode; let code = rec.ExceptionCode;
@ -27,11 +27,14 @@ pub unsafe fn reserve_stack() {
} }
} }
pub unsafe fn init() { pub fn init() {
// SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling.
unsafe {
let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
// Similar to the above, adding the stack overflow handler is allowed to fail // Similar to the above, adding the stack overflow handler is allowed to fail
// but a debug assert is used so CI will still test that it normally works. // but a debug assert is used so CI will still test that it normally works.
debug_assert!(!result.is_null(), "failed to install exception handler"); debug_assert!(!result.is_null(), "failed to install exception handler");
}
// Set the thread stack guarantee for the main thread. // Set the thread stack guarantee for the main thread.
reserve_stack(); reserve_stack();
} }

View File

@ -1,4 +1,3 @@
#![allow(unsafe_op_in_unsafe_fn)]
use crate::ffi::CStr; use crate::ffi::CStr;
use crate::io; use crate::io;
use crate::num::NonZero; use crate::num::NonZero;
@ -28,6 +27,9 @@ pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
// CreateThread rounds up values for the stack size to the nearest page size (at least 4kb). // CreateThread rounds up values for the stack size to the nearest page size (at least 4kb).
// If a value of zero is given then the default stack size is used instead. // If a value of zero is given then the default stack size is used instead.
// SAFETY: `thread_start` has the right ABI for a thread's entry point.
// `p` is simply passed through to the new thread without being touched.
let ret = unsafe {
let ret = c::CreateThread( let ret = c::CreateThread(
ptr::null_mut(), ptr::null_mut(),
stack, stack,
@ -36,13 +38,14 @@ pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
c::STACK_SIZE_PARAM_IS_A_RESERVATION, c::STACK_SIZE_PARAM_IS_A_RESERVATION,
ptr::null_mut(), ptr::null_mut(),
); );
let ret = HandleOrNull::from_raw_handle(ret); HandleOrNull::from_raw_handle(ret)
};
return if let Ok(handle) = ret.try_into() { return if let Ok(handle) = ret.try_into() {
Ok(Thread { handle: Handle::from_inner(handle) }) Ok(Thread { handle: Handle::from_inner(handle) })
} else { } else {
// The thread failed to start and as a result p was not consumed. Therefore, it is // The thread failed to start and as a result p was not consumed. Therefore, it is
// safe to reconstruct the box so that it gets deallocated. // safe to reconstruct the box so that it gets deallocated.
drop(Box::from_raw(p)); unsafe { drop(Box::from_raw(p)) };
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
}; };
@ -50,7 +53,9 @@ pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
// Next, reserve some stack space for if we otherwise run out of stack. // Next, reserve some stack space for if we otherwise run out of stack.
stack_overflow::reserve_stack(); stack_overflow::reserve_stack();
// Finally, let's run some code. // Finally, let's run some code.
Box::from_raw(main as *mut Box<dyn FnOnce()>)(); // SAFETY: We are simply recreating the box that was leaked earlier.
// It's the responsibility of the one who call `Thread::new` to ensure this is safe to call here.
unsafe { Box::from_raw(main as *mut Box<dyn FnOnce()>)() };
0 0
} }
} }
@ -70,7 +75,7 @@ pub fn set_name(name: &CStr) {
/// ///
/// `name` must end with a zero value /// `name` must end with a zero value
pub unsafe fn set_name_wide(name: &[u16]) { pub unsafe fn set_name_wide(name: &[u16]) {
c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()); unsafe { c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()) };
} }
pub fn join(self) { pub fn join(self) {