-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Const validation rejects references that might be dangling (but could actually be valid at run-time) #63197
Comments
Using
|
Correction: the rules are the same in both cases, we just don't have a lint catching the latter. There is no difference between the
That is an interesting corner case. The reason we error is that in general, it is not correct to just access some random integer cast to a pointer. This is a very good argument for making that error suppressible, likely by folding it into the deny-by-default However, also note that if this is an MMIO block, references are likely not what you want. Also see this long discussion. The gist of it is that under current rules, the compiler is allowed to insert spurious reads from references. Which crate is providing the |
All It this exact case it's https://github.com/lpc-rs/lpc-pac. But accessing "random addresses" is |
I understand. We just were not aware people would put those as references into a static, hence the check that rejects "random addresses" in references. And also see the issues mentioned above with using references. It is incorrect in Rust to have a reference to I am aware that this pattern is widely used on embedded, but that doesn't change the fact that it is incorrect, and has always been incorrect since at least Rust 1.0. Unfortunately the people implementing that pattern were not aware of this subtlety. :/ The alternative using raw pointers mostly avoids that problem, though I cannot easily tell if it does not also locally create references. So the question for rustc now is, do we want to stop erroring for this because of embedded devices? The one usecase we have here is not actually correct for references, but for reasons entirely unrelated to what this is checking. I guess it is conceivable that non-MMIO memory might also sit at a fixed address on embedded, and then references are perfectly fine. |
I know you know this, but it seems worth re-mentioning this library exists and is heavily used in the rust embedded ecosystem, and references to values of this type are often obtained by transmuting integers to references. There are other places, too, where int->ptr occurs where the integer does not originate from a ptr->int cast, such as the address returned by the |
@cramertj yes, that is a problem. :/ I mean not the part where integers are transmuted, that's fine, but the part where embedded programs use
I doubt we will ever permit calling that syscall in CTFE. ;) The bug here is strictly about the checks we do on the value computed for a |
In the embedded case, that is totally fine, except for MMIO where reading mutates hardware. We don't ever read the But all that seems orthogonal to the issue at hand. We don't permit references (in constants and statics) that can't be dereferenced during const eval. We should not change this behaviour. Also: we don't permit @axos88 why do you want these values stored inside statics? It seems to go against Rust's owernship scheme to have global state unless absolutely necessary. If you want that function to have access to that hardware register, pass the register down via arguments. |
The general expectation with volatile is that only the accesses explicitly written by the programmer happen, nothing else. This expectation can be violated with
Agreed.
Hm, true. OTOH, we do permit
In this case the hardware is global state. Just forcing people to use local definitions does not make it any less global. Sure Rust discourages global state, but I see no reason to pretend something isn't global when in fact it is. |
I disagree strongly, but I'm also heavily involved with https://github.com/embed-rs/stm32f7-discovery which treats hardware in an ownership based way, allowing us to use Anyway, independently of any opinions on ownership of hardware, the question that needs to be answered in order for this issue to be resolved is whether const X: u32 = *Y; should always compile successfully for any |
Synthetic (or “synthesized”) pointers (pointers materialized out of “thin air”) are valid and should have their own operational semantics with respect to e.g. aliasing analysis specified, just like they do in LLVM/C/C++/etc.. Even making a reference out of them should be fine as long as the requirements for references can be satisfied. Synthetic MMIO pointers only rarely do satisfy the requirements, but they sometimes do, and those cases where they do should have defined semantics. I believe that we should allow reborrowing data under synthetic pointers as references some way, but I’d be fine with rust making it hard(-er than usual) to achieve.
Hardware registers are still global state, ownership semantics these crates add are an abstraction over this global state. A perfectly reasonable alternate design would be to have these register blocks be behind some sort of a mutex, which makes globality of these register blocks more apparent.
This would have non-local semantics when |
Reborrows are already fine in const eval. It's only when you have a reference in a final constant that we prevent "invalid" (according to an on-purpose-restrictive definition). We can lift restrictions if there's a good argument for that. Allowing undereferencable references in constants is OK, promoteds can't deref anyway. At least I hope we also prevent implicit derefs in field accesses |
To reiterate, this is not at all about the dynamic semantics of Rust. This is entirely about what checks we are making on the value that we compute (at compile-time) for a |
I also disagree with this statement. Yes, there are cases where this makes sense (such as a serial port, or an I2C bus, where the hardware has complex state). But in other cases, such as driving a few LEDs for debuging purposes, it's not. I want to be able to change my debug LEDs from all over the place without having to pass down the reference to them and fight rusts ownership system, and then possibly remove them in production code (imagine the complexity conditional compilation would bring when I need to have them on in debug mode, and off in production). Also you could think about those blinking LEDs as the most simplistic stdout, and this IS global even in std, isn't it? |
Stdout in libstd uses a mutable static that is initialized at runtime. This would work for these references that can't be dereferenced at compile-time, because you only have the odd value at runtime. At the same time thisvwould fit into the ownership scheme, because you then lose access to that pin, so no other driver may attempt to use it. Many boards allow remapping pins, so "just an LED" is not a necessarily universally true statement. You can always create a global function that generates the value for you and call that instead of using a constant. Note that I'm not against changing the behaviour here just because I disagree with the way ppl are using hardware on embedded. I won't block us making these rules less strict, as long as we're clear on what such a change entails. Right now, any reference in a constant's value can be dereferenced in another constant. We'd lose this guarantee. As @nagisa said, it's not necessarily a guarantee that makes sense or is desirable, but we should be aware that we'd change it and that this may have far ranging consequences. E.g. it would forever forbid us from allowing dereferencing in promoteds, even though that would be sane to allow right now. If we had allowed it on stable, then it would be a breaking change to allow undereferencable references in constants |
I am not worried about restricting what new features can be added to promoteds. They are already too powerful anyway and extensions should IMO happen in a more explicit mechanism (such as |
At any rate, I don't think this is something that should be changed by issue or PR here since it has wider implications. I would like to see an elaborated RFC if something is to be changed here. (But please continue discussing...) |
To be fair, we introduced and strengthened those checks without much ado. So it seems weird to require an RFC for weakening them. |
Well we could also just FCP in one way (my current preference is to keep things as-is) but it seems like there's a debate here which is why an RFC seems appropriate. |
There's no debate. There's just the "is everyone aware of and OK with the consequences?". So far no one has argued against doing it. I'm mostly arguing that the use case is invalid, but that's just a personal opinion not founded in any evidence. I guess the only thing speaking against it is that you can't tell from a constant's type whether a deref is OK, but we already can't tell whether subtracting |
In which sense? The MIR subtraction operations (checked and unchecked) are AFAIK guaranteed to be okay on a |
Well.. |
That's panicking, which is very different (IMO) from raising another kind of evaluation error. |
That difference is only relevant when we're talking about implicit promotion, because there the difference to the runtime behaviour is noticable. It is very different conceptually, but not in how it's noticable from a user perspective |
I'm not sure I entirely follow your last message, however I am getting a feeling that statics in rust are conflating two things, what you call Wouldn't the clean solution be to separate these concepts? Say
Wouldn't this make it much easier to reason what can and cannot become static const? |
In this particular storing dangling references would be a no-no for |
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.
"promotion" is unfortunately a heavily overloaded term, but the one I am talking about here is the automatic creation of Compile-time constants ("constant propagation") are entirely irrelevant for the discussion in this issue.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
We discussed this in our 2019-10-10 meeting -- we considered a proposal by @RalfJung. We didn't reach any firm decisions but at least some people thought that this seemed reasonable:
As an immediate step, @ecstatic-morse has been working towards better specifying the const promotion rules, and that seems like an important part of the puzzle to work out first. |
Btw @axos88 I think we never got to the bottom of whether const gpio: &RegisterBlock = unsafe { (& (*lpc176x5x::GPIO::ptr())) }; is wrong. So, what is that |
It is MMIO, but in case of the processor we are coding for there is no MPU, so code can always read from that memory region. |
Well... Except if the peripheral is powered off, but i'm not sure that's something we would want to catch at the compiler level. One could argue that because of that caveat this should be wrapped in unsafe code, but it should be able to compile. The user of said reference should then be responsible for checking if accessing that memory area is permissible in that context, which is basically the definition of unsafe code, is it not? |
@axos88 the thing with references is that they can get accessed by the compiler even if the code does not access them. That's what it means for them to be "dereferencable". For example, if you have if foo { let val = *bar; } then if |
The "wrapping in unsafe code" is exactly what using raw pointers here achieves. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Has there been any change? Or a way to disable the compiler for checking it? I have this problem with a custom lazy static implementation. It makes static values with an
|
It is UB to have a NULL reference, so that error sounds correct to me. If you work with uninitialized memory, you need to use |
Oops my bad didn't know that existed... |
Basically the same issue but for function pointers recently came up on Zulip. |
I've been working on converting this repo over to rust: https://github.com/esp32-open-mac/esp32-open-mac but am having usability issues with making the registers work.
For easily creating pointers without having to copy and paste the internal representation for every register I make. note: erroneous constant encountered |
It's unlikely that we will do a language change to cater an unsound library. The proper solution is to use raw pointers. AFAIK there are multiple ways to use them. They might not be quite as ergonomic and |
Apparently it's undefined behaviour to use:
but not undefined to use:
with:
Error message is:
This is happening on in an embedded envrionment, where there is no MMU, and access to that address is correct.
I'm not sure why it would be undefined to use it as a const, but not undefined to use it as a variable value?
Perpahs it has something to do with the borrow checker not being able to track mutable and unmutable references to that value, but this is an immutable access, so it should be fine?
The text was updated successfully, but these errors were encountered: