diff --git a/node.gyp b/node.gyp index 207c72cdb56a29..35a81437afdfcf 100644 --- a/node.gyp +++ b/node.gyp @@ -377,6 +377,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', @@ -437,6 +438,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', @@ -950,6 +952,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..e256df267eb5a9 --- /dev/null +++ b/src/tracing/traced_value.cc @@ -0,0 +1,224 @@ +// 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(NODE_HAVE_I18N_SUPPORT) +#include +#include +#endif + +#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 { + +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 '\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; + } else { + result += c; + } + } + } +#endif // defined(NODE_HAVE_I18N_SUPPORT) + result += '"'; + return 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); + data_ += EscapeString(value); +} + +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(); + data_ += EscapeString(value); +} + +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..5329c78446ca6f --- /dev/null +++ b/test/cctest/test_traced_value.cc @@ -0,0 +1,96 @@ +#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(check, string); +} + +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(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) { + auto traced_value = TracedValue::Create(); + traced_value->SetString("a", UTF8_SEQUENCE); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "{\"a\":" UTF8_RESULT "}"; + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, EscapingArray) { + auto traced_value = TracedValue::CreateArray(); + traced_value->AppendString(UTF8_SEQUENCE); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "[" UTF8_RESULT "]"; + + EXPECT_EQ(check, string); +}