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

Lint against &T to &mut T and &T to &UnsafeCell<T> transmutes #128351

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ChayimFriedman2
Copy link
Contributor

@ChayimFriedman2 ChayimFriedman2 commented Jul 29, 2024

Conversion from & to &mut are and always were immediate UB, and we already lint against them, but until now the lint did not catch the case were the reference was in a field.

Conversion from & to &UnsafeCell is more nuanced: Stacked Borrows makes it immediate UB, but in Tree Borrows it is sound.

However, even in Tree Borrows it is UB to write into that reference (if the original value was Freeze). In all cases crater found where the lint triggered, the reference was written into.

Lints (mutable_transmutes existed before):

The mutable_transmutes lint catches transmuting from &T to &mut T because it is undefined behavior.

Example

unsafe {
    let y = std::mem::transmute::<&i32, &mut i32>(&5);
}

Produces:

error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
 --> .\example.rs:5:17
  |
5 |         let y = std::mem::transmute::<&i32, &mut i32>(&5);
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: transmute from `&i32` to `&mut i32`
  = note: `#[deny(mutable_transmutes)]` on by default

Explanation

Certain assumptions are made about aliasing of data, and this transmute
violates those assumptions. Consider using UnsafeCell instead.


The unsafe_cell_transmutes lint catches transmuting or casting from &T to &UnsafeCell<T>
because it dangerous and might be undefined behavior.

Example

use std::cell::Cell;

unsafe {
    let x = 5_i32;
    let y = std::mem::transmute::<&i32, &Cell<i32>>(&x);
    y.set(6);

    let z = &*(&x as *const i32 as *const Cell<i32>);
    z.set(7);
}

Produces:

error: transmuting &T to &UnsafeCell<T> is error-prone, rarely intentional and may cause undefined behavior
 --> .\example.rs:6:17
  |
6 |         let y = std::mem::transmute::<&i32, &Cell<i32>>(&x);
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: transmute from `*&i32` to `(*&Cell<i32>).value`
  = note: `#[deny(unsafe_cell_transmutes)]` on by default

error: transmuting &T to &UnsafeCell<T> is error-prone, rarely intentional and may cause undefined behavior
 --> .\example.rs:9:17
  |
9 |         let z = &*(&x as *const i32 as *const Cell<i32>);
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: transmute from `i32` to `Cell<i32>.value`

Explanation

Conversion from &T to &UnsafeCell<T> might be immediate undefined behavior, depending on
unspecified details of the aliasing model.

Even if it is not, writing to it will be undefined behavior if there was no UnsafeCell in
the original T, and even if there was, it might be undefined behavior (again, depending
on unspecified details of the aliasing model).

It is also highly dangerous and error-prone, and unlikely to be useful.

Crater summary is below.

cc #111229

r? compiler

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 29, 2024
@rustbot
Copy link
Collaborator

rustbot commented Jul 29, 2024

The Miri subtree was changed

cc @rust-lang/miri

@oli-obk

This comment was marked as resolved.

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from f27dba0 to d20e89c Compare July 29, 2024 15:58
@oli-obk
Copy link
Contributor

oli-obk commented Jul 29, 2024

@bors try

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from d20e89c to 5c1d66e Compare July 29, 2024 16:01
bors added a commit to rust-lang-ci/rust that referenced this pull request Jul 29, 2024
@bors
Copy link
Contributor

bors commented Jul 29, 2024

⌛ Testing commit 5c1d66e with merge 94971c2...

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Contributor

bors commented Jul 29, 2024

💔 Test failed - checks-actions

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from 5c1d66e to b62dc4c Compare July 29, 2024 21:06
@rust-log-analyzer

This comment has been minimized.

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from b62dc4c to 59c22c3 Compare July 29, 2024 21:44
@oli-obk
Copy link
Contributor

oli-obk commented Jul 29, 2024

@bors try

bors added a commit to rust-lang-ci/rust that referenced this pull request Jul 29, 2024
…e-cell, r=<try>

[crater] Lint against &T to &mut T and &T to &UnsafeCell<T> transmutes

Needs a (check-only) crater run as per https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Lint.20against.20.60.26.60-.3E.60.26UnsafeCell.60.20transmutes/near/454868964.

r? ghost
@bors
Copy link
Contributor

bors commented Jul 29, 2024

⌛ Trying commit 59c22c3 with merge be92fb7...

@rust-log-analyzer

This comment has been minimized.

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from 59c22c3 to cc72a92 Compare July 29, 2024 23:10
@ChayimFriedman2

This comment was marked as resolved.

@tgross35
Copy link
Contributor

@bors try

bors added a commit to rust-lang-ci/rust that referenced this pull request Jul 30, 2024
…e-cell, r=<try>

[crater] Lint against &T to &mut T and &T to &UnsafeCell<T> transmutes

Needs a (check-only) crater run as per https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Lint.20against.20.60.26.60-.3E.60.26UnsafeCell.60.20transmutes/near/454868964.

r? ghost
@bors
Copy link
Contributor

bors commented Jul 30, 2024

⌛ Trying commit cc72a92 with merge 0914651...

@bors
Copy link
Contributor

bors commented Jul 30, 2024

☀️ Try build successful - checks-actions
Build commit: 0914651 (09146510cdb33d15474a35fde290187a88b1cf35)

@oli-obk
Copy link
Contributor

oli-obk commented Jul 30, 2024

@craterbot check

@craterbot
Copy link
Collaborator

👌 Experiment pr-128351 created and queued.
🤖 Automatically detected try build 0914651
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-crater Status: Waiting on a crater run to be completed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 30, 2024
@craterbot
Copy link
Collaborator

🚧 Experiment pr-128351 is now running

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member

RalfJung commented Oct 17, 2024 via email

@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from 8c137f7 to 573ee3f Compare October 17, 2024 19:00
@tmandry
Copy link
Member

tmandry commented Oct 24, 2024

@rfcbot reviewed

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Oct 26, 2024
@rfcbot
Copy link

rfcbot commented Oct 26, 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.

@ChayimFriedman2
Copy link
Contributor Author

ChayimFriedman2 commented Dec 7, 2024

Since we haven't heard from the reviewer...

r? compiler

@rust-log-analyzer

This comment has been minimized.

This adds the lint against `&T`->`&UnsafeCell<T>` transmutes, and also check in struct fields, and reference casts (`&*(&a as *const u8 as *const UnsafeCell<u8>)`).

The code is quite complex; I've tried my best to simplify and comment it.

This is missing one parts: array transmutes. When transmuting an array, this only consider the first element. The reason for that is that the code is already quite complex, and I didn't want to complicate it more.
This catches the most common pattern of transmuting an array into an array of the same length with type of the same size; more complex cases are likely not properly handled.
We could take a bigger sample, for example the first and last elements to increase the chance that the lint will catch mistakes, but then the runtime complexity becomes exponential with the nesting of the arrays (`[[[[[T; 2]; 2]; 2]; 2]; 2]` has complexity of O(2**5), for instance).
@ChayimFriedman2 ChayimFriedman2 force-pushed the lint-transmute-unsafe-cell branch from 573ee3f to 2ea0578 Compare December 7, 2024 21:17
@nnethercote
Copy link
Contributor

@oli-obk: would you be willing to review? This is covering lots of territory I don't know well, and I imagine there are subtleties that I could easily overlook.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 9, 2024

😭 that's a lot of new code to review

@oli-obk oli-obk self-assigned this Dec 9, 2024
Copy link
Contributor

@nnethercote nnethercote left a comment

Choose a reason for hiding this comment

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

This feels like it's heading in the right direction.

My main observation is about terminology. To me:

  • "casting" is a specific thing, done via as or cast
  • "transmuting" is a different specific thing, done only via mem::transmute
  • "converting" is an imprecise term that possibly covers both "casting" and "transmuting".

Is that correct? It aligns with the Type Conversions chapter of the Rustonomicon. But this PR tends to use "cast" and "transmute" imprecisely and somewhat interchangeably.

  • I think mutable_transmutes only triggers on mem::transmute, but unsafe_cell_transmutes triggers on both mem::transmute and casting.
  • The crater result mentions unsafe_cell_reference_casting. Is that an old name for unsafe_cell_transmutes?
  • The error message only mention transmuting.
  • The lint descriptions (in doc comments) mention both transmuting and casting in a way that matches my understanding.
  • is_type_cast seems to cover both casting and transmuting.

I feel like this needs to be clarified, and the whole commit needs a once-over to ensure these terms are being used precisely and consistently. Should unsafe_cell_transmutes should be renamed unsafe_cell_conversions?


Beyond that, mutable_transmutes.rs has most of the new code. I reviewed it and it seems reasonable, but that kind of traversal is not my forte so it's possible I missed stuff. All the breadcrumb code is quite complicated, and I do I wonder if something like that already exists elsewhere in the compiler for some other kind of error message.

@@ -1,8 +0,0 @@
// Tests that transmuting from &T to &mut T is Undefined Behavior.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this test removed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe because there's more comprehensive testing in another file now?

@@ -0,0 +1,163 @@
use std::cell::UnsafeCell;
Copy link
Contributor

Choose a reason for hiding this comment

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

The tests/ui/lint/mutable_transmutes directory name is slightly misleading because it contains tests that cover both mutable_transmutes and unsafe_cell_transmutes. I'm not sure how to fix it, though, and whether it's worth fixing.

#[derive(LintDiagnostic)]
#[diag(lint_builtin_mutable_transmutes)]
pub(crate) struct BuiltinMutablesTransmutes;
#[note]
pub(crate) struct BuiltinMutablesTransmutes<'a> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this one have a Builtin prefix but UnsafeCellTransmutes does not?

@@ -224,9 +225,39 @@ pub(crate) struct BuiltinConstNoMangle {
pub suggestion: Span,
}

// This would be more convenient as `from: String` and `to: String`, but then we'll ICE for formatting a `Ty`
// when the lint is `#[allow]`ed.
Copy link
Contributor

Choose a reason for hiding this comment

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

I read this comment several times, but I still don't really understand it. Explain some more, perhaps? Also, the comment talks about "from"/"to" but the fields are "before"/"after", which is confusing.

@@ -0,0 +1,718 @@
//! This module check that we are not transmuting `&T` to `&mut T` or `&UnsafeCell<T>`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
//! This module check that we are not transmuting `&T` to `&mut T` or `&UnsafeCell<T>`.
//! This module checks that we are not transmuting `&T` to `&mut T` or `&UnsafeCell<T>`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does it check casting as well?

referent.translate_for_diagnostics(cx, &mut after_ty, reference_ty);
} else {
before_ty = "*";
reference.translate_for_diagnostics(cx, &mut after_ty, top_ty);
Copy link
Contributor

Choose a reason for hiding this comment

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

These two lines are shared with the if block and can be factored out.

};
debug!(?offset, ?layout, "check_overlapping");

// Then, for any entry that we overlap with (there can be many as our size can be bigger than one,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Then, for any entry that we overlap with (there can be many as our size can be bigger than one,
// Then, for any entry that we overlap with (there can be many because our size can be bigger than one,

(I had to read this sentence twice to understand it. This is a pet peeve of mine, "because" is almost always clearer than "as".)

cx.tcx.is_intrinsic(def_id, sym::transmute)
}

/// Checks for transmutes via `&*(reference as *const _ as *const _)` and lints.
Copy link
Contributor

Choose a reason for hiding this comment

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

That's a cast, not a transmute...

Copy link
Contributor

@traviscross traviscross Dec 10, 2024

Choose a reason for hiding this comment

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

For the reasons described here, I would actually call this a transmute. It's using a cast to effect a transmutation. So it's a transmute via a cast.

}

declare_lint! {
/// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut
Copy link
Contributor

Choose a reason for hiding this comment

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

This one only mentions transmuting...

}

declare_lint! {
/// The `unsafe_cell_transmutes` lint catches transmuting or casting from `&T` to [`&UnsafeCell<T>`]
Copy link
Contributor

Choose a reason for hiding this comment

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

This one mentions transmuting or casting...

@nnethercote nnethercote added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 10, 2024
@traviscross
Copy link
Contributor

My main observation is about terminology. To me:

  • "casting" is a specific thing, done via as or cast
  • "transmuting" is a different specific thing, done only via mem::transmute
  • "converting" is an imprecise term that possibly covers both "casting" and "transmuting".

Is that correct?

I'd take a somewhat more broad view of transmuting. Probably I think of that as essentially synonymous with reinterpreting some sequence of bytes as some other type without the presence of any automatic mechanism to reject invalid reinterpretations or to adjust the bytes as needed.

So whether we do that with transmute or with union or by casting pointers and then dereferencing them, it's still a transmutation.

@nnethercote
Copy link
Contributor

I'd take a somewhat more broad view of transmuting

I find it bizarre to say that transmuting covers anything other than mem::transmute (such as casts), and the Reference and the Rustonomicon appear to disagree with your definition. Having an umbrella term ("conversions") and then specific kinds of conversions (coercions, casts, transmute) is more precise and less likely to cause confusion.

But even if we accept your definition, this PR lacks internal consistency in its terminology. Sometimes it talks about casts, sometimes about transmuting, sometimes it mentions both. @ChayimFriedman2, can you talk about your understanding of these words and how you have used them?

@traviscross
Copy link
Contributor

traviscross commented Dec 11, 2024

But even if we accept your definition, this PR lacks internal consistency in its terminology...

Agreed definitely that this PR should be more consistent on this point -- I should have mentioned that. So +1 for catching that in review.

I find it bizarre to say that transmuting covers anything other than mem::transmute (such as casts), and the Reference and the Rustonomicon appear to disagree with your definition.

I don't know. I reviewed the Reference text, and I think my definition aligns with it. E.g., it says:

Unions have no notion of an "active field". Instead, every union access transmutes parts of the content of the union to the type of the accessed field. Since transmutes can cause unexpected or undefined behaviour, unsafe is
required to read from a union field.

That's directly in line with my remark that:

So whether we do that with transmute or with union or by casting pointers and then dereferencing them, it's still a transmutation.

Elsewhere, it says:

Despite pointers and references being similar to usizes in the machine code emitted on most platforms, the semantics of transmuting a reference or pointer type to a non-pointer type is currently undecided. Thus, it may not be valid to transmute a pointer or reference type, P, to a [u8; size_of::<P>()].

If we were to narrowly define transmute as "it's not a transmute unless you call mem::transmute", then this paragraph would clearly need to be broadened.

Similarly, in the Nomicon, at the bottom of the page about transmutes, it says:

Also of course you can get all of the functionality of these functions using raw pointer casts or unions, but without any of the lints or other basic sanity checks. Raw pointer casts and unions do not magically avoid the above rules.

If we were to say that it's only a "transmute" if you call mem::transmute, that would seem to render the word largely useless, as we'd nearly always need to qualify it with "or semantically equivalent operations using mem::transmute_copy, union, or pointer casts".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.