Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.14)
cmake_minimum_required(VERSION 3.15...4.0)

include(cmake/PreventInSourceBuilds.cmake)

Expand Down Expand Up @@ -156,6 +156,14 @@ target_sources(
src/platform/dbghelp_utils.cpp
)

if(HAS_CXX20_MODULES)
target_sources(
${target_name} PUBLIC
FILE_SET CXX_MODULES
FILES "src/cpptrace_module.cpp"
)
endif()

target_include_directories(
${target_name}
PUBLIC
Expand Down
13 changes: 13 additions & 0 deletions cmake/Autoconfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ else()
check_support(HAS_STACKWALK has_stackwalk.cpp "" "dbghelp" "")
endif()

if(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
# check_cxx_source_compiles doesn't support modules (yet?) so we need to drop
# to a raw try_compile.
try_compile(
HAS_CXX20_MODULES
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this test will pass for much older versions of clang than I was expecting, all the way back to clang 10 while I'm under the impression the module implementation only got more feature-complete and stable recently https://gcc.godbolt.org/z/4r73zGxd6. On the other hand, it won't work for any version of gcc because of -fmodules (which might be ok, not sure how stable gcc is yet but I'm under the impression they're at least close to feature-complete / stability). Thoughts on how best to approach this? I'm not sure what the best default behavior is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's going too far back. CMake support for modules is only very recent, so I guess we could gate on compiler versions?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking compiler version might be more robust yeah (but more of a hassle to check)

SOURCES_TYPE CXX_MODULE
SOURCES "${CMAKE_CURRENT_LIST_DIR}/has_modules.cpp"
CXX_STANDARD 20
CXX_STANDARD_REQUIRED Yes
CXX_EXTENSIONS Yes
)
endif()

if(NOT WIN32 OR MINGW)
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}")
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
Expand Down
1 change: 1 addition & 0 deletions cmake/InstallRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ install(
ARCHIVE #
COMPONENT ${package_name}_development
INCLUDES #
FILE_SET CXX_MODULES
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with this option but I'll trust this does something reasonable :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This installs the module interface unit :)

DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)

Expand Down
4 changes: 4 additions & 0 deletions cmake/has_modules.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export module cpptrace;

int main()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note main is not by current standard allowed to be attached to a named module and at least GCC15 will diagnose this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised this worked at all. The correct thing to do was to add -fsyntax-only to the try_compile job, but I think this file is redundant now anyway.

{}
18 changes: 1 addition & 17 deletions include/cpptrace/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define CPPTRACE_EXCEPTIONS_HPP

#include <cpptrace/basic.hpp>
#include <cpptrace/exceptions_macros.hpp>

#include <exception>
#include <system_error>
Expand Down Expand Up @@ -195,23 +196,6 @@ CPPTRACE_BEGIN_NAMESPACE
[[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0);
CPPTRACE_END_NAMESPACE

// Exception wrapper utilities
#define CPPTRACE_WRAP_BLOCK(statements) do { \
try { \
statements \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(); \
} \
} while(0)

#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
try { \
return expression; \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(1); \
} \
} ()

#ifdef _MSC_VER
#pragma warning(pop)
#endif
Expand Down
21 changes: 21 additions & 0 deletions include/cpptrace/exceptions_macros.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef CPPTRACE_EXCEPTIONS_MACROS_HPP
#define CPPTRACE_EXCEPTIONS_MACROS_HPP

// Exception wrapper utilities
#define CPPTRACE_WRAP_BLOCK(statements) do { \
try { \
statements \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(); \
} \
} while(0)

#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
try { \
return expression; \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(1); \
} \
} ()

#endif // CPPTRACE_EXCEPTIONS_MACROS_HPP
53 changes: 1 addition & 52 deletions include/cpptrace/from_current.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,7 @@
#endif

#include <cpptrace/basic.hpp>

// https://godbolt.org/z/4MsT6KqP1
#ifdef _MSC_VER
#define CPPTRACE_UNREACHABLE() __assume(false)
#else
#define CPPTRACE_UNREACHABLE() __builtin_unreachable()
#endif

// https://godbolt.org/z/7neGPEche
// gcc added support in 4.8 but I'm too lazy to check the minor version
#if defined(__GNUC__) && (__GNUC__ < 5)
#define CPPTRACE_NORETURN __attribute__((noreturn))
#else
#define CPPTRACE_NORETURN [[noreturn]]
#endif
#include <cpptrace/from_current_macros.hpp>

CPPTRACE_BEGIN_NAMESPACE
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
Expand Down Expand Up @@ -100,39 +86,7 @@ CPPTRACE_BEGIN_NAMESPACE
inline void nop(int) {}
#endif
}
CPPTRACE_END_NAMESPACE

#ifdef _MSC_VER
#define CPPTRACE_TYPE_FOR(param) \
::cpptrace::detail::argument<void(param)>::type
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
// exception handling (try/catch) in the same function."
#define CPPTRACE_TRY \
try { \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCH(param) \
}(); \
} __except(::cpptrace::detail::exception_filter<CPPTRACE_TYPE_FOR(param)>(GetExceptionInformation())) {} \
}(); \
} catch(param)
#else
#define CPPTRACE_UNWIND_INTERCEPTOR_FOR(param) \
::cpptrace::detail::unwind_interceptor_for<void(param)>
#define CPPTRACE_TRY \
try { \
try {
#define CPPTRACE_CATCH(param) \
} catch(const CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)&) { \
CPPTRACE_UNREACHABLE(); \
/* force instantiation of the init-er */ \
::cpptrace::detail::nop(CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)::init); \
} \
} catch(param)
#endif

CPPTRACE_BEGIN_NAMESPACE
namespace detail {
template<typename R, typename Arg>
Arg get_callable_argument_helper(R(*) (Arg));
Expand Down Expand Up @@ -194,9 +148,4 @@ CPPTRACE_BEGIN_NAMESPACE
}
CPPTRACE_END_NAMESPACE

#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
#define TRY CPPTRACE_TRY
#define CATCH(param) CPPTRACE_CATCH(param)
#endif

#endif
54 changes: 54 additions & 0 deletions include/cpptrace/from_current_macros.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef CPPTRACE_FROM_CURRENT_MACROS_HPP
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, is there a sensible way to make either of the following work instead of a new macros header?

#include <cpptrace/from_current.hpp>
import cpptrace;
// or
import cpptrace;
#include <cpptrace/from_current.hpp>

If a macros header is the best way to do this I think it's good with me - and I'm guessing if so other libraries might follow the same pattern. But I'd love to be able to keep #include <cpptrace/from_current.hpp> under modules if possible as that is currently in a sense part of the library interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do the former, but not the latter (yet). Having said that, including cpptrace/from_current.hpp will undo some or all of the hard work that modules is accomplishing. I'm not aware of a better way to import macros from a module library than to do

#include <cpptrace/from_current_macros.hpp>

import cpptrace;

Once support for modules stabilises, I'd recommend the docs encourage header users not include the _macros.hpp headers, and instead just stick with the original headers (which export the macros).

#define CPPTRACE_FROM_CURRENT_MACROS_HPP

// https://godbolt.org/z/4MsT6KqP1
#ifdef _MSC_VER
#define CPPTRACE_UNREACHABLE() __assume(false)
#else
#define CPPTRACE_UNREACHABLE() __builtin_unreachable()
#endif

// https://godbolt.org/z/7neGPEche
// gcc added support in 4.8 but I'm too lazy to check the minor version
#if defined(__GNUC__) && (__GNUC__ < 5)
#define CPPTRACE_NORETURN __attribute__((noreturn))
#else
#define CPPTRACE_NORETURN [[noreturn]]
#endif

#ifdef _MSC_VER
#define CPPTRACE_TYPE_FOR(param) \
::cpptrace::detail::argument<void(param)>::type
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
// exception handling (try/catch) in the same function."
#define CPPTRACE_TRY \
try { \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCH(param) \
}(); \
} __except(::cpptrace::detail::exception_filter<CPPTRACE_TYPE_FOR(param)>(GetExceptionInformation())) {} \
}(); \
} catch(param)
#else
#define CPPTRACE_UNWIND_INTERCEPTOR_FOR(param) \
::cpptrace::detail::unwind_interceptor_for<void(param)>
#define CPPTRACE_TRY \
try { \
try {
#define CPPTRACE_CATCH(param) \
} catch(const CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)&) { \
CPPTRACE_UNREACHABLE(); \
/* force instantiation of the init-er */ \
::cpptrace::detail::nop(CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)::init); \
} \
} catch(param)
#endif

#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
#define TRY CPPTRACE_TRY
#define CATCH(param) CPPTRACE_CATCH(param)
#endif

#endif // CPPTRACE_FROM_CURRENT_MACROS_HPP
108 changes: 108 additions & 0 deletions src/cpptrace_module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
module;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest module interface files to end with .cppm instead of .cpp. It improves the readability slightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that .cppm improves the readability. Could you elaborate, please?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniform convention can help people to identify things more easily. E.g., I can search for *.cppm in github to find the repos supporting modules. Or we can count the number of module interfaces more easily.

Copy link
Owner

@jeremy-rifkin jeremy-rifkin May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, as is the way of C++, it looks like people are already using a wide variety of extensions for modules https://github.com/search?type=code&q=%2F%5E%28%3F-i%29module%3B%2F+language%3Ac%2B%2B+-is%3Afork&p=1. I'm not super opinionated on this issue.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is not a forcement but a suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I'm avoidant is because the original GCC modules implementer told me that he was discouraging a different file extension after some experience. I don't think LLVM has that same aversion.

Having said that, I've literally named the file cpptrace_module.cpp to disambiguate it from cpptrace.cpp. I think cpptrace.cppm might be the better of the two, even if I'd rather stick with just .cpp.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion I went ahead and updated it to .cppm - since this file will be installed to share/ I think it's good to make it clear it's a module file and not just some random .cpp file.

#include <cpptrace/basic.hpp>
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/exceptions.hpp>
#include <cpptrace/formatting.hpp>
#include <cpptrace/forward.hpp>
#include <cpptrace/from_current.hpp>
#include <cpptrace/gdb_jit.hpp>

export module cpptrace;

namespace cpptrace::inline v1 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to rely on the begin/end namespace macros here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll get on that.

// cpptrace/basic
export using cpptrace::raw_trace;
export using cpptrace::object_frame;
export using cpptrace::object_trace;
export using cpptrace::nullable;
export using cpptrace::stacktrace_frame;
export using cpptrace::stacktrace;
export using cpptrace::generate_raw_trace;
export using cpptrace::generate_object_trace;
export using cpptrace::generate_trace;
export using cpptrace::safe_generate_raw_trace;
export using cpptrace::safe_object_frame;
export using cpptrace::can_get_safe_object_frame;
export using cpptrace::can_signal_safe_unwind;
export using cpptrace::can_get_safe_object_frame;
export using cpptrace::register_jit_object;
export using cpptrace::unregister_jit_object;
export using cpptrace::clear_all_jit_objects;

// cpptrace/exceptions
export using cpptrace::exception;
export using cpptrace::lazy_exception;
export using cpptrace::exception_with_message;
export using cpptrace::logic_error;
export using cpptrace::domain_error;
export using cpptrace::invalid_argument;
export using cpptrace::length_error;
export using cpptrace::out_of_range;
export using cpptrace::runtime_error;
export using cpptrace::range_error;
export using cpptrace::overflow_error;
export using cpptrace::underflow_error;
export using cpptrace::nested_exception;
export using cpptrace::system_error;
export using cpptrace::rethrow_and_wrap_if_needed;

// cpptrace/formatting
export using cpptrace::basename;
export using cpptrace::prettify_symbol;
export using cpptrace::formatter;
export using cpptrace::get_default_formatter;

// cpptrace/forward
export using cpptrace::frame_ptr;

// cpptrace/from_current.hpp
export using cpptrace::raw_trace_from_current_exception;
export using cpptrace::from_current_exception;
export using cpptrace::raw_trace_from_current_exception_rethrow;
export using cpptrace::from_current_exception_rethrow;
export using cpptrace::current_exception_was_rethrown;
export using cpptrace::rethrow;
export using cpptrace::clear_current_exception_traces;
export using cpptrace::try_catch;

namespace detail {
#ifdef _MSC_VER
export using cpptrace::detail::argument;
export using cpptrace::detail::exception_filter;
#else
export using cpptrace::detail::unwind_interceptor_for;
export using cpptrace::detail::nop;
#endif
}

// cpptrace/gdb_jit
namespace experimental {
export using cpptrace::experimental::register_jit_objects_from_gdb_jit_interface;
}

// cpptrace/io
export using cpptrace::operator<<; // FIXME: make hidden friend

// cpptrace/utils
export using cpptrace::demangle;
export using cpptrace::get_snippet;
export using cpptrace::isatty;
export using cpptrace::stdin_fileno;
export using cpptrace::stderr_fileno;
export using cpptrace::stdout_fileno;
export using cpptrace::register_terminate_handler;
export using cpptrace::absorb_trace_exceptions;
export using cpptrace::enable_inlined_call_resolution;
export using cpptrace::cache_mode;
export using cpptrace::log_level;
export using cpptrace::set_log_level;
export using cpptrace::set_log_callback;
export using cpptrace::use_default_stderr_logger;
export using cpptrace::cache_mode;

namespace experimental {
export using cpptrace::experimental::set_cache_mode;
export using cpptrace::experimental::set_dwarf_resolver_line_table_cache_size;
export using cpptrace::experimental::set_dwarf_resolver_disable_aranges;
}
}
29 changes: 28 additions & 1 deletion test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,31 @@ cc_test(
"CPPTRACE_NO_TEST_SNIPPETS"
],
linkstatic = 1,
)
)
cc_test(
name = "unittest_module",
deps = [
"//:cpptrace_module",
"@googletest//:gtest",
"@googletest//:gtest_main"
],
srcs = [
"unit/main.cpp",
"unit/tracing/common.hpp",
"unit/tracing/raw_trace.cpp",
"unit/tracing/object_trace.cpp",
"unit/tracing/stacktrace.cpp",
"unit/tracing/from_current.cpp",
"unit/tracing/traced_exception.cpp",
"unit/internals/optional.cpp",
"unit/internals/result.cpp",
"unit/internals/string_utils.cpp",
"unit/internals/general.cpp",
"unit/lib/formatting.cpp",
"unit/lib/nullable.cpp"
],
local_defines = [
"CPPTRACE_NO_TEST_SNIPPETS"
],
linkstatic = 1,
)
Loading
Loading