Skip to content

Commit

Permalink
Disable capturing backtraces with HILTI exceptions in non-debug builds.
Browse files Browse the repository at this point in the history
They can be expensive to capture, and aren't used anywhere by default
unless explicitly requested.

We change it so that the exception class still remains ABI
compatible between release and debug builds. It's the compilation
settings of the code including `exception.h` that determines if a
backtrace it captured.

Closes #1565.
  • Loading branch information
rsmmr committed Oct 21, 2023
1 parent 80cdec5 commit 305588f
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 17 deletions.
40 changes: 33 additions & 7 deletions hilti/runtime/include/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include <iostream>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
Expand All @@ -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<Backtrace>();
#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<Backtrace>();
#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<Backtrace>(*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
Expand All @@ -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 {};
Expand All @@ -66,7 +92,7 @@ class Exception : public std::runtime_error {

std::string _description;
std::string _location;
Backtrace _backtrace;
std::unique_ptr<Backtrace> _backtrace; // null if not available
};

inline std::ostream& operator<<(std::ostream& stream, const Exception& e) { return stream << e.what(); }
Expand Down
5 changes: 5 additions & 0 deletions hilti/runtime/src/configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 4 additions & 1 deletion hilti/runtime/src/exception.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
9 changes: 7 additions & 2 deletions hilti/runtime/src/tests/exception.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
20 changes: 13 additions & 7 deletions hilti/toolchain/src/compiler/driver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1167,14 +1167,20 @@ Result<Nothing> 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<Nothing> Driver::initRuntime() {
Expand Down

0 comments on commit 305588f

Please sign in to comment.