rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.09k stars 12.79k forks source link

boxed iterators cause a stack overflow #17878

Closed BurntSushi closed 10 years ago

BurntSushi commented 10 years ago

This code used to work (I'm not sure when it stopped working, but it was in the last few weeks I think):

trait MyIter<A> : Iterator<A> {}
impl<A, I: Iterator<A>> MyIter<A> for I {}

impl<A> Iterator<A> for Box<MyIter<A>+'static> {
    fn next(&mut self) -> Option<A> { self.next() }
}

fn main() {
    let xs = vec![0u, 1, 2, 3];
    let mut boxed_iter = box xs.into_iter() as Box<MyIter<uint>+'static>;
    for x in boxed_iter { println!("{}", x); }
}

When I compile and run that, I get:

[andrew@Liger rust] rustc -g scratch.rs && ./scratch
task '<main>' has overflowed its stack
Illegal instruction (core dumped)

valgrind shows that it's getting stuck in an infinite loop inside next:

[andrew@Liger rust] rustc -g scratch.rs && ./scratch
task '<main>' has overflowed its stack
Illegal instruction (core dumped)
[andrew@Liger rust] rustc -g scratch.rs && valgrind ./scratch
==20580== Memcheck, a memory error detector
==20580== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==20580== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==20580== Command: ./scratch
==20580== 
task '<main>' has overflowed its stack
==20580== valgrind: Unrecognised instruction at address 0x15acf0.
==20580==    at 0x15ACF0: rust_stack_exhausted (in /home/andrew/tmp/rust/scratch)
==20580==    by 0x1139A0: ??? (in /home/andrew/tmp/rust/scratch)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580== Your program just tried to execute an instruction that Valgrind
==20580== did not recognise.  There are two possible reasons for this.
==20580== 1. Your program has a bug and erroneously jumped to a non-code
==20580==    location.  If you are running Memcheck and you just saw a
==20580==    warning about a bad jump, it's probably your program's fault.
==20580== 2. The instruction is legitimate but Valgrind doesn't handle it,
==20580==    i.e. it's Valgrind's fault.  If you think this is the case or
==20580==    you are not sure, please let us know and we'll try to fix it.
==20580== Either way, Valgrind will now raise a SIGILL signal which will
==20580== probably kill your program.
==20580== 
==20580== Process terminating with default action of signal 4 (SIGILL)
==20580==  Illegal opcode at address 0x15ACF0
==20580==    at 0x15ACF0: rust_stack_exhausted (in /home/andrew/tmp/rust/scratch)
==20580==    by 0x1139A0: ??? (in /home/andrew/tmp/rust/scratch)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580==    by 0x113890: Box$LT$MyIter$LT$A$GT$$x2b$x20$x27static$GT$.Iterator$LT$A$GT$::next::h9056361317304617949 (scratch.rs:6)
==20580== 
==20580== HEAP SUMMARY:
==20580==     in use at exit: 592 bytes in 9 blocks
==20580==   total heap usage: 11 allocs, 2 frees, 640 bytes allocated
==20580== 
==20580== LEAK SUMMARY:
==20580==    definitely lost: 0 bytes in 0 blocks
==20580==    indirectly lost: 0 bytes in 0 blocks
==20580==      possibly lost: 0 bytes in 0 blocks
==20580==    still reachable: 592 bytes in 9 blocks
==20580==         suppressed: 0 bytes in 0 blocks
==20580== Rerun with --leak-check=full to see details of leaked memory
==20580== 
==20580== For counts of detected and suppressed errors, rerun with: -v
==20580== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Illegal instruction (core dumped)

So did something change that affects the resolution of self.next()?

FYI, quickcheck uses this trick to define an Arbitrary trait. Maybe there's another way?

reem commented 10 years ago

@BurntSushi this is due to a change in method lookup which causes the method reachable in the fewest derefs to be used. You just need to change to (**self).next() to ensure you are calling the method on the underlying iterator.

BurntSushi commented 10 years ago

@reem Do'h. I tried (*self).next() too. **self works great. Sorry for the noise!

reem commented 10 years ago

@BurntSushi I was hit by the exact same issue a week ago, no worries!