diff --git a/include/cpptrace/from_current.hpp b/include/cpptrace/from_current.hpp index 6e6ee48..3c31051 100644 --- a/include/cpptrace/from_current.hpp +++ b/include/cpptrace/from_current.hpp @@ -37,20 +37,24 @@ namespace cpptrace { } #ifdef _MSC_VER + // 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 + __try { \ + [&]() { #define CPPTRACE_CATCH(param) \ - __except(::cpptrace::detail::exception_filter()) {} \ + }(); \ + } __except(::cpptrace::detail::exception_filter()) {} \ }(); \ } catch(param) #else #define CPPTRACE_TRY \ try { \ - try + try { #define CPPTRACE_CATCH(param) \ - catch(::cpptrace::detail::unwind_interceptor&) {} \ + } catch(::cpptrace::detail::unwind_interceptor&) {} \ } catch(param) #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 02cf00a..6136004 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,11 +7,17 @@ set( ${warning_options} $<$:-Wno-infinite-recursion> ) +set( + debug + $<$>:-g> + $<$:/DEBUG> +) + macro(add_test_dependencies exec_name) target_compile_features(${exec_name} PRIVATE cxx_std_11) target_link_libraries(${exec_name} PRIVATE ${target_name}) target_compile_options(${exec_name} PRIVATE ${warning_options}) - target_compile_options(${exec_name} PRIVATE -g) + target_compile_options(${exec_name} PRIVATE ${debug}) if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) target_compile_options(${exec_name} PRIVATE -gsplit-dwarf) endif() @@ -42,7 +48,7 @@ if(UNIX) add_executable(signal_demo signal_demo.cpp) target_compile_features(signal_demo PRIVATE cxx_std_11) target_link_libraries(signal_demo PRIVATE ${target_name}) - target_compile_options(signal_demo PRIVATE -g) + target_compile_options(signal_demo PRIVATE ${debug}) if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) target_compile_options(signal_demo PRIVATE -gsplit-dwarf) endif() @@ -53,7 +59,7 @@ if(UNIX) add_executable(signal_tracer signal_tracer.cpp) target_compile_features(signal_tracer PRIVATE cxx_std_11) target_link_libraries(signal_tracer PRIVATE ${target_name}) - target_compile_options(signal_tracer PRIVATE -g) + target_compile_options(signal_tracer PRIVATE ${debug}) endif() # primarily a workaround for github actions issue https://github.com/actions/runner-images/issues/8659 @@ -77,11 +83,12 @@ if(NOT CPPTRACE_SKIP_UNIT) unit/raw_trace.cpp unit/object_trace.cpp unit/stacktrace.cpp + unit/from_current.cpp ) target_compile_features(unittest PRIVATE cxx_std_20) target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main) target_compile_options(unittest PRIVATE ${warning_options} $<$>:-Wno-pedantic -Wno-attributes>) - target_compile_options(unittest PRIVATE -g) + target_compile_options(unittest PRIVATE ${debug}) if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) target_compile_options(unittest PRIVATE -gsplit-dwarf) endif() diff --git a/test/demo.cpp b/test/demo.cpp index f8465b8..46ba38d 100644 --- a/test/demo.cpp +++ b/test/demo.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -7,10 +6,9 @@ #include void trace() { - // cpptrace::generate_trace().print(); - // cpptrace::generate_trace().print_with_snippets(); - // throw cpptrace::logic_error("foobar"); - throw std::runtime_error("foobar"); + cpptrace::generate_trace().print(); + cpptrace::generate_trace().print_with_snippets(); + throw cpptrace::logic_error("foobar"); } void foo(int n) { @@ -37,4 +35,5 @@ void function_one(int) { int main() { cpptrace::absorb_trace_exceptions(false); cpptrace::register_terminate_handler(); + function_one(0); } diff --git a/test/unit/from_current.cpp b/test/unit/from_current.cpp new file mode 100644 index 0000000..6e354fe --- /dev/null +++ b/test/unit/from_current.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace std::literals; + + +// NOTE: returning something and then return stacktrace_from_current_3(line_numbers) * 2; later helps prevent the call from +// being optimized to a jmp +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_3(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + throw std::runtime_error("foobar"); +} + +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_2(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + return stacktrace_from_current_3(line_numbers) * 2; +} + +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_1(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + return stacktrace_from_current_2(line_numbers) * 2; +} + +TEST(FromCurrent, Basic) { + std::vector line_numbers; + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::runtime_error& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& trace = cpptrace::from_current_exception(); + ASSERT_GE(trace.frames.size(), 4); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos; + } + ); + ASSERT_NE(it, trace.frames.end()); + size_t i = static_cast(it - trace.frames.begin()); + int j = 0; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody")); + } +} + +TEST(FromCurrent, CorrectHandler) { + std::vector line_numbers; + CPPTRACE_TRY { + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::logic_error&) { + FAIL(); + } + } CPPTRACE_CATCH(const std::exception& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& trace = cpptrace::from_current_exception(); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.symbol.find("FromCurrent_CorrectHandler_Test::TestBody") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + } +} + +TEST(FromCurrent, RawTrace) { + std::vector line_numbers; + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::exception& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& raw_trace = cpptrace::raw_trace_from_current_exception(); + auto trace = raw_trace.resolve(); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.symbol.find("FromCurrent_RawTrace_Test::TestBody") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + } +}