• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/api/api-inl.h"
6 #include "src/builtins/builtins-utils-inl.h"
7 #include "src/builtins/builtins.h"
8 #include "src/heap/heap-inl.h"  // For ToBoolean. TODO(jkummerow): Drop.
9 #include "src/json/json-stringifier.h"
10 #include "src/logging/counters.h"
11 #include "src/objects/objects-inl.h"
12 #include "src/tracing/traced-value.h"
13 
14 #if defined(V8_USE_PERFETTO)
15 #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
16 #include "src/base/platform/wrappers.h"
17 #endif
18 
19 namespace v8 {
20 namespace internal {
21 
22 namespace {
23 
24 using v8::tracing::TracedValue;
25 
26 #define MAX_STACK_LENGTH 100
27 
28 class MaybeUtf8 {
29  public:
MaybeUtf8(Isolate * isolate,Handle<String> string)30   explicit MaybeUtf8(Isolate* isolate, Handle<String> string) : buf_(data_) {
31     string = String::Flatten(isolate, string);
32     int len;
33     if (string->IsOneByteRepresentation()) {
34       // Technically this allows unescaped latin1 characters but the trace
35       // events mechanism currently does the same and the current consuming
36       // tools are tolerant of it. A more correct approach here would be to
37       // escape non-ascii characters but this is easier and faster.
38       len = string->length();
39       AllocateSufficientSpace(len);
40       if (len > 0) {
41         // Why copy? Well, the trace event mechanism requires null-terminated
42         // strings, the bytes we get from SeqOneByteString are not. buf_ is
43         // guaranteed to be null terminated.
44         DisallowGarbageCollection no_gc;
45         memcpy(buf_, Handle<SeqOneByteString>::cast(string)->GetChars(no_gc),
46                len);
47       }
48     } else {
49       Local<v8::String> local = Utils::ToLocal(string);
50       auto* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
51       len = local->Utf8Length(v8_isolate);
52       AllocateSufficientSpace(len);
53       if (len > 0) {
54         local->WriteUtf8(v8_isolate, reinterpret_cast<char*>(buf_));
55       }
56     }
57     buf_[len] = 0;
58   }
operator *() const59   const char* operator*() const { return reinterpret_cast<const char*>(buf_); }
60 
61  private:
AllocateSufficientSpace(int len)62   void AllocateSufficientSpace(int len) {
63     if (len + 1 > MAX_STACK_LENGTH) {
64       allocated_ = std::make_unique<uint8_t[]>(len + 1);
65       buf_ = allocated_.get();
66     }
67   }
68 
69   // In the most common cases, the buffer here will be stack allocated.
70   // A heap allocation will only occur if the data is more than MAX_STACK_LENGTH
71   // Given that this is used primarily for trace event categories and names,
72   // the MAX_STACK_LENGTH should be more than enough.
73   uint8_t* buf_;
74   uint8_t data_[MAX_STACK_LENGTH];
75   std::unique_ptr<uint8_t[]> allocated_;
76 };
77 
78 #if !defined(V8_USE_PERFETTO)
79 class JsonTraceValue : public ConvertableToTraceFormat {
80  public:
JsonTraceValue(Isolate * isolate,Handle<String> object)81   explicit JsonTraceValue(Isolate* isolate, Handle<String> object) {
82     // object is a JSON string serialized using JSON.stringify() from within
83     // the BUILTIN(Trace) method. This may (likely) contain UTF8 values so
84     // to grab the appropriate buffer data we have to serialize it out. We
85     // hold on to the bits until the AppendAsTraceFormat method is called.
86     MaybeUtf8 data(isolate, object);
87     data_ = *data;
88   }
89 
AppendAsTraceFormat(std::string * out) const90   void AppendAsTraceFormat(std::string* out) const override { *out += data_; }
91 
92  private:
93   std::string data_;
94 };
95 
GetCategoryGroupEnabled(Isolate * isolate,Handle<String> string)96 const uint8_t* GetCategoryGroupEnabled(Isolate* isolate,
97                                        Handle<String> string) {
98   MaybeUtf8 category(isolate, string);
99   return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(*category);
100 }
101 #endif  // !defined(V8_USE_PERFETTO)
102 
103 #undef MAX_STACK_LENGTH
104 
105 }  // namespace
106 
107 // Builins::kIsTraceCategoryEnabled(category) : bool
BUILTIN(IsTraceCategoryEnabled)108 BUILTIN(IsTraceCategoryEnabled) {
109   HandleScope scope(isolate);
110   Handle<Object> category = args.atOrUndefined(isolate, 1);
111   if (!category->IsString()) {
112     THROW_NEW_ERROR_RETURN_FAILURE(
113         isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError));
114   }
115   bool enabled;
116 #if defined(V8_USE_PERFETTO)
117   MaybeUtf8 category_str(isolate, Handle<String>::cast(category));
118   perfetto::DynamicCategory dynamic_category{*category_str};
119   enabled = TRACE_EVENT_CATEGORY_ENABLED(dynamic_category);
120 #else
121   enabled = *GetCategoryGroupEnabled(isolate, Handle<String>::cast(category));
122 #endif
123   return isolate->heap()->ToBoolean(enabled);
124 }
125 
126 // Builtin::kTrace(phase, category, name, id, data) : bool
BUILTIN(Trace)127 BUILTIN(Trace) {
128   HandleScope handle_scope(isolate);
129 
130   Handle<Object> phase_arg = args.atOrUndefined(isolate, 1);
131   Handle<Object> category = args.atOrUndefined(isolate, 2);
132   Handle<Object> name_arg = args.atOrUndefined(isolate, 3);
133   Handle<Object> id_arg = args.atOrUndefined(isolate, 4);
134   Handle<Object> data_arg = args.atOrUndefined(isolate, 5);
135 
136   // Exit early if the category group is not enabled.
137 #if defined(V8_USE_PERFETTO)
138   MaybeUtf8 category_str(isolate, Handle<String>::cast(category));
139   perfetto::DynamicCategory dynamic_category{*category_str};
140   if (!TRACE_EVENT_CATEGORY_ENABLED(dynamic_category))
141     return ReadOnlyRoots(isolate).false_value();
142 #else
143   const uint8_t* category_group_enabled =
144       GetCategoryGroupEnabled(isolate, Handle<String>::cast(category));
145   if (!*category_group_enabled) return ReadOnlyRoots(isolate).false_value();
146 #endif
147 
148   if (!phase_arg->IsNumber()) {
149     THROW_NEW_ERROR_RETURN_FAILURE(
150         isolate, NewTypeError(MessageTemplate::kTraceEventPhaseError));
151   }
152   char phase = static_cast<char>(DoubleToInt32(phase_arg->Number()));
153   if (!category->IsString()) {
154     THROW_NEW_ERROR_RETURN_FAILURE(
155         isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError));
156   }
157   if (!name_arg->IsString()) {
158     THROW_NEW_ERROR_RETURN_FAILURE(
159         isolate, NewTypeError(MessageTemplate::kTraceEventNameError));
160   }
161 
162   uint32_t flags = TRACE_EVENT_FLAG_COPY;
163   int32_t id = 0;
164   if (!id_arg->IsNullOrUndefined(isolate)) {
165     if (!id_arg->IsNumber()) {
166       THROW_NEW_ERROR_RETURN_FAILURE(
167           isolate, NewTypeError(MessageTemplate::kTraceEventIDError));
168     }
169     flags |= TRACE_EVENT_FLAG_HAS_ID;
170     id = DoubleToInt32(id_arg->Number());
171   }
172 
173   Handle<String> name_str = Handle<String>::cast(name_arg);
174   if (name_str->length() == 0) {
175     THROW_NEW_ERROR_RETURN_FAILURE(
176         isolate, NewTypeError(MessageTemplate::kTraceEventNameLengthError));
177   }
178   MaybeUtf8 name(isolate, name_str);
179 
180   // We support passing one additional trace event argument with the
181   // name "data". Any JSON serializable value may be passed.
182   static const char* arg_name = "data";
183   Handle<Object> arg_json;
184   int32_t num_args = 0;
185   if (!data_arg->IsUndefined(isolate)) {
186     // Serializes the data argument as a JSON string, which is then
187     // copied into an object. This eliminates duplicated code but
188     // could have perf costs. It is also subject to all the same
189     // limitations as JSON.stringify() as it relates to circular
190     // references and value limitations (e.g. BigInt is not supported).
191     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
192         isolate, arg_json,
193         JsonStringify(isolate, data_arg, isolate->factory()->undefined_value(),
194                       isolate->factory()->undefined_value()));
195     num_args++;
196   }
197 
198 #if defined(V8_USE_PERFETTO)
199   auto trace_args = [&](perfetto::EventContext ctx) {
200     // TODO(skyostil): Use interned names to reduce trace size.
201     if (phase != TRACE_EVENT_PHASE_END) {
202       ctx.event()->set_name(*name);
203     }
204     if (num_args) {
205       MaybeUtf8 arg_contents(isolate, Handle<String>::cast(arg_json));
206       auto annotation = ctx.event()->add_debug_annotations();
207       annotation->set_name(arg_name);
208       annotation->set_legacy_json_value(*arg_contents);
209     }
210     if (flags & TRACE_EVENT_FLAG_HAS_ID) {
211       auto legacy_event = ctx.event()->set_legacy_event();
212       legacy_event->set_global_id(id);
213     }
214   };
215 
216   switch (phase) {
217     case TRACE_EVENT_PHASE_BEGIN:
218       TRACE_EVENT_BEGIN(dynamic_category, nullptr, trace_args);
219       break;
220     case TRACE_EVENT_PHASE_END:
221       TRACE_EVENT_END(dynamic_category, trace_args);
222       break;
223     case TRACE_EVENT_PHASE_INSTANT:
224       TRACE_EVENT_INSTANT(dynamic_category, nullptr, trace_args);
225       break;
226     default:
227       THROW_NEW_ERROR_RETURN_FAILURE(
228           isolate, NewTypeError(MessageTemplate::kTraceEventPhaseError));
229   }
230 
231 #else   // !defined(V8_USE_PERFETTO)
232   uint8_t arg_type;
233   uint64_t arg_value;
234   if (num_args) {
235     std::unique_ptr<JsonTraceValue> traced_value(
236         new JsonTraceValue(isolate, Handle<String>::cast(arg_json)));
237     tracing::SetTraceValue(std::move(traced_value), &arg_type, &arg_value);
238   }
239 
240   TRACE_EVENT_API_ADD_TRACE_EVENT(
241       phase, category_group_enabled, *name, tracing::kGlobalScope, id,
242       tracing::kNoId, num_args, &arg_name, &arg_type, &arg_value, flags);
243 #endif  // !defined(V8_USE_PERFETTO)
244 
245   return ReadOnlyRoots(isolate).true_value();
246 }
247 
248 }  // namespace internal
249 }  // namespace v8
250