Lightweight Error Augmentation Framework

Overview

LEAF

A lightweight error handling library for C++11.

Documentation

https://boostorg.github.io/leaf/

Features

  • Small single-header format, no dependencies.
  • Designed for maximum efficiency ("happy" path and "sad" path).
  • No dynamic memory allocations, even with heavy payloads.
  • O(1) transport of arbitrary error types (independent of call stack depth).
  • Can be used with or without exception handling.
  • Support for multi-thread programming.

Support

Distribution

Besides GitHub, there are two other distribution channels:

  • LEAF is included in official Boost releases, starting with Boost 1.75.
  • For maximum portability, the library is also available in single-header format: simply download leaf.hpp (direct download link).

Copyright (C) 2018-2021 Emil Dotchevski. Distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].

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

    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

    opened by vector-of-bool 15
  • [Feature] Support for C++20 coroutines

    [Feature] Support for C++20 coroutines

    To use this library with C++20 coroutine the call in handle_error.hpp - try_handle_all or try_handle_some:

    if( auto r = std::forward<TryBlock>(try_block)() )

    Should check whether the returned time is an Awaitable and use

    if( auto r = co_await std::forward<TryBlock>(try_block)() )

    instead. Similarly for the error handling functions. This would allow users to write code among the lines of:

    boost:leaf::try_handle_some(
        []() -> boost::asio::awaitable<boost:leaf::result<void>> {
            co_return boost::leaf::result<void>{};
        }, ...);
    

    which is currently not possible. Maybe we could even achieve less verbosity. Ideally I would want to write co_return {}; just like before, but I think boost::leaf::result<> would need a constructor that takes an initializer_list then.

    opened by Tradias 14
  • Better documentation of the core mechanism

    Better documentation of the core mechanism

    It is possible to get more information how leaf is implemented? I'm particulary interested in the on_error and similar mechanisms which add information about the error context without affecting error-neutral and error-producing function implementations. Somehow leaf knows whether that extra information is useful based on the handler, but the handler can be defined in a separate translation unit.

    I would like more information about the internal mechanism:

    • How exactly the implementation knows which info is useful and how does it preserve full type information?
    • Does the library use compiler-specific features?
    • Does the library use RTTI? There is a lot of info regarding exceptions but after reading whole documentation I haven't found a single mention of RTTI.
    opened by Xeverous 9
  • How to catch objects from upper levels of class hierarchy?

    How to catch objects from upper levels of class hierarchy?

    I am implementing some dummy class hierarchy like follows (SpecialAnimalHolderParent is actually needed as the classes on the left hand side are templated):

                                             AnimalHolder         SpecialAnimalHolderParent 
                                                      /             /
                                                   SpecialAnimalHolder
                                                  /                              
                              VerySpecialAnimalHolder
    

    I would like to catch all instances of VerySpecialAnimalHolder and SpecialAnimalHolder by utlizing the following handler: [&](const SpecialAnimalHolderParent& error) { std::cout << "SpecialAnimalHolderParent error object\n"; return 0; }

    However, the handler is never entered when I create a new error of VerySpecialAnimalHolder type.

    This is the godbolt implementation: https://godbolt.org/z/14cvqvMEh

    Is there something special that needs to be implemented to enable such behavior?

    opened by arcrc 8
  • BOOST_LEAF_CHECK and extra semicolons

    BOOST_LEAF_CHECK and extra semicolons

    BOOST_LEAF_ASSIGN has no enclosing block, but BOOST_LEAF_CHECK does. This seems inconsistent and means that BOOST_LEAF_CHECK(mycoolthing()); results in an extra semicolon (and potentially a warning).

    opened by mikezackles 7
  • BOOST_LEAF_CFG_CAPTURE must be 1 for single-header build to compile

    BOOST_LEAF_CFG_CAPTURE must be 1 for single-header build to compile

    If BOOST_LEAF_CFG_CAPTURE is 0, then <memory> isn't included, but the code that uses std::shared_ptr is not stripped out by the preprocessor, causing a build failure.

    This is from a single-header build with this at the top: // Generated on 05/27/2022 from https://github.com/boostorg/leaf/tree/5e9a5b9.

    opened by dnmiller 6
  • Lambdas don't work in macros pre-c++20

    Lambdas don't work in macros pre-c++20

    Up until c++20, the uses of decltype in the static asserts for BOOST_LEAF_ASSIGN and BOOST_LEAF_CHECK prevent patterns like BOOST_LEAF_AUTO(foo, create([&] { ... }())) (lambda expression in an unevaluated context). I thought this might be a concern since this library targets c++11. Maybe unchecked versions would make sense?

    Anyway, thanks for a useful tool!

    opened by mikezackles 5
  • Documentation unclear on how to test the error-returning functions

    Documentation unclear on how to test the error-returning functions

    Hi,

    I'm trying to test some leaf-enabled code to verify that the error returned by the function is indeed the one that is expected.

    I'm using Catch2 and I can't really figure out how to test for this. The leaf tutorial does not really go into any detail, while the examples and tests seem to only handle int-based return values, and in a very complicated way.

    This is what I'm trying to do:

    enum class ParserError {
    	EmptyInput,
    	UnsupportedFormat,
    };
    
    enum class ParserType {
    	Json,
    	Text,
    };
    
    leaf::result<ParserType> detect_input_type(const std::string& input)
    {
    	if (input.empty()) {
    		return leaf::new_error(ParserError::EmptyInput);
    	}
    	// ...
    	return ParserType::Json;
    }
    
    TEST_CASE("FormatDetection", "[parser]")
    {
    	// How to write this?
    	REQUIRE(detect_input_type("").error() == ParserError::EmptyInput);  // compiler error
    }
    

    Thanks!

    opened by ashaduri 4
  • CTAD bug with GCC <11

    CTAD bug with GCC <11

    I noticed very strange behavior from boost LEAF with GCC 9.3.0 if I use CTAD for boost::leaf::result.

    Here is a small test program (try on Compiler Explorer):

    #include <boost/leaf/result.hpp>
    #include <cstdio>
    #include <type_traits>
    
    // Minimal repro based on boost::leaf::result:
    template <class T>
    class myresult {
      public:
        static int init_T_with_U( T && );
        template <class U> myresult( U && u, decltype(init_T_with_U(std::forward<U>(u))) * = nullptr );
    };
    template<> class myresult<void> { };
    
    boost::leaf::result<void> g() { return {}; }
    myresult<void> g2() { return {}; }
    
    int main() {
      boost::leaf::result r = g();
      // This printf doesn't happen:
      std::printf("!!r = %d\n", (int)!!r);
      bool temp = !!r;
      std::printf("!!r (temp) = %d\n", (int)temp);
    
      // This static_assert fails:
      //static_assert(std::is_same_v<boost::leaf::result<void>, decltype(r)>);
    
      myresult r2 = g2();
      // This static_assert fails:
      //static_assert(std::is_same_v<myresult<void>, decltype(r2)>);
    }
    

    I think there is a GCC compiler bug. GCC somewhat silently miscompiles code. Output with GCC 9.3.0 on my machine:

    !!r (temp) = 0
    

    If we add the explicit template parameter (boost::leaf::result r = g(); -> boost::leaf::result<void> r = g();), the code behaves as expected. Output with GCC 9.3.0 on my machine:

    !!r = 1
    !!r (temp) = 1
    

    With both variants, GCC 11.1.0 seems to work as expected on my machine:

    !!r = 1
    !!r (temp) = 1
    

    Although this is probably a GCC bug, perhaps boost LEAF can work around the bug? Or document it?

    opened by strager 4
  • Add leaf to the Conan Center Index

    Add leaf to the Conan Center Index

    Conan is one of the more popular C++ package managers out there with a ton of support. Having leaf on Conan will give it yet another distribution point and help with further adoption.

    opened by kammce 3
  • Question: How to correctly rethrow/propagate?

    Question: How to correctly rethrow/propagate?

    I have a situation that looks something like this, which I am attempting to translate into LEAF:

    try {
      MaybeThrowingOperation();
    } catch (const my_error_type&) {
      if (/* conditional */) {
        throw;
      }
    }
    

    The conditional is not part of the exception type, but comes from the surrounding block, so it seems I can't use a matcher. I also considered simply returning leaf::new_error(e), but if some on_error data was attached, this would be discarded. I found leaf::current_error() which seems to do the trick. Is using that correct or is there a better way to "rethrow"?

    boost::leaf::try_handle_some(
      [&] -> boost::leaf::result<void> {
        // Some operation that returns either a valid result<void>{} or a result with an error_id.
      },
      [&](const my_error_type&) -> boost::leaf::result<void> {
        if (/* conditional */) {
          return boost::leaf::current_error();
        } else {
          return {};
        }
      });
    
    opened by tchatow 3
  • Failing to use leaf with Conan and CMake

    Failing to use leaf with Conan and CMake

    Hi,

    I used for a year stx library, and wanted to give boost::leaf a try. As of writing the latest version of boost in Conan is 1.80.0. As the conan package boost-leaf (1.81.0) was marked as deprecated recently, I cannot use leaf with Conan and CMake. It seems CMake component leaf is inexistent.

    In my conanfile.txt I have: [requires] boost/1.80.0

    In my CMakeLists.txt: find_package(Boost REQUIRED COMPONENTS leaf)

    It's failing with: Conan: Component 'leaf' NOT found in package 'Boost'

    Looking at the files in ~/.conan/, the library is there, but there is no reference to it in the conan/cmake files.

    Am I doing something wrong?

    opened by up2datecyborg 3
  • Hierarchical error handling

    Hierarchical error handling

    This PR is part suggestion, part implementation. If you can think of an easier to do what I'm trying to do, I'm all ears.

    I have an application that has a lot of duplication in terms of issuing diagnostics for error handling, and I want to keep the error handling localized as much as possible. It has an intrinsic hierarchy that mirrors the error-generating code, but I can't represent that hierarchy well with just a flat sequence of handlers. Shortened example:

    leaf::try_catch(
        [] { return open_project (); },
        [](e_project_path path, e_missing_file missing) {
            log("Error while opening project in {}", path.value);
            log("Missing file: {}", missing.value);
        },
        [](e_project_path path, e_json_string json_str, e_json_parse_error json_err) {
            log("Error while opening project in {}", path.value);
            log("Error in JSON '{}'", json_str.value);
            log("Invalid JSON: {}", json_err.value);
        },
        [](e_project_path path, e_json_string json_str, e_json_schema_error err) {
            log("Error while opening project in {}", path.value);
            log("Error in JSON '{}'", json_str.value);
            log("JSON data is invalid: {}", err.value);
        });
    

    In reality there can be dozens of handlers that have some amount of duplication between them, and it can become difficult to browse and update as error conditions are added.

    This PR adds leaf::handle_more that can be used to do nested error handling. It looks like this:

    leaf::try_catch(
      [] { return open_project (); },
      [](e_project_path path) {
        log("Error while opening project in {}", path.value);
        return leaf::handle_more(
          [](e_missing_file missing) {
            log("Missing file: {}", missing.value);
          },
          [](e_json_string json_str) {
            log("Error in JSON '{}'", json_str.value);
            return leaf::handle_more(
              [](e_json_parse_error err)
                { log("Invalid JSON: {}", err.value); },
              [](e_json_schema_error err) 
                { log("JSON data is invalid: {}", err.value); });
          });
      });
    

    Here are the semantics:

    1. The return type of handle_more is an opaque type that captures the handler types and a "return type" which can be provided explicitly as the sole template argument or will be deduced as the common type of the return types of the handlers (If another nested handler returns another handle_more, the common type will "unwrap" the handle_more-return-type for that handler).
    2. The context deduction helpers recognize if a handler returns handle_more, and will recurse into its nested handlers to find additional types that it needs to create slots for.
    3. The nested handler is invoked directly within the handle_more evaluation.
    4. The return values from handle_more will propagate out: So the handle_more handlers need to have a return value that is convertible to the overall return type of the try_handle_{some,all}.

    Here's the implementation details:

    1. The return type of handle_more is leaf_detail::more_handlers_result<R, H...>, where R is the common result type and H... are the inner handlers.
    2. The machinery of handle_more is in handler_caller, which will recognize a handler that returns a more_handlers_result.
    3. The best way I can initially think to get the slots tuple down into handle_more was by using a thread-local pointer to an abstract class with a virtual R invoke(H&&...) (more_handler_invoker_base<R, H...>). The handler_caller creates a concrete instance that stores the slots and error_info, and then stores the thread-local pointer-to-base for that invoker. handle_more then loads that pointer and calls invoke(hs...), which then does the actual handle_error_(tup, ei, hs...). This trickery with thread-local pointers feels a bit hairy, though...?
    4. The handler_caller will return the actual return value of the handler by unwrapping it from the more_handlers_result object.

    Open issues with the current implementation:

    1. In the above example, the handler for e_project_path will be invoked if that slot is loaded, but it is possible that none of the nested handlers would be satisfied. Currently handle_more requires an "everything" handler at the end (like with try_handle_all), but it would be better if it recognized whether it was being called for try_handle_all or try_handle_some before requiring a catch-all handler or not.
    2. The thread-local pointer is loaded before the handler containing the handle_more is invoked, and only loaded when handle_more is invoked later on. In a pathological case, some code in between those two points could change the pointer and this will explode in handle_more :slightly_smiling_face:.
    opened by vector-of-bool 5
Releases(1.81.0)
  • 1.81.0(Dec 1, 2022)

    This release includes:

    • Fixes for compile errors in tricky configurations.
    • A fix for an API defect, throw leaf::exception(...) now becomes leaf::throw_exception(...).
    • Improvements to the TLS array support for embedded platforms.
    • Improved configuration coverage for unit tests.
    Source code(tar.gz)
    Source code(zip)
  • 1.81.0-prerelease(Sep 9, 2022)

  • 0.3.0(Jun 19, 2020)

    This release incorporates the feedback of the Boost Review, changes to improve potential integration with Boost, simplified asynchronous API. Changes:

    • The is_e_type trait is deleted. Any value type can be used as an error type.
    • Error handlers can now be captured in a tuple and used with try_handle_some, try_handle_all or try_catch. The remote_ prefixed error handling functions are removed.
    • preload, defer, and accumulate are all merged into a single API called on_error.
    • match is now compatible with std::error_code, e.g. match<std::error_code, 42>.
    • Error handlers that take arguments of exception types will automatically catch exceptions; catch_ is now optional, primarily used to catch more than one exception type.
    • Error handlers can now take mutable reference (and mutable pointer) arguments.
    • augment_id is renamed to error_monitor.
    • All LEAF_XXXX macros are renamed to BOOST_LEAF_XXXX.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.5(Nov 24, 2019)

  • 0.2.4(Nov 7, 2019)

    This release includes minor optimizations, adds a unit test for the error_id generation logic, and increased Travis CI coverage on Apple platforms.

    Source code(tar.gz)
    Source code(zip)
  • 0.2.3(Nov 3, 2019)

  • 0.2.2(Aug 2, 2019)

    Optimizations:

    • No guard variables generated by the compiler for static/thread_local objects.
    • Simpler, more optimal result<T> implementation.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Jun 25, 2019)

  • 0.2.0(Apr 24, 2019)

    In this release:

    • Added operator-> support to result<T>;
    • Added a new example to demonstrate using LEAF with outcome::result<T> instead of leaf::result<T>;
    • LEAF_AUTO uses .value() instead of operator* to access the value of a result<T> type;
    • Fixed a bug in the single header generation script, now headers are included only once;
    • Documentation fixes and refinements.
    Source code(tar.gz)
    Source code(zip)
Owner
Boost.org
Boost provides free peer-reviewed portable C++ source libraries.
Boost.org
Lightweight single-file utilities for C99. Portable & zero dependency

plainlibs Lightweight single-file utilities for C99. Key Features Portable across Unix & Windows (including MSVC) Zero dependencies (besides C stdlib)

null 5 Oct 5, 2022
BAAF-Net - Semantic Segmentation for Real Point Cloud Scenes via Bilateral Augmentation and Adaptive Fusion (CVPR 2021)

Semantic Segmentation for Real Point Cloud Scenes via Bilateral Augmentation and Adaptive Fusion (CVPR 2021) This repository is for BAAF-Net introduce

null 90 Dec 29, 2022
A lightweight C++11-compatible error-handling mechanism

Result is a modern, simple, and light-weight error-handling alternative to exceptions with a rich feature-set.

Matthew Rodusek 187 Jan 7, 2023
Investigate kernel error call stacks

retsnoop retsnoop is BPF-based tool that is meant to help debugging kernel issues. It allows to capture call stacks of kernel functions that return er

Andrii Nakryiko 75 Jan 4, 2023
HiFi error modeler and simulator with ploidy

A HiFi Shotgun Simulator Author: Gene Myers First: Aug 1, 2021 Current: Aug 1, 2021 Commands Himodel HIsim The Error Model Commands This module contai

Eugene W Myers Jr 10 Nov 29, 2022
repo to house various LLVM based SIHFT passes for RISCV 32/64 soft error resilience

compas-ft-riscv COMPAS: Compiler-assisted Software-implemented Hardware Fault Tolerance implemented in LLVM passes for the RISC-V backend Repo to hous

EDA@TUM 2 Jan 10, 2022
Either and Maybe monads for better error-handling in C++ ↔️

neither A functional implementation of Either in C++14. buckaroo add github.com/loopperfect/neither Examples Handling Unsafe Code auto unsafe = [] {

LoopPerfect 245 Nov 26, 2022
C++17 & C++ 20 error-handling and utility extensions.

C++ 17 & C++ 20 error-handling and utility extensions. Overview STX is a collection of libraries and utilities designed to make working with C++ easie

Basit Ayantunde 469 Dec 31, 2022
A lightweight unit testing framework for C++

Maintenance of UnitTest++, recently sporadic, is officially on hiatus until 26 November 2020. Subscribe to https://github.com/unittest-cpp/unittest-cp

UnitTest++ 510 Jan 1, 2023
Skynet is a lightweight online game framework

Skynet is a lightweight online game framework which can be used in many other fields.

云风 11.7k Dec 31, 2022
Lightweight Embedded Audio Framework

LEAF (Lightweight Embedded Audio Framework) is a C library for audio synthesis and processing created by Jeff Snyder, Mike Mulshine, and Matt Wang at Princeton University's New Instrument Research Lab. It was originally called OOPS when we started writing it in 2017, so you may see references to it under that name as well.

Jeff Snyder 110 Dec 27, 2022
Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework, based on QEMU.

Unicorn Engine Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework, based on QEMU. Unicorn offers some unparalleled fe

lazymio 1 Nov 7, 2021
Lightweight Python Web framework

fly Python lightweight web application framework. Event driven architecture. Usable as Web server and Application server. Lightweight and fast. Since

tatsuya.s 16 Dec 11, 2022
A lightweight C++20 serialization framework

zpp::bits A modern C++20 binary serialization library, with just one header file. This library is a successor to zpp::serializer. The library tries to

Eyal Z 299 Dec 31, 2022
C++ framework for building lightweight HTTP interfaces

Pion Network Library C++ framework for building lightweight HTTP interfaces Project Home: https://github.com/rimmartin/pion-ng Documentation Retrievin

null 1 Dec 30, 2020
Upp11 - C++11 lightweight single header unit test framework

upp11 Lightweight C++11 single header unit test framework To use framework: Copy upp11.h in you project dir. Create unit test source files or modify e

Andrey Valyaev 8 Apr 4, 2019
Caffe2 is a lightweight, modular, and scalable deep learning framework.

Source code now lives in the PyTorch repository. Caffe2 Caffe2 is a lightweight, modular, and scalable deep learning framework. Building on the origin

Meta Archive 8.4k Jan 6, 2023
A cross-platform,lightweight,scalable game server framework written in C++, and support Lua Script

Current building status Moon Moon is a lightweight online game server framework implement with multithread and multi-luaVM. One thread may have 1-N lu

Bruce 467 Dec 29, 2022
Neutralinojs is a lightweight and portable desktop application development framework

Neutralinojs is a lightweight and portable desktop application development framework. It lets you develop lightweight cross-platform desktop applications using JavaScript, HTML and CSS.

Neutralinojs 6.3k Dec 30, 2022
Lightweight framework for 3D rendering.

Ink 3D Ink 3D is a lightweight and easy to use framework for 3D rendering. Features High Performance Rendering Pipeline Forward Rendering / Deferred R

null 9 Dec 15, 2022