From 7a51ce3888a86cb04e3512ec3fbc33ce1e634cd3 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:13:31 -0700
Subject: [PATCH 1/8] Global panic hook + catch_unwind

---
 Cargo.lock         | 11 ++++++++
 Cargo.toml         |  1 +
 src/byond.rs       | 45 +++++++++++++++++++++++++++---
 src/lib.rs         |  5 ++++
 src/panic_hook.rs  | 68 ++++++++++++++++++++++++++++++++++++++++++++++
 src/panic_test.rs  |  6 ++++
 tests/dm/panic.dme |  9 ++++++
 7 files changed, 141 insertions(+), 4 deletions(-)
 create mode 100644 src/panic_hook.rs
 create mode 100644 src/panic_test.rs
 create mode 100644 tests/dm/panic.dme

diff --git a/Cargo.lock b/Cargo.lock
index 96b7ded4..ed4272d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -424,6 +424,16 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "dashmap"
 version = "5.3.4"
@@ -2005,6 +2015,7 @@ dependencies = [
  "base64",
  "chrono",
  "const-random",
+ "ctor",
  "dashmap",
  "dbpnoise",
  "flume",
diff --git a/Cargo.toml b/Cargo.toml
index ca3ed09a..88da4b9e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,6 +49,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 = ["acreplace", "cellularnoise", "dmi", "file", "git", "http", "json", "log", "noise", "sql", "time", "toml", "url"]
diff --git a/src/byond.rs b/src/byond.rs
index 385d5251..340adae4 100644
--- a/src/byond.rs
+++ b/src/byond.rs
@@ -42,6 +42,25 @@ 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) => {
@@ -50,8 +69,17 @@ macro_rules! byond_fn {
         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
+                }
+            })
         }
     };
 
@@ -61,6 +89,8 @@ macro_rules! byond_fn {
         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;
@@ -72,8 +102,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
+                }
+            })
         }
     };
 }
diff --git a/src/lib.rs b/src/lib.rs
index bbe40556..0526aa5c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -47,5 +47,10 @@ pub mod url;
 #[cfg(feature = "worleynoise")]
 pub mod worleynoise;
 
+mod panic_hook;
+
+#[cfg(test)]
+mod panic_test;
+
 #[cfg(not(target_pointer_width = "32"))]
 compile_error!("rust-g must be compiled for a 32-bit target");
diff --git a/src/panic_hook.rs b/src/panic_hook.rs
new file mode 100644
index 00000000..92e8e169
--- /dev/null
+++ b/src/panic_hook.rs
@@ -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);
+    }));
+}
diff --git a/src/panic_test.rs b/src/panic_test.rs
new file mode 100644
index 00000000..deefe9ce
--- /dev/null
+++ b/src/panic_test.rs
@@ -0,0 +1,6 @@
+byond_fn!(
+    fn panic_test() {
+        panic!("oh no");
+        Some("what".to_owned())
+    }
+);
diff --git a/tests/dm/panic.dme b/tests/dm/panic.dme
new file mode 100644
index 00000000..5840c052
--- /dev/null
+++ b/tests/dm/panic.dme
@@ -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, "last byond fn: panic_test"))

From 74b58d39edf469b3decabf30880ccd1d1b751960 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:20:32 -0700
Subject: [PATCH 2/8] Silence clippy

---
 src/byond.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/byond.rs b/src/byond.rs
index 340adae4..b709eddf 100644
--- a/src/byond.rs
+++ b/src/byond.rs
@@ -66,6 +66,7 @@ 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 {
@@ -86,6 +87,7 @@ macro_rules! byond_fn {
     (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 {

From 70c26f4785febf3f7fc708c74f5b66de6600ff03 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:21:02 -0700
Subject: [PATCH 3/8] Just check this, since catch_unwind might have it

---
 tests/dm/panic.dme | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/dm/panic.dme b/tests/dm/panic.dme
index 5840c052..b246e3e7 100644
--- a/tests/dm/panic.dme
+++ b/tests/dm/panic.dme
@@ -6,4 +6,4 @@
     ASSERT(fexists("rustg_panic.log"))
 
     var/contents = file2text("rustg_panic.log")
-    ASSERT(findtext(contents, "last byond fn: panic_test"))
+    ASSERT(findtext(contents, "panic_test"))

From 96c5add5cf6b6b70334d209a10db5d5a31e53906 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:21:40 -0700
Subject: [PATCH 4/8] Silence, this is necessary

---
 src/panic_test.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/panic_test.rs b/src/panic_test.rs
index deefe9ce..1c34de29 100644
--- a/src/panic_test.rs
+++ b/src/panic_test.rs
@@ -1,6 +1,8 @@
 byond_fn!(
     fn panic_test() {
         panic!("oh no");
+
+        #[allow(unreachable_code)]
         Some("what".to_owned())
     }
 );

From e1e60797c3643e49a6243e04e06866cc9d03b071 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:27:41 -0700
Subject: [PATCH 5/8] Include panic tests

---
 tests/dm-tests.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/dm-tests.rs b/tests/dm-tests.rs
index 40ed85e4..0c46eaab 100644
--- a/tests/dm-tests.rs
+++ b/tests/dm-tests.rs
@@ -18,6 +18,11 @@ fn url() {
     run_dm_tests("url");
 }
 
+#[test]
+fn panic() {
+    run_dm_tests("panic");
+}
+
 #[cfg(feature = "hash")]
 #[test]
 fn hash() {

From f06f18823fdb9c4b406107833a78a4453b96ff9c Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:33:28 -0700
Subject: [PATCH 6/8] Fix tests?

---
 Cargo.toml          | 1 +
 dmsrc/panic_test.dm | 1 +
 src/lib.rs          | 2 +-
 tests/dm-tests.rs   | 1 +
 tests/dm/panic.dme  | 2 +-
 5 files changed, 5 insertions(+), 2 deletions(-)
 create mode 100644 dmsrc/panic_test.dm

diff --git a/Cargo.toml b/Cargo.toml
index 88da4b9e..d0020701 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -78,6 +78,7 @@ worleynoise = ["rand","rayon"]
 
 # internal feature-like things
 jobs = ["flume"]
+panic_test = []
 
 [dev-dependencies]
 regex = "1"
diff --git a/dmsrc/panic_test.dm b/dmsrc/panic_test.dm
new file mode 100644
index 00000000..ca32f6a8
--- /dev/null
+++ b/dmsrc/panic_test.dm
@@ -0,0 +1 @@
+#define rustg_panic_test(...) call(RUST_G, "panic_test")()
diff --git a/src/lib.rs b/src/lib.rs
index 0526aa5c..a0b09237 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,7 +49,7 @@ pub mod worleynoise;
 
 mod panic_hook;
 
-#[cfg(test)]
+#[cfg(feature = "panic_test")]
 mod panic_test;
 
 #[cfg(not(target_pointer_width = "32"))]
diff --git a/tests/dm-tests.rs b/tests/dm-tests.rs
index 0c46eaab..d48828d4 100644
--- a/tests/dm-tests.rs
+++ b/tests/dm-tests.rs
@@ -18,6 +18,7 @@ fn url() {
     run_dm_tests("url");
 }
 
+#[cfg(feature = "panic_test")]
 #[test]
 fn panic() {
     run_dm_tests("panic");
diff --git a/tests/dm/panic.dme b/tests/dm/panic.dme
index b246e3e7..37406210 100644
--- a/tests/dm/panic.dme
+++ b/tests/dm/panic.dme
@@ -1,7 +1,7 @@
 #include "common.dm"
 
 /test/proc/panic()
-    call(RUST_G, "panic_test")()
+    rustg_panic_test()
 
     ASSERT(fexists("rustg_panic.log"))
 

From 7aba5d4e167eb0a21af3fc6b2fd1bd3972903c1f Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Mon, 24 Oct 2022 13:37:40 -0700
Subject: [PATCH 7/8] panic_test directly?

---
 dmsrc/panic_test.dm | 1 -
 tests/dm/panic.dme  | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)
 delete mode 100644 dmsrc/panic_test.dm

diff --git a/dmsrc/panic_test.dm b/dmsrc/panic_test.dm
deleted file mode 100644
index ca32f6a8..00000000
--- a/dmsrc/panic_test.dm
+++ /dev/null
@@ -1 +0,0 @@
-#define rustg_panic_test(...) call(RUST_G, "panic_test")()
diff --git a/tests/dm/panic.dme b/tests/dm/panic.dme
index 37406210..b246e3e7 100644
--- a/tests/dm/panic.dme
+++ b/tests/dm/panic.dme
@@ -1,7 +1,7 @@
 #include "common.dm"
 
 /test/proc/panic()
-    rustg_panic_test()
+    call(RUST_G, "panic_test")()
 
     ASSERT(fexists("rustg_panic.log"))
 

From 4ae0c6925743c2d4f7fa509c2ea121d4815c6754 Mon Sep 17 00:00:00 2001
From: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Date: Sat, 13 May 2023 11:20:11 -0700
Subject: [PATCH 8/8] Clippy should not be blocking

---
 .github/workflows/rust.yml | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 80db2bdf..19c5ae6e 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -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