Skip to content

No error_id loaded when catching an non-leaf::exception #34

@vector-of-bool

Description

@vector-of-bool

This one took a while to track down, but I have narrowed it down to this small snippet. This is admittedly convoluted in appearance, but I was able to run into this behavior on my own with a much more spread-out code.

Here are two test cases that exhibit two related (but unexpected(?)) behaviors:

Error Object is Dropped:

auto fun = [] {
    return leaf::try_catch([]() -> std::pair<int, int> {
        auto augment = leaf::on_error(info2{1729});  // [1]
        leaf::try_catch(
            [] { 
                throw my_exception(12);
            },
            [](const my_exception& e) {
                leaf::current_error().load(info1{42});  // [2]
                throw;
            });
        // unreachable
    }, [](const my_exception& e, info1* v1, info2* v2) {
        // Return the pair of {info1, info2}
        return std::make_pair(v1 ? v1->value : -1, v2 ? v2->value : -1);
    });
};
auto pair = fun();
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);
pair = fun();
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);

In this case

  1. the bare throw my_exception does not initialize a new error_id in the current thread.
  2. The handler will now try to .load() into the current in-flight error at [2].
    • Since there is no new error_id in flight, it will attach info1 to whatever error_id just happened to be loaded in the current thread (Possibly just throwing the info away).
  3. The exception is then re-thrown with a bare throw;. Still, no new error_id is generated.
  4. The augment object's destructor at [1] will detect a new exception in-flight, but also detect that no new error_id has been created. It will then call new_error() (via error_monitor::check_id()) and attach an info2 to that error. The value of info1 is now inaccessible to the intended handler immediately below.

Additional quirk: If one moves the on_error object into the innermost throwing-lambda expression, then it's destructor will call new_error() (as expected!) before the exception is caught and this code will work.

Result differences:

auto fun = [](bool use_leaf_exception) {
    return leaf::try_catch([&]() -> std::pair<int, int> {
        auto augment = leaf::on_error(info2{1729}); // [1]
        leaf::try_catch(
            [&] { 
                if (use_leaf_exception) {
                    throw leaf::exception(my_exception(12));
                } else {
                    throw my_exception(12);
                }
            },
            [](const my_exception& e) {
                leaf::current_error().load(info1{42});  // [2]
                throw;
            });
        // unreachable
    }, [](const my_exception& e, info1* v1, info2* v2) {
        // Return the pair of {info1, info2}
        return std::make_pair(v1 ? v1->value : -1, v2 ? v2->value : -1);
    });
};
auto pair = fun(false);
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);
pair = fun(true);
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);

As a side effect of the prior quirk, the result will differ depending on whether leaf::exception() is called. In this case, if use_leaf_exception is true, then the correct result appears, otherwise it will fail

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions