Skip to content

Commit 588be04

Browse files
tesch1dominichamon
authored andcommitted
escape special chars in csv and json output. (#802)
* escape special chars in csv and json output. - escape \b,\f,\n,\r,\t,\," from strings before dumping them to json or csv. - also faithfully reproduce the sign of nan in json. this fixes github issue #745. * functionalize. * split string escape functions between csv and json * Update src/csv_reporter.cc Co-Authored-By: tesch1 <tesch1@gmail.com> * Update src/json_reporter.cc Co-Authored-By: tesch1 <tesch1@gmail.com>
1 parent 1d41de8 commit 588be04

File tree

5 files changed

+71
-38
lines changed

5 files changed

+71
-38
lines changed

src/csv_reporter.cc

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ std::vector<std::string> elements = {
3737
"error_occurred", "error_message"};
3838
} // namespace
3939

40+
std::string CsvEscape(const std::string & s) {
41+
std::string tmp;
42+
tmp.reserve(s.size() + 2);
43+
for (char c : s) {
44+
switch (c) {
45+
case '"' : tmp += "\"\""; break;
46+
default : tmp += c; break;
47+
}
48+
}
49+
return '"' + tmp + '"';
50+
}
51+
4052
bool CSVReporter::ReportContext(const Context& context) {
4153
PrintBasicContext(&GetErrorStream(), context);
4254
return true;
@@ -89,18 +101,11 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
89101

90102
void CSVReporter::PrintRunData(const Run& run) {
91103
std::ostream& Out = GetOutputStream();
92-
93-
// Field with embedded double-quote characters must be doubled and the field
94-
// delimited with double-quotes.
95-
std::string name = run.benchmark_name();
96-
ReplaceAll(&name, "\"", "\"\"");
97-
Out << '"' << name << "\",";
104+
Out << CsvEscape(run.benchmark_name()) << ",";
98105
if (run.error_occurred) {
99106
Out << std::string(elements.size() - 3, ',');
100107
Out << "true,";
101-
std::string msg = run.error_message;
102-
ReplaceAll(&msg, "\"", "\"\"");
103-
Out << '"' << msg << "\"\n";
108+
Out << CsvEscape(run.error_message) << "\n";
104109
return;
105110
}
106111

@@ -130,11 +135,7 @@ void CSVReporter::PrintRunData(const Run& run) {
130135
}
131136
Out << ",";
132137
if (!run.report_label.empty()) {
133-
// Field with embedded double-quote characters must be doubled and the field
134-
// delimited with double-quotes.
135-
std::string label = run.report_label;
136-
ReplaceAll(&label, "\"", "\"\"");
137-
Out << "\"" << label << "\"";
138+
Out << CsvEscape(run.report_label);
138139
}
139140
Out << ",,"; // for error_occurred and error_message
140141

src/json_reporter.cc

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,48 @@ namespace benchmark {
3232

3333
namespace {
3434

35+
std::string StrEscape(const std::string & s) {
36+
std::string tmp;
37+
tmp.reserve(s.size());
38+
for (char c : s) {
39+
switch (c) {
40+
case '\b': tmp += "\\b"; break;
41+
case '\f': tmp += "\\f"; break;
42+
case '\n': tmp += "\\n"; break;
43+
case '\r': tmp += "\\r"; break;
44+
case '\t': tmp += "\\t"; break;
45+
case '\\': tmp += "\\\\"; break;
46+
case '"' : tmp += "\\\""; break;
47+
default : tmp += c; break;
48+
}
49+
}
50+
return tmp;
51+
}
52+
3553
std::string FormatKV(std::string const& key, std::string const& value) {
36-
return StrFormat("\"%s\": \"%s\"", key.c_str(), value.c_str());
54+
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
3755
}
3856

3957
std::string FormatKV(std::string const& key, const char* value) {
40-
return StrFormat("\"%s\": \"%s\"", key.c_str(), value);
58+
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
4159
}
4260

4361
std::string FormatKV(std::string const& key, bool value) {
44-
return StrFormat("\"%s\": %s", key.c_str(), value ? "true" : "false");
62+
return StrFormat("\"%s\": %s", StrEscape(key).c_str(), value ? "true" : "false");
4563
}
4664

4765
std::string FormatKV(std::string const& key, int64_t value) {
4866
std::stringstream ss;
49-
ss << '"' << key << "\": " << value;
67+
ss << '"' << StrEscape(key) << "\": " << value;
5068
return ss.str();
5169
}
5270

5371
std::string FormatKV(std::string const& key, double value) {
5472
std::stringstream ss;
55-
ss << '"' << key << "\": ";
73+
ss << '"' << StrEscape(key) << "\": ";
5674

5775
if (std::isnan(value))
58-
ss << "NaN";
76+
ss << (value < 0 ? "-" : "") << "NaN";
5977
else if (std::isinf(value))
6078
ss << (value < 0 ? "-" : "") << "Infinity";
6179
else {
@@ -88,12 +106,7 @@ bool JSONReporter::ReportContext(const Context& context) {
88106
out << indent << FormatKV("host_name", context.sys_info.name) << ",\n";
89107

90108
if (Context::executable_name) {
91-
// windows uses backslash for its path separator,
92-
// which must be escaped in JSON otherwise it blows up conforming JSON
93-
// decoders
94-
std::string executable_name = Context::executable_name;
95-
ReplaceAll(&executable_name, "\\", "\\\\");
96-
out << indent << FormatKV("executable", executable_name) << ",\n";
109+
out << indent << FormatKV("executable", Context::executable_name) << ",\n";
97110
}
98111

99112
CPUInfo const& info = context.cpu_info;

src/string_util.cc

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,6 @@ std::string StrFormat(const char* format, ...) {
160160
return tmp;
161161
}
162162

163-
void ReplaceAll(std::string* str, const std::string& from,
164-
const std::string& to) {
165-
std::size_t start = 0;
166-
while ((start = str->find(from, start)) != std::string::npos) {
167-
str->replace(start, from.length(), to);
168-
start += to.length();
169-
}
170-
}
171-
172163
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
173164
/*
174165
* GNU STL in Android NDK lacks support for some C++11 functions, including

src/string_util.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ inline std::string StrCat(Args&&... args) {
3737
return ss.str();
3838
}
3939

40-
void ReplaceAll(std::string* str, const std::string& from,
41-
const std::string& to);
42-
4340
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
4441
/*
4542
* GNU STL in Android NDK lacks support for some C++11 functions, including

test/reporter_output_test.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,37 @@ ADD_CASES(
704704
"manual_time_stddev\",%csv_report$"},
705705
{"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}});
706706

707+
// ========================================================================= //
708+
// ------------------------- Testing StrEscape JSON ------------------------ //
709+
// ========================================================================= //
710+
#if 0 // enable when csv testing code correctly handles multi-line fields
711+
void BM_JSON_Format(benchmark::State& state) {
712+
state.SkipWithError("val\b\f\n\r\t\\\"with\"es,capes");
713+
for (auto _ : state) {
714+
}
715+
}
716+
BENCHMARK(BM_JSON_Format);
717+
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_JSON_Format\",$"},
718+
{"\"run_name\": \"BM_JSON_Format\",$", MR_Next},
719+
{"\"run_type\": \"iteration\",$", MR_Next},
720+
{"\"repetitions\": 0,$", MR_Next},
721+
{"\"repetition_index\": 0,$", MR_Next},
722+
{"\"threads\": 1,$", MR_Next},
723+
{"\"error_occurred\": true,$", MR_Next},
724+
{R"("error_message": "val\\b\\f\\n\\r\\t\\\\\\"with\\"es,capes",$)", MR_Next}});
725+
#endif
726+
// ========================================================================= //
727+
// -------------------------- Testing CsvEscape ---------------------------- //
728+
// ========================================================================= //
729+
730+
void BM_CSV_Format(benchmark::State& state) {
731+
state.SkipWithError("\"freedom\"");
732+
for (auto _ : state) {
733+
}
734+
}
735+
BENCHMARK(BM_CSV_Format);
736+
ADD_CASES(TC_CSVOut, {{"^\"BM_CSV_Format\",,,,,,,,true,\"\"\"freedom\"\"\"$"}});
737+
707738
// ========================================================================= //
708739
// --------------------------- TEST CASES END ------------------------------ //
709740
// ========================================================================= //

0 commit comments

Comments
 (0)