diff --git a/hilti/runtime/include/exception.h b/hilti/runtime/include/exception.h index 433e82a3b..4af20e18e 100644 --- a/hilti/runtime/include/exception.h +++ b/hilti/runtime/include/exception.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -24,18 +25,43 @@ class Exception : public std::runtime_error { /** * @param desc message describing the situation */ - Exception(const std::string& desc) : Exception(Internal(), "Exception", desc) {} + Exception(const std::string& desc) : Exception(Internal(), "Exception", desc) { +#ifndef NDEBUG + _backtrace = std::make_unique(); +#endif + } /** * @param desc message describing the situation * @param location string indicating the location of the operation that failed */ - Exception(std::string_view desc, std::string_view location) : Exception(Internal(), "Exception", desc, location) {} + Exception(std::string_view desc, std::string_view location) : Exception(Internal(), "Exception", desc, location) { +#ifndef NDEBUG + _backtrace = std::make_unique(); +#endif + } Exception(); - Exception(const Exception&) = default; + + Exception(const Exception& other) + : std::runtime_error(other), _description(other._description), _location(other._location) { + if ( other._backtrace ) + _backtrace = std::make_unique(*other._backtrace); + } + Exception(Exception&&) noexcept = default; - Exception& operator=(const Exception&) = default; + Exception& operator=(const Exception& other) { + // Note: We don't copy the backtrace here, as it's not clear what + // that would mean. + if ( &other != this ) { + std::runtime_error::operator=(other); + _description = other._description; + _location = other._location; + } + + return *this; + } + Exception& operator=(Exception&&) noexcept = default; // Empty, but required to make exception handling work between library @@ -51,9 +77,9 @@ class Exception : public std::runtime_error { /** * Returns a stack backtrace captured at the time the exception was - * thrown. + * thrown, if available. Returns null if not available. */ - auto backtrace() const { return _backtrace.backtrace(); } + auto backtrace() const { return _backtrace.get(); } protected: enum Internal {}; @@ -66,7 +92,7 @@ class Exception : public std::runtime_error { std::string _description; std::string _location; - Backtrace _backtrace; + std::unique_ptr _backtrace; // null if not available }; inline std::ostream& operator<<(std::ostream& stream, const Exception& e) { return stream << e.what(); } diff --git a/hilti/runtime/src/configuration.cc b/hilti/runtime/src/configuration.cc index 9633cb4eb..2abc400c6 100644 --- a/hilti/runtime/src/configuration.cc +++ b/hilti/runtime/src/configuration.cc @@ -24,5 +24,10 @@ void configuration::set(Configuration cfg) { if ( isInitialized() ) hilti::rt::fatalError("attempt to change configuration after library has already been initialized"); +#ifndef NDEBUG + if ( cfg.show_backtraces ) + hilti::rt::warning("printing of exception backtraces enabled, but not supported in release builds"); +#endif + *detail::__configuration = std::move(cfg); } diff --git a/hilti/runtime/src/exception.cc b/hilti/runtime/src/exception.cc index 1a30eee0f..9c8d878cf 100644 --- a/hilti/runtime/src/exception.cc +++ b/hilti/runtime/src/exception.cc @@ -44,7 +44,10 @@ static void printException(const std::string& msg, const Exception& e, std::ostr if ( ! configuration::get().show_backtraces ) return; - auto bt = e.backtrace(); + if ( ! e.backtrace() ) + return; + + auto bt = e.backtrace()->backtrace(); if ( bt->empty() ) return; diff --git a/hilti/runtime/src/tests/exception.cc b/hilti/runtime/src/tests/exception.cc index 0eb60cb86..91fcbcfce 100644 --- a/hilti/runtime/src/tests/exception.cc +++ b/hilti/runtime/src/tests/exception.cc @@ -61,8 +61,13 @@ TEST_CASE("backtrace") { // - two frames from doctest's expansion of `CHECK_EQ`, and // - one frame for the current line // - three frames from the test harness to reach and expand `TEST_CASE`. -#ifdef HILTI_HAVE_BACKTRACE - CHECK_GE(Exception("description").backtrace()->size(), 7U); +#ifndef NDEBUG +#if defined(HILTI_HAVE_BACKTRACE) + CHECK_GE(Exception("description").backtrace()->backtrace()->size(), 7U); +#endif +#else + // No backtrace captured in release builds. + CHECK(! Exception("description").backtrace()); #endif } diff --git a/hilti/toolchain/src/compiler/driver.cc b/hilti/toolchain/src/compiler/driver.cc index 2c95a5792..876370d56 100644 --- a/hilti/toolchain/src/compiler/driver.cc +++ b/hilti/toolchain/src/compiler/driver.cc @@ -1167,14 +1167,20 @@ Result Driver::jitUnits() { void Driver::printHiltiException(const hilti::rt::Exception& e) { std::cerr << fmt("uncaught exception %s: %s", util::demangle(typeid(e).name()), e.what()) << std::endl; - if ( _driver_options.show_backtraces ) { - if ( auto bt = e.backtrace(); ! bt->empty() ) { - std::cerr << "backtrace:\n"; + if ( ! _driver_options.show_backtraces ) + return; - for ( const auto& s : *bt ) - std::cerr << " " << s << "\n"; - } - } + if ( ! e.backtrace() ) + return; + + auto bt = e.backtrace()->backtrace(); + if ( bt->empty() ) + return; + + std::cerr << "backtrace:\n"; + + for ( const auto& s : *bt ) + std::cerr << " " << s << "\n"; } Result Driver::initRuntime() {