Skip to content

Regression transmuting RwLockReadGuard<T: ?Sized>. #101081

Closed
@zachs18

Description

@zachs18

RwLockReadGuard<T> is now not transmutable when T is (dependent on) a generic parameter and ?Sized, when it was previously

(I don't necessarily think this is a significant enough problem to be worth "fixing", but I guess it is technically a regression).

Code

I tried this code:

use std::sync::RwLockReadGuard;
// the lifetimes aren't important
fn test1<'to, 'from: 'to, T: ?Sized>(guard: RwLockReadGuard<'from, T>) -> RwLockReadGuard<'to, T> {
    unsafe { std::mem::transmute(guard) }
}

I expected to see this happen: compilation success

Instead, this happened: compilation error

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> src/main.rs:7:14
  |
7 |     unsafe { std::mem::transmute(guard) }
  |              ^^^^^^^^^^^^^^^^^^^
  |
  = note: `RwLockReadGuard<T>` does not have a fixed size

Version it worked on

It most recently worked on: Rust 1.63.0

Version with regression

rustc --version --verbose:

rustc 1.64.0-beta.3 (82bf34178 2022-08-18)
binary: rustc
commit-hash: 82bf34178fb14562d158c4611bdab35d4d39b31c
commit-date: 2022-08-18
host: x86_64-unknown-linux-gnu
release: 1.64.0-beta.3
LLVM version: 14.0.6

Backtrace

(no backtrace, compiler didn't crash)

Why

This commit changed the definition of RwLockReadGuard from

pub struct RwLockReadGuard<'a, T: ?Sized + 'a> {
    lock: &'a RwLock<T>,
}

to

pub struct RwLockReadGuard<'a, T: ?Sized + 'a> {
    inner_lock: &'a sys::MovableRwLock,
    data: &'a T,
}

(The following is (AFAICT) why this change caused this regression; It is not itself a regression, and it applies to all (recent) versions of rustc (i.e. since at least 1.41.0))

AFICT, rustc currently only supports transmuting a subset of Sized types that it conservatively can guarantee are the same size. For generic struct types with ?Sized parameters, i think rustc can only guarantee the size if the whole struct's size does not depend on the parameters, or if the struct's only non-zst field is a (transitive transparent wrapper of a) pointer type depending on a generic parameter (rustc_middle::ty::layout::SizeSkeleton only has Known(usize) and Pointer { .. } variants, so can only express completely known and only-a-pointer sizes), e.g.

pub struct Works<T: ?Sized> { // SizeSkeleton::compute returns Ok(Pointer { .. }) for this when T: ?Sized
    inner: *mut T,
}
pub unsafe fn works<T: ?Sized>(from: Works<T>) -> Works<T> {
    std::mem::transmute(from) // works
}

#[repr(align(32))]
struct OverAlignZST;

pub struct AlsoWorks<T: ?Sized> { // SizeSkeleton::compute returns Ok(Pointer { .. }) for this when T: ?Sized
    inner: *mut T,
    other: OverAlignZST,
}
pub unsafe fn also_works<T: ?Sized>(from: AlsoWorks<T>) -> AlsoWorks<T> {
    std::mem::transmute(from) // works
}

pub struct DoesntWork<T: ?Sized> { // SizeSkeleton::compute returns Err(Unknown(T)) for this when T: ?Sized
    inner: *mut T,
    other: u8,
}
pub unsafe fn doesnt_work<T: ?Sized>(from: DoesntWork<T>) -> DoesntWork<T> {
    std::mem::transmute(from) // DoesntWork<T> does not have a fixed size
}

(See also: #101084 )

Moved to #101084

(See also: #101084) Unrelated note: I think this size-calculation ignoring all ZSTs (not just 1-ZSTs) when there is a pointer field can cause `transmute` to (incorrectly?) allow transmuting between differently-sized types, e.g. this compiles and runs normally, but ICEs under miri:

#[repr(align(32))]
struct OverAlignZST;

pub struct AlsoWorks<T: ?Sized> { // SizeSkeleton::compute returns Ok(Pointer { .. }) for this when T: ?Sized
    inner: *mut T,
    other: OverAlignZST,
}
// This does not compile if you remove ?Sized
pub unsafe fn also_works<T: ?Sized>(from: *mut T) -> AlsoWorks<T> {
    std::mem::transmute(from) // works
}
fn main() {
    let mut x = vec![0; 16];
    let x = &mut x[..];
    unsafe {
        let x = also_works(x);
      }
 }
$ cargo miri run
...
thread 'rustc' panicked at 'assertion failed: `(left == right)`
  left: `Size(16 bytes)`,
 right: `Size(32 bytes)`
...
query stack during panic:
end of query stack

Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:
note: the place in the program where the ICE was triggered
  --> src/main.rs:57:5
   |
57 |     std::mem::transmute(from) // works
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: inside `also_works3::<[i32]>` 
...

@rustbot modify labels: +regression-from-stable-to-beta -regression-untriaged

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.P-lowLow priorityT-langRelevant to the language team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.regression-from-stable-to-betaPerformance or correctness regression from stable to beta.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions