diff --git a/common.gypi b/common.gypi index 2fe304c5f07143..2abe79a498b98a 100644 --- a/common.gypi +++ b/common.gypi @@ -29,7 +29,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.10', + 'v8_embedder_string': '-node.11', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS index ba54db5505663d..e920bbf42bd3d8 100644 --- a/deps/v8/AUTHORS +++ b/deps/v8/AUTHORS @@ -86,6 +86,7 @@ Jan de Mooij Jan Krems Jay Freeman James Pike +James M Snell Jianghua Yang Joel Stanley Johan Bergström diff --git a/deps/v8/BUILD.gn b/deps/v8/BUILD.gn index 456a318c1c6052..f00a8280c10261 100644 --- a/deps/v8/BUILD.gn +++ b/deps/v8/BUILD.gn @@ -1539,6 +1539,7 @@ v8_source_set("v8_base") { "src/builtins/builtins-sharedarraybuffer.cc", "src/builtins/builtins-string.cc", "src/builtins/builtins-symbol.cc", + "src/builtins/builtins-trace.cc", "src/builtins/builtins-typed-array.cc", "src/builtins/builtins-utils.h", "src/builtins/builtins.cc", diff --git a/deps/v8/gypfiles/v8.gyp b/deps/v8/gypfiles/v8.gyp index eadbd5aeeeaafb..08abec1c3e5a35 100644 --- a/deps/v8/gypfiles/v8.gyp +++ b/deps/v8/gypfiles/v8.gyp @@ -642,6 +642,7 @@ '../src/builtins/builtins-intl.cc', '../src/builtins/builtins-intl.h', '../src/builtins/builtins-symbol.cc', + '../src/builtins/builtins-trace.cc', '../src/builtins/builtins-typed-array.cc', '../src/builtins/builtins-utils.h', '../src/builtins/builtins.cc', diff --git a/deps/v8/src/bootstrapper.cc b/deps/v8/src/bootstrapper.cc index ed8fd72c91527d..548ef5109a9a68 100644 --- a/deps/v8/src/bootstrapper.cc +++ b/deps/v8/src/bootstrapper.cc @@ -4981,6 +4981,15 @@ bool Genesis::InstallExtraNatives() { Handle extras_binding = factory()->NewJSObject(isolate()->object_function()); + + // binding.isTraceCategoryenabled(category) + SimpleInstallFunction(extras_binding, "isTraceCategoryEnabled", + Builtins::kIsTraceCategoryEnabled, 1, true); + + // binding.trace(phase, category, name, id, data) + SimpleInstallFunction(extras_binding, "trace", Builtins::kTrace, 5, + true); + native_context()->set_extras_binding_object(*extras_binding); for (int i = ExtraNatives::GetDebuggerCount(); diff --git a/deps/v8/src/builtins/builtins-definitions.h b/deps/v8/src/builtins/builtins-definitions.h index 3da81f17a4b2c5..4a4b17006cfc0e 100644 --- a/deps/v8/src/builtins/builtins-definitions.h +++ b/deps/v8/src/builtins/builtins-definitions.h @@ -1269,7 +1269,11 @@ namespace internal { /* Miscellaneous */ \ ASM(DoubleToI) \ TFC(GetProperty, GetProperty, 1) \ - ASM(MathPowInternal) + ASM(MathPowInternal) \ + \ + /* Trace */ \ + CPP(IsTraceCategoryEnabled) \ + CPP(Trace) #ifdef V8_INTL_SUPPORT #define BUILTIN_LIST(CPP, API, TFJ, TFC, TFS, TFH, ASM) \ diff --git a/deps/v8/src/builtins/builtins-trace.cc b/deps/v8/src/builtins/builtins-trace.cc new file mode 100644 index 00000000000000..a10c1363387db8 --- /dev/null +++ b/deps/v8/src/builtins/builtins-trace.cc @@ -0,0 +1,191 @@ +// Copyright 2018 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 "src/api.h" +#include "src/builtins/builtins-utils.h" +#include "src/builtins/builtins.h" +#include "src/counters.h" +#include "src/json-stringifier.h" +#include "src/objects-inl.h" + +namespace v8 { +namespace internal { + +namespace { + +using v8::tracing::TracedValue; + +#define MAX_STACK_LENGTH 100 + +class MaybeUtf8 { + public: + explicit MaybeUtf8(Isolate* isolate, Handle string) : buf_(data_) { + string = String::Flatten(string); + int len; + if (string->IsOneByteRepresentation()) { + // Technically this allows unescaped latin1 characters but the trace + // events mechanism currently does the same and the current consuming + // tools are tolerant of it. A more correct approach here would be to + // escape non-ascii characters but this is easier and faster. + len = string->length(); + AllocateSufficientSpace(len); + if (len > 0) { + // Why copy? Well, the trace event mechanism requires null-terminated + // strings, the bytes we get from SeqOneByteString are not. buf_ is + // guaranteed to be null terminated. + memcpy(buf_, Handle::cast(string)->GetChars(), len); + } + } else { + Local local = Utils::ToLocal(string); + len = local->Utf8Length(); + AllocateSufficientSpace(len); + if (len > 0) { + local->WriteUtf8(reinterpret_cast(buf_)); + } + } + buf_[len] = 0; + } + const char* operator*() const { return reinterpret_cast(buf_); } + + private: + void AllocateSufficientSpace(int len) { + if (len + 1 > MAX_STACK_LENGTH) { + allocated_.reset(new uint8_t[len + 1]); + buf_ = allocated_.get(); + } + } + + // In the most common cases, the buffer here will be stack allocated. + // A heap allocation will only occur if the data is more than MAX_STACK_LENGTH + // Given that this is used primarily for trace event categories and names, + // the MAX_STACK_LENGTH should be more than enough. + uint8_t* buf_; + uint8_t data_[MAX_STACK_LENGTH]; + std::unique_ptr allocated_; +}; + +class JsonTraceValue : public ConvertableToTraceFormat { + public: + explicit JsonTraceValue(Isolate* isolate, Handle object) { + // object is a JSON string serialized using JSON.stringify() from within + // the BUILTIN(Trace) method. This may (likely) contain UTF8 values so + // to grab the appropriate buffer data we have to serialize it out. We + // hold on to the bits until the AppendAsTraceFormat method is called. + MaybeUtf8 data(isolate, object); + data_ = *data; + } + + void AppendAsTraceFormat(std::string* out) const override { *out += data_; } + + private: + std::string data_; +}; + +const uint8_t* GetCategoryGroupEnabled(Isolate* isolate, + Handle string) { + MaybeUtf8 category(isolate, string); + return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(*category); +} + +#undef MAX_STACK_LENGTH + +} // namespace + +// Builins::kIsTraceCategoryEnabled(category) : bool +BUILTIN(IsTraceCategoryEnabled) { + HandleScope scope(isolate); + Handle category = args.atOrUndefined(isolate, 1); + if (!category->IsString()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError)); + } + return isolate->heap()->ToBoolean( + *GetCategoryGroupEnabled(isolate, Handle::cast(category))); +} + +// Builtins::kTrace(phase, category, name, id, data) : bool +BUILTIN(Trace) { + HandleScope handle_scope(isolate); + + Handle phase_arg = args.atOrUndefined(isolate, 1); + Handle category = args.atOrUndefined(isolate, 2); + Handle name_arg = args.atOrUndefined(isolate, 3); + Handle id_arg = args.atOrUndefined(isolate, 4); + Handle data_arg = args.atOrUndefined(isolate, 5); + + const uint8_t* category_group_enabled = + GetCategoryGroupEnabled(isolate, Handle::cast(category)); + + // Exit early if the category group is not enabled. + if (!*category_group_enabled) { + return isolate->heap()->false_value(); + } + + if (!phase_arg->IsNumber()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventPhaseError)); + } + if (!category->IsString()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError)); + } + if (!name_arg->IsString()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventNameError)); + } + + uint32_t flags = TRACE_EVENT_FLAG_COPY; + int32_t id = 0; + if (!id_arg->IsNullOrUndefined(isolate)) { + if (!id_arg->IsNumber()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventIDError)); + } + flags |= TRACE_EVENT_FLAG_HAS_ID; + id = DoubleToInt32(id_arg->Number()); + } + + Handle name_str = Handle::cast(name_arg); + if (name_str->length() == 0) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kTraceEventNameLengthError)); + } + MaybeUtf8 name(isolate, name_str); + + // We support passing one additional trace event argument with the + // name "data". Any JSON serializable value may be passed. + static const char* arg_name = "data"; + int32_t num_args = 0; + uint8_t arg_type; + uint64_t arg_value; + + if (!data_arg->IsUndefined(isolate)) { + // Serializes the data argument as a JSON string, which is then + // copied into an object. This eliminates duplicated code but + // could have perf costs. It is also subject to all the same + // limitations as JSON.stringify() as it relates to circular + // references and value limitations (e.g. BigInt is not supported). + JsonStringifier stringifier(isolate); + Handle result; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( + isolate, result, + stringifier.Stringify(data_arg, isolate->factory()->undefined_value(), + isolate->factory()->undefined_value())); + std::unique_ptr traced_value; + traced_value.reset( + new JsonTraceValue(isolate, Handle::cast(result))); + tracing::SetTraceValue(std::move(traced_value), &arg_type, &arg_value); + num_args++; + } + + TRACE_EVENT_API_ADD_TRACE_EVENT( + static_cast(DoubleToInt32(phase_arg->Number())), + category_group_enabled, *name, tracing::kGlobalScope, id, tracing::kNoId, + num_args, &arg_name, &arg_type, &arg_value, flags); + + return isolate->heap()->true_value(); +} + +} // namespace internal +} // namespace v8 diff --git a/deps/v8/src/debug/debug-evaluate.cc b/deps/v8/src/debug/debug-evaluate.cc index dd63a0187c0225..505bbad2dda1bf 100644 --- a/deps/v8/src/debug/debug-evaluate.cc +++ b/deps/v8/src/debug/debug-evaluate.cc @@ -630,6 +630,9 @@ SharedFunctionInfo::SideEffectState BuiltinGetSideEffectState( case Builtins::kArrayMap: case Builtins::kArrayReduce: case Builtins::kArrayReduceRight: + // Trace builtins + case Builtins::kIsTraceCategoryEnabled: + case Builtins::kTrace: // TypedArray builtins. case Builtins::kTypedArrayConstructor: case Builtins::kTypedArrayPrototypeBuffer: diff --git a/deps/v8/src/messages.h b/deps/v8/src/messages.h index 32df6d72e952ed..c4877ddf176ad3 100644 --- a/deps/v8/src/messages.h +++ b/deps/v8/src/messages.h @@ -754,7 +754,14 @@ class ErrorUtils : public AllStatic { T(DataCloneDeserializationError, "Unable to deserialize cloned data.") \ T(DataCloneDeserializationVersionError, \ "Unable to deserialize cloned data due to invalid or unsupported " \ - "version.") + "version.") \ + /* Builtins-Trace Errors */ \ + T(TraceEventCategoryError, "Trace event category must be a string.") \ + T(TraceEventNameError, "Trace event name must be a string.") \ + T(TraceEventNameLengthError, \ + "Trace event name must not be an empty string.") \ + T(TraceEventPhaseError, "Trace event phase must be a number.") \ + T(TraceEventIDError, "Trace event id must be a number.") class MessageTemplate { public: diff --git a/deps/v8/test/cctest/test-trace-event.cc b/deps/v8/test/cctest/test-trace-event.cc index 7b736b907dd9ae..47545af37f89c0 100644 --- a/deps/v8/test/cctest/test-trace-event.cc +++ b/deps/v8/test/cctest/test-trace-event.cc @@ -75,7 +75,7 @@ class MockTracingController : public v8::TracingController { const char* name, uint64_t handle) override {} const uint8_t* GetCategoryGroupEnabled(const char* name) override { - if (strcmp(name, "v8-cat")) { + if (strncmp(name, "v8-cat", 6)) { static uint8_t no = 0; return &no; } else { @@ -282,3 +282,135 @@ TEST(TestEventWithTimestamp) { CHECK_EQ(20683, GET_TRACE_OBJECT(3)->timestamp); CHECK_EQ(32832, GET_TRACE_OBJECT(4)->timestamp); } + +TEST(BuiltinsIsTraceCategoryEnabled) { + CcTest::InitializeVM(); + MockTracingPlatform platform; + + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope handle_scope(isolate); + LocalContext env; + + v8::Local binding = env->GetExtrasBindingObject(); + CHECK(!binding.IsEmpty()); + + auto undefined = v8::Undefined(isolate); + auto isTraceCategoryEnabled = + binding->Get(env.local(), v8_str("isTraceCategoryEnabled")) + .ToLocalChecked() + .As(); + + { + // Test with an enabled category + v8::Local argv[] = {v8_str("v8-cat")}; + auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv) + .ToLocalChecked() + .As(); + + CHECK(result->BooleanValue()); + } + + { + // Test with a disabled category + v8::Local argv[] = {v8_str("cat")}; + auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv) + .ToLocalChecked() + .As(); + + CHECK(!result->BooleanValue()); + } + + { + // Test with an enabled utf8 category + v8::Local argv[] = {v8_str("v8-cat\u20ac")}; + auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv) + .ToLocalChecked() + .As(); + + CHECK(result->BooleanValue()); + } +} + +TEST(BuiltinsTrace) { + CcTest::InitializeVM(); + MockTracingPlatform platform; + + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope handle_scope(isolate); + LocalContext env; + + v8::Local binding = env->GetExtrasBindingObject(); + CHECK(!binding.IsEmpty()); + + auto undefined = v8::Undefined(isolate); + auto trace = binding->Get(env.local(), v8_str("trace")) + .ToLocalChecked() + .As(); + + // Test with disabled category + { + v8::Local category = v8_str("cat"); + v8::Local name = v8_str("name"); + v8::Local argv[] = { + v8::Integer::New(isolate, 'b'), // phase + category, name, v8::Integer::New(isolate, 0), // id + undefined // data + }; + auto result = trace->Call(env.local(), undefined, 5, argv) + .ToLocalChecked() + .As(); + + CHECK(!result->BooleanValue()); + CHECK_EQ(0, GET_TRACE_OBJECTS_LIST->size()); + } + + // Test with enabled category + { + v8::Local category = v8_str("v8-cat"); + v8::Local name = v8_str("name"); + v8::Local context = isolate->GetCurrentContext(); + v8::Local data = v8::Object::New(isolate); + data->Set(context, v8_str("foo"), v8_str("bar")).FromJust(); + v8::Local argv[] = { + v8::Integer::New(isolate, 'b'), // phase + category, name, v8::Integer::New(isolate, 123), // id + data // data arg + }; + auto result = trace->Call(env.local(), undefined, 5, argv) + .ToLocalChecked() + .As(); + + CHECK(result->BooleanValue()); + CHECK_EQ(1, GET_TRACE_OBJECTS_LIST->size()); + + CHECK_EQ(123, GET_TRACE_OBJECT(0)->id); + CHECK_EQ('b', GET_TRACE_OBJECT(0)->phase); + CHECK_EQ("name", GET_TRACE_OBJECT(0)->name); + CHECK_EQ(1, GET_TRACE_OBJECT(0)->num_args); + } + + // Test with enabled utf8 category + { + v8::Local category = v8_str("v8-cat\u20ac"); + v8::Local name = v8_str("name\u20ac"); + v8::Local context = isolate->GetCurrentContext(); + v8::Local data = v8::Object::New(isolate); + data->Set(context, v8_str("foo"), v8_str("bar")).FromJust(); + v8::Local argv[] = { + v8::Integer::New(isolate, 'b'), // phase + category, name, v8::Integer::New(isolate, 123), // id + data // data arg + }; + auto result = trace->Call(env.local(), undefined, 5, argv) + .ToLocalChecked() + .As(); + + CHECK(result->BooleanValue()); + CHECK_EQ(2, GET_TRACE_OBJECTS_LIST->size()); + + CHECK_EQ(123, GET_TRACE_OBJECT(1)->id); + CHECK_EQ('b', GET_TRACE_OBJECT(1)->phase); + CHECK_EQ("name\u20ac", GET_TRACE_OBJECT(1)->name); + CHECK_EQ(1, GET_TRACE_OBJECT(1)->num_args); + } +}