在异常情况下跳过内部抓捕

时间:2018-12-16 09:02:25

标签: c++ try-catch c++17

我在我正在研究的代码库中发现了一个非常奇怪的错误,直到最近我才能够隔离并创建可​​复制的东西。错误是simulate_container_init偶数时跳过throw_threshold内部的捕获量。 throw_threshold中的ex_trigger用于模拟在容器构造或出于单元测试目的的情况下复制时抛出的对象。

我本来以为这是MSVC编译器错误(在撰写本文时为14.16),但是在GCC 7.1和Clang 3.9.1(几乎每个支持我的示例的最早的)中成功重现之后,我不知道该怎么做,因为当throw_threshold为奇数时,代码既可以正确显示又可以正确运行。

#include <cstdint>
#include <atomic>
#include <memory>
#include <random>
#include <iostream>
#include <exception>
#include <type_traits>

// Used to trigger an exception after a number of constructions have occurred.
struct ex_trigger
{
private:
    static std::atomic<uint32_t> ctor_count;
    static std::atomic<uint32_t> throw_threshold;

public:
    static inline void set_throw_threshold(uint32_t value) noexcept
    {
        throw_threshold.store(value, std::memory_order_release);
    }

    std::atomic<uint32_t> value;

    inline ex_trigger(const ex_trigger& source) :
        value(source.value.load(std::memory_order_acquire))
    {
        if (ctor_count.load(std::memory_order_acquire) >= 
            throw_threshold.load(std::memory_order_acquire)) {
            throw std::logic_error("test");
        }

        ctor_count.fetch_add(1);
    }
    inline ex_trigger(uint32_t value) noexcept :
        value(value)
    {
        ctor_count.fetch_add(1);
    }
};
std::atomic<uint32_t> ex_trigger::ctor_count;
std::atomic<uint32_t> ex_trigger::throw_threshold;

// Simulates the construction of a container by copying an initializer list.
template<class T>
inline void simulate_container_ctor(std::initializer_list<T> values) {
    // Intentionally leaked to simplify this example.
    // Alignment of T is completely ignored for simplicity.
    auto sim_data = static_cast<T*>(
        ::operator new(sizeof(T) * values.size()));

    for (auto value : values) {
        if constexpr (std::is_nothrow_copy_constructible_v<T>) {
            new (sim_data++) T(value);
        } else {
            try {
                new (sim_data++) T(value);
            } catch (...) {
                // Placeholder for cleanup code which is sometimes skipped.
                std::cout << "caught [inner]\n";
                throw;
            }
        }
    }
}

int main()
{
    // The "inner" catch handler within simulate_container_init will be skipped when the argument
    // to set_throw_threshold is even, but otherwise appears to work correctly when it's odd. Note
    // that the argument must be between 10-20 for an exception to be triggered in this example.
    ex_trigger::set_throw_threshold(11);
    try {
        simulate_container_ctor({
            ex_trigger(1),
            ex_trigger(2),
            ex_trigger(3),
            ex_trigger(4),
            ex_trigger(5),
            ex_trigger(6),
            ex_trigger(7),
            ex_trigger(8),
            ex_trigger(9),
            ex_trigger(10)
        });
    } catch (const std::logic_error&) {
        std::cout << "caught [outer]\n";
    }
    std::cin.get();
    return 0;
}

throw_threshold为偶数时,输出为(不正确):

抓住[外部]

throw_threshold为奇数时,输出为(预期):

抓住[内部]
抓住[外层]

我花了无数小时来调试和尝试不同的方法,但似乎我缺少了一些东西。任何有助于理解这一点的方法将不胜感激。

1 个答案:

答案 0 :(得分:2)

问题是JFrame副本构造了一个临时for (auto value : values) {,它将在内部异常处理程序之外引发异常。解决办法是遍历引用ex_trigger