PR #2125: fix off-by-one truncation detection in VADoRawLog and DoRawLog Imported from GitHub PR https://github.com/abseil/abseil-cpp/pull/2125 treat n == size as truncation in VADoRawLog and DoRawLog: vsnprintf writes only size-1 chars at that boundary, so an exactly-buffer-filling raw log message was losing its last byte and being emitted without the `(message truncated)` marker or trailing newline, letting a following line concatenate onto it. Merge 05db3936acd6d53167e224520fb5b55764e48c58 into d522178b54087d2c3855484c1899ce99ad278169 Merging this change closes #2125 PiperOrigin-RevId: 964803816
diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 848e69f..f705a38 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc
@@ -95,7 +95,7 @@ if (*size < 0) return false; int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); bool result = true; - if (n < 0 || n > *size) { + if (n < 0 || n >= *size) { result = false; if (static_cast<size_t>(*size) > sizeof(kTruncated)) { n = *size - static_cast<int>(sizeof(kTruncated)); @@ -125,7 +125,7 @@ va_start(ap, format); int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); va_end(ap); - if (n < 0 || n > *size) return false; + if (n < 0 || n >= *size) return false; *size -= n; *buf += n; return true;
diff --git a/absl/base/raw_logging_test.cc b/absl/base/raw_logging_test.cc index 48158c0..6d76d7b 100644 --- a/absl/base/raw_logging_test.cc +++ b/absl/base/raw_logging_test.cc
@@ -18,10 +18,17 @@ #include "absl/base/internal/raw_logging.h" +#include <cstddef> #include <string> -#include <tuple> + +#if (defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))) && \ + !defined(__EMSCRIPTEN__) +#include <unistd.h> + +#endif #include "gtest/gtest.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" namespace { @@ -75,6 +82,47 @@ kExpectedDeathOutput); } +// Raw logging writes to STDERR_FILENO via write()/syscall on POSIX platforms, +// so the truncation path can be exercised directly by capturing stderr, without +// relying on death tests. +#if (defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))) && \ + !defined(__EMSCRIPTEN__) +TEST(RawLoggingTest, TruncationMarkerAtExactBufferBoundary) { + if (!absl::raw_log_internal::RawLoggingFullySupported()) { + GTEST_SKIP() << "Raw logging output is not supported on this platform."; + } + // kLogBufSize in raw_logging.cc. + constexpr int kLogBufSize = 3000; + + // The space left for the message depends on the log prefix, which a prefix + // hook can change, so try every length up to the buffer size. Each message + // must come out either whole or ending in the truncation marker. + for (int len = 1; len <= kLogBufSize; ++len) { + int fds[2]; + ASSERT_EQ(pipe(fds), 0); + int saved_stderr = dup(STDERR_FILENO); + ASSERT_NE(saved_stderr, -1); + ASSERT_NE(dup2(fds[1], STDERR_FILENO), -1); + + const std::string msg(static_cast<size_t>(len), 'x'); + ABSL_RAW_LOG(ERROR, "%s", msg.c_str()); + + ASSERT_NE(dup2(saved_stderr, STDERR_FILENO), -1); + close(saved_stderr); + close(fds[1]); + char buf[kLogBufSize + 64]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + close(fds[0]); + ASSERT_GT(n, 0); + const std::string output(buf, static_cast<size_t>(n)); + + ASSERT_TRUE(absl::StrContains(output, msg) || + absl::EndsWith(output, " ... (message truncated)\n")) + << "message length " << len; + } +} +#endif + TEST(InternalLog, CompilationTest) { ABSL_INTERNAL_LOG(INFO, "Internal Log"); std::string log_msg = "Internal Log";