blob: 3e3011e7c22abe012675f750c76fb69fbc851510 [file] [log] [blame] [edit]
#ifndef QUICHE_COMMON_BUG_UTILS_H_
#define QUICHE_COMMON_BUG_UTILS_H_
// This file contains macros for emitting bug log events when invariants are
// violated.
//
// Each instance of a QUICHE_BUG and friends has an associated id, which can be
// helpful for quickly finding the associated source code.
//
// The IDs are free form, but are expected to be unique. Best practice is to
// provide a *short* description of the condition being detected, without
// quotes, e.g.,
//
// QUICHE_BUG(http2_decoder_invalid_parse_state) << "...";
//
// QUICHE_BUG is defined in platform/api/quiche_bug_tracker.h.
//
#include <atomic>
#include <cstdint>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include "absl/base/log_severity.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "quiche/common/platform/api/quiche_export.h"
namespace quiche {
namespace internal {
class QUICHE_EXPORT GenericBugListener {
public:
virtual ~GenericBugListener() = default;
virtual void OnBug(const char* bug_id, const char* file, int line,
absl::string_view bug_message) = 0;
};
struct QUICHE_NO_EXPORT GenericBugOptions {
explicit GenericBugOptions(absl::LogSeverity log_severity,
const char* file_name, int line)
: severity(log_severity), file_name(file_name), line(line) {}
GenericBugOptions& SetCondition(absl::string_view condition) {
this->condition_str = condition;
return *this;
}
GenericBugOptions& SetBugListener(GenericBugListener* listener) {
this->bug_listener = listener;
return *this;
}
absl::LogSeverity severity;
const char* const file_name;
const int line;
// !empty() for conditional GENERIC_BUGs.
absl::string_view condition_str;
// If not nullptr, |bug_listener| will be notified of this GENERIC_BUG hit.
// Since GenericBugListener may be a temporary object, this is only safe to
// access from GenericBugStreamHandler, whose scope is strictly narrower.
GenericBugListener* bug_listener = nullptr;
};
QUICHE_EXPORT inline GenericBugOptions DefaultBugOptions(const char* file_name,
int line) {
return GenericBugOptions(absl::kLogDebugFatal, file_name, line);
}
// Called if a GENERIC_BUG is hit, but logging is omitted.
QUICHE_EXPORT void GenericBugWithoutLog(const char* bug_id,
const GenericBugOptions& options);
// GenericBugStreamHandler exposes an interface similar to Abseil log objects,
// and is used by GENERIC_BUG to trigger a function which can be overridden in
// tests. By default, this class performs no action. SetOverrideFunction must be
// called to accomplish anything interesting.
class QUICHE_EXPORT GenericBugStreamHandler {
public:
// |prefix| and |bug_id| must be literal strings. They are used in the
// destructor.
explicit GenericBugStreamHandler(const char* prefix, const char* bug_id,
const GenericBugOptions& options);
~GenericBugStreamHandler();
template <typename T,
std::enable_if_t<absl::HasAbslStringify<T>::value, bool> = true>
GenericBugStreamHandler& operator<<(const T& v) {
absl::StrAppend(&str_, v);
return *this;
}
// For types that support only operator<<. There's a better solution in
// Abseil, but unfortunately OStringStream is in a namespace marked internal.
template <typename T,
std::enable_if_t<!absl::HasAbslStringify<T>::value, bool> = true>
GenericBugStreamHandler& operator<<(const T& v) {
absl::StrAppend(&str_, (std::ostringstream() << v).str());
return *this;
}
// Interface similar to Abseil log objects.
GenericBugStreamHandler& stream() { return *this; }
using OverrideFunction = void (*)(absl::LogSeverity severity,
const char* file, int line,
absl::string_view log_message);
// Allows overriding the internal implementation. Call with nullptr to make
// this class a no-op. This getter and setter are thread-safe.
static void SetOverrideFunction(OverrideFunction override_function);
static OverrideFunction GetOverrideFunction();
private:
static std::atomic<OverrideFunction> atomic_override_function_;
const char* bug_id_;
std::string str_;
const GenericBugOptions& options_;
};
} // namespace internal
} // namespace quiche
// The GNU compiler emits a warning for code like:
//
// if (foo)
// if (bar) { } else baz;
//
// because it thinks you might want the else to bind to the first if. This
// leads to problems with code like:
//
// if (do_expr) GENERIC_BUG(bug_id) << "Some message";
//
// The "switch (0) case 0:" idiom is used to suppress this.
#define GENERIC_BUG_UNBRACED_ELSE_BLOCKER \
switch (0) \
case 0: \
default:
#define GENERIC_BUG_IMPL(prefix, bug_id, skip_log_condition, options) \
if (skip_log_condition) { \
::quiche::internal::GenericBugWithoutLog(#bug_id, (options)); \
} else /* NOLINT */ \
::quiche::internal::GenericBugStreamHandler(prefix, #bug_id, (options)) \
.stream()
#endif // QUICHE_COMMON_BUG_UTILS_H_