mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-25 05:19:57 +00:00
This catches bugs early on. E.g., when leaving an 'Allocation' unused, it gets immediately deallocated, which is most probably not intended. For regular 'Attempt' objects, this change encourages the proper propagation of errors, or at least the logging of unexpected conditions. Fixes #5513
169 lines
4.8 KiB
C++
169 lines
4.8 KiB
C++
/*
|
|
* \brief Utility for passing return values
|
|
* \author Norman Feske
|
|
* \date 2021-11-09
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2021 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _INCLUDE__UTIL__ATTEMPT_H_
|
|
#define _INCLUDE__UTIL__ATTEMPT_H_
|
|
|
|
#include <util/reconstructible.h>
|
|
|
|
namespace Genode {
|
|
template <typename, typename> struct [[nodiscard]] Attempt;
|
|
template <typename, typename> struct [[nodiscard]] Unique_attempt;
|
|
|
|
/**
|
|
* Type used for results with no return value but error conditions
|
|
*/
|
|
struct Ok { };
|
|
}
|
|
|
|
|
|
/**
|
|
* Option type for return values
|
|
*
|
|
* The 'Attempt' type addresses the C++ limitation to only a single return
|
|
* value, which makes it difficult to propagate error information in addition
|
|
* to an actual return value from a called function back to the caller. Hence,
|
|
* errors have to be propagated as exceptions, results have to be returned via
|
|
* out parameters, or error codes have to be encoded in the form of magic
|
|
* values. Each of these approaches create its own set of robustness problems.
|
|
*
|
|
* An 'Attempt' represents the result of a function call that is either a
|
|
* meaningful value or an error code, but never both. The result value and
|
|
* error are distinct types. To consume the return value of a call, the caller
|
|
* needs to specify two functors, one for handing the value if the value exists
|
|
* (the call was successful), and one for handling the error value if the call
|
|
* failed. Thereby the use of an 'Attempt' return type reinforces the explicit
|
|
* handling of all possible error conditions at the caller site.
|
|
*/
|
|
template <typename RESULT, typename ERROR>
|
|
class Genode::Attempt
|
|
{
|
|
private:
|
|
|
|
bool _ok;
|
|
RESULT _result { };
|
|
ERROR _error { };
|
|
|
|
public:
|
|
|
|
Attempt(RESULT result) : _ok(true), _result(result) { }
|
|
Attempt(ERROR error) : _ok(false), _error(error) { }
|
|
|
|
Attempt(Attempt const &) = default;
|
|
Attempt &operator = (Attempt const &) = default;
|
|
|
|
template <typename RET>
|
|
RET convert(auto const &access_fn, auto const &fail_fn) const
|
|
{
|
|
return _ok ? RET { access_fn(_result) }
|
|
: RET { fail_fn(_error) };
|
|
}
|
|
|
|
void with_result(auto const &access_fn, auto const &fail_fn) const
|
|
{
|
|
_ok ? access_fn(_result) : fail_fn(_error);
|
|
}
|
|
|
|
void with_error(auto const &fail_fn) const
|
|
{
|
|
if (!_ok)
|
|
fail_fn(_error);
|
|
}
|
|
|
|
bool operator == (ERROR const &rhs) const {
|
|
return failed() && (_error == rhs); }
|
|
|
|
bool operator == (RESULT const &rhs) const {
|
|
return ok() && (_result == rhs); }
|
|
|
|
[[nodiscard]] bool ok() const { return _ok; }
|
|
[[nodiscard]] bool failed() const { return !_ok; }
|
|
|
|
void print(Output &out) const
|
|
{
|
|
with_result([&] (RESULT const &result) { Genode::print(out, result); },
|
|
[&] (ERROR const &error) { Genode::print(out, error); });
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Base type for the result of constrained RAM allocations
|
|
*
|
|
* The type is a unique pointer to the allocated RAM while also holding
|
|
* allocation-error information, following the conventions of 'Attempt'.
|
|
*/
|
|
template <typename RESULT, typename ERROR>
|
|
class Genode::Unique_attempt : Noncopyable
|
|
{
|
|
private:
|
|
|
|
Constructible<RESULT> _result { };
|
|
|
|
ERROR _error { };
|
|
|
|
protected:
|
|
|
|
void _construct(auto &&... args) { _result.construct(args...); }
|
|
void _destruct (ERROR e) { _result.destruct(); _error = e; }
|
|
|
|
public:
|
|
|
|
Unique_attempt(auto &&... args) { _result.construct(args...); }
|
|
Unique_attempt(ERROR error) { _destruct(error); }
|
|
|
|
template <typename RET>
|
|
RET convert(auto const &access_fn, auto const &fail_fn)
|
|
{
|
|
return _result.constructed() ? RET { access_fn(*_result) }
|
|
: RET { fail_fn(_error) };
|
|
}
|
|
|
|
template <typename RET>
|
|
RET convert(auto const &access_fn, auto const &fail_fn) const
|
|
{
|
|
return _result.constructed() ? RET { access_fn(*_result) }
|
|
: RET { fail_fn(_error) };
|
|
}
|
|
|
|
void with_result(auto const &access_fn, auto const &fail_fn)
|
|
{
|
|
_result.constructed() ? access_fn(*_result) : fail_fn(_error);
|
|
}
|
|
|
|
void with_result(auto const &access_fn, auto const &fail_fn) const
|
|
{
|
|
_result.constructed() ? access_fn(*_result) : fail_fn(_error);
|
|
}
|
|
|
|
void with_error(auto const &fail_fn) const
|
|
{
|
|
if (!_result.constructed())
|
|
fail_fn(_error);
|
|
}
|
|
|
|
bool operator == (ERROR const &rhs) const {
|
|
return failed() && (_error == rhs); }
|
|
|
|
[[nodiscard]] bool ok() const { return _result.constructed(); }
|
|
[[nodiscard]] bool failed() const { return !_result.constructed(); }
|
|
|
|
void print(auto &out) const
|
|
{
|
|
with_result([&] (RESULT const &result) { Genode::print(out, result); },
|
|
[&] (ERROR const &error) { Genode::print(out, error); });
|
|
}
|
|
};
|
|
|
|
#endif /* _INCLUDE__UTIL__ATTEMPT_H_ */
|