Commit 588be044 by Michael Tesch Committed by Dominic Hamon

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: 's avatartesch1 <tesch1@gmail.com> * Update src/json_reporter.cc Co-Authored-By: 's avatartesch1 <tesch1@gmail.com>
parent 1d41de84
...@@ -37,6 +37,18 @@ std::vector<std::string> elements = { ...@@ -37,6 +37,18 @@ std::vector<std::string> elements = {
"error_occurred", "error_message"}; "error_occurred", "error_message"};
} // namespace } // namespace
std::string CsvEscape(const std::string & s) {
std::string tmp;
tmp.reserve(s.size() + 2);
for (char c : s) {
switch (c) {
case '"' : tmp += "\"\""; break;
default : tmp += c; break;
}
}
return '"' + tmp + '"';
}
bool CSVReporter::ReportContext(const Context& context) { bool CSVReporter::ReportContext(const Context& context) {
PrintBasicContext(&GetErrorStream(), context); PrintBasicContext(&GetErrorStream(), context);
return true; return true;
...@@ -89,18 +101,11 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) { ...@@ -89,18 +101,11 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
void CSVReporter::PrintRunData(const Run& run) { void CSVReporter::PrintRunData(const Run& run) {
std::ostream& Out = GetOutputStream(); std::ostream& Out = GetOutputStream();
Out << CsvEscape(run.benchmark_name()) << ",";
// Field with embedded double-quote characters must be doubled and the field
// delimited with double-quotes.
std::string name = run.benchmark_name();
ReplaceAll(&name, "\"", "\"\"");
Out << '"' << name << "\",";
if (run.error_occurred) { if (run.error_occurred) {
Out << std::string(elements.size() - 3, ','); Out << std::string(elements.size() - 3, ',');
Out << "true,"; Out << "true,";
std::string msg = run.error_message; Out << CsvEscape(run.error_message) << "\n";
ReplaceAll(&msg, "\"", "\"\"");
Out << '"' << msg << "\"\n";
return; return;
} }
...@@ -130,11 +135,7 @@ void CSVReporter::PrintRunData(const Run& run) { ...@@ -130,11 +135,7 @@ void CSVReporter::PrintRunData(const Run& run) {
} }
Out << ","; Out << ",";
if (!run.report_label.empty()) { if (!run.report_label.empty()) {
// Field with embedded double-quote characters must be doubled and the field Out << CsvEscape(run.report_label);
// delimited with double-quotes.
std::string label = run.report_label;
ReplaceAll(&label, "\"", "\"\"");
Out << "\"" << label << "\"";
} }
Out << ",,"; // for error_occurred and error_message Out << ",,"; // for error_occurred and error_message
......
...@@ -32,30 +32,48 @@ namespace benchmark { ...@@ -32,30 +32,48 @@ namespace benchmark {
namespace { namespace {
std::string StrEscape(const std::string & s) {
std::string tmp;
tmp.reserve(s.size());
for (char c : s) {
switch (c) {
case '\b': tmp += "\\b"; break;
case '\f': tmp += "\\f"; break;
case '\n': tmp += "\\n"; break;
case '\r': tmp += "\\r"; break;
case '\t': tmp += "\\t"; break;
case '\\': tmp += "\\\\"; break;
case '"' : tmp += "\\\""; break;
default : tmp += c; break;
}
}
return tmp;
}
std::string FormatKV(std::string const& key, std::string const& value) { std::string FormatKV(std::string const& key, std::string const& value) {
return StrFormat("\"%s\": \"%s\"", key.c_str(), value.c_str()); return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
} }
std::string FormatKV(std::string const& key, const char* value) { std::string FormatKV(std::string const& key, const char* value) {
return StrFormat("\"%s\": \"%s\"", key.c_str(), value); return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
} }
std::string FormatKV(std::string const& key, bool value) { std::string FormatKV(std::string const& key, bool value) {
return StrFormat("\"%s\": %s", key.c_str(), value ? "true" : "false"); return StrFormat("\"%s\": %s", StrEscape(key).c_str(), value ? "true" : "false");
} }
std::string FormatKV(std::string const& key, int64_t value) { std::string FormatKV(std::string const& key, int64_t value) {
std::stringstream ss; std::stringstream ss;
ss << '"' << key << "\": " << value; ss << '"' << StrEscape(key) << "\": " << value;
return ss.str(); return ss.str();
} }
std::string FormatKV(std::string const& key, double value) { std::string FormatKV(std::string const& key, double value) {
std::stringstream ss; std::stringstream ss;
ss << '"' << key << "\": "; ss << '"' << StrEscape(key) << "\": ";
if (std::isnan(value)) if (std::isnan(value))
ss << "NaN"; ss << (value < 0 ? "-" : "") << "NaN";
else if (std::isinf(value)) else if (std::isinf(value))
ss << (value < 0 ? "-" : "") << "Infinity"; ss << (value < 0 ? "-" : "") << "Infinity";
else { else {
...@@ -88,12 +106,7 @@ bool JSONReporter::ReportContext(const Context& context) { ...@@ -88,12 +106,7 @@ bool JSONReporter::ReportContext(const Context& context) {
out << indent << FormatKV("host_name", context.sys_info.name) << ",\n"; out << indent << FormatKV("host_name", context.sys_info.name) << ",\n";
if (Context::executable_name) { if (Context::executable_name) {
// windows uses backslash for its path separator, out << indent << FormatKV("executable", Context::executable_name) << ",\n";
// which must be escaped in JSON otherwise it blows up conforming JSON
// decoders
std::string executable_name = Context::executable_name;
ReplaceAll(&executable_name, "\\", "\\\\");
out << indent << FormatKV("executable", executable_name) << ",\n";
} }
CPUInfo const& info = context.cpu_info; CPUInfo const& info = context.cpu_info;
......
...@@ -160,15 +160,6 @@ std::string StrFormat(const char* format, ...) { ...@@ -160,15 +160,6 @@ std::string StrFormat(const char* format, ...) {
return tmp; return tmp;
} }
void ReplaceAll(std::string* str, const std::string& from,
const std::string& to) {
std::size_t start = 0;
while ((start = str->find(from, start)) != std::string::npos) {
str->replace(start, from.length(), to);
start += to.length();
}
}
#ifdef BENCHMARK_STL_ANDROID_GNUSTL #ifdef BENCHMARK_STL_ANDROID_GNUSTL
/* /*
* GNU STL in Android NDK lacks support for some C++11 functions, including * GNU STL in Android NDK lacks support for some C++11 functions, including
......
...@@ -37,9 +37,6 @@ inline std::string StrCat(Args&&... args) { ...@@ -37,9 +37,6 @@ inline std::string StrCat(Args&&... args) {
return ss.str(); return ss.str();
} }
void ReplaceAll(std::string* str, const std::string& from,
const std::string& to);
#ifdef BENCHMARK_STL_ANDROID_GNUSTL #ifdef BENCHMARK_STL_ANDROID_GNUSTL
/* /*
* GNU STL in Android NDK lacks support for some C++11 functions, including * GNU STL in Android NDK lacks support for some C++11 functions, including
......
...@@ -705,6 +705,37 @@ ADD_CASES( ...@@ -705,6 +705,37 @@ ADD_CASES(
{"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}}); {"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}});
// ========================================================================= // // ========================================================================= //
// ------------------------- Testing StrEscape JSON ------------------------ //
// ========================================================================= //
#if 0 // enable when csv testing code correctly handles multi-line fields
void BM_JSON_Format(benchmark::State& state) {
state.SkipWithError("val\b\f\n\r\t\\\"with\"es,capes");
for (auto _ : state) {
}
}
BENCHMARK(BM_JSON_Format);
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_JSON_Format\",$"},
{"\"run_name\": \"BM_JSON_Format\",$", MR_Next},
{"\"run_type\": \"iteration\",$", MR_Next},
{"\"repetitions\": 0,$", MR_Next},
{"\"repetition_index\": 0,$", MR_Next},
{"\"threads\": 1,$", MR_Next},
{"\"error_occurred\": true,$", MR_Next},
{R"("error_message": "val\\b\\f\\n\\r\\t\\\\\\"with\\"es,capes",$)", MR_Next}});
#endif
// ========================================================================= //
// -------------------------- Testing CsvEscape ---------------------------- //
// ========================================================================= //
void BM_CSV_Format(benchmark::State& state) {
state.SkipWithError("\"freedom\"");
for (auto _ : state) {
}
}
BENCHMARK(BM_CSV_Format);
ADD_CASES(TC_CSVOut, {{"^\"BM_CSV_Format\",,,,,,,,true,\"\"\"freedom\"\"\"$"}});
// ========================================================================= //
// --------------------------- TEST CASES END ------------------------------ // // --------------------------- TEST CASES END ------------------------------ //
// ========================================================================= // // ========================================================================= //
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment