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 #include "common/libs/utils/result.h"
17 
18 #include <optional>
19 #include <sstream>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include <android-base/format.h>
25 #include <android-base/logging.h>
26 #include <android-base/result.h>
27 
28 namespace cuttlefish {
29 
StackTraceEntry(std::string file,size_t line,std::string pretty_function,std::string function)30 StackTraceEntry::StackTraceEntry(std::string file, size_t line,
31                                  std::string pretty_function,
32                                  std::string function)
33     : file_(std::move(file)),
34       line_(line),
35       pretty_function_(std::move(pretty_function)),
36       function_(std::move(function)) {}
37 
StackTraceEntry(std::string file,size_t line,std::string pretty_function,std::string function,std::string expression)38 StackTraceEntry::StackTraceEntry(std::string file, size_t line,
39                                  std::string pretty_function,
40                                  std::string function, std::string expression)
41     : file_(std::move(file)),
42       line_(line),
43       pretty_function_(std::move(pretty_function)),
44       function_(std::move(function)),
45       expression_(std::move(expression)) {}
46 
StackTraceEntry(const StackTraceEntry & other)47 StackTraceEntry::StackTraceEntry(const StackTraceEntry& other)
48     : file_(other.file_),
49       line_(other.line_),
50       pretty_function_(other.pretty_function_),
51       function_(other.function_),
52       expression_(other.expression_),
53       message_(other.message_.str()) {}
54 
operator =(const StackTraceEntry & other)55 StackTraceEntry& StackTraceEntry::operator=(const StackTraceEntry& other) {
56   file_ = other.file_;
57   line_ = other.line_;
58   pretty_function_ = other.pretty_function_;
59   function_ = other.function_;
60   expression_ = other.expression_;
61   message_.str(other.message_.str());
62   return *this;
63 }
64 
HasMessage() const65 bool StackTraceEntry::HasMessage() const { return !message_.str().empty(); }
66 
67 /*
68  * Print a single stack trace entry out of a list of format specifiers.
69  * Some format specifiers [a,c,n] cause changes that affect all lines, while
70  * the rest amount to printing a single line in the output. This code is
71  * reused by formatting code for both rendering individual stack trace
72  * entries, and rendering an entire stack trace with multiple entries.
73  */
format(fmt::format_context & ctx,const std::vector<FormatSpecifier> & specifiers,std::optional<int> index) const74 fmt::format_context::iterator StackTraceEntry::format(
75     fmt::format_context& ctx, const std::vector<FormatSpecifier>& specifiers,
76     std::optional<int> index) const {
77   static constexpr char kTerminalBoldRed[] = "\033[0;1;31m";
78   static constexpr char kTerminalCyan[] = "\033[0;36m";
79   static constexpr char kTerminalRed[] = "\033[0;31m";
80   static constexpr char kTerminalReset[] = "\033[0m";
81   static constexpr char kTerminalUnderline[] = "\033[0;4m";
82   static constexpr char kTerminalYellow[] = "\033[0;33m";
83   auto out = ctx.out();
84   std::vector<FormatSpecifier> filtered_specs;
85   bool arrow = false;
86   bool color = false;
87   bool numbers = false;
88   for (auto spec : specifiers) {
89     switch (spec) {
90       case FormatSpecifier::kArrow:
91         arrow = true;
92         continue;
93       case FormatSpecifier::kColor:
94         color = true;
95         continue;
96       case FormatSpecifier::kLongExpression:
97       case FormatSpecifier::kShortExpression:
98         if (expression_.empty()) {
99           continue;
100         }
101         break;
102       case FormatSpecifier::kMessage:
103         if (!HasMessage()) {
104           continue;
105         }
106         break;
107       case FormatSpecifier::kNumbers:
108         numbers = true;
109         continue;
110       default:  // fall through
111         break;
112     }
113     filtered_specs.emplace_back(spec);
114   }
115   if (filtered_specs.empty()) {
116     filtered_specs.push_back(FormatSpecifier::kShort);
117   }
118   for (size_t i = 0; i < filtered_specs.size(); i++) {
119     if (index.has_value() && numbers) {
120       if (color) {
121         out = fmt::format_to(out, "{}{}{}. ", kTerminalYellow, *index,
122                              kTerminalReset);
123       } else {
124         out = fmt::format_to(out, "{}. ", *index);
125       }
126     }
127     if (color) {
128       out = fmt::format_to(out, "{}", kTerminalRed);
129     }
130     if (numbers) {
131       if (arrow && (int)i < ((int)filtered_specs.size()) - 2) {
132         out = fmt::format_to(out, "|  ");
133       } else if (arrow && i == filtered_specs.size() - 2) {
134         out = fmt::format_to(out, "v  ");
135       }
136     } else {
137       if (arrow && (int)i < ((int)filtered_specs.size()) - 2) {
138         out = fmt::format_to(out, " | ");
139       } else if (arrow && i == filtered_specs.size() - 2) {
140         out = fmt::format_to(out, " v ");
141       }
142     }
143     if (color) {
144       out = fmt::format_to(out, "{}", kTerminalReset);
145     }
146     switch (filtered_specs[i]) {
147       case FormatSpecifier::kFunction:
148         if (color) {
149           out = fmt::format_to(out, "{}{}{}", kTerminalCyan, function_,
150                                kTerminalReset);
151         } else {
152           out = fmt::format_to(out, "{}", function_);
153         }
154         break;
155       case FormatSpecifier::kLongExpression:
156         out = fmt::format_to(out, "CF_EXPECT({})", expression_);
157         break;
158       case FormatSpecifier::kLongLocation:
159         if (color) {
160           out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline, file_,
161                                kTerminalReset, kTerminalYellow, line_,
162                                kTerminalYellow);
163         } else {
164           out = fmt::format_to(out, "{}:{}", file_, line_);
165         }
166         break;
167       case FormatSpecifier::kMessage:
168         if (color) {
169           out = fmt::format_to(out, "{}{}{}", kTerminalBoldRed, message_.str(),
170                                kTerminalReset);
171         } else {
172           out = fmt::format_to(out, "{}", message_.str());
173         }
174         break;
175       case FormatSpecifier::kPrettyFunction:
176         if (color) {
177           out = fmt::format_to(out, "{}{}{}", kTerminalCyan, pretty_function_,
178                                kTerminalReset);
179         } else {
180           out = fmt::format_to(out, "{}", pretty_function_);
181         }
182         break;
183       case FormatSpecifier::kShort: {
184         auto last_slash = file_.rfind("/");
185         auto short_file =
186             file_.substr(last_slash == std::string::npos ? 0 : last_slash + 1);
187         std::string last;
188         if (HasMessage()) {
189           last = color ? kTerminalBoldRed + message_.str() + kTerminalReset
190                        : message_.str();
191         }
192         if (color) {
193           out = fmt::format_to(out, "{}{}{}:{}{}{} | {}{}{} | {}",
194                                kTerminalUnderline, short_file, kTerminalReset,
195                                kTerminalYellow, line_, kTerminalReset,
196                                kTerminalCyan, function_, kTerminalReset, last);
197         } else {
198           out = fmt::format_to(out, "{}:{} | {} | {}", short_file, line_,
199                                function_, last);
200         }
201         break;
202       }
203       case FormatSpecifier::kShortExpression:
204         out = fmt::format_to(out, "{}", expression_);
205         break;
206       case FormatSpecifier::kShortLocation: {
207         auto last_slash = file_.rfind("/");
208         auto short_file =
209             file_.substr(last_slash == std::string::npos ? 0 : last_slash + 1);
210         if (color) {
211           out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline,
212                                short_file, kTerminalReset, kTerminalYellow,
213                                line_, kTerminalReset);
214         } else {
215           out = fmt::format_to(out, "{}:{}", short_file, line_);
216         }
217         break;
218       }
219       default:
220         fmt::format_to(out, "unknown specifier");
221     }
222     if (i < filtered_specs.size() - 1) {
223       out = fmt::format_to(out, "\n");
224     }
225   }
226   return out;
227 }
228 
ResultErrorFormat(bool color)229 std::string ResultErrorFormat(bool color) {
230   auto error_format = getenv("CF_ERROR_FORMAT");
231   std::string default_error_format = (color ? "cns/acLFEm" : "ns/aLFEm");
232   std::string fmt_str =
233       error_format == nullptr ? default_error_format : error_format;
234   if (fmt_str.find("}") != std::string::npos) {
235     fmt_str = "v";
236   }
237   return "{:" + fmt_str + "}";
238 }
239 
240 }  // namespace cuttlefish
241 
242 fmt::format_context::iterator
format(const cuttlefish::StackTraceError & error,format_context & ctx) const243 fmt::formatter<cuttlefish::StackTraceError>::format(
244     const cuttlefish::StackTraceError& error, format_context& ctx) const {
245   auto out = ctx.out();
246   auto& stack = error.Stack();
247   int begin = inner_to_outer_ ? 0 : stack.size() - 1;
248   int end = inner_to_outer_ ? stack.size() : -1;
249   int step = inner_to_outer_ ? 1 : -1;
250   for (int i = begin; i != end; i += step) {
251     auto& specs = has_inner_fmt_spec_ && i == 0 ? inner_fmt_specs_ : fmt_specs_;
252     out = stack[i].format(ctx, specs, i);
253     if (i != end - step) {
254       out = fmt::format_to(out, "\n");
255     }
256   }
257   return out;
258 }
259