Skip to content

Commit

Permalink
feat: support failure functions that throw (#1074)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiud committed Feb 17, 2024
1 parent de77d0b commit b24e4d9
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 56 deletions.
63 changes: 33 additions & 30 deletions src/glog/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,10 @@ using PrefixFormatterCallback = void (*)(std::ostream&, const LogMessage&,
GLOG_EXPORT void InstallPrefixFormatter(PrefixFormatterCallback callback,
void* data = nullptr);

// Install a function which will be called after LOG(FATAL).
GLOG_EXPORT void InstallFailureFunction(logging_fail_func_t fail_func);
// Install a function which will be called after LOG(FATAL). Returns the
// previously set function.
GLOG_EXPORT logging_fail_func_t
InstallFailureFunction(logging_fail_func_t fail_func);

[[deprecated(
"Use the type-safe std::chrono::minutes EnableLogCleaner overload "
Expand Down Expand Up @@ -606,13 +608,11 @@ namespace internal {
// A container for a string pointer which can be evaluated to a bool -
// true iff the pointer is nullptr.
struct CheckOpString {
CheckOpString(std::string* str) : str_(str) {}
// No destructor: if str_ is non-nullptr, we're about to LOG(FATAL),
// so there's no point in cleaning up str_.
CheckOpString(std::unique_ptr<std::string> str) : str_(std::move(str)) {}
explicit operator bool() const noexcept {
return GOOGLE_PREDICT_BRANCH_NOT_TAKEN(str_ != nullptr);
}
std::string* str_;
std::unique_ptr<std::string> str_;
};

// Function is overloaded for integral types to allow static const
Expand Down Expand Up @@ -671,7 +671,8 @@ GLOG_EXPORT void MakeCheckOpValueString(std::ostream* os,

// Build the error message string. Specify no inlining for code size.
template <typename T1, typename T2>
std::string* MakeCheckOpString(const T1& v1, const T2& v2, const char* exprtext)
std::unique_ptr<std::string> MakeCheckOpString(const T1& v1, const T2& v2,
const char* exprtext)
#if defined(__has_attribute)
# if __has_attribute(used)
__attribute__((noinline))
Expand All @@ -696,15 +697,15 @@ class GLOG_EXPORT CheckOpMessageBuilder {
// For inserting the second variable (adds an intermediate " vs. ").
std::ostream* ForVar2();
// Get the result (inserts the closing ")").
std::string* NewString();
std::unique_ptr<std::string> NewString();

private:
std::ostringstream* stream_;
};

template <typename T1, typename T2>
std::string* MakeCheckOpString(const T1& v1, const T2& v2,
const char* exprtext) {
std::unique_ptr<std::string> MakeCheckOpString(const T1& v1, const T2& v2,
const char* exprtext) {
CheckOpMessageBuilder comb(exprtext);
MakeCheckOpValueString(comb.ForVar1(), v1);
MakeCheckOpValueString(comb.ForVar2(), v2);
Expand All @@ -715,17 +716,18 @@ std::string* MakeCheckOpString(const T1& v1, const T2& v2,
// The (int, int) specialization works around the issue that the compiler
// will not instantiate the template version of the function on values of
// unnamed enum type - see comment below.
#define DEFINE_CHECK_OP_IMPL(name, op) \
template <typename T1, typename T2> \
inline std::string* name##Impl(const T1& v1, const T2& v2, \
const char* exprtext) { \
if (GOOGLE_PREDICT_TRUE(v1 op v2)) \
return nullptr; \
else \
return MakeCheckOpString(v1, v2, exprtext); \
} \
inline std::string* name##Impl(int v1, int v2, const char* exprtext) { \
return name##Impl<int, int>(v1, v2, exprtext); \
#define DEFINE_CHECK_OP_IMPL(name, op) \
template <typename T1, typename T2> \
inline std::unique_ptr<std::string> name##Impl(const T1& v1, const T2& v2, \
const char* exprtext) { \
if (GOOGLE_PREDICT_TRUE(v1 op v2)) { \
return nullptr; \
} \
return MakeCheckOpString(v1, v2, exprtext); \
} \
inline std::unique_ptr<std::string> name##Impl(int v1, int v2, \
const char* exprtext) { \
return name##Impl<int, int>(v1, v2, exprtext); \
}

// We use the full name Check_EQ, Check_NE, etc. in case the file including
Expand Down Expand Up @@ -757,14 +759,15 @@ DEFINE_CHECK_OP_IMPL(Check_GT, >)
// with other string implementations that get defined after this
// file is included). Save the current meaning now and use it
// in the macro.
typedef std::string _Check_string;
using _Check_string = std::string;
# define CHECK_OP_LOG(name, op, val1, val2, log) \
while (google::logging::internal::_Check_string* _result = \
while (std::unique_ptr<google::logging::internal::_Check_string> _result = \
google::logging::internal::Check##name##Impl( \
google::logging::internal::GetReferenceableValue(val1), \
google::logging::internal::GetReferenceableValue(val2), \
#val1 " " #op " " #val2)) \
log(__FILE__, __LINE__, google::logging::internal::CheckOpString(_result)) \
log(__FILE__, __LINE__, \
google::logging::internal::CheckOpString(std::move(_result))) \
.stream()
#else
// In optimized mode, use CheckOpString to hint to compiler that
Expand Down Expand Up @@ -820,8 +823,8 @@ typedef std::string _Check_string;

// Helper functions for string comparisons.
// To avoid bloat, the definitions are in logging.cc.
#define DECLARE_CHECK_STROP_IMPL(func, expected) \
GLOG_EXPORT std::string* Check##func##expected##Impl( \
#define DECLARE_CHECK_STROP_IMPL(func, expected) \
GLOG_EXPORT std::unique_ptr<std::string> Check##func##expected##Impl( \
const char* s1, const char* s2, const char* names);

DECLARE_CHECK_STROP_IMPL(strcmp, true)
Expand All @@ -840,7 +843,7 @@ DECLARE_CHECK_STROP_IMPL(strcasecmp, false)
while (google::logging::internal::CheckOpString _result = \
google::logging::internal::Check##func##expected##Impl( \
(s1), (s2), #s1 " " #op " " #s2)) \
LOG(FATAL) << *_result.str_
LOG(FATAL) << (*_result.str_)

// String (char*) equality/inequality checks.
// CASE versions are case-insensitive.
Expand Down Expand Up @@ -1332,7 +1335,7 @@ class GLOG_EXPORT LogMessage {
LogMessage(const char* file, int line,
const logging::internal::CheckOpString& result);

~LogMessage();
~LogMessage() noexcept(false);

// Flush a buffered message to the sink set in the constructor. Always
// called by the destructor, it may also be called from elsewhere if
Expand Down Expand Up @@ -1409,7 +1412,7 @@ class GLOG_EXPORT LogMessageFatal : public LogMessage {
LogMessageFatal(const char* file, int line);
LogMessageFatal(const char* file, int line,
const logging::internal::CheckOpString& result);
[[noreturn]] ~LogMessageFatal();
[[noreturn]] ~LogMessageFatal() noexcept(false);
};

// A non-macro interface to the log facility; (useful
Expand Down Expand Up @@ -1462,7 +1465,7 @@ namespace internal {
template <typename T>
T CheckNotNull(const char* file, int line, const char* names, T&& t) {
if (t == nullptr) {
LogMessageFatal(file, line, new std::string(names));
LogMessageFatal(file, line, std::make_unique<std::string>(names));
}
return std::forward<T>(t);
}
Expand Down
13 changes: 12 additions & 1 deletion src/googletest.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,19 @@ void InitGoogleTest(int*, char**) {}
} \
} while (0)

vector<void (*)()> g_testlist; // the tests to run
# define EXPECT_THROW(statement, exception) \
do { \
try { \
statement; \
} catch (const exception&) { \
printf("ok\n"); \
} catch (...) { \
fprintf(stderr, "%s\n", "Unexpected exception thrown"); \
exit(EXIT_FAILURE); \
} \
} while (0)

vector<void (*)()> g_testlist; // the tests to run
# define TEST(a, b) \
struct Test_##a##_##b { \
Test_##a##_##b() { g_testlist.push_back(&Run); } \
Expand Down
56 changes: 31 additions & 25 deletions src/logging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1723,8 +1723,9 @@ const char* LogMessage::fullname() const noexcept { return data_->fullname_; }
const char* LogMessage::basename() const noexcept { return data_->basename_; }
const LogMessageTime& LogMessage::time() const noexcept { return time_; }

LogMessage::~LogMessage() {
LogMessage::~LogMessage() noexcept(false) {
Flush();
bool fail = data_->severity_ == GLOG_FATAL && exit_on_dfatal;
#ifdef GLOG_THREAD_LOCAL_STORAGE
if (data_ == static_cast<void*>(&thread_msg_data)) {
data_->~LogMessageData();
Expand All @@ -1735,6 +1736,26 @@ LogMessage::~LogMessage() {
#else // !defined(GLOG_THREAD_LOCAL_STORAGE)
delete allocated_;
#endif // defined(GLOG_THREAD_LOCAL_STORAGE)
//

if (fail) {
const char* message = "*** Check failure stack trace: ***\n";
if (write(fileno(stderr), message, strlen(message)) < 0) {
// Ignore errors.
}
AlsoErrorWrite(GLOG_FATAL,
glog_internal_namespace_::ProgramInvocationShortName(),
message);
#if defined(__cpp_lib_uncaught_exceptions) && \
(__cpp_lib_uncaught_exceptions >= 201411L)
if (std::uncaught_exceptions() == 0)
#else
if (!std::uncaught_exception())
#endif
{
Fail();
}
}
}

int LogMessage::preserved_errno() const { return data_->preserved_errno_; }
Expand Down Expand Up @@ -1894,22 +1915,7 @@ void LogMessage::SendToLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) {
}
}

// release the lock that our caller (directly or indirectly)
// LogMessage::~LogMessage() grabbed so that signal handlers
// can use the logging facility. Alternately, we could add
// an entire unsafe logging interface to bypass locking
// for signal handlers but this seems simpler.
log_mutex.unlock();
LogDestination::WaitForSinks(data_);

const char* message = "*** Check failure stack trace: ***\n";
if (write(fileno(stderr), message, strlen(message)) < 0) {
// Ignore errors.
}
AlsoErrorWrite(GLOG_FATAL,
glog_internal_namespace_::ProgramInvocationShortName(),
message);
Fail();
}
}

Expand Down Expand Up @@ -1941,8 +1947,8 @@ NullStreamFatal::~NullStreamFatal() {
std::abort();
}

void InstallFailureFunction(logging_fail_func_t fail_func) {
g_logging_fail_func = fail_func;
logging_fail_func_t InstallFailureFunction(logging_fail_func_t fail_func) {
return std::exchange(g_logging_fail_func, fail_func);
}

void LogMessage::Fail() { g_logging_fail_func(); }
Expand Down Expand Up @@ -2533,17 +2539,17 @@ namespace logging {
namespace internal {
// Helper functions for string comparisons.
#define DEFINE_CHECK_STROP_IMPL(name, func, expected) \
string* Check##func##expected##Impl(const char* s1, const char* s2, \
const char* names) { \
std::unique_ptr<string> Check##func##expected##Impl( \
const char* s1, const char* s2, const char* names) { \
bool equal = s1 == s2 || (s1 && s2 && !func(s1, s2)); \
if (equal == expected) \
if (equal == (expected)) \
return nullptr; \
else { \
ostringstream ss; \
if (!s1) s1 = ""; \
if (!s2) s2 = ""; \
ss << #name " failed: " << names << " (" << s1 << " vs. " << s2 << ")"; \
return new string(ss.str()); \
return std::make_unique<std::string>(ss.str()); \
} \
}
DEFINE_CHECK_STROP_IMPL(CHECK_STREQ, strcmp, true)
Expand Down Expand Up @@ -2635,7 +2641,7 @@ LogMessageFatal::LogMessageFatal(const char* file, int line,
const logging::internal::CheckOpString& result)
: LogMessage(file, line, result) {}

LogMessageFatal::~LogMessageFatal() {
LogMessageFatal::~LogMessageFatal() noexcept(false) {
Flush();
LogMessage::Fail();
}
Expand All @@ -2655,9 +2661,9 @@ ostream* CheckOpMessageBuilder::ForVar2() {
return stream_;
}

string* CheckOpMessageBuilder::NewString() {
std::unique_ptr<string> CheckOpMessageBuilder::NewString() {
*stream_ << ")";
return new string(stream_->str());
return std::make_unique<std::string>(stream_->str());
}

template <>
Expand Down
15 changes: 15 additions & 0 deletions src/logging_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <mutex>
#include <queue>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
Expand Down Expand Up @@ -1571,3 +1572,17 @@ TEST(EmailLogging, MaliciousAddress) {
EXPECT_FALSE(
SendEmail("!/bin/true@example.com", "Example subject", "Example body"));
}

TEST(Logging, FatalThrow) {
auto const fail_func =
InstallFailureFunction(+[]()
#if defined(__has_attribute)
# if __has_attribute(noreturn)
__attribute__((noreturn))
# endif // __has_attribute(noreturn)
#endif // defined(__has_attribute)
{ throw std::logic_error{"fail"}; });
auto restore_fail = [fail_func] { InstallFailureFunction(fail_func); };
ScopedExit<decltype(restore_fail)> restore{restore_fail};
EXPECT_THROW({ LOG(FATAL) << "must throw to fail"; }, std::logic_error);
}

0 comments on commit b24e4d9

Please sign in to comment.