Useless line number on panic in release mode

Hey y'all.

I just had a problem with some multi-threaded code. I solved it myself but figured I would share it none the less since I couldn't find a solution online. I'm sure it exists somewhere.

use std::thread;

use ndarray::array;

fn main() {
    let mut pool = vec![];
    for i in 0..4 {
        let handle = thread::spawn(move || {
            let arr = array![0, 1, 2];
            println!("{}", &arr[i]);
        });
        pool.push(handle);
    }
    pool.into_iter().for_each(|handle| handle.join().unwrap());
}
0
1
thread '<unnamed>' panicked at 'ndarray: index out of bounds', $HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/ndarray-0.15.6/src/arraytraits.rs:27:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Any { .. }', src/bin/test.rs:14:54

Of course here it is obvious that the error is happening at the array access &arr[i] because i will go out of bounds.
But rust didn't show that line as the source of the error, but a line inside ndarray:
$HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/ndarray-0.15.6/src/arraytraits.rs:27:5.
I thought this had something to do with multi-threading but actually it's just because I ran it in release mode. Running it in debug produced the expected output.

0
1
2
thread '<unnamed>' panicked at 'ndarray: index 3 is out of bounds for array of shape [3]', src/bin/test.rs:10:29
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Any { .. }', src/bin/test.rs:14:54

So for anyone running into this issue and thinking it has something to do with multi-threading... It doesn't :slight_smile:

Looks like ndarray doesn't use #[track_caller] in the Index impls to propagate the caller's location to panics.

3 Likes

Looking at the code, it is actually somewhat unexpected that the backtrace gives you the "correct" location in caller code in debug mode!

If you take a look at the relevant parts of arraytraits.rs, you can see that in release mode, the array_out_of_bounds function gets called. It's marked as #[inline(never)] in order to reduce code size and improve instruction cache locality for the happy path. But this also means that when the panic is triggered, array_out_of_bounds gets reported as the topmost stack frame. ndarray could use the #[track_caller] attribute which exists specifically to make stack traces more ergonomic.

In debug mode, on the other hand, the debug_bounds_check, which is marked as #[inline(always)], gets invoked. Moreover, the Index::index impl is #[inline] and it appears that LLVM chooses to inline both functions even in debug mode, which means the top stack frame is in user code.

Now, this is neither intentional nor guaranteed, and completely depends on what exactly LLVM decides to do, so in general one should take note of the note (heh) and run with RUST_BACKTRACE=1 to get a more informative stack trace. But this is an ndarray issue rather than a core Rust issue.

3 Likes

Thanks for all the additional info. For me RUST_BACKTRACE=1 didn't help in release mode as it didn't show show the line number in my offending code. Neither did RUST_BACKTRACE=full

If you want lineinfo in backtraces you will have to enable debuginfo. debug = 1 is enough. That enables just the lineinfo, but not variable debuginfo and things like that, saving space.

2 Likes