From 3894020d3d8e1f3be6af4699de339213c512daf0 Mon Sep 17 00:00:00 2001 From: Aryan Sachin Date: Fri, 30 Oct 2026 13:58:19 +0530 Subject: [PATCH] fix(json_reporter): escape C0 control characters per RFC 8259 (#2299) RFC 8259 requires characters U+0000 through U+001F in JSON strings to be escaped. While common escapes (\b, \f, \n, \r, \t) are already handled, other control bytes below 0x20 were previously passed through raw. Escape all remaining C0 control bytes as \u00XX and add unit tests verifying C0 control byte escaping in JSON reporter list output. Fixes #2299. AI Disclaimer: AI tools were used to assist in drafting and testing these changes; all changes have been reviewed and validated per AGENTS.md. Signed-off-by: Aryan Sachin --- src/json_reporter.cc | 14 ++++++++++++-- test/reporter_list_gtest.cc | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/json_reporter.cc b/src/json_reporter.cc index 37da17ba03..ef5117c259 100644 --- a/src/json_reporter.cc +++ b/src/json_reporter.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include // for setprecision #include #include @@ -60,9 +61,18 @@ std::string StrEscape(const std::string& s) { case '"': tmp += "\\\""; break; - default: - tmp += c; + default: { + auto uc = static_cast(c); + if (uc < 0x20) { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04x", + static_cast(uc)); + tmp += buf; + } else { + tmp += c; + } break; + } } } return tmp; diff --git a/test/reporter_list_gtest.cc b/test/reporter_list_gtest.cc index 74d5d02dd6..4358f4ae05 100644 --- a/test/reporter_list_gtest.cc +++ b/test/reporter_list_gtest.cc @@ -82,6 +82,36 @@ TEST(ReporterListTest, CSVListsNameColumn) { BENCHMARK_RESTORE_DEPRECATED_WARNING } +const std::vector& ListControlCharBenchmarks() { + static const std::vector* const benchmarks = [] { + std::string name = "BM_ControlChar_"; + name += '\x01'; + name += "_"; + name += '\x1f'; + RegisterBenchmark(name, BM_ReporterListDummy); + auto* result = new std::vector(); + std::ostringstream err_stream; + FindBenchmarksInternal("^BM_ControlChar.*", result, &err_stream); + return result; + }(); + return *benchmarks; +} + +TEST(ReporterListTest, JSONEscapesC0ControlCharacters) { + JSONReporter reporter; + std::ostringstream out; + reporter.SetOutputStream(&out); + reporter.List(ListControlCharBenchmarks()); + EXPECT_EQ(out.str(), + "{\n" + " \"benchmarks\": [\n" + " {\n" + " \"name\": \"BM_ControlChar_\\u0001_\\u001f\"\n" + " }\n" + " ]\n" + "}\n"); +} + } // namespace } // namespace internal } // namespace benchmark