-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Creating a raw reference/pointer to an extern never value claims following code is unreachable #74840
Comments
To step it up a notch, I then tried giving #![feature(never_type)]
#![feature(raw_ref_macros)]
#![feature(raw_ref_op)]
extern {
static FOO: !;
}
mod foo {
#[no_mangle]
static FOO: u32 = 5;
}
fn main() {
dbg!(unsafe { core::ptr::raw_const!(FOO) });
dbg!(unsafe { &raw const FOO });
} I expected this to run and exit fine, since it is just printing the address of a symbol that definitely has an address. Instead, it ran and then SIGILLed (after giving the same warnings as above):
|
The relevant MIR looks like this: _2 = const {alloc0: &!};
_1 = &raw const (*_2); @oli-obk looks like creating a raw ptr to a static goes through a reference, with the new scheme? Also Cc @matthewjasper FWIW I am not sure if this is really a bug; an explicitly uninhabited static seems at the very least highly suspicious to me. This is currently certainly not a blessed pattern. But the generated MIR is still odd. |
The context in which I noticed it was an attempt by @repnop at using them to pass the heap start/end addresses from the linker to a Rust program (discord convo). It seemed like a somewhat correct way to encode a symbol that has a known address, but should not be readable. |
I think an inhabited ZST like |
Btw I think this should be merged with #74843; both are caused by the same odd MIR generation. |
We're computing the type of the "address of" constant of statics in rust/src/librustc_mir_build/hair/cx/expr.rs Line 839 in 06e7b93
rust/src/librustc_middle/ty/util.rs Lines 543 to 552 in 39d5a61
I thought we had some more logic there a while back... oh well. We need an |
You can check this out by changing all your extern statics to be mutable, then you get the codegen you'd get with my suggested change above. Though the never type example still dies horribly, so there seems to be a bit more to the never type, even if there's never a reference to it. EDIT: I'm not sure what's causing this, but I'm guessing the deref projection for the reborrow, as just having a constant of raw pointer to never type doesn't cause any problems: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=fb80e957cebf8bf982ce50f914bd9236 |
Yeah, the problem is here already:
We are embedding the const at reference type (note that Maybe @matthewjasper knows more about how/why this happens. |
The fix is described in #74840 (comment) where you'll end up with a raw pointer in that constant. But there's some additional messiness around never types, but without doing the fix first, I don't know what it is exactly. |
@oli-obk I am not sure if that is the right fix, or at least not a complete one. For this code: static FOO: T = ...;
let ptr = ptr::raw_const!(FOO); I'd expect no reference to be created. This makes a difference when there's invalid data in |
there can't be invalid data for |
There can be invalid data. |
I was under the impression that it's UB to have a (non extern) static that has invalid data. I thought static items and local variables had the same rules with that. Is there any discussion about this? Because it's definitely news to me. |
It's the same for statics and locals. There is nothing in the UB list that says that locals or statics always have valid data. It only says you may not create invalid data. Through pointer type casts, it is possible to have invalid data in a local without ever creating invalid data. Whether we want to do something about this and if it has any negative consequences, is tracked at rust-lang/unsafe-code-guidelines#84. Here's a small demo, where Miri cannot see any UB, and indeed none of the clauses in our UB list is met: fn main() {
let mut x: bool = true;
let ptr = &mut x as *mut _ as *mut u8;
unsafe { *ptr = 33; }
} |
oh wow... ok. We'll need to rewrite the entire system around |
I'm not sure if this issue classifies as "E-Easy" any more :D |
Not sure if we need to rewrite the entire system... an alternative might be to always use raw pointers, but make unsafety checking recognize these derefs of const raw ptrs that point to a |
Stop reading my mind 😝. I considered this, but the complexity for that would imo warrant rewriting the system and simplifying everything. Right now we need to look at a deref, check whether its local is a |
I don't think I follow. My proposed scheme should make the insertion of these pointers simpler -- just always use raw pointers. This is in exchange for a bit more complexity in the unsafety checks (as otherwise all static accesses would be considered unsafe). I am not sure if that's a good tradeoff considering how critical the safety checks are, but it doesn't match what you are saying about this being more complex during MIR construction. |
I like the sound of "simplify everything", but I am not sure what alternative you are thinking of. |
So... we have the following cases:
And right now we're translating all of them to a constant of What we could do instead is to lower This is simpler imo because everything is now very explicit. If we want we can also put everything into the The only thing that stays odd are assignments to mutable statics, as they need to end up on the lhs of an assignment, but I think our THIR infrastructure will automatically make that work |
IMO then it would make more sense to just bring back static places. If we can rely on such larger patterns, my proposal would be:
However, I am not sure if the first case is correct -- does unsafety checking still detect |
So... I looked a bit deeper and realized that it's not related to anything in the MIR or MIR building at all, but it's typeck which treats all diverging expression (path expression to an extern static in this case) as causing all following expressions to be unreachable, even if in the raw pointer case this is not technically true. More details can be found in #76982 (comment) TLDR: I don't see a good reason to allow uninhabited extern statics, since their only use case is getting their address taken. So I'm proposing to instead add a future incompat lint that tells you to not use extern statics of uninhabited type and instead use a dummy type that you can safely take the address of. |
Agreed. It could directly recommend That said, isn't something like this still needed to make (Also |
Yea, I am currently rewriting that PR, but I wanted to separate it from the UB situation here. |
The reason uninhabited extern statics are so problematic is that they are the only uninhabited places that one can actually access in Rust -- normal statics cannot be uninhabited as then computing their initial value would error, and uninhabited locals cannot be accessed before being initialized which can never happen. So, with that lense on, it makes perfect sense to just make these a hard error after some transition period. @oli-obk are you taking care of that PR or should I put it on my list? |
I opened #77096 for the problems the current static handling is causing, and the refactoring we might want to do. |
I am not working on that |
fix static_ptr_ty for foreign statics Cc rust-lang#74840 This does not fix that issue but fixes a problem in `static_ptr_ty` that we noticed while discussing that issue. I also added and updated a few comments. The one about `internal` locals being ignored does not seem to have been true [even in the commit that introduced it](https://github.com/rust-lang/rust/pull/44700/files#diff-ae2f3c7e2f9744f7ef43e96072b10e98d4e3fe74a3a399a3ad8a810fbe56c520R139). r? @oli-obk
fix static_ptr_ty for foreign statics Cc rust-lang#74840 This does not fix that issue but fixes a problem in `static_ptr_ty` that we noticed while discussing that issue. I also added and updated a few comments. The one about `internal` locals being ignored does not seem to have been true [even in the commit that introduced it](https://github.com/rust-lang/rust/pull/44700/files#diff-ae2f3c7e2f9744f7ef43e96072b10e98d4e3fe74a3a399a3ad8a810fbe56c520R139). r? @oli-obk
I have written the code for rejecting uninhabited statics... but I realized I have no idea where to put it.^^ I first put it to the other static checks such as |
In fact this means that |
I think I found a reasonable place: #78324 |
…-obk ensure that statics are inhabited Fixes rust-lang#74840 r? @oli-obk
…-obk ensure that statics are inhabited Fixes rust-lang#74840 r? @oli-obk
So I just hit this warning testing some code doing interop with Objective-C (ugh). The relevant code here is approximately: // roughly the content of the objc headers
pub enum objc_class {}
pub type Class = *const objc_class;
...
// presumably this could change to ptr::raw_const!()?
impl NSObject {
pub fn class() -> Class {
extern "C" {
#[link_name = "OBJC_CLASS_$_NSObject"]
static CLASS: objc_class;
}
&NS_OBJECT_CLASS
}
} Essentially, using the dynamic linker to generate runtime identifiers. This is emulating what Objective-C and Swift emit to avoid (or rather optimize, since the dynamic linker is doing it) the runtime cost of looking up classes. As far as I can tell from this thread, the issue is approximately that it's illegal to take the address of a value that can't exist (in Rust) as it's uninhabited, and the replacement is to use a valid (ie. inhabited) type? Here the issue is that Would the suggestion be to change this simply to: extern "C" {
#[link_name = "OBJC_CLASS_$_NSObject"]
static CLASS: ();
}
(&NS_OBJECT_CLASS as *const ()).cast() Or is there a better |
Possible you should use extern type syntax.
It requires Nightly now. |
The lack of a Size is indeed problematic without If you want to make sure ppl realize the size is problematic, put a |
@simonbuchan using an uninhabited type to model an opaque type is a very bad idea, because Rust assumes that uninhabited types cannot be constructed by anyone ever. That is not what you want to express -- you want to express that the (Rust) client of your library cannot construct this type, but other parts of the code (across an FFI boundary, but that makes no difference) can construct it. The "proper" but nightly-only solution is indeed |
Thanks everyone! |
It works if you only ever use it as the pointee of a raw pointer, like |
Empty enums are uninhabited types and can cause issues like rust-lang/rust#74840 This fixes the compiler warning: static of uninhabited type
I tried this code (playground):
I expected to see this happen: just a linker failure because
FOO
doesn't exist.Instead, this happened: before the linker failure,
rustc
complained that code was unreachable.(The code should also build without the
unsafe
; that is caused by the same problem.)Meta
cc @rust-lang/wg-unsafe-code-guidelines
The text was updated successfully, but these errors were encountered: