Skip to content

Commit 3294997

Browse files
authored
Catch panics in encoding NIF results (#656)
Also adds tests for panicking in parameters, return values and the NIF function itself. Fixes #655
1 parent cc28889 commit 3294997

File tree

6 files changed

+58
-3
lines changed

6 files changed

+58
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ versions.
2424
now (#638)
2525
- API functions for Windows are correctly assigned now for NIF version 2.15 and
2626
above (#635)
27+
- Panics in encoding the result of NIF function are caught (#656)
2728

2829
### Changed
2930

rustler/src/codegen_runtime.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::ffi::CString;
44
use std::fmt;
55

6+
use crate::types::atom;
67
use crate::{Encoder, Env, OwnedBinary, Term};
78

89
// Re-export of inventory
@@ -26,10 +27,16 @@ pub unsafe trait NifReturnable {
2627

2728
unsafe impl<T> NifReturnable for T
2829
where
29-
T: crate::Encoder,
30+
T: crate::Encoder + std::panic::RefUnwindSafe,
3031
{
3132
unsafe fn into_returned(self, env: Env) -> NifReturned {
32-
NifReturned::Term(self.encode(env).as_c_arg())
33+
if let Ok(res) = std::panic::catch_unwind(|| NifReturned::Term(self.encode(env).as_c_arg()))
34+
{
35+
res
36+
} else {
37+
let term = atom::nif_panicked().as_c_arg();
38+
NifReturned::Raise(term)
39+
}
3340
}
3441
}
3542

@@ -126,7 +133,7 @@ where
126133
Err(err) => match err.downcast::<NifReturned>() {
127134
Ok(ty) => NifReturned::Term(ty.apply(env)),
128135
Err(_) => {
129-
let term = crate::types::atom::nif_panicked().as_c_arg();
136+
let term = atom::nif_panicked().as_c_arg();
130137
NifReturned::Raise(term)
131138
}
132139
},

rustler_tests/lib/rustler_test.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ defmodule RustlerTest do
148148

149149
def append_to_path(_path, _to_append), do: err()
150150

151+
def panic_in_nif(), do: err()
152+
def panic_in_encode(), do: err()
153+
def panic_in_decode(_), do: err()
154+
151155
if Helper.has_nif_version("2.16") do
152156
def perform_dyncall(_res, _a, _b, _c), do: err()
153157
end

rustler_tests/native/rustler_test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod test_list;
1010
mod test_local_pid;
1111
mod test_map;
1212
mod test_nif_attrs;
13+
mod test_panic;
1314
mod test_path;
1415
mod test_primitives;
1516
mod test_range;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use rustler::{Decoder, Encoder, Env, Term};
2+
3+
#[rustler::nif]
4+
pub fn panic_in_nif() -> i32 {
5+
panic!("panic!")
6+
}
7+
8+
struct Panicking;
9+
10+
impl Encoder for Panicking {
11+
fn encode<'a>(&self, _env: Env<'a>) -> Term<'a> {
12+
panic!("panic in encode!")
13+
}
14+
}
15+
16+
impl<'a> Decoder<'a> for Panicking {
17+
fn decode(_term: Term<'a>) -> rustler::NifResult<Self> {
18+
panic!("panic in decode!")
19+
}
20+
}
21+
22+
#[rustler::nif]
23+
pub fn panic_in_encode() -> Panicking {
24+
Panicking
25+
}
26+
27+
#[rustler::nif]
28+
pub fn panic_in_decode(_p: Panicking) -> i32 {
29+
0
30+
}

rustler_tests/test/panic_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule PanicTest do
2+
use ExUnit.Case
3+
4+
test "panics in NIFs are caught" do
5+
assert_raise ErlangError, &RustlerTest.panic_in_nif/0
6+
assert_raise ErlangError, &RustlerTest.panic_in_encode/0
7+
8+
assert_raise ErlangError, fn ->
9+
RustlerTest.panic_in_decode(:anything)
10+
end
11+
end
12+
end

0 commit comments

Comments
 (0)