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

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

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 15 (6 by maintainers)

Most upvoted comments

@vector-of-bool I’m closing this but feel free to reopen it if you think I’ve missed something.