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