Skip to content

Suggested lint: avoid &mut to *const coercions #12791

Open
@joshlf

Description

@joshlf

What it does

Recommends avoiding &mut to *const coercions

Advantage

As described in rust-lang/rust#56604, these coercions have surprising behavior that can result in unsoundness in unsafe code. They implicitly first coerce to a shared reference (&), with the result that the resulting raw pointer may have different semantics than a seemingly-equivalent raw pointer produced by first coercing to *mut and then casting to *const. (I say "may" because this depends on how references are formalized in Rust - Stacked Borrows, Tree Borrows, etc.)

Drawbacks

Authors might be relying on this behavior. It strikes me as unlikely to be common - anecdotally, seasoned unsafe programmers I've spoken did not know about this behavior.

Example

let x = &mut 0;
let y: *const i32 = x;

Could be written as:

let x = &mut 0;
let y: *mut i32 = x;
let y = y.cast_const();

cc @RalfJung @jswrenn

Activity

RalfJung

RalfJung commented on May 12, 2024

@RalfJung
Member

As described in rust-lang/rust#56604, these coercions have surprising behavior that can result in unsoundness in unsafe code.

FWIW, I consider this a bug in our spec that I want to fix. Not sure if it's worth making clippy deal with that.

joshlf

joshlf commented on May 13, 2024

@joshlf
Author

Agreed, but do we have a concrete timeline on this? My experience with Rust (as with many open source projects) is that it's hard to make promises about how quickly things will move since it's mostly volunteers, and so it's risky to say "let's not do this because soon it won't matter" since "soon" could end up being multiple years.

In order to deal with this reality, I usually prefer to put all of the irons in the fire at the same time so that hopefully something will pan out in relatively short order. E.g., in google/zerocopy#29, we needed some way of computing layout information for an unsized type. Since we weren't sure what we could stabilize at what pace, we "raced" three different approaches (see the "Support deriving KnownLayout for unsized types" bullet):

In the end, we managed to stabilize offset_of!, but we didn't make progress on getting it to support unsized types. We didn't make much progress on stabilizing {size,align}_of_val_raw. We ended up taking the approach of reimplementing the repr(C) layout algorithm manually.

My point in mentioning that example is just to suggest that we might want to write this lint anyway as a hedge unless we're confident that the spec bug will be fixed in a reliably short time frame.

RalfJung

RalfJung commented on May 13, 2024

@RalfJung
Member

It seems strange to land a lint for something that largely only exists in an experimental, never-approved memory model I proposed.

Are you saying having this issue affect Miri executions (as that's the only thing it affects) is bad enough to warrant a lint?

joshlf

joshlf commented on May 13, 2024

@joshlf
Author

I'm confused - does rust-lang/rust#56604 not apply to the current language semantics?

RalfJung

RalfJung commented on May 13, 2024

@RalfJung
Member

What do you mean by "current language semantics"? We have no aliasing model, other than the experiments implemented in Miri (SB, TB).

It doesn't affect LLVM codegen.

joshlf

joshlf commented on May 13, 2024

@joshlf
Author

My point is that the implicit &mut to & coercion does happen on today's Rust, right?

IIUC, your point is that &mut as *mut as *const and & as *const are semantically identical on today's Rust, and that they are only semantically distinguished under SB (and maybe also TB?), so the implicit &mut to & coercion is true-but-irrelevant on today's Rust?

If that's right, then my thinking was that this is a forwards-compatibility issue. But I suppose if we promise to not formally adopt SB or TB (or something else) without first resolving this issue, then I agree that this is probably premature.

briansmith

briansmith commented on May 28, 2024

@briansmith
let x = &mut 0;
let y: *const i32 = x;

I'm in the process now of doing rewrites from:

let y: *const T = x;
let ... = f(y)

to:

let ... = f(ptr::from_ref(x));

Basically, this is rewriting explicit cocersions of &T to *const T to use from_ref, which is documented as being "safer" than casts.

This lint, if implemented, should also then pick up cases where ptr::from_ref is passed a &mut T, because it has the same issue.

I guess it should also detect this pattern then:

let y: &T = x;   // &mut T -> &T
let y = y as *const T;

It seems like the real issue, though, is that *const T to *mut T casts are extremely dangerous, just like in any language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintArea: New lints

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @briansmith@RalfJung@joshlf

        Issue actions

          Suggested lint: avoid `&mut` to `*const` coercions · Issue #12791 · rust-lang/rust-clippy