File: exception.hpp

package info (click to toggle)
dnf5 5.4.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 17,960 kB
  • sloc: cpp: 94,312; python: 3,370; xml: 1,073; ruby: 600; sql: 250; ansic: 232; sh: 104; perl: 62; makefile: 30
file content (302 lines) | stat: -rw-r--r-- 13,341 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// Copyright Contributors to the DNF5 project.
// Copyright Contributors to the libdnf project.
// SPDX-License-Identifier: LGPL-2.1-or-later
//
// This file is part of libdnf: https://github.com/rpm-software-management/libdnf/
//
// Libdnf is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Libdnf is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with libdnf.  If not, see <https://www.gnu.org/licenses/>.

#ifndef LIBDNF5_COMMON_EXCEPTION_HPP
#define LIBDNF5_COMMON_EXCEPTION_HPP

#include "libdnf5/defs.h"
#include "libdnf5/utils/bgettext/bgettext-mark-common.h"
#include "libdnf5/utils/format.hpp"

#include <fmt/format.h>

#include <filesystem>
#include <functional>
#include <stdexcept>
#include <type_traits>

#define LIBDNF_LOCATION {__FILE__, __LINE__, __PRETTY_FUNCTION__}

/// An assert macro that throws `libdnf5::AssertionError`.
///
/// @param msg_format The format string for the message.
/// @param ... The format arguments.
/// @exception libdnf5::AssertionError Thrown always.
#define libdnf_throw_assertion(msg_format, ...) \
    (throw libdnf5::AssertionError(nullptr, LIBDNF_LOCATION, fmt::format(msg_format, ##__VA_ARGS__)))

/// An assert macro that throws `libdnf5::AssertionError` when `condition` is not met.
///
/// @param condition The assertion condition. Throws AssertionError if it's not met.
/// @param msg_format The format string for the message.
/// @param ... The format arguments.
/// @exception libdnf5::AssertionError Thrown when condition is not met.
#define libdnf_assert(condition, msg_format, ...) \
    (static_cast<bool>(condition)                 \
         ? void(0)                                \
         : throw libdnf5::AssertionError(#condition, LIBDNF_LOCATION, fmt::format(msg_format, ##__VA_ARGS__)))

/// An assert macro that throws `libdnf5::UserAssertionError` when `condition` is not met.
///
/// @param condition The assertion condition. Throws UserAssertionError if it's not met.
/// @param msg_format The format string for the message.
/// @param ... The format arguments.
/// @exception libdnf5::UserAssertionError Thrown when condition is not met.
#define libdnf_user_assert(condition, msg_format, ...) \
    (static_cast<bool>(condition)                      \
         ? void(0)                                     \
         : throw libdnf5::UserAssertionError(#condition, LIBDNF_LOCATION, fmt::format(msg_format, ##__VA_ARGS__)))

/// Indicates the availability of `libdnf_assert` and` libdnf_throw_assertion` macros.
/// These macros may be removed in the future. E.g. when migrating the asserts implementation
/// to C++20 `std::source_location`.
#define LIBDNF_ASSERTION_MACROS 1

namespace libdnf5 {

/// The source_location structure represents location information in the source code.
/// Specifically, the file name, line number, and function name.
struct SourceLocation {
    const char * file_name;
    unsigned int source_line;
    const char * function_name;
};

/// An AssertionError is a fault in the program logic, it is thrown when an
/// incorrect sequence of actions has led to an invalid state in which it is
/// impossible to continue running the program.
class LIBDNF_API AssertionError : public std::logic_error {
public:
    explicit AssertionError(const char * assertion, const SourceLocation & location, const std::string & message);

    const char * what() const noexcept override;
    const char * assertion() const noexcept { return condition; }
    const char * file_name() const noexcept { return location.file_name; }
    unsigned int source_line() const noexcept { return location.source_line; }
    const char * function_name() const noexcept { return location.function_name; }
    const char * message() const noexcept { return logic_error::what(); }

private:
    const char * condition;
    SourceLocation location;
    mutable std::string str_what;
};

/// A UserAssertionError is an error which is thrown when the
/// libdnf public API is used in an unexpected way and continuing
/// would led to an invalid state.

/// For the bindings users, this exception is intended to be translated
/// into a standard runtime exception which could be handled,
/// whereas with the previous `AssertionError` exception the process
/// is terminated and the system state is captured for debugging purposes.
class LIBDNF_API UserAssertionError : public std::logic_error {
public:
    explicit UserAssertionError(const char * assertion, const SourceLocation & location, const std::string & message);

    const char * what() const noexcept override;
    const char * assertion() const noexcept { return condition; }
    const char * file_name() const noexcept { return location.file_name; }
    unsigned int source_line() const noexcept { return location.source_line; }
    const char * function_name() const noexcept { return location.function_name; }
    const char * message() const noexcept { return logic_error::what(); }

private:
    const char * condition;
    SourceLocation location;
    mutable std::string str_what;
};


// The exception stores the passed arguments and uses them in the catch phase.
// A problem can occur if the stored argument points to a value in memory that it does not own.
// The value that the stored argument points to may no longer be in memory - it is destroyed
// when the program leaves the scope of the variable with the value.
// A safer solution than passing a pointer is to use passing by value. For example, pass
// "std::string" instead of `char *` or `std::string_view`.
/// "Concept" defines safe types that can be passed by value as arguments to the `libdnf5::Error`
/// and `libdnf5::SystemError` exception constructors.
/// Pointers to void and nullptr are allowed because they are not dereferenced, but their value is printed.
template <typename T>
concept AllowedErrorArgTypes =
    std::is_arithmetic_v<T> || std::is_base_of_v<std::string, T> || std::is_null_pointer_v<T> ||
    std::is_same_v<const void *, T> || std::is_same_v<void *, T>;


/// Base class for libdnf exceptions. Virtual methods `get_name()` and
/// `get_domain_name()` should always return the exception's class name and its
/// namespace (including enclosing class names in case the exception is nested in
/// other classes) respectively.
class LIBDNF_API Error : public std::runtime_error {
public:
    /// A constructor that supports formatting the error message.
    ///
    /// @param format The format string for the message.
    /// @param args The format arguments.
    template <AllowedErrorArgTypes... Args>
    explicit Error(BgettextMessage format, Args... args)
        : std::runtime_error(b_gettextmsg_get_id(format)),
          format(format),
          // stores the format args in the lambda's closure
          formatter([args...](const char * format) { return libdnf5::utils::sformat(format, args...); }) {}

    Error(const Error & e) noexcept;
    Error & operator=(const Error & e) noexcept;

    const char * what() const noexcept override;

    /// @return The exception class name.
    virtual const char * get_name() const noexcept { return "Error"; }

    /// @return The domain name (namespace and enclosing class names) of the exception.
    virtual const char * get_domain_name() const noexcept { return "libdnf5"; }

protected:
    mutable std::string message;
    BgettextMessage format;
    std::function<std::string(const char * format)> formatter;
};


/// An exception class for system errors represented by the `errno` error code.
class LIBDNF_API SystemError : public Error {
public:
    /// Constructs the error from the `errno` error code and generates the
    /// message from the system error description.
    ///
    /// @param error_code The `errno` of the error.
    explicit SystemError(int error_code);

    /// Constructs the error from the `errno` error code and a formatted message.
    /// The formatted message is prepended to the generated system error message.
    ///
    /// @param error_code The `errno` of the error.
    /// @param format The format string for the message.
    /// @param args The format arguments.
    template <AllowedErrorArgTypes... Args>
    explicit SystemError(int error_code, BgettextMessage format, Args... args)
        : Error(format, std::forward<Args>(args)...),
          error_code(error_code),
          has_user_message(true) {}

    const char * what() const noexcept override;

    const char * get_domain_name() const noexcept override { return "libdnf5"; }
    const char * get_name() const noexcept override { return "SystemError"; }

    /// @return The error code (`errno`) of the error.
    int get_error_code() const noexcept { return error_code; }

    /// @return The system error message associated with the error code.
    std::string get_error_message() const;

private:
    int error_code;
    bool has_user_message;
};

/// An exception class for file system errors represented by the `errno` error code and a path.
class LIBDNF_API FileSystemError : public Error {
public:
    /// Constructs the error from the `errno` error code, filesystem path and a formatted message.
    /// The formatted message is prepended to the generated system error message.
    ///
    /// @param error_code The `errno` of the error.
    /// @param filesystem::path The `path` to the file.
    /// @param format The format string for the message.
    /// @param args The format arguments.
    template <AllowedErrorArgTypes... Args>
    explicit FileSystemError(int error_code, std::filesystem::path path, BgettextMessage format, Args... args)
        : Error(format, std::forward<Args>(args)...),
          error_code(error_code),
          path(std::move(path)) {}

    const char * what() const noexcept override;

    const char * get_domain_name() const noexcept override { return "libdnf5::utils::fs"; }
    const char * get_name() const noexcept override { return "FileSystemError"; }

    /// @return The error code (`errno`) of the error.
    int get_error_code() const noexcept { return error_code; }

private:
    int error_code;
    std::filesystem::path path;
};


// TODO(lukash) This class is used throughout the code where more specific exceptions should be thrown.
// Kept as a reminder to replace all those with the correct exception classes.
class LIBDNF_API RuntimeError : public Error {
public:
    using Error::Error;
    const char * get_name() const noexcept override { return "RuntimeError"; }
    virtual const char * get_description() const noexcept;
};


/// A template of the `NestedException` class that is thrown by the `throw_with_nested` function.
template <typename TException>
class NestedException : public TException, public std::nested_exception {
public:
    explicit NestedException(const TException & ex) : TException(ex) {}
    explicit NestedException(TException && ex) : TException(static_cast<TException &&>(ex)) {}
};

/// Throws an exception that also stores the currently active exception.
///
/// It does the same thing as `std::throw_with_nested(TException && ex)`, except that instead of
/// an **unspecified** type that is publicly derived from both `std::nested_exception` and
/// `std::decay<TException>::type`, it throws a type **`NestedException<std::decay<TException>::type>`**
/// that is publicly derived from both `std::nested_exception` and `std::decay<TException>::type`.
///
/// In other words, it replaces the unspecified type (the type defined by the implementation in the standard library)
/// with our specification. Knowing the type can simplify exception handling in some cases. For example, avoiding
/// the need to define another type for SWIG.
template <typename TException>
[[noreturn]] inline void throw_with_nested(TException && ex) {
    using TUpEx = typename std::decay<TException>::type;

    using IsCopyConstructible = std::conjunction<std::is_copy_constructible<TUpEx>, std::is_move_constructible<TUpEx>>;
    static_assert(IsCopyConstructible::value, "throw_with_nested argument must be CopyConstructible");

    if constexpr (!std::is_final_v<TUpEx> && std::is_class_v<TUpEx> && !std::is_base_of_v<std::nested_exception, TUpEx>)
        throw NestedException<TUpEx>{std::forward<TException>(ex)};
    throw std::forward<TException>(ex);
}


/// Describes the contents of the error message generated by the `format` function.
/// It is only considered for exceptions inheriting from `libdnf5::Error`. Others always use the `Plain` format.
enum class FormatDetailLevel {
    /// "what()\n"
    Plain,
    /// "get_name(): what()\n"
    WithName,
    /// "get_domain_name()::get_name(): what()\n"
    WithDomainAndName,
};

/// Formats the error message of an exception.
/// If the exception is nested, recurses to format the message of the exception it holds.
LIBDNF_API std::string format(const std::exception & e, FormatDetailLevel detail);

}  // namespace libdnf5

#endif