From 509a71590f5934f298ecb86d4e737c64315485e6 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 22 Jun 2018 12:50:01 -0700 Subject: [PATCH 1/5] trace_events: add traced_value.cc/traced_value.h Port of the V8 internal v8::tracing::TracedValue that allows structured data to be included in the trace event. The v8 class is not exported in the public API so we cannot use it directly. This is a simplified and slightly modified port. This commit only adds the class, it does not add uses of it. Those will come in separate PRs/commits. --- node.gyp | 3 + src/tracing/traced_value.cc | 191 +++++++++++++++++++++++++++++++ src/tracing/traced_value.h | 68 +++++++++++ test/cctest/test_traced_value.cc | 63 ++++++++++ 4 files changed, 325 insertions(+) create mode 100644 src/tracing/traced_value.cc create mode 100644 src/tracing/traced_value.h create mode 100644 test/cctest/test_traced_value.cc diff --git a/node.gyp b/node.gyp index 4b6bc4c108cfb5..d89296246c3f84 100644 --- a/node.gyp +++ b/node.gyp @@ -380,6 +380,7 @@ 'src/tracing/node_trace_buffer.cc', 'src/tracing/node_trace_writer.cc', 'src/tracing/trace_event.cc', + 'src/tracing/traced_value.cc', 'src/tty_wrap.cc', 'src/udp_wrap.cc', 'src/util.cc', @@ -440,6 +441,7 @@ 'src/tracing/node_trace_buffer.h', 'src/tracing/node_trace_writer.h', 'src/tracing/trace_event.h', + 'src/tracing/traced_value.h', 'src/util.h', 'src/util-inl.h', 'deps/http_parser/http_parser.h', @@ -953,6 +955,7 @@ 'test/cctest/test_node_postmortem_metadata.cc', 'test/cctest/test_environment.cc', 'test/cctest/test_platform.cc', + 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', 'test/cctest/test_url.cc' ], diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc new file mode 100644 index 00000000000000..11919008268b56 --- /dev/null +++ b/src/tracing/traced_value.cc @@ -0,0 +1,191 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tracing/traced_value.h" + +#include +#include +#include +#include + +#if defined(_STLP_VENDOR_CSTD) +// STLPort doesn't import fpclassify into the std namespace. +#define FPCLASSIFY_NAMESPACE +#else +#define FPCLASSIFY_NAMESPACE std +#endif + +namespace node { +namespace tracing { + +namespace { + +void EscapeAndAppendString(const char* value, std::string* result) { + *result += '"'; + char number_buffer[10]; + while (*value) { + char c = *value++; + switch (c) { + case '\t': + *result += "\\t"; + break; + case '\n': + *result += "\\n"; + break; + case '\"': + *result += "\\\""; + break; + case '\\': + *result += "\\\\"; + break; + default: + if (c < '\x20') { + snprintf( + number_buffer, arraysize(number_buffer), "\\u%04X", + static_cast(static_cast(c))); + *result += number_buffer; + } else { + *result += c; + } + } + } + *result += '"'; +} + +std::string DoubleToCString(double v) { + switch (FPCLASSIFY_NAMESPACE::fpclassify(v)) { + case FP_NAN: return "\"NaN\""; + case FP_INFINITE: return (v < 0.0 ? "\"-Infinity\"" : "\"Infinity\""); + case FP_ZERO: return "0"; + default: + // This is a far less sophisticated version than the one used inside v8. + std::ostringstream stream; + stream.imbue(std::locale("C")); // Ignore locale + stream << v; + return stream.str(); + } +} + +} // namespace + +std::unique_ptr TracedValue::Create() { + return std::unique_ptr(new TracedValue(false)); +} + +std::unique_ptr TracedValue::CreateArray() { + return std::unique_ptr(new TracedValue(true)); +} + +TracedValue::TracedValue(bool root_is_array) : + first_item_(true), root_is_array_(root_is_array) {} + +TracedValue::~TracedValue() {} + +void TracedValue::SetInteger(const char* name, int value) { + WriteName(name); + data_ += std::to_string(value); +} + +void TracedValue::SetDouble(const char* name, double value) { + WriteName(name); + data_ += DoubleToCString(value); +} + +void TracedValue::SetBoolean(const char* name, bool value) { + WriteName(name); + data_ += value ? "true" : "false"; +} + +void TracedValue::SetNull(const char* name) { + WriteName(name); + data_ += "null"; +} + +void TracedValue::SetString(const char* name, const char* value) { + WriteName(name); + EscapeAndAppendString(value, &data_); +} + +void TracedValue::BeginDictionary(const char* name) { + WriteName(name); + data_ += '{'; + first_item_ = true; +} + +void TracedValue::BeginArray(const char* name) { + WriteName(name); + data_ += '['; + first_item_ = true; +} + +void TracedValue::AppendInteger(int value) { + WriteComma(); + data_ += std::to_string(value); +} + +void TracedValue::AppendDouble(double value) { + WriteComma(); + data_ += DoubleToCString(value); +} + +void TracedValue::AppendBoolean(bool value) { + WriteComma(); + data_ += value ? "true" : "false"; +} + +void TracedValue::AppendNull() { + WriteComma(); + data_ += "null"; +} + +void TracedValue::AppendString(const char* value) { + WriteComma(); + EscapeAndAppendString(value, &data_); +} + +void TracedValue::BeginDictionary() { + WriteComma(); + data_ += '{'; + first_item_ = true; +} + +void TracedValue::BeginArray() { + WriteComma(); + data_ += '['; + first_item_ = true; +} + +void TracedValue::EndDictionary() { + data_ += '}'; + first_item_ = false; +} + +void TracedValue::EndArray() { + data_ += ']'; + first_item_ = false; +} + +void TracedValue::WriteComma() { + if (first_item_) { + first_item_ = false; + } else { + data_ += ','; + } +} + +void TracedValue::WriteName(const char* name) { + WriteComma(); + data_ += '"'; + data_ += name; + data_ += "\":"; +} + +void TracedValue::AppendAsTraceFormat(std::string* out) const { + *out += root_is_array_ ? '[' : '{'; + *out += data_; + *out += root_is_array_ ? ']' : '}'; +} + +} // namespace tracing +} // namespace node diff --git a/src/tracing/traced_value.h b/src/tracing/traced_value.h new file mode 100644 index 00000000000000..84e24c952528f5 --- /dev/null +++ b/src/tracing/traced_value.h @@ -0,0 +1,68 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SRC_TRACING_TRACED_VALUE_H_ +#define SRC_TRACING_TRACED_VALUE_H_ + +#include "node_internals.h" +#include "v8.h" + +#include +#include +#include + +namespace node { +namespace tracing { + +class TracedValue : public v8::ConvertableToTraceFormat { + public: + ~TracedValue() override; + + static std::unique_ptr Create(); + static std::unique_ptr CreateArray(); + + void EndDictionary(); + void EndArray(); + + // These methods assume that |name| is a long lived "quoted" string. + void SetInteger(const char* name, int value); + void SetDouble(const char* name, double value); + void SetBoolean(const char* name, bool value); + void SetNull(const char* name); + void SetString(const char* name, const char* value); + void SetString(const char* name, const std::string& value) { + SetString(name, value.c_str()); + } + void BeginDictionary(const char* name); + void BeginArray(const char* name); + + void AppendInteger(int); + void AppendDouble(double); + void AppendBoolean(bool); + void AppendNull(); + void AppendString(const char*); + void AppendString(const std::string& value) { AppendString(value.c_str()); } + void BeginArray(); + void BeginDictionary(); + + // ConvertableToTraceFormat implementation. + void AppendAsTraceFormat(std::string* out) const override; + + private: + explicit TracedValue(bool root_is_array = false); + + void WriteComma(); + void WriteName(const char* name); + + std::string data_; + bool first_item_; + bool root_is_array_; + + DISALLOW_COPY_AND_ASSIGN(TracedValue); +}; + +} // namespace tracing +} // namespace node + +#endif // SRC_TRACING_TRACED_VALUE_H_ diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc new file mode 100644 index 00000000000000..1749a10ecc8024 --- /dev/null +++ b/test/cctest/test_traced_value.cc @@ -0,0 +1,63 @@ +#include "tracing/traced_value.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +using node::tracing::TracedValue; + +TEST(TracedValue, Object) { + auto traced_value = TracedValue::Create(); + traced_value->SetString("a", "b"); + traced_value->SetInteger("b", 1); + traced_value->SetDouble("c", 1.234); + traced_value->SetDouble("d", NAN); + traced_value->SetDouble("e", INFINITY); + traced_value->SetDouble("f", -INFINITY); + traced_value->SetDouble("g", 1.23e7); + traced_value->SetBoolean("h", false); + traced_value->SetBoolean("i", true); + traced_value->SetNull("j"); + traced_value->BeginDictionary("k"); + traced_value->SetString("l", "m"); + traced_value->EndDictionary(); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "{\"a\":\"b\",\"b\":1,\"c\":1.234,\"d\":\"NaN\"," + "\"e\":\"Infinity\",\"f\":\"-Infinity\",\"g\":" + "1.23e+07,\"h\":false,\"i\":true,\"j\":null,\"k\":" + "{\"l\":\"m\"}}"; + + EXPECT_EQ(string, check); +} + +TEST(TracedValue, Array) { + auto traced_value = TracedValue::CreateArray(); + traced_value->AppendString("a"); + traced_value->AppendInteger(1); + traced_value->AppendDouble(1.234); + traced_value->AppendDouble(NAN); + traced_value->AppendDouble(INFINITY); + traced_value->AppendDouble(-INFINITY); + traced_value->AppendDouble(1.23e7); + traced_value->AppendBoolean(false); + traced_value->AppendBoolean(true); + traced_value->AppendNull(); + traced_value->BeginDictionary(); + traced_value->BeginArray("foo"); + traced_value->EndArray(); + traced_value->EndDictionary(); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "[\"a\",1,1.234,\"NaN\",\"Infinity\"," + "\"-Infinity\",1.23e+07,false,true,null," + "{\"foo\":[]}]"; + + EXPECT_EQ(string, check); +} From 629f936db19ba42780181401caf5a409d8b07e45 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 11 Jul 2018 16:40:55 -0700 Subject: [PATCH 2/5] [Squash] add test, better escaping[Squash] add test, better escaping --- src/tracing/traced_value.cc | 69 +++++++++++++++++++++++--------- test/cctest/test_traced_value.cc | 42 ++++++++++++++++++- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index 11919008268b56..153de123e93315 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -9,6 +9,9 @@ #include #include +#include +#include + #if defined(_STLP_VENDOR_CSTD) // STLPort doesn't import fpclassify into the std namespace. #define FPCLASSIFY_NAMESPACE @@ -21,36 +24,64 @@ namespace tracing { namespace { -void EscapeAndAppendString(const char* value, std::string* result) { - *result += '"'; +std::string EscapeString(const char* value) { + std::string result; + result += '"'; char number_buffer[10]; +#if defined(NODE_HAVE_I18N_SUPPORT) + int32_t len = strlen(value); + int32_t p = 0; + int32_t i = 0; + for (;i < len; p = i) { + UChar32 c; + U8_NEXT_OR_FFFD(value, i, len, c); + switch (c) { + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + case '\\': result += "\\\\"; break; + case '"': result += "\\\""; break; + default: + if (c < 32 || c > 126) { + snprintf( + number_buffer, arraysize(number_buffer), "\\u%04X", + static_cast(static_cast(c))); + result += number_buffer; + } else { + result.append(value + p, i - p); + } + } + } +#else + // If we do not have ICU, use a modified version of the non-UTF8 aware + // code from V8's own TracedValue implementation. Note, however, This + // will not produce correctly serialized results for UTF8 values. while (*value) { char c = *value++; switch (c) { - case '\t': - *result += "\\t"; - break; - case '\n': - *result += "\\n"; - break; - case '\"': - *result += "\\\""; - break; - case '\\': - *result += "\\\\"; - break; + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + case '\\': result += "\\\\"; break; + case '"': result += "\\\""; break; default: if (c < '\x20') { snprintf( number_buffer, arraysize(number_buffer), "\\u%04X", static_cast(static_cast(c))); - *result += number_buffer; + result += number_buffer; } else { - *result += c; + result += c; } } } - *result += '"'; +#endif // defined(NODE_HAVE_I18N_SUPPORT) + result += '"'; + return result; } std::string DoubleToCString(double v) { @@ -104,7 +135,7 @@ void TracedValue::SetNull(const char* name) { void TracedValue::SetString(const char* name, const char* value) { WriteName(name); - EscapeAndAppendString(value, &data_); + data_ += EscapeString(value); } void TracedValue::BeginDictionary(const char* name) { @@ -141,7 +172,7 @@ void TracedValue::AppendNull() { void TracedValue::AppendString(const char* value) { WriteComma(); - EscapeAndAppendString(value, &data_); + data_ += EscapeString(value); } void TracedValue::BeginDictionary() { diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc index 1749a10ecc8024..57e6bbd779147b 100644 --- a/test/cctest/test_traced_value.cc +++ b/test/cctest/test_traced_value.cc @@ -32,7 +32,7 @@ TEST(TracedValue, Object) { "1.23e+07,\"h\":false,\"i\":true,\"j\":null,\"k\":" "{\"l\":\"m\"}}"; - EXPECT_EQ(string, check); + EXPECT_EQ(check, string); } TEST(TracedValue, Array) { @@ -59,5 +59,43 @@ TEST(TracedValue, Array) { "\"-Infinity\",1.23e+07,false,true,null," "{\"foo\":[]}]"; - EXPECT_EQ(string, check); + EXPECT_EQ(check, string); +} + +TEST(TracedValue, EscapingObject) { + // Verify that special characters in the value are escaped properly + auto traced_value = TracedValue::Create(); + traced_value->SetString("a", "1\u20ac23\"\u0001\b\f\n\r\t\\"); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + +#if defined(NODE_HAVE_I18N_SUPPORT) + static const char* check = + "{\"a\":\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"}"; +#else + static const char* check = + "{\"a\":\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"}"; +#endif + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, EscapingArray) { + // Verify that special characters in the value are escaped properly + auto traced_value = TracedValue::CreateArray(); + traced_value->AppendString("1\u20ac23\"\u0001\b\f\n\r\t\\"); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + +#if defined(NODE_HAVE_I18N_SUPPORT) + static const char* check = + "[\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"]"; +#else + static const char* check = + "[\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"]"; +#endif + + EXPECT_EQ(check, string); } From 239e8709af413dda4af66c51ef85cb5744b8f8fa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 11 Jul 2018 16:58:36 -0700 Subject: [PATCH 3/5] [Squash] fix lint --- src/tracing/traced_value.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index 153de123e93315..eea35bca66a6e4 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -32,7 +32,7 @@ std::string EscapeString(const char* value) { int32_t len = strlen(value); int32_t p = 0; int32_t i = 0; - for (;i < len; p = i) { + for (; i < len; p = i) { UChar32 c; U8_NEXT_OR_FFFD(value, i, len, c); switch (c) { From 8f25cc1d41a5494a22c54f0dcab7c66b82543bf5 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 11 Jul 2018 21:42:46 -0700 Subject: [PATCH 4/5] [Squash] hopefully avoid failure on windows --- test/cctest/test_traced_value.cc | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc index 57e6bbd779147b..5329c78446ca6f 100644 --- a/test/cctest/test_traced_value.cc +++ b/test/cctest/test_traced_value.cc @@ -62,40 +62,35 @@ TEST(TracedValue, Array) { EXPECT_EQ(check, string); } +#define UTF8_SEQUENCE "1" "\xE2\x82\xAC" "23\"\x01\b\f\n\r\t\\" +#if defined(NODE_HAVE_I18N_SUPPORT) +# define UTF8_RESULT \ + "\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"" +#else +# define UTF8_RESULT \ + "\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"" +#endif + TEST(TracedValue, EscapingObject) { - // Verify that special characters in the value are escaped properly auto traced_value = TracedValue::Create(); - traced_value->SetString("a", "1\u20ac23\"\u0001\b\f\n\r\t\\"); + traced_value->SetString("a", UTF8_SEQUENCE); std::string string; traced_value->AppendAsTraceFormat(&string); -#if defined(NODE_HAVE_I18N_SUPPORT) - static const char* check = - "{\"a\":\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"}"; -#else - static const char* check = - "{\"a\":\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"}"; -#endif + static const char* check = "{\"a\":" UTF8_RESULT "}"; EXPECT_EQ(check, string); } TEST(TracedValue, EscapingArray) { - // Verify that special characters in the value are escaped properly auto traced_value = TracedValue::CreateArray(); - traced_value->AppendString("1\u20ac23\"\u0001\b\f\n\r\t\\"); + traced_value->AppendString(UTF8_SEQUENCE); std::string string; traced_value->AppendAsTraceFormat(&string); -#if defined(NODE_HAVE_I18N_SUPPORT) - static const char* check = - "[\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"]"; -#else - static const char* check = - "[\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"]"; -#endif + static const char* check = "[" UTF8_RESULT "]"; EXPECT_EQ(check, string); } From c62c8104c7c77108e8b90d3f0c1b49f56c2463aa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 12 Jul 2018 05:43:16 -0700 Subject: [PATCH 5/5] [Squash] fix without-intl[Squash] fix without-intl --- src/tracing/traced_value.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index eea35bca66a6e4..e256df267eb5a9 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -9,8 +9,10 @@ #include #include +#if defined(NODE_HAVE_I18N_SUPPORT) #include #include +#endif #if defined(_STLP_VENDOR_CSTD) // STLPort doesn't import fpclassify into the std namespace.