Does RUST_BACKTRACE=full ignore #[track_caller]?

I was reading the issue Better runtime panic message for RefCell borrow and noticed "this is probably just missing a few #[track_caller]". But then I thought, sometimes it is probably useful to have the full backtrace, although the default should be short ones.

My first idea was to have a RUST_BACKTRACE=short, but while digging around before posting this I found the issue Rust never lets you know about RUST_BACKTRACE=full which led me to this parsing part of it in the code and I saw that the "short" option is already present. The book does not mention full backtraces unless I missed it, and maybe it could be useful if it taught about full backtraces too.

The main question is then, does BACKTRACE=full ignore #[track_caller] while RUST_BACKTRACE=1 honours it? If that's the case, great, there's nothing new on my original idea as things work as intended (but could be documented to avoid questions like these).

If it doesn't though (all of them honour #[track_caller]), then I guess my proposed idea boils down to RUST_BACKTRACE=full to ignore #[track_caller] because we already have a short one, which is the one honouring #[track_caller].

2 Likes

#[track_caller] has no effect at all on backtraces.

1 Like

Thanks for the answer, in that case "full" is not truly full. But I don't know how worth or easy looking into my idea would be.

I think this would be hard to do in general, since backtraces use debuginfo, while #[track_caller] is more of a "Rust ABI" thing (i.e. it lowers to an extra function argument).

Although we've moved to parsing DWARF ourselves just recently, so maybe we could flag some functions as #[track_caller] in the DWARF debuginfo (not sure LLVM allows us to do that though).

Also, presumably that wouldn't work for non-DWARF platforms (i.e. MSVC Windows targets).

cc @bjorn3 @anp

1 Like

Yes instead of omitting they would need to be flagged in some special way, although as you say different platforms may make this complicated. I guess adding this information within the mangled is a no-go (would be weird and not work for #[no_mangle], not the right place for the information, etc.).

What should None::<()>.unwrap() give as short backtrace? It currently gives:

   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/libunwind.rs:86
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:78
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src/libstd/sys_common/backtrace.rs:59
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1076
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1537
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:62
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:49
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:198
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:218
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:486
  11: rust_begin_unwind
             at src/libstd/panicking.rs:388
  12: core::panicking::panic_fmt
             at src/libcore/panicking.rs:101
  13: core::panicking::panic
             at src/libcore/panicking.rs:56
  14: core::option::Option<T>::unwrap
             at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/macros/mod.rs:10
  15: playground::main
             at src/main.rs:2
  16: std::rt::lang_start::{{closure}}
             at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libstd/rt.rs:67
  17: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:52
  18: std::panicking::try::do_call
             at src/libstd/panicking.rs:297
  19: std::panicking::try
             at src/libstd/panicking.rs:274
  20: std::panic::catch_unwind
             at src/libstd/panic.rs:394
  21: std::rt::lang_start_internal
             at src/libstd/rt.rs:51
  22: std::rt::lang_start
             at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libstd/rt.rs:67
  23: main
  24: __libc_start_main
  25: _start

I think the backtrace should start at frame 14 in this case. However how would this be handled when a #[track_caller] function calls a non-#[track_caller] function? To get the backtrace to start at frame 14, it would have to skip all non-#[track_caller] functions before the last #[track_caller] function. However what if a #[track_caller] function calls a closure provided by the caller. It probably shouldn't trim that part of the backtrace.

I think it would make sense to annotate functions in the debug info if they’re tracked. If I’m honest, I’d love for Location to be linked with debug symbols somehow to allow for stripping them from binaries without losing the ability to reconstruct the message from debug symbols.

That said, I don’t think that’s possible with LLVM today but I could be wrong.

My understanding is that, on short backtraces, any function annotated #[track_caller] would not show at all (and instead the next parent non-annotated function would show in its place). However, I don't actually know much about how #[track_caller] works precisely, or what should happen when one encounters normal-tracked-normal-tracked.

I think of #[track_caller] functions as "inlined" that never even exist or show up anywhere even if underneath there is a call.

For unwrap I would expect the unwrap to be part of the backtrace, as that is the action that the caller performed causing the panic. For the panic location, it makes sense to show the call site. However for backtraces, excluding the unwrap would make the backtrace much less useful when debuginfo is disabled, as you can't see the call site, only the function that contained the call site.

I think I was misunderstanding what #[track_caller] does. I was understanding it as a way of "ignore my current location and pretend I'm the caller instead", but re-reading the RFC on it made it obvious that all it does is "guarantees a function has access to the caller information".

So I guess what I meant is an attribute that says "ignore me on backtraces", sorry for the confusion. After understanding it, yeah it doesn't make much sense to exclude track caller's from backtraces but rather an attribute to be able to shorten them.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.