blob: 6ad2a429beff8c906f1eaabb392c919141af3d85 [file] [log] [blame] [edit]
//
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <android-base/logging.h>
#include <android-base/result.h> // IWYU pragma: export
#include <fmt/core.h> // IWYU pragma: export
#include <fmt/format.h>
namespace cuttlefish {
class StackTraceError;
class StackTraceEntry {
public:
enum class FormatSpecifier : char {
/** Prefix multi-line output with an arrow. */
kArrow = 'a',
/** Use colors in all other output specifiers. */
kColor = 'c',
/** The function name without namespace or arguments. */
kFunction = 'f',
/** The CF_EXPECT(exp) expression. */
kLongExpression = 'E',
/** The source file path relative to ANDROID_BUILD_TOP and line number. */
kLongLocation = 'L',
/** The user-friendly string provided to CF_EXPECT. */
kMessage = 'm',
/** Prefix output with the stack frame index. */
kNumbers = 'n',
/** The function signature with fully-qualified types. */
kPrettyFunction = 'F',
/** The short location and short filename. */
kShort = 's',
/** The `exp` inside `CF_EXPECT(exp)` */
kShortExpression = 'e',
/** The source file basename and line number. */
kShortLocation = 'l',
};
static constexpr auto kVerbose = {
FormatSpecifier::kArrow,
FormatSpecifier::kColor,
FormatSpecifier::kNumbers,
FormatSpecifier::kShort,
};
static constexpr auto kVeryVerbose = {
FormatSpecifier::kArrow, FormatSpecifier::kColor,
FormatSpecifier::kNumbers, FormatSpecifier::kLongLocation,
FormatSpecifier::kPrettyFunction, FormatSpecifier::kLongExpression,
FormatSpecifier::kMessage,
};
StackTraceEntry(std::string file, size_t line, std::string pretty_function,
std::string function)
: file_(std::move(file)),
line_(line),
pretty_function_(std::move(pretty_function)),
function_(std::move(function)) {}
StackTraceEntry(std::string file, size_t line, std::string pretty_function,
std::string function, std::string expression)
: file_(std::move(file)),
line_(line),
pretty_function_(std::move(pretty_function)),
function_(std::move(function)),
expression_(std::move(expression)) {}
StackTraceEntry(const StackTraceEntry& other)
: file_(other.file_),
line_(other.line_),
pretty_function_(other.pretty_function_),
function_(other.function_),
expression_(other.expression_),
message_(other.message_.str()) {}
StackTraceEntry(StackTraceEntry&&) = default;
StackTraceEntry& operator=(const StackTraceEntry& other) {
file_ = other.file_;
line_ = other.line_;
pretty_function_ = other.pretty_function_;
function_ = other.function_;
expression_ = other.expression_;
message_.str(other.message_.str());
return *this;
}
StackTraceEntry& operator=(StackTraceEntry&&) = default;
template <typename T>
StackTraceEntry& operator<<(T&& message_ext) & {
message_ << std::forward<T>(message_ext);
return *this;
}
template <typename T>
StackTraceEntry operator<<(T&& message_ext) && {
message_ << std::forward<T>(message_ext);
return std::move(*this);
}
operator StackTraceError() &&;
template <typename T>
operator android::base::expected<T, StackTraceError>() &&;
bool HasMessage() const { return !message_.str().empty(); }
/*
* Print a single stack trace entry out of a list of format specifiers.
* Some format specifiers [a,c,n] cause changes that affect all lines, while
* the rest amount to printing a single line in the output. This code is
* reused by formatting code for both rendering individual stack trace
* entries, and rendering an entire stack trace with multiple entries.
*/
fmt::format_context::iterator format(
fmt::format_context& ctx, const std::vector<FormatSpecifier>& specifiers,
std::optional<int> index) const {
static constexpr char kTerminalBoldRed[] = "\033[0;1;31m";
static constexpr char kTerminalCyan[] = "\033[0;36m";
static constexpr char kTerminalRed[] = "\033[0;31m";
static constexpr char kTerminalReset[] = "\033[0m";
static constexpr char kTerminalUnderline[] = "\033[0;4m";
static constexpr char kTerminalYellow[] = "\033[0;33m";
auto out = ctx.out();
std::vector<FormatSpecifier> filtered_specs;
bool arrow = false;
bool color = false;
bool numbers = false;
for (auto spec : specifiers) {
switch (spec) {
case FormatSpecifier::kArrow:
arrow = true;
continue;
case FormatSpecifier::kColor:
color = true;
continue;
case FormatSpecifier::kLongExpression:
case FormatSpecifier::kShortExpression:
if (expression_.empty()) {
continue;
}
break;
case FormatSpecifier::kMessage:
if (!HasMessage()) {
continue;
}
break;
case FormatSpecifier::kNumbers:
numbers = true;
continue;
default: // fall through
break;
}
filtered_specs.emplace_back(spec);
}
if (filtered_specs.empty()) {
filtered_specs.push_back(FormatSpecifier::kShort);
}
for (size_t i = 0; i < filtered_specs.size(); i++) {
if (index.has_value() && numbers) {
if (color) {
out = fmt::format_to(out, "{}{}{}. ", kTerminalYellow, *index,
kTerminalReset);
} else {
out = fmt::format_to(out, "{}. ", *index);
}
}
if (color) {
out = fmt::format_to(out, "{}", kTerminalRed);
}
if (numbers) {
if (arrow && (int)i < ((int)filtered_specs.size()) - 2) {
out = fmt::format_to(out, "| ");
} else if (arrow && i == filtered_specs.size() - 2) {
out = fmt::format_to(out, "v ");
}
} else {
if (arrow && (int)i < ((int)filtered_specs.size()) - 2) {
out = fmt::format_to(out, " | ");
} else if (arrow && i == filtered_specs.size() - 2) {
out = fmt::format_to(out, " v ");
}
}
if (color) {
out = fmt::format_to(out, "{}", kTerminalReset);
}
switch (filtered_specs[i]) {
case FormatSpecifier::kFunction:
if (color) {
out = fmt::format_to(out, "{}{}{}", kTerminalCyan, function_,
kTerminalReset);
} else {
out = fmt::format_to(out, "{}", function_);
}
break;
case FormatSpecifier::kLongExpression:
out = fmt::format_to(out, "CF_EXPECT({})", expression_);
break;
case FormatSpecifier::kLongLocation:
if (color) {
out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline,
file_, kTerminalReset, kTerminalYellow, line_,
kTerminalYellow);
} else {
out = fmt::format_to(out, "{}:{}", file_, line_);
}
break;
case FormatSpecifier::kMessage:
if (color) {
out = fmt::format_to(out, "{}{}{}", kTerminalBoldRed,
message_.str(), kTerminalReset);
} else {
out = fmt::format_to(out, "{}", message_.str());
}
break;
case FormatSpecifier::kPrettyFunction:
out = fmt::format_to(out, "{}{}{}", kTerminalCyan, pretty_function_,
kTerminalReset);
break;
case FormatSpecifier::kShort: {
auto last_slash = file_.rfind("/");
auto short_file = file_.substr(
last_slash == std::string::npos ? 0 : last_slash + 1);
std::string last;
if (HasMessage()) {
last = color ? kTerminalBoldRed + message_.str() + kTerminalReset
: message_.str();
}
if (color) {
out = fmt::format_to(
out, "{}{}{}:{}{}{} | {}{}{} | {}", kTerminalUnderline,
short_file, kTerminalReset, kTerminalYellow, line_,
kTerminalReset, kTerminalCyan, function_, kTerminalReset, last);
} else {
out = fmt::format_to(out, "{}:{} | {} | {}", short_file, line_,
function_, last);
}
break;
}
case FormatSpecifier::kShortExpression:
out = fmt::format_to(out, "{}", expression_);
break;
case FormatSpecifier::kShortLocation: {
auto last_slash = file_.rfind("/");
auto short_file = file_.substr(
last_slash == std::string::npos ? 0 : last_slash + 1);
if (color) {
out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline,
short_file, kTerminalReset, kTerminalYellow,
line_, kTerminalReset);
} else {
out = fmt::format_to(out, "{}:{}", short_file, line_);
}
break;
}
default:
fmt::format_to(out, "unknown specifier");
}
if (i < filtered_specs.size() - 1) {
out = fmt::format_to(out, "\n");
}
}
return out;
}
private:
std::string file_;
size_t line_;
std::string pretty_function_;
std::string function_;
std::string expression_;
std::stringstream message_;
};
inline std::string ResultErrorFormat() {
auto error_format = getenv("CF_ERROR_FORMAT");
std::string fmt_str = error_format == nullptr ? "cns/acLFEm" : error_format;
if (fmt_str.find("}") != std::string::npos) {
fmt_str = "v";
}
return "{:" + fmt_str + "}";
}
#define CF_STACK_TRACE_ENTRY(expression) \
StackTraceEntry(__FILE__, __LINE__, __PRETTY_FUNCTION__, __func__, expression)
} // namespace cuttlefish
/**
* Specialized formatting for StackTraceEntry based on user-provided specifiers.
*
* A StackTraceEntry can be formatted with {:specifiers} in a `fmt::format`
* string, where `specifiers` is an ordered list of characters deciding on the
* format. `v` provides "verbose" output and `V` provides "very verbose" output.
* See `StackTraceEntry::FormatSpecifiers` for more fine-grained specifiers.
*/
template <>
struct fmt::formatter<cuttlefish::StackTraceEntry> {
public:
constexpr auto parse(format_parse_context& ctx)
-> format_parse_context::iterator {
auto it = ctx.begin();
while (it != ctx.end() && *it != '}') {
if (*it == 'v') {
for (const auto& specifier : cuttlefish::StackTraceEntry::kVerbose) {
fmt_specs_.push_back(specifier);
}
} else if (*it == 'V') {
for (const auto& specifier :
cuttlefish::StackTraceEntry::kVeryVerbose) {
fmt_specs_.push_back(specifier);
}
} else {
fmt_specs_.push_back(
static_cast<cuttlefish::StackTraceEntry::FormatSpecifier>(*it));
}
it++;
}
return it;
}
auto format(const cuttlefish::StackTraceEntry& entry,
format_context& ctx) const -> format_context::iterator {
return entry.format(ctx, fmt_specs_, std::nullopt);
}
private:
std::vector<cuttlefish::StackTraceEntry::FormatSpecifier> fmt_specs_;
};
namespace cuttlefish {
class StackTraceError {
public:
StackTraceError& PushEntry(StackTraceEntry entry) & {
stack_.emplace_back(std::move(entry));
return *this;
}
StackTraceError PushEntry(StackTraceEntry entry) && {
stack_.emplace_back(std::move(entry));
return std::move(*this);
}
const std::vector<StackTraceEntry>& Stack() const { return stack_; }
std::string Message() const { return fmt::format("{:m}", *this); }
std::string Trace() const { return fmt::format("{:v}", *this); }
std::string FormatForEnv() const {
return fmt::format(ResultErrorFormat(), *this);
}
template <typename T>
operator android::base::expected<T, StackTraceError>() && {
return android::base::unexpected(std::move(*this));
}
private:
std::vector<StackTraceEntry> stack_;
};
inline StackTraceEntry::operator StackTraceError() && {
return StackTraceError().PushEntry(std::move(*this));
}
template <typename T>
inline StackTraceEntry::operator android::base::expected<T,
StackTraceError>() && {
return android::base::unexpected(std::move(*this));
}
} // namespace cuttlefish
/**
* Specialized formatting for a collection of StackTraceEntry elements.
*
* Can be formatted by a `fmt::format` string as {:specifiers}. See
* `fmt::formatter<cuttlefish::StackTraceEntry>` for the format specifiers of
* individual entries. By default the specifier list is passed down to all
* indivudal entries, with the following additional rules. The `^` specifier
* will change the ordering from inner-to-outer instead of outer-to-inner, and
* using the `/` specifier like `<abc>/<xyz>` will apply <xyz> only to the
* innermost stack entry, and <abc> to all other stack entries.
*/
template <>
struct fmt::formatter<cuttlefish::StackTraceError> {
public:
constexpr auto parse(format_parse_context& ctx)
-> format_parse_context::iterator {
auto it = ctx.begin();
while (it != ctx.end() && *it != '}') {
if (*it == 'v') {
for (const auto& spec : StackTraceEntry::kVerbose) {
(has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec);
}
} else if (*it == 'V') {
for (const auto& spec : StackTraceEntry::kVeryVerbose) {
(has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec);
}
} else if (*it == '/') {
has_inner_fmt_spec_ = true;
} else if (*it == '^') {
inner_to_outer_ = true;
} else {
(has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_)
.push_back(static_cast<StackTraceEntry::FormatSpecifier>(*it));
}
it++;
}
return it;
}
format_context::iterator format(const cuttlefish::StackTraceError& error,
format_context& ctx) const {
auto out = ctx.out();
auto& stack = error.Stack();
int begin = inner_to_outer_ ? 0 : stack.size() - 1;
int end = inner_to_outer_ ? stack.size() : -1;
int step = inner_to_outer_ ? 1 : -1;
for (int i = begin; i != end; i += step) {
auto& specs =
has_inner_fmt_spec_ && i == 0 ? inner_fmt_specs_ : fmt_specs_;
out = stack[i].format(ctx, specs, i);
if (i != end - step) {
out = fmt::format_to(out, "\n");
}
}
return out;
}
private:
using StackTraceEntry = cuttlefish::StackTraceEntry;
using StackTraceError = cuttlefish::StackTraceError;
bool inner_to_outer_ = false;
bool has_inner_fmt_spec_ = false;
std::vector<StackTraceEntry::FormatSpecifier> fmt_specs_;
std::vector<StackTraceEntry::FormatSpecifier> inner_fmt_specs_;
};
namespace cuttlefish {
template <typename T>
using Result = android::base::expected<T, StackTraceError>;
/**
* Error return macro that includes the location in the file in the error
* message. Use CF_ERRNO when including information from errno, otherwise use
* the base CF_ERR macro.
*
* Example usage:
*
* if (mkdir(path.c_str()) != 0) {
* return CF_ERRNO("mkdir(\"" << path << "\") failed: "
* << strerror(errno));
* }
*
* This will return an error with the text
*
* mkdir(...) failed: ...
* at path/to/file.cpp:50
* in Result<std::string> MyFunction()
*/
#define CF_ERR(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
#define CF_ERRNO(MSG) (CF_STACK_TRACE_ENTRY("") << MSG)
#define CF_ERRF(MSG, ...) \
(CF_STACK_TRACE_ENTRY("") << fmt::format(FMT_STRING(MSG), __VA_ARGS__))
template <typename T>
T OutcomeDereference(std::optional<T>&& value) {
return std::move(*value);
}
template <typename T>
typename std::conditional_t<std::is_void_v<T>, bool, T> OutcomeDereference(
Result<T>&& result) {
if constexpr (std::is_void<T>::value) {
return result.ok();
} else {
return std::move(*result);
}
}
template <typename T>
typename std::enable_if<std::is_convertible_v<T, bool>, T>::type
OutcomeDereference(T&& value) {
return std::forward<T>(value);
}
inline bool TypeIsSuccess(bool value) { return value; }
template <typename T>
bool TypeIsSuccess(std::optional<T>& value) {
return value.has_value();
}
template <typename T>
bool TypeIsSuccess(Result<T>& value) {
return value.ok();
}
template <typename T>
bool TypeIsSuccess(Result<T>&& value) {
return value.ok();
}
inline auto ErrorFromType(bool) { return StackTraceError(); }
template <typename T>
inline auto ErrorFromType(std::optional<T>) {
return StackTraceError();
}
template <typename T>
auto ErrorFromType(Result<T>& value) {
return value.error();
}
template <typename T>
auto ErrorFromType(Result<T>&& value) {
return value.error();
}
#define CF_EXPECT_OVERLOAD(_1, _2, NAME, ...) NAME
#define CF_EXPECT2(RESULT, MSG) \
({ \
decltype(RESULT)&& macro_intermediate_result = RESULT; \
if (!TypeIsSuccess(macro_intermediate_result)) { \
auto current_entry = CF_STACK_TRACE_ENTRY(#RESULT); \
current_entry << MSG; \
auto error = ErrorFromType(macro_intermediate_result); \
error.PushEntry(std::move(current_entry)); \
return error; \
}; \
OutcomeDereference(std::move(macro_intermediate_result)); \
})
#define CF_EXPECT1(RESULT) CF_EXPECT2(RESULT, "")
/**
* Error propagation macro that can be used as an expression.
*
* The first argument can be either a Result or a type that is convertible to
* a boolean. A successful result will return the value inside the result, or
* a conversion to a `true` value will return the unconverted value. This is
* useful for e.g. pointers which can be tested through boolean conversion.
*
* In the failure case, this macro will return from the containing function
* with a failing Result. The failing result will include information about the
* call site, details from the optional second argument if given, and details
* from the failing inner expression if it is a Result.
*
* This macro must be invoked only in functions that return a Result.
*
* Example usage:
*
* Result<std::string> CreateTempDir();
*
* Result<std::string> CreatePopulatedTempDir() {
* std::string dir = CF_EXPECT(CreateTempDir(), "Failed to create dir");
* // Do something with dir
* return dir;
* }
*
* If CreateTempDir fails, the function will returna Result with an error
* message that looks like
*
* Internal error
* at /path/to/otherfile.cpp:50
* in Result<std::string> CreateTempDir()
* Failed to create dir
* at /path/to/file.cpp:81:
* in Result<std::string> CreatePopulatedTempDir()
* for CF_EXPECT(CreateTempDir())
*/
#define CF_EXPECT(...) \
CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__)
#define CF_EXPECTF(RESULT, MSG, ...) \
CF_EXPECT(RESULT, fmt::format(FMT_STRING(MSG), __VA_ARGS__))
#define CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, MSG) \
({ \
auto&& lhs_macro_intermediate_result = LHS_RESULT; \
auto&& rhs_macro_intermediate_result = RHS_RESULT; \
bool comparison_result = lhs_macro_intermediate_result COMPARE_OP \
rhs_macro_intermediate_result; \
if (!comparison_result) { \
auto current_entry = CF_STACK_TRACE_ENTRY(""); \
current_entry << "Expected \"" << #LHS_RESULT << "\" " << #COMPARE_OP \
<< " \"" << #RHS_RESULT << "\" but was " \
<< lhs_macro_intermediate_result << " vs " \
<< rhs_macro_intermediate_result << ". "; \
current_entry << MSG; \
auto error = ErrorFromType(false); \
error.PushEntry(std::move(current_entry)); \
return error; \
}; \
comparison_result; \
})
#define CF_COMPARE_EXPECT3(COMPARE_OP, LHS_RESULT, RHS_RESULT) \
CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, "")
#define CF_COMPARE_EXPECT_OVERLOAD(_1, _2, _3, _4, NAME, ...) NAME
#define CF_COMPARE_EXPECT(...) \
CF_COMPARE_EXPECT_OVERLOAD(__VA_ARGS__, CF_COMPARE_EXPECT4, \
CF_COMPARE_EXPECT3) \
(__VA_ARGS__)
#define CF_EXPECT_EQ(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(==, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
#define CF_EXPECT_NE(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(!=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
#define CF_EXPECT_LE(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(<=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
#define CF_EXPECT_LT(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(<, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
#define CF_EXPECT_GE(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(>=, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
#define CF_EXPECT_GT(LHS_RESULT, RHS_RESULT, ...) \
CF_COMPARE_EXPECT(>, LHS_RESULT, RHS_RESULT, ##__VA_ARGS__)
} // namespace cuttlefish