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/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) diff --git a/src/controller.cpp b/src/controller.cpp index 30cf085..57efedf 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,6 +1,7 @@ #include "controller.h" #include +#include #include #include #include @@ -680,6 +681,11 @@ 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"; view.display_message(msg, rawterm::Colors::red); diff --git a/src/main.cpp b/src/main.cpp index 3cbd150..109e414 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,34 @@ // 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(); + rawterm::disable_raw_mode(); +} + +[[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 +81,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; } diff --git a/tests/integration/signals_test.py b/tests/integration/signals_test.py index 929d7cc..d906cf5 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") @@ -56,3 +58,15 @@ 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") + r.await_text("READ") + + r.iris_cmd("die") + time.sleep(0.1) + + traceback: list[str] = r.lines() + assert any(["std::terminate()" in line for line in traceback])