-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
[tracking issue] raw ptr to usize cast inside constants #51910
Comments
Just encountered this, when i try to define a constant that uses
|
@crlf0710 what is your use case for casting a raw pointer to an integer? Is there any reason you can't use |
@oli-obk actually it is an integer in the first place, however the upstream library ( I can work around this by (re-)defining those integers myself again. (Arguably the |
I can offer you a stable workaround, but it's very unsafe and if you screw up by placing a real pointer in an integer constant we will error, and if we don't, we might so in the future. If you do undefined behaviour at compile-time, it means the behaviour (compilation success) is also undefined. union Transmuter {
from: LPCWSTR,
to: usize,
}
const MY_CONST: usize = unsafe { Transmuter { from: IDC_ARROW }.to }; |
@oli-obk Great to know, thanks a lot! |
I'm confused, what's wrong with putting a real pointer in an integer constant? Btw, the following code compiles just fine: use std::ptr::null;
#[repr(C)]
struct Fields {
field1: u32,
field2: [u8; 3],
field3: u64,
field4: i32,
}
union Transmuter<T: 'static> {
ptr: *const T,
reference: &'static T,
integer: usize,
}
const FIELD4_OFFSET: usize =
unsafe { Transmuter { reference: &(Transmuter { ptr: null::<Fields>() }.reference).field4 }.integer };
const _STATIC_ASSERT_EQ_: [(); 16] = [(); FIELD4_OFFSET]; |
you are converting a null pointer to a reference (which is UB) and then back to an integer (which is fine, since |
Hmm actually, if the value is an int it's still fine. So we'd still allow |
One case where union transmute hacks are used currently is to have a const-capable |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Why does regular casting requires unsafe?
Error:
EDIT: I see that after adding "unsafe" it prints |
You can cast pointers that you derived from integers. You just can't cast real pointers. This works as intended, and the reason the feature gate is not stable is because we can't detect statically whether your cast is going to be fine. We actually need to evaluate the constant with the cast as you saw. In case of associated constants of generic traits, we can't actually do the cast unless you instantiate the trait with specific types. This means that you could build an associated constant that is broken, but only users of your trait see that and not you (that's why it's unsafe. This operation is "const unsafe"). |
rust-lang/rust#51910 We will have to provide the drop_in_place::<T> ptr upon Arena<T> registration
One temporary way by now is to using |
I find it less and less likely for raw ptr to int casts to ever become stable in CTFE. See also #51909 for the related discussion on unconst things and soundness concerns in CTFE. |
The thing is, if we don't stabilize the casts, people will just transmute instead once that is stable... |
But in consquence that means that we should also stabilize raw pointer comparisons as people will otherwise just cast to usize and compare there. |
Depends on what you mean by "stabilize". I think the But you have a point... almost anything people would usually do after casting to usize does not actually work (like doing arithmetic on that integer or comparing it). Literally the only thing you can do is cast it back. So in that regard, disallowing the cast could be useful -- the error could explain what the cast would be rather pointless anyway. Maybe it should be a lint instead of a hard error. |
One could argue that if they transmute, they are doing something unsafe, so it is their problem... |
Pointer comparison and casting raw pointers to usize would also be unsafe if we stabilized it in const fn |
This might be a stupid question, but why impose such strict restrictions on pointers? What's so bad about subtracting two pointers, or comparing them, or casting to I understand that you don't want to allow |
@mjbshaw IIUC, the problem is that the correctness of const BAD : [f32; (&1u8 as *const u8 as usize) % 2] = [4.2]; If such code is allowed, this array definition (and thus the underlying program) will sometimes compile, sometimes throw a compiler error, depending on star alignment. This is, by definition, compile-time undefined behavior, and already looks like something Rust might not want to support without at least some kind of const fn coin() -> usize {
let randomness = 1u8;
(&randomness as *const u8 as usize) % 2
}
/* lots of other code */
const BAD : [f32; coin()] = [4.2; coin()]; ...it may not be immediately obvious to either a human or a compiler that something evil is going on, and accepting this fate by forcing compilers to do the in-depth check may preclude improving the efficiency of rustc's const evaluator through optimizations that deduplicate code in the future. |
@mjbshaw please read #51909 if you are interested in the icky details. There's no point in rehashing it here in detail as it's just another shade of the same thing. But yea, as explained by @HadrienG2, the TLDR is that const eval cannot possibly compute
That by itself is not a problem we already have this if you make an array length rely on |
Speaking specifically about subtraction, we could (dynamically) allow subtraction ( For comparison, unfortunately we don't have a convenient runtime operation to borrow from. But again I think we should rather not (dynamically) allow Casting to usize and back we could support. Indeed, if you replace cast by transmute we cannot prevent it. The only reason to forbid it would be to help people avoid the above pitfalls where operations such as comparison and subtraction only work under specific circumstances. Thus my suggestion to make it a lint. This is based on me being unable to imagine any correct CTFE code that uses ptr-int-casts and that cannot be written better without them -- thus, the lint would not have false positives. |
I just had another idea, based on overarching principles rather than a case-by-case analysis: we could statically forbid all safe operations that can cause CTFE trouble: raw ptr comparison and ptr <-> int casts. Dynamically, we make no attempt to support pointer values in integer operations (so if people transmute to get around the lack of casts, that doesn't buy them anything). As a replacement for what is lost this way, we provide dedicated raw pointer operations that are meaningful during CTFE: This means |
So... how about this action plan:
|
I agree with that plan. The documentation on casts, and |
Now that constant transmutes are stabilized in #85769 does that mean this can be stabilized as well? Transmutes will allow ptr to usize casts, just in the less safe way. |
transmuting a pointer to a usize (or the other way) is probably UB because of how LLVM works. This is an open question, but the current leaning is "don't do that". |
This is explicitly excluded in the transmute docs:
The feature this issue was tracking has been removed in #87020. I just forgot to close the tracking issue, so let's do that now. |
i just encountered a possibly valid use case of this, when trying to create an x86 page table at compile time: (The example is using legacy non-PAE paging, although paging with PAE should have the same problem) #[repr(C)]
#[repr(align(4096))]
pub struct PageTable([u32; 1024]);
static PAGE_TABLE: PageTable = PageTable({
let mut arr = [0; 1024];
let mut i = 0;
while i < 1024 {
// supervisor level, read/write, present, identity mapped
arr[i] = (i as u32 * 0x1000) | 3;
i += 1;
}
arr
});
static PAGE_DIRECTORY: PageTable = PageTable({
// all other entries are not present
let mut arr = [2; 1024];
// supervisor level, read/write, present
arr[0] = addr_of!(PAGE_TABLE) as u32 | 3; // ERROR
arr
}); I had semi expected this to just work and the page directory would be somehow magically wired to have the first entry point to the page table. Unfortunately I forgot about ptr-to-int cast in compile time being UB. How much effort is needed for this use case to be valid Rust? I'd assume a lot because it means CTFE for statics now happens after we decide the actual address to place the statics. |
Does the The big problem in your example is not the cast, it is precisely the bit-or operation. We could specifically add operations to change the bits of a pointer that are guaranteed zero due to the alignment of the reference that the pointer came from. |
It can be EDIT: I have now adopted to populate the entries at runtime, but the example above could identity map enough memory to eliminate the need to locate the kernel so I still think this use case is valid. |
I was so happy when I deleted the code from Miri that enabled "sub-alignment arithmetic and bitops"... But I agree it is possible to support this so we should at least consider it. Then the question remains, how? IMO the typical API (cast ptr to int, to bitops there, cast ptr back) is just not a good API -- it is way too powerful, and int-to-ptr casts are super hard to define properly. The fiction that pointers and integers are isomorphic is already brittle for runtime code (keyword "pointer provenance"), it breaks down entirely for compile-time code. So I would be much more happy if we could find a way to support something like So basically I am saying, this is the wrong tracking issue for that discussion. :) |
Keep in mind getting something like In other words, any computation must be expressed through relocations, and the only computation that can be portably expressed through relocations is "symbol + constant". Even the non-portable options are usually meant for specific purposes and very limited. (Here is the list for x86-64 ELF.) In this case, In theory Rust could also provide a |
Just encountered another thing I couldn't achieve: extern crate x86_64;
static GDT: x86_64::GlobalDescriptorTable = /* ... */;
/// Const-friendly gdt pointer
#[repr(C, packed)]
struct GdtPointer {
limit: u16,
base: *const GlobalDescriptorTable,
}
unsafe impl Send for GdtPointer {}
unsafe impl Sync for GdtPointer {}
static GDTPTR: GdtPointer = GdtPointer {
limit: 2,
base: &GDT,
}; I wrote the code above, then I realized the The solution to this might be a type named like |
I am fully aware, that is why I specifically restricted the bitops to require the part of the bitmask that interacts with the unknown (and unknoweable) part of the pointer to be al-0 or all-1. :)
I think the bitops are slightly more powerful than addition, for example they permit implementing "tagged pointer" schemes that pack an aligned pointer and a But indeed it seems that for your first example, @fee1-dead, |
I'm not sure about the second example either.... how can it be 32 bit if it's supposed to be the address of a static item which may be anywhere in the 64 bit space of the Edit: let's talk on zulip, this is probably not the right place |
But if it has to be emitted as addition then it can't be more powerful… Oh, I guess your point is that bitops would allow further const evaluation to extract the tag if needed? |
Yeah. Also I don't think using just addition in the language we can implement "reset the last bit of the ptr [to a 2-aligned allocation] to 0" (if we do not have ptr-to-int casts), but we can totally implement this in the CTFE interpreter using just addition in the linker relocations we emit. |
This is the tracking issue for the
const_raw_ptr_to_usize_cast
feature.Activating the feature allows casting
*const T
and*mut T
tousize
.I did not open an RFC for this because I believe we want to experiment with the feature (and we can already emulate it with union transmute hacks).
The text was updated successfully, but these errors were encountered: