1 //
2 // Copyright (C) 2022 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 #pragma once
17
18 #include <optional>
19 #include <type_traits>
20
21 #include <android-base/logging.h>
22 #include <android-base/result.h> // IWYU pragma: export
23
24 namespace cuttlefish {
25
26 class StackTraceError;
27
28 class StackTraceEntry {
29 public:
StackTraceEntry(std::string file,size_t line,std::string pretty_function)30 StackTraceEntry(std::string file, size_t line, std::string pretty_function)
31 : file_(std::move(file)),
32 line_(line),
33 pretty_function_(std::move(pretty_function)) {}
34
StackTraceEntry(std::string file,size_t line,std::string pretty_function,std::string expression)35 StackTraceEntry(std::string file, size_t line, std::string pretty_function,
36 std::string expression)
37 : file_(std::move(file)),
38 line_(line),
39 pretty_function_(std::move(pretty_function)),
40 expression_(std::move(expression)) {}
41
StackTraceEntry(const StackTraceEntry & other)42 StackTraceEntry(const StackTraceEntry& other)
43 : file_(other.file_),
44 line_(other.line_),
45 pretty_function_(other.pretty_function_),
46 expression_(other.expression_),
47 message_(other.message_.str()) {}
48
49 StackTraceEntry(StackTraceEntry&&) = default;
50 StackTraceEntry& operator=(const StackTraceEntry& other) {
51 file_ = other.file_;
52 line_ = other.line_;
53 pretty_function_ = other.pretty_function_;
54 expression_ = other.expression_;
55 message_.str(other.message_.str());
56 return *this;
57 }
58 StackTraceEntry& operator=(StackTraceEntry&&) = default;
59
60 template <typename T>
61 StackTraceEntry& operator<<(T&& message_ext) & {
62 message_ << std::forward<T>(message_ext);
63 return *this;
64 }
65 template <typename T>
66 StackTraceEntry operator<<(T&& message_ext) && {
67 message_ << std::forward<T>(message_ext);
68 return std::move(*this);
69 }
70
71 operator StackTraceError() &&;
72 template <typename T>
73 operator android::base::expected<T, StackTraceError>() &&;
74
HasMessage()75 bool HasMessage() const { return !message_.str().empty(); }
76
Write(std::ostream & stream)77 void Write(std::ostream& stream) const { stream << message_.str(); }
WriteVerbose(std::ostream & stream)78 void WriteVerbose(std::ostream& stream) const {
79 auto str = message_.str();
80 if (str.empty()) {
81 stream << "Failure\n";
82 } else {
83 stream << message_.str() << "\n";
84 }
85 stream << " at " << file_ << ":" << line_ << "\n";
86 stream << " in " << pretty_function_;
87 if (!expression_.empty()) {
88 stream << " for CF_EXPECT(" << expression_ << ")\n";
89 }
90 }
91
92 private:
93 std::string file_;
94 size_t line_;
95 std::string pretty_function_;
96 std::string expression_;
97 std::stringstream message_;
98 };
99
100 #define CF_STACK_TRACE_ENTRY(expression) \
101 StackTraceEntry(__FILE__, __LINE__, __PRETTY_FUNCTION__, expression)
102
103 class StackTraceError {
104 public:
PushEntry(StackTraceEntry entry)105 StackTraceError& PushEntry(StackTraceEntry entry) & {
106 stack_.emplace_back(std::move(entry));
107 return *this;
108 }
PushEntry(StackTraceEntry entry)109 StackTraceError PushEntry(StackTraceEntry entry) && {
110 stack_.emplace_back(std::move(entry));
111 return std::move(*this);
112 }
Stack()113 const std::vector<StackTraceEntry>& Stack() const { return stack_; }
114
Message()115 std::string Message() const {
116 std::stringstream writer;
117 for (const auto& entry : stack_) {
118 entry.Write(writer);
119 }
120 return writer.str();
121 }
122
Trace()123 std::string Trace() const {
124 std::stringstream writer;
125 for (const auto& entry : stack_) {
126 entry.WriteVerbose(writer);
127 }
128 return writer.str();
129 }
130
131 template <typename T>
132 operator android::base::expected<T, StackTraceError>() && {
133 return android::base::unexpected(std::move(*this));
134 }
135
136 private:
137 std::vector<StackTraceEntry> stack_;
138 };
139
StackTraceError()140 inline StackTraceEntry::operator StackTraceError() && {
141 return StackTraceError().PushEntry(std::move(*this));
142 }
143
144 template <typename T>
145 inline StackTraceEntry::operator android::base::expected<T,
146 StackTraceError>() && {
147 return android::base::unexpected(std::move(*this));
148 }
149
150 template <typename T>
151 using Result = android::base::expected<T, StackTraceError>;
152
153 /**
154 * Error return macro that includes the location in the file in the error
155 * message. Use CF_ERRNO when including information from errno, otherwise use
156 * the base CF_ERR macro.
157 *
158 * Example usage:
159 *
160 * if (mkdir(path.c_str()) != 0) {
161 * return CF_ERRNO("mkdir(\"" << path << "\") failed: "
162 * << strerror(errno));
163 * }
164 *
165 * This will return an error with the text
166 *
167 * mkdir(...) failed: ...
168 * at path/to/file.cpp:50
169 * in Result<std::string> MyFunction()
170 */
171 #define CF_ERR(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
172 #define CF_ERRNO(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
173
174 template <typename T>
OutcomeDereference(std::optional<T> && value)175 T OutcomeDereference(std::optional<T>&& value) {
176 return std::move(*value);
177 }
178
179 template <typename T>
OutcomeDereference(Result<T> && result)180 typename std::conditional_t<std::is_void_v<T>, bool, T> OutcomeDereference(
181 Result<T>&& result) {
182 if constexpr (std::is_void<T>::value) {
183 return result.ok();
184 } else {
185 return std::move(*result);
186 }
187 }
188
189 template <typename T>
190 typename std::enable_if<std::is_convertible_v<T, bool>, T>::type
OutcomeDereference(T && value)191 OutcomeDereference(T&& value) {
192 return std::forward<T>(value);
193 }
194
TypeIsSuccess(bool value)195 inline bool TypeIsSuccess(bool value) { return value; }
196
197 template <typename T>
TypeIsSuccess(std::optional<T> & value)198 bool TypeIsSuccess(std::optional<T>& value) {
199 return value.has_value();
200 }
201
202 template <typename T>
TypeIsSuccess(Result<T> & value)203 bool TypeIsSuccess(Result<T>& value) {
204 return value.ok();
205 }
206
207 template <typename T>
TypeIsSuccess(Result<T> && value)208 bool TypeIsSuccess(Result<T>&& value) {
209 return value.ok();
210 }
211
ErrorFromType(bool)212 inline auto ErrorFromType(bool) { return StackTraceError(); }
213
214 template <typename T>
ErrorFromType(std::optional<T>)215 inline auto ErrorFromType(std::optional<T>) {
216 return StackTraceError();
217 }
218
219 template <typename T>
ErrorFromType(Result<T> & value)220 auto ErrorFromType(Result<T>& value) {
221 return value.error();
222 }
223
224 template <typename T>
ErrorFromType(Result<T> && value)225 auto ErrorFromType(Result<T>&& value) {
226 return value.error();
227 }
228
229 #define CF_EXPECT_OVERLOAD(_1, _2, NAME, ...) NAME
230
231 #define CF_EXPECT2(RESULT, MSG) \
232 ({ \
233 decltype(RESULT)&& macro_intermediate_result = RESULT; \
234 if (!TypeIsSuccess(macro_intermediate_result)) { \
235 auto current_entry = CF_STACK_TRACE_ENTRY(#RESULT); \
236 current_entry << MSG; \
237 auto error = ErrorFromType(macro_intermediate_result); \
238 error.PushEntry(std::move(current_entry)); \
239 return error; \
240 }; \
241 OutcomeDereference(std::move(macro_intermediate_result)); \
242 })
243
244 #define CF_EXPECT1(RESULT) CF_EXPECT2(RESULT, "")
245
246 /**
247 * Error propagation macro that can be used as an expression.
248 *
249 * The first argument can be either a Result or a type that is convertible to
250 * a boolean. A successful result will return the value inside the result, or
251 * a conversion to a `true` value will return the unconverted value. This is
252 * useful for e.g. pointers which can be tested through boolean conversion.
253 *
254 * In the failure case, this macro will return from the containing function
255 * with a failing Result. The failing result will include information about the
256 * call site, details from the optional second argument if given, and details
257 * from the failing inner expression if it is a Result.
258 *
259 * This macro must be invoked only in functions that return a Result.
260 *
261 * Example usage:
262 *
263 * Result<std::string> CreateTempDir();
264 *
265 * Result<std::string> CreatePopulatedTempDir() {
266 * std::string dir = CF_EXPECT(CreateTempDir(), "Failed to create dir");
267 * // Do something with dir
268 * return dir;
269 * }
270 *
271 * If CreateTempDir fails, the function will returna Result with an error
272 * message that looks like
273 *
274 * Internal error
275 * at /path/to/otherfile.cpp:50
276 * in Result<std::string> CreateTempDir()
277 * Failed to create dir
278 * at /path/to/file.cpp:81:
279 * in Result<std::string> CreatePopulatedTempDir()
280 * for CF_EXPECT(CreateTempDir())
281 */
282 #define CF_EXPECT(...) \
283 CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__)
284
285 #define CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, MSG) \
286 ({ \
287 auto&& lhs_macro_intermediate_result = LHS_RESULT; \
288 auto&& rhs_macro_intermediate_result = RHS_RESULT; \
289 bool comparison_result = lhs_macro_intermediate_result COMPARE_OP \
290 rhs_macro_intermediate_result; \
291 if (!comparison_result) { \
292 auto current_entry = CF_STACK_TRACE_ENTRY(""); \
293 current_entry << "Expected \"" << #LHS_RESULT << "\" " << #COMPARE_OP \
294 << " \"" << #RHS_RESULT << "\" but was " \
295 << lhs_macro_intermediate_result << " vs " \
296 << rhs_macro_intermediate_result << "."; \
297 current_entry << MSG; \
298 auto error = ErrorFromType(false); \
299 error.PushEntry(std::move(current_entry)); \
300 return error; \
301 }; \
302 comparison_result; \
303 })
304
305 #define CF_COMPARE_EXPECT3(COMPARE_OP, LHS_RESULT, RHS_RESULT) \
306 CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, "")
307
308 #define CF_COMPARE_EXPECT_OVERLOAD(_1, _2, _3, _4, NAME, ...) NAME
309
310 #define CF_COMPARE_EXPECT(...) \
311 CF_COMPARE_EXPECT_OVERLOAD(__VA_ARGS__, CF_COMPARE_EXPECT4, \
312 CF_COMPARE_EXPECT3) \
313 (__VA_ARGS__)
314
315 #define CF_EXPECT_EQ(LHS_RESULT, RHS_RESULT, ...) \
316 CF_COMPARE_EXPECT(==, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
317 #define CF_EXPECT_NE(LHS_RESULT, RHS_RESULT, ...) \
318 CF_COMPARE_EXPECT(!=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
319 #define CF_EXPECT_LE(LHS_RESULT, RHS_RESULT, ...) \
320 CF_COMPARE_EXPECT(<=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
321 #define CF_EXPECT_LT(LHS_RESULT, RHS_RESULT, ...) \
322 CF_COMPARE_EXPECT(<, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
323 #define CF_EXPECT_GE(LHS_RESULT, RHS_RESULT, ...) \
324 CF_COMPARE_EXPECT(>=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
325 #define CF_EXPECT_GT(LHS_RESULT, RHS_RESULT, ...) \
326 CF_COMPARE_EXPECT(>, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
327
328 } // namespace cuttlefish
329