From c94c4361299683729a3125dc820761493020bfce Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Fri, 5 Jun 2026 07:06:01 +0000 Subject: [PATCH 1/8] intentionally cause a crash --- src/controller.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controller.cpp b/src/controller.cpp index 30cf085..4ee3ea3 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,6 +1,7 @@ #include "controller.h" #include +#include #include #include #include @@ -680,6 +681,9 @@ bool Controller::parse_command() { } else if (cmd == ";lb") { return display_all_buffers(); + } else if (cmd == ";die") { + std::terminate(); + } else { std::string msg = "Unknown command"; view.display_message(msg, rawterm::Colors::red); From 7a17dd2b9cd741ab8f312a8d7fe6973d5f306a76 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Fri, 5 Jun 2026 14:13:53 +0000 Subject: [PATCH 2/8] Add new compiling flags to link --- CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbd8653..d199e90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_compile_options(-Wextra) add_compile_options(-pedantic) add_compile_options(-Wconversion) add_compile_options(-Wimplicit-fallthrough) +add_compile_options(-lstdc++exp) if(ENABLE_ASAN) add_compile_options(-g) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e22368e..f4d55a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,3 +27,4 @@ endif() add_executable(${PROJECT_NAME} main.cpp) target_link_libraries(${PROJECT_NAME} PUBLIC rawterm iris_src) +target_link_libraries(${PROJECT_NAME} PRIVATE stdc++exp) From d5f3848a5df59e72a17ee39925930371c7159003 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Fri, 5 Jun 2026 14:23:43 +0000 Subject: [PATCH 3/8] Implement handler logic --- src/main.cpp | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3cbd150..fb07cc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,8 @@ +#include #include #include +// To link stacktrace, we need `-lstdc++exp +#include #include #include @@ -17,6 +20,33 @@ // NOTE: Maybe post a message in the command bar too // TODO: A way of detecting if the file is already open in another iris instance +void exit_app() { + rawterm::exit_alt_screen(); + rawterm::Cursor::cursor_block(); +} + +[[noreturn]] constexpr void handler() noexcept { + exit_app(); + + std::shared_ptr err_log = spdlog::get("basic_logger"); + auto log_msg = [&](std::string_view sv) { + std::println("{}", sv); + err_log->error("{}", sv); + }; + + const std::string bt = std::to_string(std::stacktrace::current()); + + const auto eptr = std::current_exception(); + if (eptr) { + try { + std::rethrow_exception(eptr); + } catch (const std::exception& e) { log_msg(e.what()); } + } + + log_msg(std::format("\n{}", bt)); + std::exit(-1); +} + int main(int argc, char* argv[]) { CLI::App app {"Iris text editor"}; Flags flags; @@ -50,19 +80,12 @@ int main(int argc, char* argv[]) { rawterm::enter_alt_screen(); rawterm::enable_raw_mode(); - try { - std::println("Setup..."); - Controller c; - c.create_view(flags); - c.start_action_engine(); - } catch (const std::exception& e) { - rawterm::exit_alt_screen(); - rawterm::Cursor::cursor_block(); - spdlog::get("basic_logger")->info(e.what()); - std::println("{}", e.what()); - } + std::set_terminate(&handler); - rawterm::exit_alt_screen(); - rawterm::Cursor::cursor_block(); + Controller c; + c.create_view(flags); + c.start_action_engine(); + + exit_app(); return 0; } From d1ecc6cce7a6024785f7707f4d7c11fedb748023 Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sat, 6 Jun 2026 15:54:32 +0100 Subject: [PATCH 4/8] only allow intentional crash command in debug builds --- src/controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controller.cpp b/src/controller.cpp index 4ee3ea3..57efedf 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -681,8 +681,10 @@ bool Controller::parse_command() { } else if (cmd == ";lb") { return display_all_buffers(); +#ifndef NDEBUG } else if (cmd == ";die") { std::terminate(); +#endif } else { std::string msg = "Unknown command"; From 43e3be75f212ec95462634fc9a3518fab9ae7a74 Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sat, 6 Jun 2026 16:23:06 +0100 Subject: [PATCH 5/8] disable raw mode at the right time to ensure proper stacktrace printing --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index fb07cc4..109e414 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ void exit_app() { rawterm::exit_alt_screen(); rawterm::Cursor::cursor_block(); + rawterm::disable_raw_mode(); } [[noreturn]] constexpr void handler() noexcept { From 0812e489726199f8717b37328c8a006e99083c74 Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sat, 6 Jun 2026 17:07:56 +0100 Subject: [PATCH 6/8] begin integration test for stacktrace --- tests/integration/signals_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/integration/signals_test.py b/tests/integration/signals_test.py index 929d7cc..c5239f6 100644 --- a/tests/integration/signals_test.py +++ b/tests/integration/signals_test.py @@ -56,3 +56,14 @@ def test_resize_while_suspended(): # Check assert r.lines()[0].endswith("67\u00BB") + + +def test_terminate_handler(): + with TmuxRunner("bash", "--norc") as r: + r.press_and_enter("./build/src/iris t.txt") + r.await_text("READ") + + # This won't trigger for some reason + r.press(';') + r.type_str("die") + r.press("Enter") From b6d547a747de4653fa46def3f9114c9d51a975cf Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sat, 6 Jun 2026 21:31:57 +0100 Subject: [PATCH 7/8] test passes --- tests/integration/signals_test.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/integration/signals_test.py b/tests/integration/signals_test.py index c5239f6..a70315a 100644 --- a/tests/integration/signals_test.py +++ b/tests/integration/signals_test.py @@ -1,3 +1,5 @@ +import time + from setup import setup from setup import TmuxRunner @@ -36,7 +38,7 @@ def test_resize(r: TmuxRunner): def test_resize_while_suspended(): - dims = {"width": 100, "height": 24} + dims: dict[str, int] = {"width": 100, "height": 24} with TmuxRunner("bash", "--norc", **dims) as r: # Open file r.press_and_enter("./build/src/iris tests/fixture/very_long_line.txt") @@ -60,10 +62,12 @@ def test_resize_while_suspended(): def test_terminate_handler(): with TmuxRunner("bash", "--norc") as r: - r.press_and_enter("./build/src/iris t.txt") + r.press_and_enter("./build/src/iris") r.await_text("READ") - # This won't trigger for some reason - r.press(';') - r.type_str("die") - r.press("Enter") + r.iris_cmd("die") + time.sleep(0.1) + + traceback: list[str] = r.lines() + assert any(["2# std::terminate()" in line for line in traceback]) + # assert "2# std::terminate()" in traceback[4] From 31569f6a0b6739bc00867bdc3b9926f4bbda50be Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sat, 6 Jun 2026 21:34:11 +0100 Subject: [PATCH 8/8] Changelog --- CHANGELOG.md | 2 ++ tests/integration/signals_test.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4236d5..062c7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ for details * `M` now marks the current row to the given letter on the keyboard * `--version` now states `Unknown` if the git tag/hash cannot be found +* Implemented a proper termination handler to present tracebacks when iris +crashes * Resolve crash when user tries to switch top or bottom line outside of scope * Resolved crash when the user tries to `delete word` past the end of a line diff --git a/tests/integration/signals_test.py b/tests/integration/signals_test.py index a70315a..d906cf5 100644 --- a/tests/integration/signals_test.py +++ b/tests/integration/signals_test.py @@ -69,5 +69,4 @@ def test_terminate_handler(): time.sleep(0.1) traceback: list[str] = r.lines() - assert any(["2# std::terminate()" in line for line in traceback]) - # assert "2# std::terminate()" in traceback[4] + assert any(["std::terminate()" in line for line in traceback])