Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unsafe attributes #3325

Merged
merged 7 commits into from
May 6, 2024
Merged

unsafe attributes #3325

merged 7 commits into from
May 6, 2024

Conversation

RalfJung
Copy link
Member

@RalfJung RalfJung commented Oct 11, 2022

Consider some attributes 'unsafe', so that they must only be used like this:

#[unsafe(no_mangle)]

Rendered
Prior discussion on IRLO

@RalfJung RalfJung changed the title add unsafe attributes RFC unsafe attributes Oct 11, 2022
Copy link

@ojeda ojeda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please! :)

text/0000-unsafe-attributes.md Outdated Show resolved Hide resolved
text/0000-unsafe-attributes.md Outdated Show resolved Hide resolved
text/0000-unsafe-attributes.md Outdated Show resolved Hide resolved
text/0000-unsafe-attributes.md Outdated Show resolved Hide resolved
@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Oct 11, 2022
- **Unsafe attributes on statements.** For now, the only unsafe attributes we
have don't make sense on the statement level. Once we do have unsafe statement
attributes, we need to figure out whether inside `unsafe {}` blocks one still
needs to also write `unsafe(...)`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have to settle this. You can put declarations inside a block within a function, including AFAIK an unsafe block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fn inside of an unsafe block inside of another fn doesn't "inherit" the unsafe block over its body, so it would be most logical for it to also not inherit the unsafe block over its attributes.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=59b2b073cba046f252f19218933b0d21

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will add a note on that.

`unsafe { ... }`.)
- **Unsafe derive.** We could use `#[unsafe(derive(Trait))]` to derive an
`unsafe impl` where the deriving macro itself cannot check all required safety
conditions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to bikeshed something that doesn't need to be in this RFC. But I do think this is the wrong syntax. unsafe used like that should be "unsafe to derive", not "derives an unsafe trait".

Copy link
Member Author

@RalfJung RalfJung Oct 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the intention is that this is an example for "unsafe to derive". Note that the text says "the deriving macro itself cannot check all required safety conditions".

@Lokathor
Copy link
Contributor

I don't think that this RFC offers much for library developers.

Normally, when you write a library and use an unsafe block, you can know for sure (because of a runtime check or because of a type system trick, or for some other reason) that your unsafe operation is justified at that point in the code. That is actually why you write the unsafe block. If you can't be sure that everything is alright, the you do not write that unsafe block. Perhaps you make the entire function an unsafe fn and pass the obligation on to the caller. It doesn't matter. What matters is that you do not use unsafe blocks unless you have the situational knowledge to justify why the contents of the block won't cause UB in this situation. I think we all agree on this position.

However, say that a library needs to export a function under a particular name. The library author needs to use either no_mangle or export_name. These are unsafe attributes. The unsafety here is that if a given symbol is defined more than once among all the object files being linked together to form an executable, it's UB. But the library author does not control what object files get linked into the final executable, and so they absolutely should not write "SAFETY: there is no other global function of this name" next to #[unsafe(no_mangle)]. That is not a promise that the library author can keep.

The same goes for link_section. The library author does not decide on the linker script used to generate the executable, so they can't make a promise about the validity of using a special link section on a particular fn or static.

If you write an unsafe block without the proper pre-conditions, the code is considered to be unsound code. "please don't publish that on crates.io", and so on. This RFC forces library authors to write unsafe(...) around attribute usage that they are incapable of validating the safety pre-conditions for.

The RFC makes unsafe attribute usage more "grep-able", but not actually less unsafe or less error prone. This doesn't give a library author the ability to communicate to a user, "I'm doing this unusual thing, here's what you need to do on your end to make sure things work out". The RFC doesn't prevent UB at all. It just follows the absolute letter of the rust law that says "if there's UB then someone must have used unsafe", and now we can point to the literal ascii string having appeared in some file somewhere.

@afetisov
Copy link

"More greppable" is largely the point. While the specific attributes (no_mangle, link_section, link, link_name) may be grepped for manually, It also provides a general mechanism to communicate unsafety of arbitrary other attributes (including proc macros, custom attributes and external tool attributes). It's also much easier to grep for unsafe than to remember the full list of all attributes with unsafe behaviour. It also adds the concept of unsafety for attributes, which allows tools to depend on it (e.g. forbid usage of these attributes in the #![forbid(unsafe_code)] lint, or propagate unsafety through macros).

With regards to usage by library authors, you should think about it like unsafe fn rather than unsafe { } block. I.e. you, as the author, specify preconditions which all users must satisfy, even though you can't enforce them yourself. It's the same situation as an extern fn: you provide an unsafe API, but you can't know or make any guarantees about its consumers.

But I think @Lokathor 's objection can be rephrased in the following way: with unsafe fn, there are really two concepts at play. The function may be unsafe to use, enforcing preconditions on its consumers, or unsafe to implement, requiring extra care from its author and allowing the usage of unsafe calls inside. Many people think that it was a mistake to merge those two concepts into a single unsafe fn. We have the same issue with unsafe attributes: do they mean that the API consumers must uphold extra invariants, or that the function author promises that some invariants are satisfied? With #[unsafe(no_mangle)] (and all other proposed existing unsafe attributes) it's the former. With #[derive(unsafe(DangerousTrait))] it's likely the latter.

@Lokathor
Copy link
Contributor

I absolutely would not say that about unsafe fn, I think it is factually wrong.

@Nemo157
Copy link
Member

Nemo157 commented Oct 15, 2022

It sounds like the missing step there is the ability to mark your crate as unsafe

/// SAFETY: We require users of this crate to promise there is no other global `write` symbol
#[unsafe(no_mangle)]
pub fn write() {}

This provides something similar to item-level unsafe {} blocks, but we don't have a corresponding item-level unsafe fn declaration to wrap them.

@Nemo157
Copy link
Member

Nemo157 commented Oct 15, 2022

We have the same issue with unsafe attributes: do they mean that the API consumers must uphold extra invariants, or that the function author promises that some invariants are satisfied? With #[unsafe(no_mangle)] (and all other proposed existing unsafe attributes) it's the former. With #[derive(unsafe(DangerousTrait))] it's likely the latter.

Both of these examples are the latter. You are discharging the unsafety of the attribute or the derive. There is no way existing or with this RFC to do the former.

@Lokathor
Copy link
Contributor

Lokathor commented Oct 15, 2022

The other thing is that if the compiler handled it then we could make no_mangle a safe attribute, but we're just not doing that.

@comex
Copy link

comex commented Oct 16, 2022

The other thing is that if the compiler handled it then we could make no_mangle a safe attribute, but we're just not doing that.

That’s quite impossible if you’re statically linking any C code or other precompiled objects into the executable, since the compiler doesn’t know what names that code might define or depend on. And on some platforms (Linux), symbol definitions can leak even across dynamically linked libraries.

@Lokathor
Copy link
Contributor

Well the compiler could scan at least all objects it knows go into the linking, even if they're written in non-rust.

But if symbols can leak in even after the initial linking then not only is there no hope to make no_mangle fully safe, but there's clearly also no hope you could ever discharge your safety obligation within a library. So I think that supports my point that just putting the string "unsafe" next to no_mangle is not itself improving things by much.

Idea for a separate RFC: Perhaps cargo could generate a list of all the unmangled symbols all your deps are bringing in so that you can check the list?

@RalfJung
Copy link
Member Author

However, say that a library needs to export a function under a particular name. The library author needs to use either no_mangle or export_name. These are unsafe attributes. The unsafety here is that if a given symbol is defined more than once among all the object files being linked together to form an executable, it's UB. But the library author does not control what object files get linked into the final executable, and so they absolutely should not write "SAFETY: there is no other global function of this name" next to #[unsafe(no_mangle)]. That is not a promise that the library author can keep.

That is just fundamental in the nature of these attributes -- they can only be checked on the whole-program "post-linking" level. There is no way to soundly use any of them in a library. The RFC doesn't change that, it just makes this existing fact more clear. I have amended the text to clarify that.

@ojeda
Copy link

ojeda commented Oct 22, 2022

Well the compiler could scan at least all objects it knows go into the linking, even if they're written in non-rust.

It would not cover all cases, so you would still need to mark them as unsafe.

I think such a scan would be orthogonal to the RFC: it could be performed (or not) regardless of whether we get unsafe attributes.

no hope to make no_mangle fully safe [...] putting the string "unsafe" next to no_mangle is not itself improving things by much.

Yeah, but since there is no way to make it happen in the general case, at least this one should be marked as such.

Of course, that doesn't mean you cannot add new, safe attributes in the future for similar use cases.

Perhaps cargo could generate a list of all the unmangled symbols all your deps are bringing in so that you can check the list?

The generated docs of rustdoc could be another good place to put them.

@joshtriplett
Copy link
Member

@rfcbot merge

@rfcbot
Copy link
Collaborator

rfcbot commented Jan 19, 2023

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Jan 19, 2023
@tmandry
Copy link
Member

tmandry commented Jan 26, 2023

@rfcbot reviewed

However that could make it tricky to consistently support non-builtin unsafe attributes in the future, so the RFC proposes to not do that yet.

Not a blocking concern since this is forward-compatible, but I don't see how supporting safe attributes makes it difficult to support non-builtin unsafe attributes in the future. I suppose nesting could be problematic if we support nesting under arbitrary unknown attributes.

@RalfJung
Copy link
Member Author

I really don't know what is hard or easy with proc macro attributes and things like that, so after the first version of this PR (which was very liberal with where unsafe could be used) got feedback pointing out that could be tricky, I made it super restrictive.

- **Other syntax.** Obviously we could pick a different syntax for the same
concept, but this seems like the most natural marriage of the idea of unsafe
blocks from regular code, and the existing attributes syntax.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pondering: this leans more into the use of unsafe as the keyword to discharge the safety obligation.

Would this be a place to add the hold_my_beer (not under that name) term that is for discarding specifically, to leave unsafe as the marker for introducing obligations?

Or would it be too weird to have the different word here but not in the other places?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or would it be too weird to have the different word here but not in the other places?

Yeah that feels pretty weird to me. If we want to use another keyword for this use of unsafe we should do it consistently.

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Feb 21, 2023
@rfcbot
Copy link
Collaborator

rfcbot commented Feb 21, 2023

🔔 This is now entering its final comment period, as per the review above. 🔔

@pnkfelix
Copy link
Member

@rfcbot concern maybe-make-this-part-of-next-edition

During T-lang triage, I posed the question of whether we should not push to land this change across all Rust editions, and instead have it only become a change on e.g. Rust 2024.

If we do want this to be coupled to the edition boundary, then it would be good for the RFC to spell out how it is coupled to the edition. (E.g. maybe it is always warn-by-default for Rust editions < 2024, but is a hard error to leave out the unsafe-discharge for edition >= 2024.)

@RalfJung
Copy link
Member Author

RalfJung commented Mar 4, 2024

Since this RFC has reached the "syntax" stage of discussion, maybe someone else should take over -- I don't have the patience required for these discussions.

Once there is a consensus, I'm happy to edit the RFC. I don't care what the syntax is. I'm just convinced we need this concept of unsafe attributes.

@tgross35
Copy link
Contributor

tgross35 commented Mar 4, 2024

For lack of any better idea, I created a discussion with poll to try to estimate the general feel. https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Unsafe.20attribute.20syntax/near/424767855

(personally I still feel there is little convincing reason to differ from the parentheses syntax currently in this RFC - it is consistent with cfg_attr(...), feature(...) and seemingly almost every popular ecosystem crate)

@RalfJung
Copy link
Member Author

RalfJung commented Mar 5, 2024

(personally I still feel there is convincing reason to be inconsistent with what this RFC has - it is consistent with cfg_attr(...), feature(...) and seemingly almost every popular ecosystem crate)

I am confused now, you think it should use unsafe(...) like it does now, which seems consistent with existing attributes, or something else?

@tgross35
Copy link
Contributor

tgross35 commented Mar 5, 2024

(personally I still feel there is convincing reason to be inconsistent with what this RFC has - it is consistent with cfg_attr(...), feature(...) and seemingly almost every popular ecosystem crate)

I am confused now, you think it should use unsafe(...) like it does now, which seems consistent with existing attributes, or something else?

Whoops, I missed a very important "feel there is little convincing reason" keyword 🙂

@kpreid
Copy link

kpreid commented Mar 5, 2024

Here is a technical question that might give a reason beyond subjective readability to pick a syntax: Is unsafe itself an attribute that can be shadowed? (For example, the built-in attribute derive can, which is used in macro_rules_attribute to useful effect).

  • If it cannot be shadowed, then perhaps it should have a different syntax that falls outside of normal attribute() and attribute =, to highlight that it is not just an attribute.
  • If it can be shadowed, then it's weird that we're enabling "shadowing a keyword" which is normally impossible, but maybe that's valuable regularity, in which case I think the syntax should be regular too.

@tgross35
Copy link
Contributor

tgross35 commented Mar 7, 2024

For lack of any better idea, I created a discussion with poll to try to estimate the general feel. https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Unsafe.20attribute.20syntax/near/424767855

Well, currently parentheses has a pretty big lead 23-3-3. I don't think anyone likes making decisions by poll, but that does seem to be a reasonable enough level of consensus to pass this on to the lang team, barring any technical/grammar issues that have been brought up.

Here is a technical question that might give a reason beyond subjective readability to pick a syntax: Is unsafe itself an attribute that can be shadowed? (For example, the built-in attribute derive can, which is used in macro_rules_attribute to useful effect).

* If it cannot be shadowed, then perhaps it should have a different syntax that falls outside of normal `attribute()` and `attribute =`, to highlight that it is not _just an attribute_.

* If it can be shadowed, then it's weird that we're enabling "shadowing a keyword" which is normally impossible, but maybe that's valuable regularity, in which case I think the syntax should be regular too.

Great concern - but to create an attribute macro you would need to create a function of the same name, which you cannot do. I guess that is just keyword vs. more or less a builtin function with name scope.

extern crate proc_macro;
use proc_macro::TokenStream;


#[proc_macro]
pub fn unsafe(_: TokenStream) -> TokenStream { todo!() }

mod unsafe {
    macro_rules! unsafe { ()=>() }
}
$ rustc foo.rs --crate-type=proc-macro
error: expected identifier, found keyword `unsafe`
 --> foo.rs:6:8
  |
6 | pub fn unsafe(_: TokenStream) -> TokenStream { todo!() }
  |        ^^^^^^ expected identifier, found keyword

error: expected identifier, found keyword `unsafe`
 --> foo.rs:8:5
  |
8 | mod unsafe {
  |     ^^^^^^ expected identifier, found keyword

error: expected identifier, found keyword `unsafe`
 --> foo.rs:9:18
  |
9 |     macro_rules! unsafe { ()=>() }
  |                  ^^^^^^ expected identifier, found keyword

@Jules-Bertholet
Copy link
Contributor

Great concern - but to create an attribute macro you would need to create a function of the same name, which you cannot do.

Technically, you can use the r#unsafe syntax, but the attribute then requires r#unsafe as well.

@joshtriplett
Copy link
Member

joshtriplett commented Mar 25, 2024

Given all the support the parenthesis option has, and the opposition to some of the other options, and the impending edition cutoff, I'm going to resolve this concern. I no longer think we should block on this. If another team member would like to raise a blocking concern, please go ahead.

@rfcbot resolve syntax-not-ideal

@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Mar 25, 2024
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 25, 2024

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Mar 25, 2024
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. to-announce and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Apr 4, 2024
@rfcbot
Copy link
Collaborator

rfcbot commented Apr 4, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@RalfJung
Copy link
Member Author

RalfJung commented May 2, 2024

I have updated the filename to match the RFC number.
@rust-lang/lang I think merging the PR is usually done by someone on the team?

@traviscross traviscross removed the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label May 6, 2024
The FCP has completed for RFC 3325.  Let's prepare it to be merged.
@traviscross traviscross merged commit 55a275c into rust-lang:master May 6, 2024
@traviscross
Copy link
Contributor

The lang team has accepted this RFC, and we've now merged it.

Thanks to @RalfJung for pushing this work forward, and thanks to all those who reviewed this RFC and contributed helpful feedback.

For further updates on this work, follow the tracking issue:

@Nemo157 Nemo157 mentioned this pull request May 7, 2024
@RalfJung RalfJung deleted the unsafe-attributes branch May 29, 2024 16:50
tgross35 added a commit to tgross35/rust that referenced this pull request Aug 17, 2024
…r, r=nnethercote

Stabilize `unsafe_attributes`

# Stabilization report

## Summary

This is a tracking issue for the RFC 3325: unsafe attributes

We are stabilizing `#![feature(unsafe_attributes)]`,  which makes certain attributes considered 'unsafe', meaning that they must be surrounded by an `unsafe(...)`, as in `#[unsafe(no_mangle)]`.

RFC: rust-lang/rfcs#3325
Tracking issue: rust-lang#123757

## What is stabilized

### Summary of stabilization

Certain attributes will now be designated as unsafe attributes, namely, `no_mangle`, `export_name`, and `link_section` (stable only), and these attributes will need to be called by surrounding them in `unsafe(...)` syntax. On editions prior to 2024, this is simply an edition lint, but it will become a hard error in 2024. This also works in `cfg_attr`, but `unsafe` is not allowed for any other attributes, including proc-macros ones.

```rust
#[unsafe(no_mangle)]
fn a() {}

#[cfg_attr(any(), unsafe(export_name = "c"))]
fn b() {}
```

For a table showing the attributes that were considered to be included in the list to require unsafe, and subsequent reasoning about why each such attribute was or was not included, see [this comment here](rust-lang#124214 (comment))

## Tests

The relevant tests are in `tests/ui/rust-2024/unsafe-attributes` and `tests/ui/attributes/unsafe`.
bors added a commit to rust-lang-ci/rust that referenced this pull request Aug 17, 2024
… r=nnethercote

Stabilize `unsafe_attributes`

# Stabilization report

## Summary

This is a tracking issue for the RFC 3325: unsafe attributes

We are stabilizing `#![feature(unsafe_attributes)]`,  which makes certain attributes considered 'unsafe', meaning that they must be surrounded by an `unsafe(...)`, as in `#[unsafe(no_mangle)]`.

RFC: rust-lang/rfcs#3325
Tracking issue: rust-lang#123757

## What is stabilized

### Summary of stabilization

Certain attributes will now be designated as unsafe attributes, namely, `no_mangle`, `export_name`, and `link_section` (stable only), and these attributes will need to be called by surrounding them in `unsafe(...)` syntax. On editions prior to 2024, this is simply an edition lint, but it will become a hard error in 2024. This also works in `cfg_attr`, but `unsafe` is not allowed for any other attributes, including proc-macros ones.

```rust
#[unsafe(no_mangle)]
fn a() {}

#[cfg_attr(any(), unsafe(export_name = "c"))]
fn b() {}
```

For a table showing the attributes that were considered to be included in the list to require unsafe, and subsequent reasoning about why each such attribute was or was not included, see [this comment here](rust-lang#124214 (comment))

## Tests

The relevant tests are in `tests/ui/rust-2024/unsafe-attributes` and `tests/ui/attributes/unsafe`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC. to-announce
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet