Skip to content

Commit

Permalink
Auto merge of rust-lang#124636 - tbu-:pr_env_unsafe, r=petrochenkov
Browse files Browse the repository at this point in the history
Make `std::env::{set_var, remove_var}` unsafe in edition 2024

Allow calling these functions without `unsafe` blocks in editions up until 2021, but don't trigger the `unused_unsafe` lint for `unsafe` blocks containing these functions.

Fixes rust-lang#27970.
Fixes rust-lang#90308.
CC rust-lang#124866.
  • Loading branch information
bors committed May 30, 2024
2 parents bbaaa79 + 4feb881 commit ba98164
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 69 deletions.
102 changes: 66 additions & 36 deletions std/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,22 +318,25 @@ impl Error for VarError {
///
/// # Safety
///
/// Even though this function is currently not marked as `unsafe`, it needs to
/// be because invoking it can cause undefined behaviour. The function will be
/// marked `unsafe` in a future version of Rust. This is tracked in
/// [rust#27970](https://github.com/rust-lang/rust/issues/27970).
///
/// This function is safe to call in a single-threaded program.
///
/// In multi-threaded programs, you must ensure that are no other threads
/// concurrently writing or *reading*(!) from the environment through functions
/// other than the ones in this module. You are responsible for figuring out
/// how to achieve this, but we strongly suggest not using `set_var` or
/// `remove_var` in multi-threaded programs at all.
///
/// Most C libraries, including libc itself do not advertise which functions
/// read from the environment. Even functions from the Rust standard library do
/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`].
/// This function is also always safe to call on Windows, in single-threaded
/// and multi-threaded programs.
///
/// In multi-threaded programs on other operating systems, we strongly suggest
/// not using `set_var` or `remove_var` at all. The exact requirement is: you
/// must ensure that there are no other threads concurrently writing or
/// *reading*(!) the environment through functions or global variables other
/// than the ones in this module. The problem is that these operating systems
/// do not provide a thread-safe way to read the environment, and most C
/// libraries, including libc itself, do not advertise which functions read
/// from the environment. Even functions from the Rust standard library may
/// read the environment without going through this module, e.g. for DNS
/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about
/// which functions may read from the environment in future versions of a
/// library. All this makes it not practically possible for you to guarantee
/// that no other thread will read the environment, so the only safe option is
/// to not use `set_var` or `remove_var` in multi-threaded programs at all.
///
/// Discussion of this unsafety on Unix may be found in:
///
Expand All @@ -353,15 +356,26 @@ impl Error for VarError {
/// use std::env;
///
/// let key = "KEY";
/// env::set_var(key, "VALUE");
/// unsafe {
/// env::set_var(key, "VALUE");
/// }
/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
/// ```
#[cfg(not(bootstrap))]
#[rustc_deprecated_safe_2024]
#[stable(feature = "env", since = "1.0.0")]
pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
pub unsafe fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
_set_var(key.as_ref(), value.as_ref())
}

fn _set_var(key: &OsStr, value: &OsStr) {
#[cfg(bootstrap)]
#[allow(missing_docs)]
#[stable(feature = "env", since = "1.0.0")]
pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
unsafe { _set_var(key.as_ref(), value.as_ref()) }
}

unsafe fn _set_var(key: &OsStr, value: &OsStr) {
os_imp::setenv(key, value).unwrap_or_else(|e| {
panic!("failed to set environment variable `{key:?}` to `{value:?}`: {e}")
})
Expand All @@ -371,22 +385,25 @@ fn _set_var(key: &OsStr, value: &OsStr) {
///
/// # Safety
///
/// Even though this function is currently not marked as `unsafe`, it needs to
/// be because invoking it can cause undefined behaviour. The function will be
/// marked `unsafe` in a future version of Rust. This is tracked in
/// [rust#27970](https://github.com/rust-lang/rust/issues/27970).
///
/// This function is safe to call in a single-threaded program.
///
/// In multi-threaded programs, you must ensure that are no other threads
/// concurrently writing or *reading*(!) from the environment through functions
/// other than the ones in this module. You are responsible for figuring out
/// how to achieve this, but we strongly suggest not using `set_var` or
/// `remove_var` in multi-threaded programs at all.
///
/// Most C libraries, including libc itself do not advertise which functions
/// read from the environment. Even functions from the Rust standard library do
/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`].
/// This function is also always safe to call on Windows, in single-threaded
/// and multi-threaded programs.
///
/// In multi-threaded programs on other operating systems, we strongly suggest
/// not using `set_var` or `remove_var` at all. The exact requirement is: you
/// must ensure that there are no other threads concurrently writing or
/// *reading*(!) the environment through functions or global variables other
/// than the ones in this module. The problem is that these operating systems
/// do not provide a thread-safe way to read the environment, and most C
/// libraries, including libc itself, do not advertise which functions read
/// from the environment. Even functions from the Rust standard library may
/// read the environment without going through this module, e.g. for DNS
/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about
/// which functions may read from the environment in future versions of a
/// library. All this makes it not practically possible for you to guarantee
/// that no other thread will read the environment, so the only safe option is
/// to not use `set_var` or `remove_var` in multi-threaded programs at all.
///
/// Discussion of this unsafety on Unix may be found in:
///
Expand All @@ -403,22 +420,35 @@ fn _set_var(key: &OsStr, value: &OsStr) {
///
/// # Examples
///
/// ```
/// ```no_run
/// use std::env;
///
/// let key = "KEY";
/// env::set_var(key, "VALUE");
/// unsafe {
/// env::set_var(key, "VALUE");
/// }
/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
///
/// env::remove_var(key);
/// unsafe {
/// env::remove_var(key);
/// }
/// assert!(env::var(key).is_err());
/// ```
#[cfg(not(bootstrap))]
#[rustc_deprecated_safe_2024]
#[stable(feature = "env", since = "1.0.0")]
pub fn remove_var<K: AsRef<OsStr>>(key: K) {
pub unsafe fn remove_var<K: AsRef<OsStr>>(key: K) {
_remove_var(key.as_ref())
}

fn _remove_var(key: &OsStr) {
#[cfg(bootstrap)]
#[allow(missing_docs)]
#[stable(feature = "env", since = "1.0.0")]
pub fn remove_var<K: AsRef<OsStr>>(key: K) {
unsafe { _remove_var(key.as_ref()) }
}

unsafe fn _remove_var(key: &OsStr) {
os_imp::unsetenv(key)
.unwrap_or_else(|e| panic!("failed to remove environment variable `{key:?}`: {e}"))
}
Expand Down
14 changes: 5 additions & 9 deletions std/src/sys/pal/hermit/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,14 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
unsafe { ENV.as_ref().unwrap().lock().unwrap().get_mut(k).cloned() }
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
unsafe {
let (k, v) = (k.to_owned(), v.to_owned());
ENV.as_ref().unwrap().lock().unwrap().insert(k, v);
}
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
let (k, v) = (k.to_owned(), v.to_owned());
ENV.as_ref().unwrap().lock().unwrap().insert(k, v);
Ok(())
}

pub fn unsetenv(k: &OsStr) -> io::Result<()> {
unsafe {
ENV.as_ref().unwrap().lock().unwrap().remove(k);
}
pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> {
ENV.as_ref().unwrap().lock().unwrap().remove(k);
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/sgx/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
get_env_store().and_then(|s| s.lock().unwrap().get(k).cloned())
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
let (k, v) = (k.to_owned(), v.to_owned());
create_env_store().lock().unwrap().insert(k, v);
Ok(())
}

pub fn unsetenv(k: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> {
if let Some(env) = get_env_store() {
env.lock().unwrap().remove(k);
}
Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/solid/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
.flatten()
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
run_with_cstr(k.as_bytes(), &|k| {
run_with_cstr(v.as_bytes(), &|v| {
let _guard = ENV_LOCK.write();
Expand All @@ -200,7 +200,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
})
}

pub fn unsetenv(n: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
run_with_cstr(n.as_bytes(), &|nbuf| {
let _guard = ENV_LOCK.write();
cvt_env(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop)
Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/teeos/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ pub fn getenv(_: &OsStr) -> Option<OsString> {
None
}

pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}

pub fn unsetenv(_: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}

Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/uefi/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ pub fn getenv(_: &OsStr) -> Option<OsString> {
None
}

pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}

pub fn unsetenv(_: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}

Expand Down
8 changes: 4 additions & 4 deletions std/src/sys/pal/unix/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,19 +675,19 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
.flatten()
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
run_with_cstr(k.as_bytes(), &|k| {
run_with_cstr(v.as_bytes(), &|v| {
let _guard = ENV_LOCK.write();
cvt(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop)
cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop)
})
})
}

pub fn unsetenv(n: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
run_with_cstr(n.as_bytes(), &|nbuf| {
let _guard = ENV_LOCK.write();
cvt(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop)
cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
})
}

Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/unsupported/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ pub fn getenv(_: &OsStr) -> Option<OsString> {
None
}

pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}

pub fn unsetenv(_: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}

Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/wasi/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
.flatten()
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
run_with_cstr(k.as_bytes(), &|k| {
run_with_cstr(v.as_bytes(), &|v| unsafe {
let _guard = env_write_lock();
Expand All @@ -253,7 +253,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
})
}

pub fn unsetenv(n: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
run_with_cstr(n.as_bytes(), &|nbuf| unsafe {
let _guard = env_write_lock();
cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
Expand Down
8 changes: 4 additions & 4 deletions std/src/sys/pal/windows/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,16 +302,16 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
.ok()
}

pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
let k = to_u16s(k)?;
let v = to_u16s(v)?;

cvt(unsafe { c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) }).map(drop)
cvt(c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())).map(drop)
}

pub fn unsetenv(n: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
let v = to_u16s(n)?;
cvt(unsafe { c::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) }).map(drop)
cvt(c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())).map(drop)
}

pub fn temp_dir() -> PathBuf {
Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/xous/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ pub fn getenv(_: &OsStr) -> Option<OsString> {
None
}

pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}

pub fn unsetenv(_: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}

Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/zkvm/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ pub fn getenv(varname: &OsStr) -> Option<OsString> {
Some(OsString::from_inner(os_str::Buf { inner: u8s.to_vec() }))
}

pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}

pub fn unsetenv(_: &OsStr) -> io::Result<()> {
pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}

Expand Down

0 comments on commit ba98164

Please sign in to comment.