POC statically check if paths are panic free
Hacky way to statically check if there are panics in the function call tree within the blocks labelled with
'deny-panic' that (almost) works.
Intro
I needed something with the following poperties:
- Check at compile time if code paths are panic free
- If a path end up calling something in another crate check the other crate
- Use cargo to solve the dependencies so that I don't have to do it manually
I started experimenting with the approach used by dtolnay/no-panic. But I ended with big issues
here the full post. I also experimented a little bit with clippy but at the end I decided to
go with the below approach.
Approach
The main idea is to use the rustc_*
libraries to parse the code of a crate and look for block labelled
deny-panic
. For each founded block recursively check all the function's call. When a function end up
calling std::begin_panic
emit an error.
The above is achieved by a binary called unpanic
that can be used with cargo
with
RUSTC_WRAPPER=unpanic TARGET_CRATE=[name] cargo +nightly build
How it works
When executed, unpanic
verifies whether the crate being checked is the target crate, i.e., the one we want to analyze. If the crate is not the target, it builds it using rustc
and appends a line to ./target/no-panic/deps
containing the crate name and all rustc
arguments.
If the crate is the target crate it will build it with rustc
, then it will get the deps build info from
./target/no-panic/deps
.
Then unpanic
will collect all the blocks in the target crate labelled with deny-panic
and check
if they are panic free. If a block labelled allow-panic
is found that block it is skipped. If the skipped
block is not in the target crate an warning is emitted. Right now nested
deny-panic
are not supported: allow-panic { deny-panic {} }
will be ignored.
Test
To see unpanic
in action clone the repo go in the workspace root and run:
cargo clean && cargo +nightly build -p unpanic && RUSTC_WRAPPER=./target/debug/unpanic TARGET_CRATE=test1_bin cargo +nightly build -p test1_bin
Below an extract of the output:
OMG A PANIC
test_if_see_panics_in_imported_functions in tests/test1_bin/src/main.rs:13:1: 18:2 (#0)
function_test in tests/test1_bin/src/main.rs:16:9: 16:22 (#0)
it_panic in tests/test1_lib/src/function_test.rs:2:5: 2:20 (#0)
begin_panic in /rustc/371994e0d8380600ddda78ca1be937c7fb179b49/library/std/src/panic.rs:19:9: 19:32 (#7)
./run_test.sh
will run the tests.
Std lib
Right now unpanic
is not able to build the std lib so if a panic
is invoked by std
, unpanic
will not emit an error. This is not a big issue for my particular use case cause I can assume
infinite memory.
The best solution in my opinion is to introduce another label deny-alloc
that check for
allocations.
Main issues
Given that this is a POC everything is very hacky and there are a lot of issues.
These 2 particular things blocks me and I'm not sure how to solve them: extern issue and assoc fn
solver.
Extern issue (1)
If extern crates are not declared with extern crate [crate-name]
in the anlyzed source code, unpanic
do not works. I do not have idea of how I could solve this. I already put edition=2018
in the rustc_interface::run_compiler
configs.
Here is where I build the configs and here is where I invoke run_compiler
.
Assoc fn solver (2)
When I have to solve the path for extrern assoc functions I do I thing that seems very wrong.
Here the thing. And here a post with more details.
Other issues
Another big limitations is that right now if we have any loop in the call tree the checker panics,
I left it as a todo cause before spend time on it I wanted to have some feedback on the approach and
on the solvibility of 1 and 2.