Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
RError.hxx
Go to the documentation of this file.
1/// \file ROOT/RError.hxx
2/// \author Jakob Blomer <jblomer@cern.ch>
3/// \date 2019-12-11
4
5/*************************************************************************
6 * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
13#ifndef ROOT_RError
14#define ROOT_RError
15
16#include <ROOT/RConfig.hxx> // for R__[un]likely
17#include <ROOT/RLogger.hxx> // for R__LOG_PRETTY_FUNCTION
18
19#include <cstddef>
20#include <memory>
21#include <new>
22#include <optional>
23#include <stdexcept>
24#include <string>
25#include <string_view>
26#include <utility>
27#include <vector>
28
29namespace ROOT {
30
31// clang-format off
32/**
33\class ROOT::RError
34\ingroup Base
35\brief Captures diagnostics related to a ROOT runtime error
36*/
37// clang-format on
38class RError {
39public:
40 struct RLocation {
41 RLocation() = default;
42 RLocation(const char *func, const char *file, unsigned int line)
43 : fFunction(func), fSourceFile(file), fSourceLine(line)
44 {
45 }
46
47 // TODO(jblomer) use std::source_location as of C++20
48 const char *fFunction;
49 const char *fSourceFile;
50 unsigned int fSourceLine;
51 };
52
53private:
54 /// User-facing error message
55 std::string fMessage;
56 /// The location of the error related to fMessage plus upper frames if the error is forwarded through the call stack
57 std::vector<RLocation> fStackTrace;
58
59public:
60 /// Used by R__FAIL
61 RError(std::string_view message, RLocation &&sourceLocation);
62 /// Used by R__FORWARD_RESULT
63 void AddFrame(RLocation &&sourceLocation);
64 /// Add more information to the diagnostics
65 void AppendToMessage(std::string_view info) { fMessage += info; }
66 /// Format a dignostics report, e.g. for an exception message
67 std::string GetReport() const;
68 const std::vector<RLocation> &GetStackTrace() const { return fStackTrace; }
69};
70
71// clang-format off
72/**
73\class ROOT::RException
74\ingroup Base
75\brief Base class for all ROOT issued exceptions
76*/
77// clang-format on
78class RException : public std::runtime_error {
80
81public:
82 explicit RException(const RError &error) : std::runtime_error(error.GetReport()), fError(error) {}
83 const RError &GetError() const { return fError; }
84};
85
86// clang-format off
87/**
88\class ROOT::RResultBase
89\ingroup Base
90\brief Common handling of the error case for RResult<T> (T != void) and RResult<void>
91
92RResultBase captures a possible runtime error that might have occurred. If the RResultBase leaves the scope unchecked,
93it will throw an exception. RResultBase should only be allocated on the stack, which is helped by deleting the
94new operator. RResultBase is movable but not copyable to avoid throwing multiple exceptions about the same failure.
95*/
96// clang-format on
98protected:
99 /// This is the nullptr for an RResult representing success
100 std::unique_ptr<RError> fError;
101 /// Switches to true once the user of an RResult object checks the object status
102 bool fIsChecked{false};
103
104 RResultBase() = default;
105 explicit RResultBase(RError &&error) : fError(std::make_unique<RError>(std::move(error))) {}
106
107 /// Used by the RResult<T> bool operator
108 bool Check()
109 {
110 fIsChecked = true;
111 return !fError;
112 }
113
114public:
115 RResultBase(const RResultBase &other) = delete;
116 RResultBase(RResultBase &&other) = default;
117 RResultBase &operator=(const RResultBase &other) = delete;
118 RResultBase &operator=(RResultBase &&other) = default;
119
120 ~RResultBase() noexcept(false);
121
122 std::optional<RError> GetError() const { return fError ? *fError : std::optional<RError>(); }
123 /// Throws an RException with fError
124 void Throw();
125
126 /// Used by R__FORWARD_ERROR in order to keep track of the stack trace.
127 [[nodiscard]]
128 static RError ForwardError(RResultBase &&result, RError::RLocation &&sourceLocation)
129 {
130 if (!result.fError) {
131 return RError("internal error: attempt to forward error of successful operation", std::move(sourceLocation));
132 }
133 result.fError->AddFrame(std::move(sourceLocation));
134 return *result.fError;
135 }
136}; // class RResultBase
137
138// clang-format off
139/**
140\class ROOT::RResult
141\ingroup Base
142\brief The class is used as a return type for operations that can fail; wraps a value of type T or an RError
143
144The RResult<T> class and their related classes are used for call chains that can throw exceptions,
145such as I/O code paths. Throwing of the exception is deferred to allow for `if (result)` style error
146checking where it makes sense. If an RResult in error state leaves the scope unchecked, it will throw.
147
148A function returning an RResult might look like this:
149
150~~~ {.cpp}
151RResult<int> MyIOFunc()
152{
153 int rv = syscall(...);
154 if (rv == -1)
155 return R__FAIL("user-facing error message");
156 if (rv == kShortcut)
157 return 42;
158 return R__FORWARD_RESULT(FuncThatReturnsRResultOfInt());
159}
160~~~
161
162Code using MyIOFunc might look like this:
163
164~~~ {.cpp}
165auto result = MyIOOperation();
166if (!result) {
167 // custom error handling or result.Throw()
168}
169switch (result.Inspect()) {
170 ...
171}
172~~~
173
174Note that RResult<void> can be used for a function without return value, like this
175
176~~~ {.cpp}
177RResult<void> DoSomething()
178{
179 if (failure)
180 return R__FAIL("user-facing error messge");
181 return RResult<void>::Success();
182}
183~~~
184
185RResult<T>::Unwrap() can be used as a short hand for
186"give me the wrapped value or, in case of an error, throw". For instance:
187
188~~~ {.cpp}
189int value = FuncThatReturnsRResultOfInt().Unwrap(); // may throw
190~~~
191
192There is no implicit operator that converts RResult<T> to T. This is intentional to make it clear in the calling code
193where an exception may be thrown.
194*/
195// clang-format on
196template <typename T>
197class RResult : public RResultBase {
198private:
199 /// The result value, only present in case of successful execution.
200 std::optional<T> fValue;
201
202 // Ensure accessor methods throw in case of errors
203 inline void ThrowOnError()
204 {
205 if (R__unlikely(fError)) {
206 // Accessors can be wrapped in a try-catch block, so throwing the
207 // exception here is akin to checking the error.
208 //
209 // Setting fIsChecked to true also avoids a spurious warning in the RResult destructor
210 fIsChecked = true;
211
212 fError->AppendToMessage(" (unchecked RResult access!)");
213 throw RException(*fError);
214 }
215 }
216
217public:
218 RResult(const T &value) : fValue(value) {}
219 RResult(T &&value) : fValue(std::move(value)) {}
220 RResult(RError &&error) : RResultBase(std::move(error)) {}
221
222 RResult(const RResult &other) = delete;
223 RResult(RResult &&other) = default;
224 RResult &operator=(const RResult &other) = delete;
225 RResult &operator=(RResult &&other) = default;
226
227 ~RResult() = default;
228
229 /// Used by R__FORWARD_RESULT in order to keep track of the stack trace in case of errors
231 {
232 if (fError)
233 fError->AddFrame(std::move(sourceLocation));
234 return *this;
235 }
236
237 /// If the operation was successful, returns a const reference to the inner type.
238 /// If there was an error, Inspect() instead throws an exception.
239 const T &Inspect()
240 {
241 ThrowOnError();
242 return *fValue;
243 }
244
245 /// If the operation was successful, returns the inner type by value.
246 ///
247 /// For move-only types, Unwrap can only be called once, as it yields ownership of
248 /// the inner value to the caller using std::move, potentially leaving the
249 /// RResult in an unspecified state.
250 ///
251 /// If there was an error, Unwrap() instead throws an exception.
253 {
254 ThrowOnError();
255 return std::move(*fValue);
256 }
257
258 explicit operator bool() { return Check(); }
259};
260
261/// RResult<void> has no data member and no Inspect() method but instead a Success() factory method
262template <>
263class RResult<void> : public RResultBase {
264private:
265 RResult() = default;
266
267public:
268 /// Returns a RResult<void> that captures the successful execution of the function
269 static RResult Success() { return RResult(); }
270 RResult(RError &&error) : RResultBase(std::move(error)) {}
271
272 RResult(const RResult &other) = delete;
273 RResult(RResult &&other) = default;
274 RResult &operator=(const RResult &other) = delete;
275 RResult &operator=(RResult &&other) = default;
276
277 ~RResult() = default;
278
279 /// Used by R__FORWARD_RESULT in order to keep track of the stack trace in case of errors
281 {
282 if (fError)
283 fError->AddFrame(std::move(sourceLocation));
284 return *this;
285 }
286
287 /// Short-hand method to throw an exception in the case of errors. Does nothing for
288 /// successful RResults.
290 {
291 if (!Check())
292 Throw();
293 }
294
295 explicit operator bool() { return Check(); }
296};
297
298/// Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult<T>
299#define R__FAIL(msg) ROOT::RError(msg, {R__LOG_PRETTY_FUNCTION, __FILE__, __LINE__})
300/// Short-hand to return an RResult<T> value from a subroutine to the calling stack frame
301#define R__FORWARD_RESULT(res) std::move(res.Forward({R__LOG_PRETTY_FUNCTION, __FILE__, __LINE__}))
302/// Short-hand to return an RResult<T> in an error state (i.e. after checking)
303#define R__FORWARD_ERROR(res) res.ForwardError(std::move(res), {R__LOG_PRETTY_FUNCTION, __FILE__, __LINE__})
304
305} // namespace ROOT
306
307#endif
#define R__unlikely(expr)
Definition RConfig.hxx:586
Captures diagnostics related to a ROOT runtime error.
Definition RError.hxx:38
RError(std::string_view message, RLocation &&sourceLocation)
Used by R__FAIL.
Definition RError.cxx:31
void AddFrame(RLocation &&sourceLocation)
Used by R__FORWARD_RESULT.
Definition RError.cxx:39
std::string fMessage
User-facing error message.
Definition RError.hxx:55
std::vector< RLocation > fStackTrace
The location of the error related to fMessage plus upper frames if the error is forwarded through the...
Definition RError.hxx:57
std::string GetReport() const
Format a dignostics report, e.g. for an exception message.
Definition RError.cxx:21
void AppendToMessage(std::string_view info)
Add more information to the diagnostics.
Definition RError.hxx:65
const std::vector< RLocation > & GetStackTrace() const
Definition RError.hxx:68
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
RException(const RError &error)
Definition RError.hxx:82
const RError & GetError() const
Definition RError.hxx:83
RResultBase & operator=(RResultBase &&other)=default
~RResultBase() noexcept(false)
Definition RError.cxx:44
void Throw()
Throws an RException with fError.
Definition RError.cxx:61
RResultBase(RResultBase &&other)=default
static RError ForwardError(RResultBase &&result, RError::RLocation &&sourceLocation)
Used by R__FORWARD_ERROR in order to keep track of the stack trace.
Definition RError.hxx:128
bool Check()
Used by the RResult<T> bool operator.
Definition RError.hxx:108
RResultBase(const RResultBase &other)=delete
RResultBase(RError &&error)
Definition RError.hxx:105
RResultBase & operator=(const RResultBase &other)=delete
RResultBase()=default
bool fIsChecked
Switches to true once the user of an RResult object checks the object status.
Definition RError.hxx:102
std::optional< RError > GetError() const
Definition RError.hxx:122
std::unique_ptr< RError > fError
This is the nullptr for an RResult representing success.
Definition RError.hxx:100
void ThrowOnError()
Short-hand method to throw an exception in the case of errors.
Definition RError.hxx:289
RResult(RError &&error)
Definition RError.hxx:270
RResult & operator=(RResult &&other)=default
RResult & operator=(const RResult &other)=delete
RResult(const RResult &other)=delete
RResult(RResult &&other)=default
static RResult Success()
Returns a RResult<void> that captures the successful execution of the function.
Definition RError.hxx:269
RResult & Forward(RError::RLocation &&sourceLocation)
Used by R__FORWARD_RESULT in order to keep track of the stack trace in case of errors.
Definition RError.hxx:280
RResult(RError &&error)
Definition RError.hxx:220
T Unwrap()
If the operation was successful, returns the inner type by value.
Definition RError.hxx:252
RResult(RResult &&other)=default
RResult & Forward(RError::RLocation &&sourceLocation)
Used by R__FORWARD_RESULT in order to keep track of the stack trace in case of errors.
Definition RError.hxx:230
RResult(T &&value)
Definition RError.hxx:219
RResult(const RResult &other)=delete
void ThrowOnError()
Definition RError.hxx:203
RResult(const T &value)
Definition RError.hxx:218
RResult & operator=(RResult &&other)=default
std::optional< T > fValue
The result value, only present in case of successful execution.
Definition RError.hxx:200
~RResult()=default
const T & Inspect()
If the operation was successful, returns a const reference to the inner type.
Definition RError.hxx:239
RResult & operator=(const RResult &other)=delete
TLine * line
const char * fSourceFile
Definition RError.hxx:49
const char * fFunction
Definition RError.hxx:48
RLocation(const char *func, const char *file, unsigned int line)
Definition RError.hxx:42
unsigned int fSourceLine
Definition RError.hxx:50