Внутренний улов пропущен при странных обстоятельствах - PullRequest
0 голосов
/ 16 декабря 2018

Я обнаружил действительно странную ошибку в кодовой базе, над которой я работал, которую я только недавно смог выделить и создать что-то воспроизводимое.Ошибка заключается в том, что улов внутри 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 является четным, вывод (неверно):

catch [external]

Когда throw_threshold равностранно, результат (как и ожидалось):

catch [inner]
catch [external]

Я потратил бесчисленные часы на отладку и пробовал разные подходы, но, похоже, ячего-то не хватаетВсе, что поможет понять это, будет высоко ценится.

1 Ответ

0 голосов
/ 16 декабря 2018

Проблема заключается в for (auto value : values) { копировании, создающем временную ex_trigger, которая будет генерировать исключение вне внутреннего обработчика исключений.Исправление будет заключаться в том, чтобы перебрать ссылки for (auto const & value : values) {

...