1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
//@ compile-flags: --crate-type=lib
//@ check-pass
//@ edition: 2024
#![feature(const_raw_ptr_comparison)]
#![feature(fn_align)]
// Generally:
// For any `Some` return, `None` would also be valid, unless otherwise noted.
// For any `None` return, only `None` is valid, unless otherwise noted.
macro_rules! do_test {
($a:expr, $b:expr, $expected:pat) => {
const _: () = {
let a: *const _ = $a;
let b: *const _ = $b;
assert!(matches!(<*const u8>::guaranteed_eq(a.cast(), b.cast()), $expected));
};
};
}
#[repr(align(2))]
struct T(#[allow(unused)] u16);
#[repr(align(2))]
struct AlignedZst;
static A: T = T(42);
static B: T = T(42);
static mut MUT_STATIC: T = T(42);
static ZST: () = ();
static ALIGNED_ZST: AlignedZst = AlignedZst;
static LARGE_WORD_ALIGNED: [usize; 2] = [0, 1];
static mut MUT_LARGE_WORD_ALIGNED: [usize; 2] = [0, 1];
const FN_PTR: *const () = {
fn foo() {}
unsafe { std::mem::transmute(foo as fn()) }
};
const ALIGNED_FN_PTR: *const () = {
#[rustc_align(2)]
fn aligned_foo() {}
unsafe { std::mem::transmute(aligned_foo as fn()) }
};
trait Trait {
#[allow(unused)]
fn method(&self) -> u8;
}
impl Trait for u32 {
fn method(&self) -> u8 { 1 }
}
impl Trait for i32 {
fn method(&self) -> u8 { 2 }
}
const VTABLE_PTR_1: *const () = {
let [_data, vtable] = unsafe {
std::mem::transmute::<&dyn Trait, [*const (); 2]>(&42_u32 as &dyn Trait)
};
vtable
};
const VTABLE_PTR_2: *const () = {
let [_data, vtable] = unsafe {
std::mem::transmute::<&dyn Trait, [*const (); 2]>(&42_i32 as &dyn Trait)
};
vtable
};
// Cannot be `None`: `is_null` is stable with strong guarantees about integer-valued pointers.
do_test!(0 as *const u8, 0 as *const u8, Some(true));
do_test!(0 as *const u8, 1 as *const u8, Some(false));
// Integer-valued pointers can always be compared.
do_test!(1 as *const u8, 1 as *const u8, Some(true));
do_test!(1 as *const u8, 2 as *const u8, Some(false));
// Cannot be `None`: `static`s' addresses, references, (and within and one-past-the-end of those),
// and `fn` pointers cannot be null, and `is_null` is stable with strong guarantees, and
// `is_null` is implemented using `guaranteed_cmp`.
do_test!(&A, 0 as *const u8, Some(false));
do_test!((&raw const A).cast::<u8>().wrapping_add(1), 0 as *const u8, Some(false));
do_test!((&raw const A).wrapping_add(1), 0 as *const u8, Some(false));
do_test!(&ZST, 0 as *const u8, Some(false));
do_test!(&(), 0 as *const u8, Some(false));
do_test!(const { &() }, 0 as *const u8, Some(false));
do_test!(FN_PTR, 0 as *const u8, Some(false));
// This pointer is out-of-bounds, but still cannot be equal to 0 because of alignment.
do_test!((&raw const A).cast::<u8>().wrapping_add(size_of::<T>() + 1), 0 as *const u8, Some(false));
// aside from 0, these pointers might end up pretty much anywhere.
do_test!(&A, align_of::<T>() as *const u8, None);
do_test!((&raw const A).wrapping_byte_add(1), (align_of::<T>() + 1) as *const u8, None);
// except that they must still be aligned
do_test!(&A, 1 as *const u8, Some(false));
do_test!((&raw const A).wrapping_byte_add(1), align_of::<T>() as *const u8, Some(false));
// If `ptr.wrapping_sub(int)` cannot be null (because it is in-bounds or one-past-the-end of
// `ptr`'s allocation, or because it is misaligned from `ptr`'s allocation), then we know that
// `ptr != int`, even if `ptr` itself is out-of-bounds or one-past-the-end of its allocation.
do_test!((&raw const A).wrapping_byte_add(1), 1 as *const u8, Some(false));
do_test!((&raw const A).wrapping_byte_add(2), 2 as *const u8, Some(false));
do_test!((&raw const A).wrapping_byte_add(3), 1 as *const u8, Some(false));
do_test!((&raw const ZST).wrapping_byte_add(1), 1 as *const u8, Some(false));
do_test!(VTABLE_PTR_1.wrapping_byte_add(1), 1 as *const u8, Some(false));
do_test!(FN_PTR.wrapping_byte_add(1), 1 as *const u8, Some(false));
do_test!(&A, size_of::<T>().wrapping_neg() as *const u8, Some(false));
do_test!(&LARGE_WORD_ALIGNED, size_of::<usize>().wrapping_neg() as *const u8, Some(false));
// (`ptr - int != 0` due to misalignment)
do_test!((&raw const A).wrapping_byte_add(2), 1 as *const u8, Some(false));
do_test!((&raw const ALIGNED_ZST).wrapping_byte_add(2), 1 as *const u8, Some(false));
// When pointers go out-of-bounds, they *might* become null, so these comparions cannot work.
do_test!((&raw const A).wrapping_add(2), 0 as *const u8, None);
do_test!((&raw const A).wrapping_sub(1), 0 as *const u8, None);
// Statics cannot be duplicated
do_test!(&A, &A, Some(true));
// Two non-ZST statics cannot have the same address
do_test!(&A, &B, Some(false));
do_test!(&A, &raw const MUT_STATIC, Some(false));
// One-past-the-end of one static can be equal to the address of another static.
do_test!(&A, (&raw const B).wrapping_add(1), None);
// Cannot know if ZST static is at the same address with anything non-null (if alignment allows).
do_test!(&A, &ZST, None);
do_test!(&A, &ALIGNED_ZST, None);
// Unclear if ZST statics can be placed "in the middle of" non-ZST statics.
// For now, we conservatively say they could, and return None here.
do_test!(&ZST, (&raw const A).wrapping_byte_add(1), None);
// As per https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.storage-disjointness
// immutable statics are allowed to overlap with const items and promoteds.
do_test!(&A, &T(42), None);
do_test!(&A, const { &T(42) }, None);
do_test!(&A, { const X: T = T(42); &X }, None);
// These could return Some(false), since only immutable statics can overlap with const items
// and promoteds.
do_test!(&raw const MUT_STATIC, &T(42), None);
do_test!(&raw const MUT_STATIC, const { &T(42) }, None);
do_test!(&raw const MUT_STATIC, { const X: T = T(42); &X }, None);
// An odd offset from a 2-aligned allocation can never be equal to an even offset from a
// 2-aligned allocation, even if the offsets are out-of-bounds.
do_test!(&A, (&raw const B).wrapping_byte_add(1), Some(false));
do_test!(&A, (&raw const B).wrapping_byte_add(5), Some(false));
do_test!(&A, (&raw const ALIGNED_ZST).wrapping_byte_add(1), Some(false));
do_test!(&ALIGNED_ZST, (&raw const A).wrapping_byte_add(1), Some(false));
do_test!(&A, (&T(42) as *const T).wrapping_byte_add(1), Some(false));
do_test!(&A, (const { &T(42) } as *const T).wrapping_byte_add(1), Some(false));
do_test!(&A, ({ const X: T = T(42); &X } as *const T).wrapping_byte_add(1), Some(false));
// We could return `Some(false)` for these, as pointers to different statics can never be equal if
// that would require the statics to overlap, even if the pointers themselves are offset out of
// bounds or one-past-the-end. We currently only check strictly in-bounds pointers when comparing
// pointers to different statics, however.
do_test!((&raw const A).wrapping_add(1), (&raw const B).wrapping_add(1), None);
do_test!(
(&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(2),
(&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1),
None
);
// Pointers into the same static are equal if and only if their offset is the same,
// even if either is out-of-bounds.
do_test!(&A, &A, Some(true));
do_test!(&A, &A.0, Some(true));
do_test!(&A, (&raw const A).wrapping_byte_add(1), Some(false));
do_test!(&A, (&raw const A).wrapping_byte_add(2), Some(false));
do_test!(&A, (&raw const A).wrapping_byte_add(51), Some(false));
do_test!((&raw const A).wrapping_byte_add(51), (&raw const A).wrapping_byte_add(51), Some(true));
// Pointers to the same fn may be unequal, since `fn`s can be duplicated.
do_test!(FN_PTR, FN_PTR, None);
do_test!(ALIGNED_FN_PTR, ALIGNED_FN_PTR, None);
// Pointers to different fns may be equal, since `fn`s can be deduplicated.
do_test!(FN_PTR, ALIGNED_FN_PTR, None);
// Pointers to the same vtable may be unequal, since vtables can be duplicated.
do_test!(VTABLE_PTR_1, VTABLE_PTR_1, None);
// Pointers to different vtables may be equal, since vtables can be deduplicated.
do_test!(VTABLE_PTR_1, VTABLE_PTR_2, None);
// Function pointers to aligned function allocations are not necessarily actually aligned,
// due to platform-specific semantics.
// See https://github.com/rust-lang/rust/issues/144661
// FIXME: This could return `Some` on platforms where function pointers' addresses actually
// correspond to function addresses including alignment, or on platforms where all functions
// are aligned to some amount (e.g. ARM where a32 function pointers are at least 4-aligned,
// and t32 function pointers are 2-aligned-offset-by-1).
do_test!(ALIGNED_FN_PTR, ALIGNED_FN_PTR.wrapping_byte_offset(1), None);
// Conservatively say we don't know.
do_test!(FN_PTR, VTABLE_PTR_1, None);
do_test!((&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), VTABLE_PTR_1, None);
do_test!((&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), VTABLE_PTR_1, None);
do_test!((&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), FN_PTR, None);
do_test!((&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), FN_PTR, None);
|