Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global panic hook + catch_unwind #120

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ jobs:

- uses: Swatinem/rust-cache@v2

- name: Clippy (all features)
uses: actions-rs/cargo@v1
with:
toolchain: stable
command: clippy
args: --target i686-pc-windows-msvc --all-features --locked -- -D warnings

- name: Rustfmt
uses: actions-rs/cargo@v1
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ rayon = { version = "1.5", optional = true }
dbpnoise = { version = "0.1.2", optional = true }
pathfinding = { version = "3.0.13", optional = true }
num = { version = "0.4.0", optional = true }
ctor = "0.1.26"

[features]
default = [
Expand Down Expand Up @@ -108,6 +109,7 @@ worleynoise = ["rand", "rayon"]

# internal feature-like things
jobs = ["flume"]
panic_test = []

[dev-dependencies]
regex = "1"
47 changes: 43 additions & 4 deletions src/byond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,57 @@ pub fn byond_return(value: Option<Vec<u8>>) -> *const c_char {
}
}

pub fn panicked(
name: &str,
thread_error: Box<dyn std::any::Any + Send + 'static>,
args: &[Cow<str>],
) {
let thread_error_message = if let Some(payload) = thread_error.downcast_ref::<&str>() {
payload
} else if let Some(payload) = thread_error.downcast_ref::<String>() {
payload
} else {
"unknown panic"
};

crate::panic_hook::write_to_error_log(&format!(
"panic occurred while calling {name}({}):\n{thread_error_message}",
args.join(", "),
));
}

#[macro_export]
macro_rules! byond_fn {
(fn $name:ident() $body:block) => {
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::redundant_closure_call)] // Without it, this errors
pub unsafe extern "C" fn $name(
_argc: ::std::os::raw::c_int, _argv: *const *const ::std::os::raw::c_char
) -> *const ::std::os::raw::c_char {
let closure = || ($body);
$crate::byond::byond_return(closure().map(From::from))
$crate::panic_hook::set_last_byond_fn(stringify!($name));

$crate::byond::byond_return(match std::panic::catch_unwind(|| -> Option<Vec<u8>> {
(|| { $body })().map(From::from)
}) {
Ok(output) => output,
Err(thread_error) => {
$crate::byond::panicked(stringify!($name), thread_error, &[]);
None
}
})
}
};

(fn $name:ident($($arg:ident),* $(, ...$rest:ident)?) $body:block) => {
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::redundant_closure_call)] // Without it, this errors
pub unsafe extern "C" fn $name(
_argc: ::std::os::raw::c_int, _argv: *const *const ::std::os::raw::c_char
) -> *const ::std::os::raw::c_char {
$crate::panic_hook::set_last_byond_fn(stringify!($name));

let __args = unsafe { $crate::byond::parse_args(_argc, _argv) };

let mut __argn = 0;
Expand All @@ -72,8 +104,15 @@ macro_rules! byond_fn {
let $rest = __args.get(__argn..).unwrap_or(&[]);
)?

let closure = || ($body);
$crate::byond::byond_return(closure().map(From::from))
$crate::byond::byond_return(match std::panic::catch_unwind(|| -> Option<Vec<u8>> {
(|| { $body })().map(From::from)
}) {
Ok(output) => output,
Err(thread_error) => {
$crate::byond::panicked(stringify!($name), thread_error, &__args);
None
}
})
}
};
}
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,10 @@ pub mod url;
#[cfg(feature = "worleynoise")]
pub mod worleynoise;

mod panic_hook;

#[cfg(feature = "panic_test")]
mod panic_test;

#[cfg(not(target_pointer_width = "32"))]
compile_error!("rust-g must be compiled for a 32-bit target");
68 changes: 68 additions & 0 deletions src/panic_hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::{cell::RefCell, fs::OpenOptions, io::Write};

use chrono::Utc;

thread_local! {
static LAST_BYOND_FN: RefCell<Option<String>> = RefCell::new(None);
}

pub fn write_to_error_log(contents: &str) {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open("rustg_panic.log")
.unwrap();

writeln!(file, "[{}] {}", Utc::now().format("%F %T%.3f"), contents).ok();
}

pub fn set_last_byond_fn(name: &str) {
// Be overly cautious because anything that happens in this file is all about caring about stuff that shouldn't happen
if LAST_BYOND_FN
.try_with(|cell| match cell.try_borrow_mut() {
Ok(mut cell) => {
*cell = Some(name.to_owned());
}

Err(_) => {
write_to_error_log("Failed to borrow LAST_BYOND_FN");
}
})
.is_err()
{
write_to_error_log("Failed to access LAST_BYOND_FN");
}
}

#[ctor::ctor]
fn set_panic_hook() {
std::panic::set_hook(Box::new(|panic_info| {
let mut message = "global panic hook triggered: ".to_owned();

if let Some(location) = panic_info.location() {
message.push_str(&format!("{}:{}: ", location.file(), location.line()));
}

if let Some(payload) = panic_info.payload().downcast_ref::<&str>() {
message.push_str(payload);
} else if let Some(payload) = panic_info.payload().downcast_ref::<String>() {
message.push_str(payload);
} else {
message.push_str("unknown panic");
}

LAST_BYOND_FN.with(|cell| match cell.try_borrow() {
Ok(cell) => {
if let Some(last_byond_fn) = &*cell {
message.push_str(&format!(" (last byond fn: {})", last_byond_fn));
}
}

Err(_) => {
message.push_str(" (failed to get last byond fn)");
}
});

write_to_error_log(&message);
}));
}
8 changes: 8 additions & 0 deletions src/panic_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
byond_fn!(
fn panic_test() {
panic!("oh no");

#[allow(unreachable_code)]
Some("what".to_owned())
}
);
6 changes: 6 additions & 0 deletions tests/dm-tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ fn url() {
run_dm_tests("url");
}

#[cfg(feature = "panic_test")]
#[test]
fn panic() {
run_dm_tests("panic");
}

#[cfg(feature = "hash")]
#[test]
fn hash() {
Expand Down
9 changes: 9 additions & 0 deletions tests/dm/panic.dme
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "common.dm"

/test/proc/panic()
call(RUST_G, "panic_test")()

ASSERT(fexists("rustg_panic.log"))

var/contents = file2text("rustg_panic.log")
ASSERT(findtext(contents, "panic_test"))