-
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
Iterators over slices of ZST behave strange #42789
Comments
To clarify, the way the test fails is that the slice iterator returns |
By "dangling element" you mean In particular, LLVM does not try to know about all allocas in address space, it only cares about the ones it knows about, so |
IMHO that would be fine, if the
|
We could treat any cast to a raw pointer to a zst as a constant producing an integer. Should be trivial to do as a Mir pass and has clear semantics imo. |
This can't be done on polymorphic code though, can it? |
@arielb1 Could you show where LLVM talks about treating ~0 specially? |
I was going to fix this for the slice iterator in #46223, but I have yet to pick it up again (feel free to do that anyone). |
Thanks to @rkruppe for pointing out that this is actually a soundness issue. The following module should never panic: mod unique_token {
use std::sync::atomic::{AtomicBool, Ordering};
// With a private field so it can only be constructed inside this module
pub struct Token(());
// Stores whether we have already created a token
static GUARD : AtomicBool = AtomicBool::new(false);
impl Token {
pub fn new() -> Option<Token> {
if GUARD.compare_and_swap(false, true, Ordering::AcqRel) == false {
Some(Token(()))
} else {
None
}
}
pub fn take_two(a: &Token, b: &Token) {
// There's only ever one token so these have to be the same
assert_eq!(a as *const _, b as *const _,
"How can there be two tokes?!?");
}
}
} and yet here is how we can make it panic: use unique_token::Token;
fn main() {
let t = Token::new().expect("Couldn't even get a single token");
// make sure we can't get a second
assert!(Token::new().is_none());
let s = &[t];
let t1 = &s[0];
let t2 = s.iter().next().expect("Slice can't be empty");
Token::take_two(t1, t2);
} |
You can also get elements of slices through slice patterns, which don't squash ZST pointers to 0x1. You have a good argument for having slice iterators use the "real" address of the ZST instead of squashing it. cc @rust-lang/libs - what's the obstacle to not squashing pointers in iterators? |
Not sure whether we should tag this as I-unsound |
The iterators also play fast-and-loose with the alignment rules. For example, |
I have a proposed fix at #52206. |
slices: fix ZST slice iterators making up pointers; debug_assert alignment in from_raw_parts This fixes the problem that we are fabricating pointers out of thin air. I also managed to share more code between the mutable and shared iterators, while reducing the amount of macros. I am not sure how useful it really is to add a `debug_assert!` in libcore. Everybody gets a release version of that anyway, right? Is there at least a CI job that runs the test suite with a debug version? Fixes #42789
slices: fix ZST slice iterators making up pointers; debug_assert alignment in from_raw_parts This fixes the problem that we are fabricating pointers out of thin air. I also managed to share more code between the mutable and shared iterators, while reducing the amount of macros. I am not sure how useful it really is to add a `debug_assert!` in libcore. Everybody gets a release version of that anyway, right? Is there at least a CI job that runs the test suite with a debug version? Fixes #42789
slices: fix ZST slice iterators making up pointers; debug_assert alignment in from_raw_parts This fixes the problem that we are fabricating pointers out of thin air. I also managed to share more code between the mutable and shared iterators, while reducing the amount of macros. I am not sure how useful it really is to add a `debug_assert!` in libcore. Everybody gets a release version of that anyway, right? Is there at least a CI job that runs the test suite with a debug version? Fixes #42789
debug_assert to ensure that from_raw_parts is only used properly aligned This does not help nearly as much as I would hope because everybody uses the distributed libstd which is compiled without debug assertions. For this reason, I am not sure if this is even worth it. OTOH, this would have caught the misalignment fixed by #42789 *if* there had been any tests actually using ZSTs with alignment >1 (we have a CI runner which has debug assertions in libstd enabled), and it seems to currently [fail in the rg testsuite](https://ci.appveyor.com/project/rust-lang/rust/build/1.0.8403/job/v7dfdcgn8ay5j6sb). So maybe it is worth it, after all. I have seen the attribute `#[rustc_inherit_overflow_checks]` in some places, does that make it so that the *caller's* debug status is relevant? Is there a similar attribute for `debug_assert!`? That could even subsume `rustc_inherit_overflow_checks`: Something like `rustc_inherit_debug_flag` could affect *all* places that change the generated code depending on whether we are in debug or release mode. In fact, given that we have to keep around the MIR for generic functions anyway, is there ever a reason *not* to handle the debug flag that way? I guess currently we apply debug flags like `cfg` so this is dropped early during the MIR pipeline? EDIT: I learned from @eddyb that because of how `debug_assert!` works, this is not realistic. Well, we could still have it for the rustc CI runs and then maybe, eventually, when libstd gets compiled client-side and there is both a debug and a release build... then this will also benefit users.^^
debug_assert to ensure that from_raw_parts is only used properly aligned This does not help nearly as much as I would hope because everybody uses the distributed libstd which is compiled without debug assertions. For this reason, I am not sure if this is even worth it. OTOH, this would have caught the misalignment fixed by rust-lang/rust#42789 *if* there had been any tests actually using ZSTs with alignment >1 (we have a CI runner which has debug assertions in libstd enabled), and it seems to currently [fail in the rg testsuite](https://ci.appveyor.com/project/rust-lang/rust/build/1.0.8403/job/v7dfdcgn8ay5j6sb). So maybe it is worth it, after all. I have seen the attribute `#[rustc_inherit_overflow_checks]` in some places, does that make it so that the *caller's* debug status is relevant? Is there a similar attribute for `debug_assert!`? That could even subsume `rustc_inherit_overflow_checks`: Something like `rustc_inherit_debug_flag` could affect *all* places that change the generated code depending on whether we are in debug or release mode. In fact, given that we have to keep around the MIR for generic functions anyway, is there ever a reason *not* to handle the debug flag that way? I guess currently we apply debug flags like `cfg` so this is dropped early during the MIR pipeline? EDIT: I learned from @eddyb that because of how `debug_assert!` works, this is not realistic. Well, we could still have it for the rustc CI runs and then maybe, eventually, when libstd gets compiled client-side and there is both a debug and a release build... then this will also benefit users.^^
…r=the8472 Simplify the implementation of iterators over slices of ZSTs Currently, slice iterators over ZSTs store `end = start.wrapping_byte_add(len)`. That's slightly convenient for `is_empty`, but kinda annoying for pretty much everything else -- see bugs like rust-lang#42789, for example. This PR instead changes it to just `end = ptr::invalid(len)` instead. That's easier to think about (IMHO, at least) as well as easier to represent. `next` is still to big to get inlined into the mir-opt/pre-codegen/ tests, but if I bump the inline threshold to force it to show the whole thing, this implementation is also less MIR: ``` > git diff --numstat 241 370 tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.mir 255 329 tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.mir 184 216 tests/mir-opt/pre-codegen/slice_iter.slice_iter_mut_next_back.PreCodegen.after.mir 182 254 tests/mir-opt/pre-codegen/slice_iter.slice_iter_next.PreCodegen.after.mir ``` (That's ≈70 lines less for `Iter::next`, for example.) r? `@ghost` ~~Built atop rust-lang#111282, so draft until that lands.~~
The following test passes for slices over non-ZST, but fails for ZST:
Playpen
While pointers to ZSTs don't really matter as they will never be dereferenced, I think providing consistent addresses for the same element would still make sense.
Yet,
x2
turns out to be0x55e742ee85dc
, no idea where that address is coming from. The LLVM IR contains someundef
, but I am not sure if that always indicates a problem.Generally, the iterator's treatment of ZST's seems a little fishy. Below
rust/src/libcore/slice/mod.rs
Line 777 in 4450779
get
methods defer to.offset
to compute the address of an element of the slice.offset
is only well-defined for in-bounds accesses, yet it is used here on a dangling pointer-to-ZST, with some non-0 offset.The text was updated successfully, but these errors were encountered: