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

offset_of_slice exposes whether a custom DST has a private slice field #138327

Open
kpreid opened this issue Mar 11, 2025 · 5 comments
Open

offset_of_slice exposes whether a custom DST has a private slice field #138327

kpreid opened this issue Mar 11, 2025 · 5 comments
Labels
C-bug Category: This is a bug. F-offset_of_slice `#![feature(offset_of_slice)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@kpreid
Copy link
Contributor

kpreid commented Mar 11, 2025

The following program compiles:

#![feature(offset_of_slice)]

mod foo {
    pub struct Inner {
        foo: u8,
        tail: [u8],
        // tail: dyn core::fmt::Debug,
    }
    pub struct Outer {
        pub dst: Inner,
    }
}

fn main() {
    assert_eq!(core::mem::offset_of!(foo::Outer, dst), 0);
}

It does not compile if the type of tail is changed to a dyn type. I believe neither version should compile, because tail is a private field, and therefore information about its type should not leak to places where it is not visible.

rustc version: 1.87.0-nightly (2025-03-09 3ea711f)

@rustbot label F-offset_of_slice C-bug

@rustbot rustbot added needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. C-bug Category: This is a bug. F-offset_of_slice `#![feature(offset_of_slice)]` labels Mar 11, 2025
@kpreid kpreid changed the title offset_of_slice exposes the type of private fields offset_of_slice exposes whether a custom DST has a private slice field Mar 11, 2025
@jieyouxu jieyouxu added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Mar 11, 2025
@asquared31415
Copy link
Contributor

I would expect both of those cases to compile, as the user is not attempting to observe anything about Inner at all, only the offset of the field dst within Outer.

@kpreid
Copy link
Contributor Author

kpreid commented Mar 11, 2025

I’m sorry, I should have given more context for the issue. The entire point of the base restriction which offset_of_slice relaxes for slices is that it is impossible to statically obtain the offset of dyn Trait, because its alignment and therefore its required offset is dynamic. Consider this program:

use std::sync::atomic::*;
use std::fmt::Debug;
use std::mem::align_of_val;

#[repr(C)]
pub struct Dst<T: ?Sized> {
    head: u8,
    tail: T,
}

fn dynamic_offset_of_tail<T: ?Sized>(value: &Dst<T>) -> usize {
    (&raw const value.tail).addr() - (&raw const *value).addr()
}

fn main() {
    let dst1: &Dst<dyn Debug> = &Dst { head: 0, tail: AtomicU8::new(0) };
    let dst8: &Dst<dyn Debug> = &Dst { head: 0, tail: AtomicU64::new(0) };

    assert_eq!(dynamic_offset_of_tail(dst1), 1);
    assert_eq!(dynamic_offset_of_tail(dst8), 8);
    assert_eq!(align_of_val(dst1), 1);
    assert_eq!(align_of_val(dst8), 8);
}

Two values of identical type, Dst<dyn Debug>, have different offsets for their fields, and different alignments for the entire value. Therefore, in the original program, if foo::Inner's field is made to be dyn Debug, it is impossible for offset_of!(foo::Outer, dst) to succeed — there is no one correct value for it to return (in the general case where Outer may also have other fields). Therefore, the fact that it does succeed when tail is a slice leaks implementation details.

@asquared31415
Copy link
Contributor

Ah right, if Outer had a head: u8 then the offset of dst inside Outer would change depending on the alignment of Inner, which is possibly dynamic. This is quite tricky indeed :c

@zachs18
Copy link
Contributor

zachs18 commented Mar 12, 2025

I think "because tail is a private field, and therefore information about its type should not leak to places where it is not visible" might not necessarily be true (even ignoring feature(offset_of_slice)), since users can observe that casts between *const [T] and *const Outer compile, so they know that Outer has the same pointer metadata as slices, and currently the only way for that to be the case is for it to have a slice tail.

@kpreid
Copy link
Contributor Author

kpreid commented Mar 12, 2025

Personally, I would argue it’s a flaw in the language that that is already observable, and there shouldn’t be even more occurrences of the same flaw. But I understand that’s much more debatable than revealing not-previously-revealed information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-offset_of_slice `#![feature(offset_of_slice)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants