Add #[deprecated_safe] attribute to allow functions be be marked unsafe in a backwards compatible fashion #147
Labels
final-comment-period
The FCP has started, most (if not all) team members are in agreement
major-change
Major change proposal
T-lang
to-announce
Not yet announced MCP proposals
Proposal
Summary and problem statement
Create a new #[deprecated_safe] attribute allowing a function to be marked unsafe in a backwards compatible fashion.
Motivation, use-cases, and solution sketches
This is being proposed specifically to resolve the env::set_var/remove_var unsoundness. The same issue has previously occurred with CommandExt::before_exec, and this fix could retroactively be applied there as well.
The new attribute would eliminate the need to deprecate an unsound function and create an identical duplicate, other than having a different name and with unsafe added. The unsound function would instead be marked unsafe as it originally should have been, with #[deprecated_safe] added to indicate that its "safeness" was deprecated. This avoids the need to come up with a "good" alternate name, which may not always be available. It's also useful to avoid duplicates of set_var/remove_var in case these are ever able to be made safe again in the future.
#[deprecated_safe] would act as an escape hatch anywhere that existing code would error due to the unsafe being added, and would instead issue a "safeness deprecated" lint. The affected function would have the unsafe modifier added and behave just like any other unsafe function, aside from this escape hatch behavior. Applying the attribute to a function that isn't marked unsafe should not be allowed.
The signature of e.g.
set_var
would thus change like so:to:
The attribute could potentially accept an edition specifier, after which the lint would become an error/deny-by-default. The attribute could potentially be made applicable to other places like traits as well, if the need arises in the future. This is out of scope for this MCP, however, as it is only intended to be applied to functions.
Note: Are edition changes still required to always be auto-fixable? If the attribute supports specifying an edition after which the missing unsafe becomes an error, I don't see how we could ever wrap existing code in an unsafe block automatically. Personally I think requiring a manual fix is fine since we are talking about unsoundness.
This attribute could be made available for use for by all libraries as well, however the MCP only focuses on what's needed for set_var/remove_var. Because the attribute would initially be unstable, it could be used internally by libstd to fix set_var/remove_var before addressing any broader questions that would need to be answered before making this a publicly available stable attribute.
Note: Discussion on the zulip stream seems potentially amenable to putting this on a route to stability, if accepted. If that's the case it would make sense to include things like
trait Bla {}
becoming#[deprecated_safe] unsafe trait Bla {}
in the design up front, instead of as a future possibility. Whether this attribute is useful outside of libstd (since libraries can making breaking changes), is questionable however.The following places would break when adding unsafe after the fact to a function, but would instead lint with #[deprecated_safe] applied.
Note: This list may be incomplete, these are just the places I currently know about.
Example output
The lint messages here are obviously very TODO, but this is an example of what the output might look like. All of these examples would previously have been errors when adding unsafe to set_var, before the use of the new attribute.
Proof-of-concept exploratory changes
I have explored making these known places work in a proof-of-concept fashion here: https://github.com/skippy10110/rust/commits/rustc_deprecated_safe
However, I'm unfamiliar with rustc and this is my first contribution, so the particular approaches taken may not be correct.
Call sites
This is a fairly straightforward change to the MIR unsafety checker in check_unsafety.rs. Modelled after the UNSAFE_OP_DEPRECATED_SAFE lint, a new UnsafetyViolationKind is added for DeprecatedSafe. The main change is the following to visit_terminator:
Note that the new THIR unsafety checker will also need to be updated. But this should be fairly straightforward.
Function pointer coercions
In coercion.rs the change is modelled after the existing code to coerce a "fn" pointer to an "unsafe fn" pointer. A new PointerCast::DeprecatedSafeFnPointer is added, the opposite of PointerCast::UnsafeFnPointer, and then coerce_from_safe_fn is modified to take advantage of this when the deprecated_safe attribute is present:
Fn trait family coercions
These are the proof-of-concept changes I'm the least certain about, by far. In candidate_assembly.rs, assemble_closure_candidates is modified to add the escape hatch here:
The
if !obligation.cause.span.is_dummy()
strikes me as especially suspicious, see the comment in the code above. This lint will also only be emitted at the first place the coercion is needed, not subsequent places. E.g. this will only output a single lint:However, this is fixed by modifying candidate_from_obligation to also emit the lint when the cached candidate is used. This strikes me as even more suspicious:
Also, just going by the name "candidate" in candidate_from_obligation alone, I wonder if these candidates might not even get used in certain scenarios and thus a false positive lint would be emitted.
Links and related work
Main IRLO discussion thread around the env unsoundness: https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475
My initial idea for something like #[deprecated_safe]: https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475/56
jhpratt describes the idea in more technical detail: https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475/58
Future deprecation of env::{set, remove}_var: rust-lang/rust#92365
Alternative to this MCP, Introduce unsafe methods for mutating environment: rust-lang/rust#94619
Prior occurrence of this same issue, std::os::unix::process::CommandExt::before_exec should be unsafe: rust-lang/rust#39575
Discussion thread on Zulip where I was working through implementing a proof-of-concept: https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/deprecated_safe.20attribute
Initial people involved
What happens now?
This issue is part of the lang-team initiative process. Once this issue is filed, a Zulip topic will be opened for discussion, and the lang-team will review open proposals in its weekly triage meetings. You should receive feedback within a week or two.
This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.
The text was updated successfully, but these errors were encountered: