I was trying to implement IntoIterator
for [T; N]
. I wrote a completely safe version using Default
and swap
(PlayPen). Then I ported it to use uninitialized
, ptr::copy
, Drop
and forget
(PlayPen).
My Iterator struct looks like this:
struct IntoIter<T> {
inner: Option<[T; N]>,
i: usize,
}
impl<T> Iterator for IntoIter<T> { ... }
Since I did not want to create an Iterator struct per value of N
, I changed the struct to
struct IntoIter<U> {
inner: Option<U>,
i: usize,
}
impl<T> Iterator for IntoIter<[T; N]> { ... }
Obviously I had to adjust the Iterator
and Drop
implementations (PlayPen).
But now I somehow introduced undefined behavior. Panics happen or not depending on println
s, optimization levels or zodiacal signs.
thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is 139924442675478', <anon>:25
thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is 140451355506257', <anon>:25
application terminated abnormally with signal 4 (Illegal instruction)
Either my second implementation already exhibits undefined behavior, or there is a difference between the second and third implementations. Looking at the generated (not optimized) LLVM-IR, I found that the only fundamental differences happen in the third version which ends up with [[Box<i32>; 5]; 5]
types. I can see how I might accidentally create such a type, but I specifically checked the third version for such a mistake and cannot find it.
I believe that you are hitting some bug with #[unsafe_destructor]
. I reduced your code to this:
#![feature(unsafe_destructor)]
struct IntoIter<U> {
inner: Option<U>,
}
impl<T> Iterator for IntoIter<[T; 8]> {
type Item = T;
fn next(&mut self) -> Option<T> { None }
}
#[unsafe_destructor]
impl<T> Drop for IntoIter<[T; 8]> {
fn drop(&mut self) {
// destroy the remaining elements
for _ in self.by_ref() {}
unsafe { std::intrinsics::forget(self.inner.take()) }
}
}
fn main() {
let arr = [1; 8];
IntoIter { inner: Some(arr) };
}
I then compiled (rustc -g unsafe.rs
) and ran it in rust-lldb
. I set a breakpoint on the drop implementation and printed out self
:
(lldb) p self
(unsafe::IntoIter<[[i32; 8]; 8]> *) $0 = &0x7fff5fbff568
You can see that it thinks that the type parameter is an array of arrays, just like you noticed. At this point, we are going to trash memory if we actually drop. I believe that Rust still zeroes memory on drop, so we could possibly be writing zeroes all over some arbitrary chunk of memory.
For good measure:
rustc --verbose --version
rustc 1.0.0-dev (cfea8ec41 2015-03-10) (built 2015-03-10)
binary: rustc
commit-hash: cfea8ec41699e25c8fb524d625190f0cb860dc71
commit-date: 2015-03-10
build-date: 2015-03-10
host: x86_64-apple-darwin
release: 1.0.0-dev
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With