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 <ostream>
20 #include <sstream>
21 #include <string>
22 #include <type_traits>
23 #include <utility>
24 #include <vector>
25 
26 #include <android-base/format.h>  // IWYU pragma: export
27 #include <android-base/logging.h>
28 #include <android-base/result.h>  // IWYU pragma: export
29 
30 #if FMT_VERSION < 80000
31 namespace fmt {
32   // fmt::runtime was added in v8.0.0
33   template<typename T>
runtime(const T & param)34   const T& runtime(const T& param) {
35     return param;
36   }
37 }
38 #endif
39 
40 namespace cuttlefish {
41 
42 class StackTraceError;
43 
44 class StackTraceEntry {
45  public:
46   enum class FormatSpecifier : char {
47     /** Prefix multi-line output with an arrow. */
48     kArrow = 'a',
49     /** Use colors in all other output specifiers. */
50     kColor = 'c',
51     /** The function name without namespace or arguments. */
52     kFunction = 'f',
53     /** The CF_EXPECT(exp) expression. */
54     kLongExpression = 'E',
55     /** The source file path relative to ANDROID_BUILD_TOP and line number. */
56     kLongLocation = 'L',
57     /** The user-friendly string provided to CF_EXPECT. */
58     kMessage = 'm',
59     /** Prefix output with the stack frame index. */
60     kNumbers = 'n',
61     /** The function signature with fully-qualified types. */
62     kPrettyFunction = 'F',
63     /** The short location and short filename. */
64     kShort = 's',
65     /** The `exp` inside `CF_EXPECT(exp)` */
66     kShortExpression = 'e',
67     /** The source file basename and line number. */
68     kShortLocation = 'l',
69   };
70   static constexpr auto kVerbose = {
71       FormatSpecifier::kArrow,
72       FormatSpecifier::kColor,
73       FormatSpecifier::kNumbers,
74       FormatSpecifier::kShort,
75   };
76   static constexpr auto kVeryVerbose = {
77       FormatSpecifier::kArrow,          FormatSpecifier::kColor,
78       FormatSpecifier::kNumbers,        FormatSpecifier::kLongLocation,
79       FormatSpecifier::kPrettyFunction, FormatSpecifier::kLongExpression,
80       FormatSpecifier::kMessage,
81   };
82 
83   StackTraceEntry(std::string file, size_t line, std::string pretty_function,
84                   std::string function);
85 
86   StackTraceEntry(std::string file, size_t line, std::string pretty_function,
87                   std::string function, std::string expression);
88 
89   StackTraceEntry(const StackTraceEntry& other);
90 
91   StackTraceEntry(StackTraceEntry&&) = default;
92   StackTraceEntry& operator=(const StackTraceEntry& other);
93   StackTraceEntry& operator=(StackTraceEntry&&) = default;
94 
95   template <typename T>
96   StackTraceEntry& operator<<(T&& message_ext) & {
97     message_ << std::forward<T>(message_ext);
98     return *this;
99   }
100   template <typename T>
101   StackTraceEntry operator<<(T&& message_ext) && {
102     message_ << std::forward<T>(message_ext);
103     return std::move(*this);
104   }
105 
106   operator StackTraceError() &&;
107   template <typename T>
108   operator android::base::expected<T, StackTraceError>() &&;
109 
110   bool HasMessage() const;
111 
112   /*
113    * Print a single stack trace entry out of a list of format specifiers.
114    * Some format specifiers [a,c,n] cause changes that affect all lines, while
115    * the rest amount to printing a single line in the output. This code is
116    * reused by formatting code for both rendering individual stack trace
117    * entries, and rendering an entire stack trace with multiple entries.
118    */
119   fmt::format_context::iterator format(
120       fmt::format_context& ctx, const std::vector<FormatSpecifier>& specifiers,
121       std::optional<int> index) const;
122 
123  private:
124   std::string file_;
125   size_t line_;
126   std::string pretty_function_;
127   std::string function_;
128   std::string expression_;
129   std::stringstream message_;
130 };
131 
132 std::string ResultErrorFormat(bool color);
133 
134 #define CF_STACK_TRACE_ENTRY(expression) \
135   StackTraceEntry(__FILE__, __LINE__, __PRETTY_FUNCTION__, __func__, expression)
136 
137 }  // namespace cuttlefish
138 
139 /**
140  * Specialized formatting for StackTraceEntry based on user-provided specifiers.
141  *
142  * A StackTraceEntry can be formatted with {:specifiers} in a `fmt::format`
143  * string, where `specifiers` is an ordered list of characters deciding on the
144  * format. `v` provides "verbose" output and `V` provides "very verbose" output.
145  * See `StackTraceEntry::FormatSpecifiers` for more fine-grained specifiers.
146  */
147 template <>
148 struct fmt::formatter<cuttlefish::StackTraceEntry> {
149  public:
150   constexpr auto parse(format_parse_context& ctx)
151       -> format_parse_context::iterator {
152     auto it = ctx.begin();
153     while (it != ctx.end() && *it != '}') {
154       if (*it == 'v') {
155         for (const auto& specifier : cuttlefish::StackTraceEntry::kVerbose) {
156           fmt_specs_.push_back(specifier);
157         }
158       } else if (*it == 'V') {
159         for (const auto& specifier :
160              cuttlefish::StackTraceEntry::kVeryVerbose) {
161           fmt_specs_.push_back(specifier);
162         }
163       } else {
164         fmt_specs_.push_back(
165             static_cast<cuttlefish::StackTraceEntry::FormatSpecifier>(*it));
166       }
167       it++;
168     }
169     return it;
170   }
171 
172   auto format(const cuttlefish::StackTraceEntry& entry,
173               format_context& ctx) const -> format_context::iterator {
174     return entry.format(ctx, fmt_specs_, std::nullopt);
175   }
176 
177  private:
178   std::vector<cuttlefish::StackTraceEntry::FormatSpecifier> fmt_specs_;
179 };
180 
181 namespace cuttlefish {
182 
183 class StackTraceError {
184  public:
185   StackTraceError& PushEntry(StackTraceEntry entry) & {
186     stack_.emplace_back(std::move(entry));
187     return *this;
188   }
189   StackTraceError PushEntry(StackTraceEntry entry) && {
190     stack_.emplace_back(std::move(entry));
191     return std::move(*this);
192   }
193   const std::vector<StackTraceEntry>& Stack() const { return stack_; }
194 
195   std::string Message() const {
196     return fmt::format(fmt::runtime("{:m}"), *this);
197   }
198 
199   std::string Trace() const { return fmt::format(fmt::runtime("{:v}"), *this); }
200 
201   std::string FormatForEnv(bool color = (isatty(STDERR_FILENO) == 1)) const {
202     return fmt::format(fmt::runtime(ResultErrorFormat(color)), *this);
203   }
204 
205   template <typename T>
206   operator android::base::expected<T, StackTraceError>() && {
207     return android::base::unexpected(std::move(*this));
208   }
209 
210  private:
211   std::vector<StackTraceEntry> stack_;
212 };
213 
214 inline StackTraceEntry::operator StackTraceError() && {
215   return StackTraceError().PushEntry(std::move(*this));
216 }
217 
218 template <typename T>
219 inline StackTraceEntry::operator android::base::expected<T,
220                                                          StackTraceError>() && {
221   return android::base::unexpected(std::move(*this));
222 }
223 
224 }  // namespace cuttlefish
225 
226 /**
227  * Specialized formatting for a collection of StackTraceEntry elements.
228  *
229  * Can be formatted by a `fmt::format` string as {:specifiers}. See
230  * `fmt::formatter<cuttlefish::StackTraceEntry>` for the format specifiers of
231  * individual entries. By default the specifier list is passed down to all
232  * indivudal entries, with the following additional rules. The `^` specifier
233  * will change the ordering from inner-to-outer instead of outer-to-inner, and
234  * using the `/` specifier like `<abc>/<xyz>` will apply <xyz> only to the
235  * innermost stack entry, and <abc> to all other stack entries.
236  */
237 template <>
238 struct fmt::formatter<cuttlefish::StackTraceError> {
239  public:
240   constexpr auto parse(format_parse_context& ctx)
241       -> format_parse_context::iterator {
242     auto it = ctx.begin();
243     while (it != ctx.end() && *it != '}') {
244       if (*it == 'v') {
245         for (const auto& spec : StackTraceEntry::kVerbose) {
246           (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec);
247         }
248       } else if (*it == 'V') {
249         for (const auto& spec : StackTraceEntry::kVeryVerbose) {
250           (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec);
251         }
252       } else if (*it == '/') {
253         has_inner_fmt_spec_ = true;
254       } else if (*it == '^') {
255         inner_to_outer_ = true;
256       } else {
257         (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_)
258             .push_back(static_cast<StackTraceEntry::FormatSpecifier>(*it));
259       }
260       it++;
261     }
262     return it;
263   }
264 
265   format_context::iterator format(const cuttlefish::StackTraceError& error,
266                                   format_context& ctx) const;
267 
268  private:
269   using StackTraceEntry = cuttlefish::StackTraceEntry;
270   using StackTraceError = cuttlefish::StackTraceError;
271 
272   bool inner_to_outer_ = false;
273   bool has_inner_fmt_spec_ = false;
274   std::vector<StackTraceEntry::FormatSpecifier> fmt_specs_;
275   std::vector<StackTraceEntry::FormatSpecifier> inner_fmt_specs_;
276 };
277 
278 namespace cuttlefish {
279 
280 template <typename T>
281 using Result = android::base::expected<T, StackTraceError>;
282 
283 /**
284  * Error return macro that includes the location in the file in the error
285  * message. Use CF_ERRNO when including information from errno, otherwise use
286  * the base CF_ERR macro.
287  *
288  * Example usage:
289  *
290  *     if (mkdir(path.c_str()) != 0) {
291  *       return CF_ERRNO("mkdir(\"" << path << "\") failed: "
292  *                       << strerror(errno));
293  *     }
294  *
295  * This will return an error with the text
296  *
297  *     mkdir(...) failed: ...
298  *       at path/to/file.cpp:50
299  *       in Result<std::string> MyFunction()
300  */
301 #define CF_ERR(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
302 #define CF_ERRNO(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
303 #define CF_ERRF(MSG, ...) \
304   (CF_STACK_TRACE_ENTRY("") << fmt::format(FMT_STRING(MSG), __VA_ARGS__))
305 
306 template <typename T>
307 T OutcomeDereference(std::optional<T>&& value) {
308   return std::move(*value);
309 }
310 
311 template <typename T>
312 typename std::conditional_t<std::is_void_v<T>, bool, T> OutcomeDereference(
313     Result<T>&& result) {
314   if constexpr (std::is_void<T>::value) {
315     return result.ok();
316   } else {
317     return std::move(*result);
318   }
319 }
320 
321 template <typename T>
322 typename std::enable_if<std::is_convertible_v<T, bool>, T>::type
323 OutcomeDereference(T&& value) {
324   return std::forward<T>(value);
325 }
326 
327 inline bool TypeIsSuccess(bool value) { return value; }
328 
329 template <typename T>
330 bool TypeIsSuccess(std::optional<T>& value) {
331   return value.has_value();
332 }
333 
334 template <typename T>
335 bool TypeIsSuccess(Result<T>& value) {
336   return value.ok();
337 }
338 
339 template <typename T>
340 bool TypeIsSuccess(Result<T>&& value) {
341   return value.ok();
342 }
343 
344 inline auto ErrorFromType(bool) { return StackTraceError(); }
345 
346 template <typename T>
347 inline auto ErrorFromType(std::optional<T>) {
348   return StackTraceError();
349 }
350 
351 template <typename T>
352 auto ErrorFromType(Result<T>& value) {
353   return value.error();
354 }
355 
356 template <typename T>
357 auto ErrorFromType(Result<T>&& value) {
358   return value.error();
359 }
360 
361 #define CF_EXPECT_OVERLOAD(_1, _2, NAME, ...) NAME
362 
363 #define CF_EXPECT2(RESULT, MSG)                               \
364   ({                                                          \
365     decltype(RESULT)&& macro_intermediate_result = RESULT;    \
366     if (!TypeIsSuccess(macro_intermediate_result)) {          \
367       auto current_entry = CF_STACK_TRACE_ENTRY(#RESULT);     \
368       current_entry << MSG;                                   \
369       auto error = ErrorFromType(macro_intermediate_result);  \
370       error.PushEntry(std::move(current_entry));              \
371       return std::move(error);                                \
372     };                                                        \
373     OutcomeDereference(std::move(macro_intermediate_result)); \
374   })
375 
376 #define CF_EXPECT1(RESULT) CF_EXPECT2(RESULT, "")
377 
378 /**
379  * Error propagation macro that can be used as an expression.
380  *
381  * The first argument can be either a Result or a type that is convertible to
382  * a boolean. A successful result will return the value inside the result, or
383  * a conversion to a `true` value will return the unconverted value. This is
384  * useful for e.g. pointers which can be tested through boolean conversion.
385  *
386  * In the failure case, this macro will return from the containing function
387  * with a failing Result. The failing result will include information about the
388  * call site, details from the optional second argument if given, and details
389  * from the failing inner expression if it is a Result.
390  *
391  * This macro must be invoked only in functions that return a Result.
392  *
393  * Example usage:
394  *
395  *     Result<std::string> CreateTempDir();
396  *
397  *     Result<std::string> CreatePopulatedTempDir() {
398  *       std::string dir = CF_EXPECT(CreateTempDir(), "Failed to create dir");
399  *       // Do something with dir
400  *       return dir;
401  *     }
402  *
403  * If CreateTempDir fails, the function will returna Result with an error
404  * message that looks like
405  *
406  *      Internal error
407  *        at /path/to/otherfile.cpp:50
408  *        in Result<std::string> CreateTempDir()
409  *      Failed to create dir
410  *        at /path/to/file.cpp:81:
411  *        in Result<std::string> CreatePopulatedTempDir()
412  *        for CF_EXPECT(CreateTempDir())
413  */
414 #define CF_EXPECT(...) \
415   CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__)
416 
417 #define CF_EXPECTF(RESULT, MSG, ...) \
418   CF_EXPECT(RESULT, fmt::format(FMT_STRING(MSG), __VA_ARGS__))
419 
420 #define CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, MSG)         \
421   ({                                                                        \
422     auto&& lhs_macro_intermediate_result = LHS_RESULT;                      \
423     auto&& rhs_macro_intermediate_result = RHS_RESULT;                      \
424     bool comparison_result = lhs_macro_intermediate_result COMPARE_OP       \
425         rhs_macro_intermediate_result;                                      \
426     if (!comparison_result) {                                               \
427       auto current_entry = CF_STACK_TRACE_ENTRY("");                        \
428       current_entry << "Expected \"" << #LHS_RESULT << "\" " << #COMPARE_OP \
429                     << " \"" << #RHS_RESULT << "\" but was "                \
430                     << lhs_macro_intermediate_result << " vs "              \
431                     << rhs_macro_intermediate_result << ". ";               \
432       current_entry << MSG;                                                 \
433       auto error = ErrorFromType(false);                                    \
434       error.PushEntry(std::move(current_entry));                            \
435       return std::move(error);                                              \
436     };                                                                      \
437     comparison_result;                                                      \
438   })
439 
440 #define CF_COMPARE_EXPECT3(COMPARE_OP, LHS_RESULT, RHS_RESULT) \
441   CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, "")
442 
443 #define CF_COMPARE_EXPECT_OVERLOAD(_1, _2, _3, _4, NAME, ...) NAME
444 
445 #define CF_COMPARE_EXPECT(...)                                \
446   CF_COMPARE_EXPECT_OVERLOAD(__VA_ARGS__, CF_COMPARE_EXPECT4, \
447                              CF_COMPARE_EXPECT3)              \
448   (__VA_ARGS__)
449 
450 #define CF_EXPECT_EQ(LHS_RESULT, RHS_RESULT, ...) \
451   CF_COMPARE_EXPECT(==, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
452 #define CF_EXPECT_NE(LHS_RESULT, RHS_RESULT, ...) \
453   CF_COMPARE_EXPECT(!=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
454 #define CF_EXPECT_LE(LHS_RESULT, RHS_RESULT, ...) \
455   CF_COMPARE_EXPECT(<=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
456 #define CF_EXPECT_LT(LHS_RESULT, RHS_RESULT, ...) \
457   CF_COMPARE_EXPECT(<, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
458 #define CF_EXPECT_GE(LHS_RESULT, RHS_RESULT, ...) \
459   CF_COMPARE_EXPECT(>=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
460 #define CF_EXPECT_GT(LHS_RESULT, RHS_RESULT, ...) \
461   CF_COMPARE_EXPECT(>, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
462 
463 }  // namespace cuttlefish
464