catchorg / Catch2

A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)
https://discord.gg/4CWS9zD
Boost Software License 1.0
18.51k stars 3.04k forks source link

Fixture's destructor called before retrieving uncaught exception's message #2868

Open nowakowsk opened 5 months ago

nowakowsk commented 5 months ago

I have a unit test project in which exceptions rely on test fixture's state. This can lead to use-after-free situations, because uncaught exception objects can outlive fixture. Following code illustrates the problem:

#include <iostream>
#include <exception>
#include <catch2/catch_test_macros.hpp>

struct Context
{
  Context() { std::cout << "Context::Context\n"; }
  ~Context() { std::cout << "Context::~Context\n"; }
};

struct ErrorUsingContext : std::exception
{
  ErrorUsingContext(Context&) { std::cout << "ErrorUsingContext::ErrorUsingContext\n"; }
  ~ErrorUsingContext() { std::cout << "ErrorUsingContext::~ErrorUsingContext\n"; }

  const char* what() const noexcept override
  {
    std::cout << "ErrorUsingContext::what\n";
    return "description allocated and managed by Context";
  }
};

struct Test
{
  Context ctx;
};

TEST_CASE_METHOD(Test, "Test")
{
  throw ErrorUsingContext(ctx);
}

Godbolt: https://godbolt.org/z/xf3x65rhe

Output:

Context::Context
ErrorUsingContext::ErrorUsingContext
Context::~Context
ErrorUsingContext::what

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
output.s is a Catch v3.0.0-preview.3 host application.
Run with -? for options

-------------------------------------------------------------------------------
Test
-------------------------------------------------------------------------------
/app/example.cpp:32
...............................................................................

/app/example.cpp:32: FAILED:
due to unexpected exception with message:
  description allocated and managed by Context

ErrorUsingContext::~ErrorUsingContext
===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed

ErrorUsingContext::what is called after Context::~Context which is a problem for me, because, in my case, memory of exception's description string is allocated and managed by the Context (strings use custom allocators).

I can work around it, but this could be avoided if uncaught exceptions' description were retrieved and saved by Catch2 before calling fixture's destructor.