Fix undefined behavior identified by Miri by jgallagher · Pull Request #280 · rust-embedded/heapless (original) (raw)
👋
Since the rules in Rust aren't set, the right way to interpret Miri's errors are the interpreter reporting "This is UB with respect to the model you have requested with whatever combination of MIRIFLAGS
you passed". This is why you tend to get more reports of UB with -Zmiri-tag-raw-pointers
, you're asking for a stricter (but IMO more sensible) model.
I'll admit I don't fully understand this anchoring system, but I've poked around it enough to mostly grasp what's going on from Miri's perspective. First off, it's entirely possible to remove the ptr-int-ptr roundtrips from this code. As is very often the case, the key is to not store a usize
, and to store some kind of pointer instead. In this case you need a Send + Sync
pointer wrapper, annoying but just 4 lines of code.
The reason you need this is that pointers in Rust (and probably in C) have provenance, extra shadow state that identifies the allocation they are for. In C and Rust, integers do not have provenance. So a cast from a pointer to an integer loses data and thus cannot necessarily round-trip. Pointers definitely do not round-trip through integers in Stacked Borrows with raw pointer tagging. That's the error you're seeing above.
But in the existing Stacked Borrows with Untagged which is the default behavior in Miri, a cast from an integer to a pointer will check to see if there is an allocation at the address represented by the value of the usize
, then assign that allocation's provenance to the new pointer: https://github.com/rust-lang/miri/blob/c4dd3f4ef98f8527aa652e8154ff044ca3e88455/src/intptrcast.rs#L139-L148
That is the crux of why this code works without raw pointer tagging and as far as I can tell, cannot work with raw pointer tagging: A single anchor needs to be able to hand out multiple provenances. If the anchor is adjusted to contain a pointer, it will always hand out pointers with the provenance of that pointer, and you will get errors of a different sort, about attempting to dereference a pointer way outside its allocation. The anchor will be initialized once for example in the test called grow
then it will be re-used with a different allocation in grow_exact
, and the pointers you get back will have the provenance of the MEMORY
in the grow
test when you're trying to use them with the MEMORY
in the grow_exact
test.