How to trace errors when using `anyhow` - rust

When using the anyhow crate errors can be conveniently bubbled up to the root of the app, where they are handled.
Sometimes, however, I want to know where the error happened and I can't find a way to do that with anyhow.
My backtrace only mentions the root:
4: mp_parser::main
at ./src/main.rs:37:5
And running RUST_BACKTRACE=full gives me a detailed stack of internal calls, but it doesn't show me where the error originated inside my own code.
As a result I'm often left uncommenting different parts of the code to figure out where the error actually took place.
Is there some way to get the original line where it occured?

I ran a few tests with the following app (all in release mode):
use anyhow::{ensure, Result};
fn main() -> Result<()> {
aa()?;
Ok(())
}
fn aa() -> Result<()> {
bb(33)?;
bb(77)?;
bb(5)?;
Ok(())
}
fn bb(p: i32) -> Result<i32> {
ensure!(p >= 10, "param not big enough!");
Ok(p)
}
I tested various combinations:
On Stable (1.58) and Nightly (1.60) toolchains.
With and without the "backtrace" feature;
Without setting RUST_BACKTRACE and setting it to 1 or full (there was no difference between 1 and full in this test).
When running the app with RUST_BACKTRACE=1 or RUST_BACKTRACE=full, we get a backtrace like this:
Error: param not big enough!
Stack backtrace:
0: anyhow::error::<impl anyhow::Error>::msg
1: an::main
2: std::sys_common::backtrace::__rust_begin_short_backtrace
3: std::rt::lang_start::{{closure}}
4: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/core/src/ops/function.rs:259:13
5: std::panicking::try::do_call
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panicking.rs:485:40
6: std::panicking::try
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panicking.rs:449:19
7: std::panic::catch_unwind
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panic.rs:136:14
8: std::rt::lang_start_internal::{{closure}}
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/rt.rs:128:48
9: std::panicking::try::do_call
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panicking.rs:485:40
10: std::panicking::try
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panicking.rs:449:19
11: std::panic::catch_unwind
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/panic.rs:136:14
12: std::rt::lang_start_internal
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/rt.rs:128:20
13: main
14: __libc_start_main
15: _start
I also tested with the "backtrace" feature on:
anyhow = { version = "1.0.52", features = ["backtrace"] }
, but that didn't seem to add any valuable information to the stack trace.
The reason we only see 1: an::main is that in this simple program the other functions are inlined.
We can experiment with disabling inlining for a particular function like this:
#[inline(never)]
fn aa() -> Result<()> {...
Now we get this:
Stack backtrace:
0: anyhow::error::<impl anyhow::Error>::msg
1: an::aa
2: std::sys_common::backtrace::__rust_begin_short_backtrace
...
That might help a bit in narrowing down the location where the error originated to a single function, but it is still far from perfect. And, obviously, it's generally not a great idea to disable inlining just for this purpose.
It seems we can do this though:
ensure!(p >= 10, "param not big enough! {}:{}", file!(), line!());
And even in release mode we can obtain info about the file & line:
Error: param not big enough! src/main.rs:18
So obviously, it is possible to build something around that, but I'm not familiar with how exactly those macros work and how much the overhead would be. Would be happy if someone could shed more light on that.
As suggested by Rodrigo, I also tried this:
[profile.release]
debug = true
The result looks great:
Stack backtrace:
0: anyhow::error::<impl anyhow::Error>::msg
at /home/xyz/.cargo/registry/src/github.com-1ecc6299db9ec823/anyhow-1.0.52/src/error.rs:79:36
1: an::bb
at ./src/main.rs:18:5
2: an::aa
at ./src/main.rs:13:2
3: an::main
at ./src/main.rs:6:2
4: core::ops::function::FnOnce::call_once
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/core/src/ops/function.rs:227:5
5: std::sys_common::backtrace::__rust_begin_short_backtrace
at /rustc/5e57faa78aa7661c6000204591558f6665f11abc/library/std/src/sys_common/backtrace.rs:123:18
...
The binary size grew up by 16% as a result of this.
Setting debug = 1 yields the same stack-trace as debug = true (which BTW is the same as debug = 2), but with only 6% bigger binary size, compared to the default debug = 0
I haven't tested if/how that setting affects performance.

Related

How to handle "attempted to leave type linked_hash_map::Node uninitialized" when using the hjson crate?

I want to use the hjson format with my program. However, it seems the crate is pretty old. An updated fork is at https://github.com/shi-yan/hjson-rust, which is what I used in my program. Below is my testing program.
# Cargo.toml
[dependencies]
hjson = { git = "https://github.com/shi-yan/hjson-rust", branch = "master" }
serde = "1.0.102"
serde-hjson = "0.9.1"
//! src/main.rs
use serde_hjson::{Value, Map};
fn main() {
let data = r###"
{
name: test3
tag : username, password
}
"###;
let mut xx: Map<String, Value> = serde_hjson::from_str(&data).unwrap();
}
When I run it, I got the following error:
thread 'main' panicked at 'attempted to leave type `linked_hash_map::Node<alloc::string::String, value::Value>` uninitialized, which is invalid', C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\linked-hash-map-0.3.0\src\lib.rs:203:52
stack backtrace:
0: std::panicking::begin_panic_handler
at /rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library\std\src\panicking.rs:584
1: core::panicking::panic_fmt
at /rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library\core\src\panicking.rs:142
2: core::panicking::panic
at /rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library\core\src\panicking.rs:48
3: core::mem::uninitialized
at /rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f\library\core\src\mem\mod.rs:685
4: linked_hash_map::LinkedHashMap<alloc::string::String,enum$<serde_hjson::value::Value>,std::collections::hash::map::RandomState>::insert<alloc::string::String,enum$<serde_hjson::value::Value>,std::collections::hash::map::RandomState>
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\linked-hash-map-0.3.0\src\lib.rs:203
5: linked_hash_map::serde::impl$2::visit_map<alloc::string::String,enum$<serde_hjson::value::Value>,serde_hjson::de::MapVisitor<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_iter::closure_env$2<core::iter::adapters::map::Ma
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\linked-hash-map-0.3.0\src\serde.rs:72
6: serde_hjson::de::Deserializer<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_iter::closure_env$2<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_slice::closure_env$0<linked_hash_map::Linke
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\de.rs:151
7: serde_hjson::de::impl$1::deserialize<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_iter::closure_env$2<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_slice::closure_env$0<linked_hash_map
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\de.rs:443
8: serde_hjson::de::impl$1::deserialize_map<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_iter::closure_env$2<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_slice::closure_env$0<linked_hash
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\forward.rs:35
9: linked_hash_map::serde::impl$3::deserialize<alloc::string::String,enum$<serde_hjson::value::Value>,serde_hjson::de::Deserializer<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_iter::closure_env$2<core::iter::adapters::map
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\linked-hash-map-0.3.0\src\serde.rs:88
10: serde_hjson::de::from_iter<core::iter::adapters::map::Map<core::slice::iter::Iter<u8>,serde_hjson::de::from_slice::closure_env$0<linked_hash_map::LinkedHashMap<alloc::string::String,enum$<serde_hjson::value::Value>,std::collections::hash::map::RandomState
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\de.rs:813
11: serde_hjson::de::from_slice<linked_hash_map::LinkedHashMap<alloc::string::String,enum$<serde_hjson::value::Value>,std::collections::hash::map::RandomState> >
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\de.rs:846
12: serde_hjson::de::from_str<linked_hash_map::LinkedHashMap<alloc::string::String,enum$<serde_hjson::value::Value>,std::collections::hash::map::RandomState> >
at C:\Users\user1\.cargo\registry\src\github.com-1ecc6299db9ec823\serde-hjson-0.9.1\src\de.rs:853
13: test3::main
at .\src\main.rs:13
14: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
at /rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f\library\core\src\ops\function.rs:248
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `target\debug\test3.exe` (exit code: 101)
Process finished with exit code 101
I have no idea what's happening here. Could anyone give me some help?
In short: the version you're using is unsound, and std converted this unsoundness to panic.
You can see that the panic originates inside std::mem::uninitialized. This panic shows that the type it was trying to create has invalid bit patterns (since it contains the corresponding key, which in your case is String, and String has a non-null pointer field), therefore having an uninitialized value of this type is UB, which panic prevents. There's nothing you can do while remaining on the same versions of dependencies - that's the bug in linked_hash_map.
Now, what you can do?
You can disable the preserve-order feature of serde_hjson, which is the reason for pulling in linked_hash_map. This is done by using the following line in your Cargo.toml:
serde-hjson = { version = "0.9.1", default-features = false }
In this case you, obviously, will lose the order of keys in your maps, but I assume that they're not so important.
Or you can move to the fork of serde_hjson, used by nushell - that is, to nu-json; it uses the updated version of linked_hash_map, which doesn't have this unsoundness.

Unable to trace an error from the backtrace caused by Serde in a Rust app

I have a Rust web on a remote server which I run as:
RUST_BACKTRACE=1 nohup /home/my_user/app123 &
When it goes down and I check nohup.log, I see only this:
04:52:22 [WARN] unexpected chunk when body cannot write
04:52:23 [WARN] unexpected chunk when body cannot write
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ErrorImpl { code: EofWhileParsingValue, line: 1, column: 0 }', /checkout/src/libcore/result.rs:906:4
stack backtrace:
0: <std::time::Instant as core::fmt::Debug>::fmt
1: <std::time::Instant as core::fmt::Debug>::fmt
2: <std::time::Instant as core::fmt::Debug>::fmt
3: <std::time::Instant as core::fmt::Debug>::fmt
4: <std::time::Instant as core::fmt::Debug>::fmt
5: <std::time::Instant as core::fmt::Debug>::fmt
6: <std::time::Instant as core::fmt::Debug>::fmt
7: <std::time::Instant as core::fmt::Debug>::fmt
8: <core::cell::BorrowMutError as core::fmt::Debug>::fmt
9:
10:
11:
12:
13: <hyper::version::HttpVersion as core::fmt::Debug>::fmt
14:
15: <std::time::duration::Duration as core::fmt::Debug>::fmt
16: <std::time::Instant as core::fmt::Debug>::fmt
17:
18: <unknown>
How can I trace the error? It's something related to Serde JSON, but what exactly? There are many places in my app where it could occur.
There aren't any line numbers in your stack backtrace so the binary running on your server must have been built without debug symbols. As described in How to get a release build with debugging information when using cargo?, add the following section to Cargo.toml of your main binary:
[profile.release]
debug = true
This will include filename and line number information in the binary that can be printed when a panic occurs, making your stack backtraces more useful in the future.
ErrorImpl { code: EofWhileParsingValue, line: 1, column: 0 }
For now, the best we can tell without a backtrace is that you tried to parse an empty JSON string. For example the following triggers the same error.
extern crate serde_json;
fn main() {
let _: u64 = serde_json::from_str("").unwrap();
}
As a reminder, Result::unwrap() is not an appropriate way to handle errors in a production application. ;-)

gfx-rs assertion failure when trying to draw to a texture render target

I'm trying to draw to an offscreen render target
type ColorFormat = gfx::format::Rgba8;
gfx_defines! {
pipeline terrain {
// snip vbuffer / uniforms
out: gfx::RenderTarget<ColorFormat> = "f_color",
selection: gfx::RenderTarget<ColorFormat> = "f_selection",
}
}
with a texture render target set to the same size as my window
let builder = glutin::WindowBuilder::new()
.with_dimensions(1024, 768);
let (_, _, selection_target) = factory.create_render_target(1024, 768).unwrap();
let mut pd_terrain = terrain::Data {
// snip
out: color_view.clone(),
selection: selection_target.clone(),
};
The code compiles fine, but at runtime I get a panic message that says something about dimensions
thread 'main' panicked at 'assertion failed: self.dimensions.map(|d| d == dim).unwrap_or(true)', /home/larvyde/.cargo/registry/src/github.com-1ecc6299db9ec823/gfx_core-0.8.2/src/pso.rs:293:8
The code works if I leave the selection target out of the pipeline.
My understanding is that it's complaining that the selection render target's dimensions don't match the color buffer's, but since both are set to the window dimensions, they should have been the same. So what is wrong here? Am I missing an initialization step or is my problem something else entirely?
EDIT: the libraries and versions I'm using are as follows
[dependencies]
cgmath = "0.16"
gfx = "0.17"
gfx_window_glutin = "0.20"
glutin = "0.12"
running with RUST_BACKTRACE=1 gives this:
0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::_print
at /checkout/src/libstd/sys_common/backtrace.rs:69
2: std::panicking::default_hook::{{closure}}
at /checkout/src/libstd/sys_common/backtrace.rs:58
at /checkout/src/libstd/panicking.rs:381
3: std::panicking::default_hook
at /checkout/src/libstd/panicking.rs:397
4: std::panicking::rust_panic_with_hook
at /checkout/src/libstd/panicking.rs:577
5: std::panicking::begin_panic
at /checkout/src/libstd/panicking.rs:538
6: <gfx_core::pso::PixelTargetSet<R>>::set_dimensions
at ./<panic macros>:3
7: <gfx_core::pso::PixelTargetSet<R>>::add_color
at /home/larvyde/.cargo/registry/src/github.com-1ecc6299db9ec823/gfx_core-0.8.2/src/pso.rs:274
8: <gfx::pso::target::RenderTarget<T> as gfx::pso::DataBind<R>>::bind_to
at /home/larvyde/.cargo/registry/src/github.com-1ecc6299db9ec823/gfx-0.17.1/src/pso/target.rs:130
9: <thera::terrain::Data<R> as gfx::pso::PipelineData<R>>::bake_to
at ./<gfx_pipeline_inner macros>:99
10: <gfx::encoder::Encoder<R, C>>::draw
at /home/larvyde/.cargo/registry/src/github.com-1ecc6299db9ec823/gfx-0.17.1/src/encoder.rs:537
11: thera::main
at src/main.rs:155
12: __rust_maybe_catch_panic
at /checkout/src/libpanic_unwind/lib.rs:99
13: std::rt::lang_start
at /checkout/src/libstd/panicking.rs:459
at /checkout/src/libstd/panic.rs:361
at /checkout/src/libstd/rt.rs:59
14: main
15: __libc_start_main
16: _start
The dimension is more than just a width and height, it also includes a depth. See https://docs.rs/gfx/0.17.1/gfx/handle/struct.DepthStencilView.html#method.get_dimensions. This must also be the same between the texture and target.
For reference, I hit the same assert in my code when using gfx_glpyh's draw_queued function. The function has the following signature
pub fn draw_queued<C, T, D>(
&mut self,
encoder: &mut Encoder<R, C>,
target: &RenderTargetView<R, T>,
depth_target: &DepthStencilView<R, D>
) -> Result<(), String>
The issue in my case was the render target (texture) had a depth of 0, but the depth_target (from my screen) had a depth of 1. To fix the issue I added the following to my pipeline definition
depth: gfx::DepthTarget<Depth> = gfx::preset::depth::PASS_TEST,
and created the following in my initialization passed to ::Data
let _depth = renderer.factory.create_depth_stencil_view_only(w as u16, h as u16).unwrap();
Using this depth instead of the one from gfx_window_glutin::init fixed the crash. I hope this guides you to a similar solution.

Groovy - Type checking in script not working as expected

I have a Groovy application in which I allow the user to add custom behavior via Groovy scripts. I include those scripts via GroovyShell and type check them via Type Checking Extensions. The full code of how I include the script in my application is:
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked)
)
def shell = new GroovyShell(config)
shell.evaluate(new File("path/to/some/file.groovy"))
This works fine. However, type checking in the script seems to be seriously broken. For example, I can include the following scripts without any complaint from the compiler:
String test = getTestValue() // automatic conversion from Integer to String. But WHY?
println "The value is $test" // shows as "The value is 0" on the console
private Integer getTestValue(){
return 0
}
I can even go further than that. When creating a class inside the script, I can assign it to a String without any error:
String y = new Test()
println y // shows Test#somenr on the console
class Test { }
Other type checks do work. I have not discovered any logic behind it yet, so any pointers in the right direction are greatly appreciated.
If in doubt, disasm. This is the bit around a call similar to yours: String x = new T():
0: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_1
4: aload_1
5: ldc #40 // int 1
7: aaload
8: ldc #42 // class T
10: invokeinterface #46, 2 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callConstructor:(Ljava/lang/Object;)Ljava/lang/Object;
15: invokestatic #52 // Method org/codehaus/groovy/runtime/typehandling/ShortTypeHandling.castToString:(Ljava/lang/Object;)Ljava/lang/String;
18: checkcast #54 // class java/lang/String
So this is the culprit for that cast. This seems also to hold true for #TypeChecked/#CompileStatic.
This is most likely a bug in the Static Type Checker. When LHS of the expression is a String variable, a conversion invoking ShortTypeHandling.castToString() is applied to the RHS.
This holds true as of Groovy 2.4.13.

"panicked at 'assertion failed: begin <= end" when using map

I've just started to touch Rust.
fn main() {
let s = "aaabbb\naaaccc".to_string();
let a: Vec<&str> = s.split('\n').map(|s| s.slice_chars(3, s.len())).collect();
assert_eq!(a, vec!["bbb", "ccc"]);
}
While above code works, this doesn't. It dies at map.
#![feature(process, collections)]
use std::process::Command;
fn main() {
let output = Command::new("git").args(&["status", "--porcelain"]).output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});
let s = String::from_utf8_lossy(output.stdout.as_slice()).to_string();
let a: Vec<&str> = s.split('\n').map(|s| s.slice_chars(3, s.len())).collect();
}
These are backtraces.
$ RUST_BACKTRACE=1 cargo run
Running `target/hello_world`
thread '<main>' panicked at 'assertion failed: begin <= end', /Users/rustbuild/src/rust-buildbot/slave/nightly-dist-rustc-mac/build/src/libcore/str/mod.rs:1478
stack backtrace:
1: 0x1091266b3 - sys::backtrace::write::hc8e3cee73e646c590nC
2: 0x10912ba0e - panicking::on_panic::h00b47941f5bc8a02HOL
3: 0x109110de8 - rt::unwind::begin_unwind_inner::h539538ef7f909326UvL
4: 0x1091115fe - rt::unwind::begin_unwind_fmt::h7ee8242816be0431quL
5: 0x10912b29e - rust_begin_unwind
6: 0x10914a487 - panicking::panic_fmt::hbdb6961ecc952cf7cSv
7: 0x10914a35a - panicking::panic::h2860b801a6212420fQv
8: 0x10914bfd1 - str::str.StrExt::slice_chars::hb48fc0a9452c1b98PGD
9: 0x109110791 - str::StrExt::slice_chars::h13298185343564271120
10: 0x10910fe14 - main::closure.2239
11: 0x10910fd13 - iter::Map<I, F>::do_map::h17245677587133977247
12: 0x10910e7dd - iter::Map<I, F>.Iterator::next::h11146527951811133470
13: 0x10910dc39 - vec::Vec<T>.FromIterator<T>::from_iter::h11956274897938765189
14: 0x10910d8bf - iter::IteratorExt::collect::h15101737251385262689
15: 0x109106f22 - main::h2fa2dff98d35cbb8faa
16: 0x10912d279 - rust_try_inner
17: 0x10912d266 - rust_try
18: 0x10912c1f2 - rt::lang_start::h660a0b4ce4c9ac40HIL
19: 0x109106f9f - main
Thanks for the help!
$ rustc --version
rustc 1.0.0-nightly (522d09dfe 2015-02-19) (built 2015-02-20)
You are going outside the bounds of your string. Here's an example:
fn main() {
let s = "a";
let s2 = s.slice_chars(3, s.len());
}
I don't really know why you are doing this slicing, so it's hard to recommend a better solution.
I guess this is because the output of git doesn't have the form you expect: it is probably because there is a line that has len() less than 3 in s.slice_chars(3, s.len()), and that is invalid for that function. So, you'll need to ensure you're understanding the format correctly, and/or e.g. bracket the call in an if, e.g. if ... { s.slice_chars(3, s.len()) } else { "" }.
Also, there's a domain mismatch here: slice_chars operates on codepoints, but s.len() returns the number of bytes in the string. Bytes and codepoints aren't necessarily equivalent (in fact, they are only equivalent in UTF-8 when the string is purely ASCII), so you should either use char_len (to use codepoints everywhere), or use s[3..] (to use bytes everywhere).
Your code will fail if the program's output contains lines that are less than 3 characters long (for example, lines that contain only newlines).
You haven't described what you're trying to do, but one possible solution is to filter out lines that have less than 3 characters:
#![feature(process, collections)]
use std::process::Command;
fn main() {
let output = Command::new("git").args(&["status", "--porcelain"]).output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});
let s = String::from_utf8_lossy(output.stdout.as_slice()).to_string();
let a: Vec<&str> = s.split('\n')
.filter(|s| s.len() >= 3)
.map(|s| s.slice_chars(3, s.len()))
.collect();
}

Resources