Skip to content

Commit 91c0823

Browse files
committed
Auto merge of rust-lang#124636 - tbu-:pr_env_unsafe, r=petrochenkov
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.
2 parents f3ff2f1 + 44f9f8b commit 91c0823

File tree

24 files changed

+366
-74
lines changed

24 files changed

+366
-74
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
578578
"rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
579579
through unstable paths"
580580
),
581+
rustc_attr!(
582+
rustc_deprecated_safe_2024, Normal, template!(Word), WarnFollowing,
583+
EncodeCrossCrate::Yes,
584+
"rustc_deprecated_safe_2024 is supposed to be used in libstd only",
585+
),
586+
581587

582588
// ==========================================================================
583589
// Internal attributes: Type system related:

compiler/rustc_lint_defs/src/builtin.rs

+49
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ declare_lint_pass! {
3737
DEPRECATED,
3838
DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
3939
DEPRECATED_IN_FUTURE,
40+
DEPRECATED_SAFE,
4041
DEPRECATED_WHERE_CLAUSE_LOCATION,
4142
DUPLICATE_MACRO_ATTRIBUTES,
4243
ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT,
@@ -4844,3 +4845,51 @@ declare_lint! {
48444845
reference: "issue #124559 <https://github.com/rust-lang/rust/issues/124559>",
48454846
};
48464847
}
4848+
4849+
declare_lint! {
4850+
/// The `deprecated_safe` lint detects unsafe functions being used as safe
4851+
/// functions.
4852+
///
4853+
/// ### Example
4854+
///
4855+
/// ```rust,edition2021,compile_fail
4856+
/// #![deny(deprecated_safe)]
4857+
/// // edition 2021
4858+
/// use std::env;
4859+
/// fn enable_backtrace() {
4860+
/// env::set_var("RUST_BACKTRACE", "1");
4861+
/// }
4862+
/// ```
4863+
///
4864+
/// {{produces}}
4865+
///
4866+
/// ### Explanation
4867+
///
4868+
/// Rust [editions] allow the language to evolve without breaking backward
4869+
/// compatibility. This lint catches code that uses `unsafe` functions that
4870+
/// were declared as safe (non-`unsafe`) in earlier editions. If you switch
4871+
/// the compiler to a new edition without updating the code, then it
4872+
/// will fail to compile if you are using a function previously marked as
4873+
/// safe.
4874+
///
4875+
/// You can audit the code to see if it suffices the preconditions of the
4876+
/// `unsafe` code, and if it does, you can wrap it in an `unsafe` block. If
4877+
/// you can't fulfill the preconditions, you probably need to switch to a
4878+
/// different way of doing what you want to achieve.
4879+
///
4880+
/// This lint can automatically wrap the calls in `unsafe` blocks, but this
4881+
/// obviously cannot verify that the preconditions of the `unsafe`
4882+
/// functions are fulfilled, so that is still up to the user.
4883+
///
4884+
/// The lint is currently "allow" by default, but that might change in the
4885+
/// future.
4886+
///
4887+
/// [editions]: https://doc.rust-lang.org/edition-guide/
4888+
pub DEPRECATED_SAFE,
4889+
Allow,
4890+
"detects unsafe functions being used as safe functions",
4891+
@future_incompatible = FutureIncompatibleInfo {
4892+
reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
4893+
reference: "issue #27970 <https://github.com/rust-lang/rust/issues/27970>",
4894+
};
4895+
}

compiler/rustc_mir_build/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ mir_build_borrow_of_moved_value = borrow of moved value
2828
.value_borrowed_label = value borrowed here after move
2929
.suggestion = borrow this binding in the pattern to avoid moving the value
3030
31+
mir_build_call_to_deprecated_safe_fn_requires_unsafe =
32+
call to deprecated safe function `{$function}` is unsafe and requires unsafe block
33+
.note = consult the function's documentation for information on how to avoid undefined behavior
34+
.label = call to unsafe function
35+
.suggestion = you can wrap the call in an `unsafe` block if you can guarantee the code is only ever called from single-threaded code
36+
3137
mir_build_call_to_fn_with_requires_unsafe =
3238
call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block
3339
.help = in order for the call to be safe, the context requires the following additional target {$missing_target_features_count ->

compiler/rustc_mir_build/src/check_unsafety.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_middle::thir::visit::Visitor;
99
use rustc_middle::thir::*;
1010
use rustc_middle::ty::print::with_no_trimmed_paths;
1111
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
12-
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
12+
use rustc_session::lint::builtin::{DEPRECATED_SAFE, UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
1313
use rustc_session::lint::Level;
1414
use rustc_span::def_id::{DefId, LocalDefId};
1515
use rustc_span::symbol::Symbol;
@@ -110,14 +110,34 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
110110
);
111111
self.suggest_unsafe_block = false;
112112
}
113-
SafetyContext::Safe => {
114-
kind.emit_requires_unsafe_err(
113+
SafetyContext::Safe => match kind {
114+
// Allow calls to deprecated-safe unsafe functions if the
115+
// caller is from an edition before 2024.
116+
UnsafeOpKind::CallToUnsafeFunction(Some(id))
117+
if !span.at_least_rust_2024()
118+
&& self.tcx.has_attr(id, sym::rustc_deprecated_safe_2024) =>
119+
{
120+
self.tcx.emit_node_span_lint(
121+
DEPRECATED_SAFE,
122+
self.hir_context,
123+
span,
124+
CallToDeprecatedSafeFnRequiresUnsafe {
125+
span,
126+
function: with_no_trimmed_paths!(self.tcx.def_path_str(id)),
127+
sub: CallToDeprecatedSafeFnRequiresUnsafeSub {
128+
left: span.shrink_to_lo(),
129+
right: span.shrink_to_hi(),
130+
},
131+
},
132+
)
133+
}
134+
_ => kind.emit_requires_unsafe_err(
115135
self.tcx,
116136
span,
117137
self.hir_context,
118138
unsafe_op_in_unsafe_fn_allowed,
119-
);
120-
}
139+
),
140+
},
121141
}
122142
}
123143

compiler/rustc_mir_build/src/errors.rs

+19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ pub struct UnconditionalRecursion {
2020
pub call_sites: Vec<Span>,
2121
}
2222

23+
#[derive(LintDiagnostic)]
24+
#[diag(mir_build_call_to_deprecated_safe_fn_requires_unsafe)]
25+
pub struct CallToDeprecatedSafeFnRequiresUnsafe {
26+
#[label]
27+
pub span: Span,
28+
pub function: String,
29+
#[subdiagnostic]
30+
pub sub: CallToDeprecatedSafeFnRequiresUnsafeSub,
31+
}
32+
33+
#[derive(Subdiagnostic)]
34+
#[multipart_suggestion(mir_build_suggestion, applicability = "machine-applicable")]
35+
pub struct CallToDeprecatedSafeFnRequiresUnsafeSub {
36+
#[suggestion_part(code = "unsafe {{ ")]
37+
pub left: Span,
38+
#[suggestion_part(code = " }}")]
39+
pub right: Span,
40+
}
41+
2342
#[derive(LintDiagnostic)]
2443
#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe, code = E0133)]
2544
#[note]

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,7 @@ symbols! {
15791579
rustc_def_path,
15801580
rustc_default_body_unstable,
15811581
rustc_deny_explicit_impl,
1582+
rustc_deprecated_safe_2024,
15821583
rustc_diagnostic_item,
15831584
rustc_diagnostic_macros,
15841585
rustc_dirty,

library/std/src/env.rs

+66-36
Original file line numberDiff line numberDiff line change
@@ -318,22 +318,25 @@ impl Error for VarError {
318318
///
319319
/// # Safety
320320
///
321-
/// Even though this function is currently not marked as `unsafe`, it needs to
322-
/// be because invoking it can cause undefined behaviour. The function will be
323-
/// marked `unsafe` in a future version of Rust. This is tracked in
324-
/// [rust#27970](https://github.com/rust-lang/rust/issues/27970).
325-
///
326321
/// This function is safe to call in a single-threaded program.
327322
///
328-
/// In multi-threaded programs, you must ensure that are no other threads
329-
/// concurrently writing or *reading*(!) from the environment through functions
330-
/// other than the ones in this module. You are responsible for figuring out
331-
/// how to achieve this, but we strongly suggest not using `set_var` or
332-
/// `remove_var` in multi-threaded programs at all.
333-
///
334-
/// Most C libraries, including libc itself do not advertise which functions
335-
/// read from the environment. Even functions from the Rust standard library do
336-
/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`].
323+
/// This function is also always safe to call on Windows, in single-threaded
324+
/// and multi-threaded programs.
325+
///
326+
/// In multi-threaded programs on other operating systems, we strongly suggest
327+
/// not using `set_var` or `remove_var` at all. The exact requirement is: you
328+
/// must ensure that there are no other threads concurrently writing or
329+
/// *reading*(!) the environment through functions or global variables other
330+
/// than the ones in this module. The problem is that these operating systems
331+
/// do not provide a thread-safe way to read the environment, and most C
332+
/// libraries, including libc itself, do not advertise which functions read
333+
/// from the environment. Even functions from the Rust standard library may
334+
/// read the environment without going through this module, e.g. for DNS
335+
/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about
336+
/// which functions may read from the environment in future versions of a
337+
/// library. All this makes it not practically possible for you to guarantee
338+
/// that no other thread will read the environment, so the only safe option is
339+
/// to not use `set_var` or `remove_var` in multi-threaded programs at all.
337340
///
338341
/// Discussion of this unsafety on Unix may be found in:
339342
///
@@ -353,15 +356,26 @@ impl Error for VarError {
353356
/// use std::env;
354357
///
355358
/// let key = "KEY";
356-
/// env::set_var(key, "VALUE");
359+
/// unsafe {
360+
/// env::set_var(key, "VALUE");
361+
/// }
357362
/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
358363
/// ```
364+
#[cfg(not(bootstrap))]
365+
#[rustc_deprecated_safe_2024]
359366
#[stable(feature = "env", since = "1.0.0")]
360-
pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
367+
pub unsafe fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
361368
_set_var(key.as_ref(), value.as_ref())
362369
}
363370

364-
fn _set_var(key: &OsStr, value: &OsStr) {
371+
#[cfg(bootstrap)]
372+
#[allow(missing_docs)]
373+
#[stable(feature = "env", since = "1.0.0")]
374+
pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
375+
unsafe { _set_var(key.as_ref(), value.as_ref()) }
376+
}
377+
378+
unsafe fn _set_var(key: &OsStr, value: &OsStr) {
365379
os_imp::setenv(key, value).unwrap_or_else(|e| {
366380
panic!("failed to set environment variable `{key:?}` to `{value:?}`: {e}")
367381
})
@@ -371,22 +385,25 @@ fn _set_var(key: &OsStr, value: &OsStr) {
371385
///
372386
/// # Safety
373387
///
374-
/// Even though this function is currently not marked as `unsafe`, it needs to
375-
/// be because invoking it can cause undefined behaviour. The function will be
376-
/// marked `unsafe` in a future version of Rust. This is tracked in
377-
/// [rust#27970](https://github.com/rust-lang/rust/issues/27970).
378-
///
379388
/// This function is safe to call in a single-threaded program.
380389
///
381-
/// In multi-threaded programs, you must ensure that are no other threads
382-
/// concurrently writing or *reading*(!) from the environment through functions
383-
/// other than the ones in this module. You are responsible for figuring out
384-
/// how to achieve this, but we strongly suggest not using `set_var` or
385-
/// `remove_var` in multi-threaded programs at all.
386-
///
387-
/// Most C libraries, including libc itself do not advertise which functions
388-
/// read from the environment. Even functions from the Rust standard library do
389-
/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`].
390+
/// This function is also always safe to call on Windows, in single-threaded
391+
/// and multi-threaded programs.
392+
///
393+
/// In multi-threaded programs on other operating systems, we strongly suggest
394+
/// not using `set_var` or `remove_var` at all. The exact requirement is: you
395+
/// must ensure that there are no other threads concurrently writing or
396+
/// *reading*(!) the environment through functions or global variables other
397+
/// than the ones in this module. The problem is that these operating systems
398+
/// do not provide a thread-safe way to read the environment, and most C
399+
/// libraries, including libc itself, do not advertise which functions read
400+
/// from the environment. Even functions from the Rust standard library may
401+
/// read the environment without going through this module, e.g. for DNS
402+
/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about
403+
/// which functions may read from the environment in future versions of a
404+
/// library. All this makes it not practically possible for you to guarantee
405+
/// that no other thread will read the environment, so the only safe option is
406+
/// to not use `set_var` or `remove_var` in multi-threaded programs at all.
390407
///
391408
/// Discussion of this unsafety on Unix may be found in:
392409
///
@@ -403,22 +420,35 @@ fn _set_var(key: &OsStr, value: &OsStr) {
403420
///
404421
/// # Examples
405422
///
406-
/// ```
423+
/// ```no_run
407424
/// use std::env;
408425
///
409426
/// let key = "KEY";
410-
/// env::set_var(key, "VALUE");
427+
/// unsafe {
428+
/// env::set_var(key, "VALUE");
429+
/// }
411430
/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
412431
///
413-
/// env::remove_var(key);
432+
/// unsafe {
433+
/// env::remove_var(key);
434+
/// }
414435
/// assert!(env::var(key).is_err());
415436
/// ```
437+
#[cfg(not(bootstrap))]
438+
#[rustc_deprecated_safe_2024]
416439
#[stable(feature = "env", since = "1.0.0")]
417-
pub fn remove_var<K: AsRef<OsStr>>(key: K) {
440+
pub unsafe fn remove_var<K: AsRef<OsStr>>(key: K) {
418441
_remove_var(key.as_ref())
419442
}
420443

421-
fn _remove_var(key: &OsStr) {
444+
#[cfg(bootstrap)]
445+
#[allow(missing_docs)]
446+
#[stable(feature = "env", since = "1.0.0")]
447+
pub fn remove_var<K: AsRef<OsStr>>(key: K) {
448+
unsafe { _remove_var(key.as_ref()) }
449+
}
450+
451+
unsafe fn _remove_var(key: &OsStr) {
422452
os_imp::unsetenv(key)
423453
.unwrap_or_else(|e| panic!("failed to remove environment variable `{key:?}`: {e}"))
424454
}

library/std/src/sys/pal/hermit/os.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,14 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
172172
unsafe { ENV.as_ref().unwrap().lock().unwrap().get_mut(k).cloned() }
173173
}
174174

175-
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
176-
unsafe {
177-
let (k, v) = (k.to_owned(), v.to_owned());
178-
ENV.as_ref().unwrap().lock().unwrap().insert(k, v);
179-
}
175+
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
176+
let (k, v) = (k.to_owned(), v.to_owned());
177+
ENV.as_ref().unwrap().lock().unwrap().insert(k, v);
180178
Ok(())
181179
}
182180

183-
pub fn unsetenv(k: &OsStr) -> io::Result<()> {
184-
unsafe {
185-
ENV.as_ref().unwrap().lock().unwrap().remove(k);
186-
}
181+
pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> {
182+
ENV.as_ref().unwrap().lock().unwrap().remove(k);
187183
Ok(())
188184
}
189185

library/std/src/sys/pal/sgx/os.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
157157
get_env_store().and_then(|s| s.lock().unwrap().get(k).cloned())
158158
}
159159

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

166-
pub fn unsetenv(k: &OsStr) -> io::Result<()> {
166+
pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> {
167167
if let Some(env) = get_env_store() {
168168
env.lock().unwrap().remove(k);
169169
}

library/std/src/sys/pal/solid/os.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
191191
.flatten()
192192
}
193193

194-
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
194+
pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
195195
run_with_cstr(k.as_bytes(), &|k| {
196196
run_with_cstr(v.as_bytes(), &|v| {
197197
let _guard = ENV_LOCK.write();
@@ -200,7 +200,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
200200
})
201201
}
202202

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

library/std/src/sys/pal/teeos/os.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ pub fn getenv(_: &OsStr) -> Option<OsString> {
109109
None
110110
}
111111

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

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

0 commit comments

Comments
 (0)