1 // Copyright 2018 The Chromium Authors
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 "base/trace_event/trace_arguments.h"
6
7 #include <inttypes.h>
8 #include <stddef.h>
9 #include <stdio.h>
10 #include <string.h>
11
12 #include <cmath>
13 #include <ostream>
14
15 #include "base/check_op.h"
16 #include "base/json/string_escape.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/notreached.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23
24 namespace base {
25 namespace trace_event {
26
27 namespace {
28
GetAllocLength(const char * str)29 size_t GetAllocLength(const char* str) {
30 return str ? strlen(str) + 1 : 0;
31 }
32
33 // Copies |*member| into |*buffer|, sets |*member| to point to this new
34 // location, and then advances |*buffer| by the amount written.
CopyTraceEventParameter(char ** buffer,const char ** member,const char * end)35 void CopyTraceEventParameter(char** buffer,
36 const char** member,
37 const char* end) {
38 if (*member) {
39 DCHECK_GE(end, *buffer);
40 size_t written =
41 strlcpy(*buffer, *member, static_cast<size_t>(end - *buffer)) + 1;
42 DCHECK_LE(static_cast<ptrdiff_t>(written), end - *buffer);
43 *member = *buffer;
44 *buffer += written;
45 }
46 }
47
48 // Append |val| as a JSON output value to |*out|.
AppendDouble(double val,bool as_json,std::string * out)49 void AppendDouble(double val, bool as_json, std::string* out) {
50 // FIXME: base/json/json_writer.cc is using the same code,
51 // should be made into a common method.
52 std::string real;
53 if (std::isfinite(val)) {
54 real = NumberToString(val);
55 // Ensure that the number has a .0 if there's no decimal or 'e'. This
56 // makes sure that when we read the JSON back, it's interpreted as a
57 // real rather than an int.
58 if (real.find('.') == std::string::npos &&
59 real.find('e') == std::string::npos &&
60 real.find('E') == std::string::npos) {
61 real.append(".0");
62 }
63 // The JSON spec requires that non-integer values in the range (-1,1)
64 // have a zero before the decimal point - ".52" is not valid, "0.52" is.
65 if (real[0] == '.') {
66 real.insert(0, "0");
67 } else if (real.length() > 1 && real[0] == '-' && real[1] == '.') {
68 // "-.1" bad "-0.1" good
69 real.insert(1, "0");
70 }
71 } else if (std::isnan(val)) {
72 // The JSON spec doesn't allow NaN and Infinity (since these are
73 // objects in EcmaScript). Use strings instead.
74 real = as_json ? "\"NaN\"" : "NaN";
75 } else if (val < 0) {
76 real = as_json ? "\"-Infinity\"" : "-Infinity";
77 } else {
78 real = as_json ? "\"Infinity\"" : "Infinity";
79 }
80 StringAppendF(out, "%s", real.c_str());
81 }
82
TypeToString(unsigned char arg_type)83 const char* TypeToString(unsigned char arg_type) {
84 switch (arg_type) {
85 case TRACE_VALUE_TYPE_INT:
86 return "int";
87 case TRACE_VALUE_TYPE_UINT:
88 return "uint";
89 case TRACE_VALUE_TYPE_DOUBLE:
90 return "double";
91 case TRACE_VALUE_TYPE_BOOL:
92 return "bool";
93 case TRACE_VALUE_TYPE_POINTER:
94 return "pointer";
95 case TRACE_VALUE_TYPE_STRING:
96 return "string";
97 case TRACE_VALUE_TYPE_COPY_STRING:
98 return "copy_string";
99 case TRACE_VALUE_TYPE_CONVERTABLE:
100 return "convertable";
101 default:
102 NOTREACHED();
103 return "UNKNOWN_TYPE";
104 }
105 }
106
AppendValueDebugString(const TraceArguments & args,size_t idx,std::string * out)107 void AppendValueDebugString(const TraceArguments& args,
108 size_t idx,
109 std::string* out) {
110 *out += (args.names()[idx] ? args.names()[idx] : "NULL_NAME");
111 *out += "=";
112 *out += TypeToString(args.types()[idx]);
113 *out += "(";
114 args.values()[idx].AppendAsJSON(args.types()[idx], out);
115 *out += ")";
116 }
117
118 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
119 class PerfettoProtoAppender : public ConvertableToTraceFormat::ProtoAppender {
120 public:
PerfettoProtoAppender(perfetto::protos::pbzero::DebugAnnotation * proto)121 explicit PerfettoProtoAppender(
122 perfetto::protos::pbzero::DebugAnnotation* proto)
123 : annotation_proto_(proto) {}
124 ~PerfettoProtoAppender() override = default;
125
AddBuffer(uint8_t * begin,uint8_t * end)126 void AddBuffer(uint8_t* begin, uint8_t* end) override {
127 ranges_.emplace_back();
128 ranges_.back().begin = begin;
129 ranges_.back().end = end;
130 }
131
Finalize(uint32_t field_id)132 size_t Finalize(uint32_t field_id) override {
133 return annotation_proto_->AppendScatteredBytes(field_id, ranges_.data(),
134 ranges_.size());
135 }
136
137 private:
138 std::vector<protozero::ContiguousMemoryRange> ranges_;
139 raw_ptr<perfetto::protos::pbzero::DebugAnnotation> annotation_proto_;
140 };
141 #endif // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
142
143 } // namespace
144
Reset(size_t alloc_size)145 void StringStorage::Reset(size_t alloc_size) {
146 if (!alloc_size) {
147 if (data_)
148 ::free(data_);
149 data_ = nullptr;
150 } else if (!data_ || alloc_size != data_->size) {
151 data_ = static_cast<Data*>(::realloc(data_, sizeof(size_t) + alloc_size));
152 data_->size = alloc_size;
153 }
154 }
155
Contains(const TraceArguments & args) const156 bool StringStorage::Contains(const TraceArguments& args) const {
157 for (size_t n = 0; n < args.size(); ++n) {
158 if (args.types()[n] == TRACE_VALUE_TYPE_COPY_STRING &&
159 !Contains(args.values()[n].as_string)) {
160 return false;
161 }
162 }
163 return true;
164 }
165
166 static_assert(
167 std::is_pod<TraceValue>::value,
168 "TraceValue must be plain-old-data type for performance reasons!");
169
AppendAsJSON(unsigned char type,std::string * out) const170 void TraceValue::AppendAsJSON(unsigned char type, std::string* out) const {
171 Append(type, true, out);
172 }
173
AppendAsString(unsigned char type,std::string * out) const174 void TraceValue::AppendAsString(unsigned char type, std::string* out) const {
175 Append(type, false, out);
176 }
177
Append(unsigned char type,bool as_json,std::string * out) const178 void TraceValue::Append(unsigned char type,
179 bool as_json,
180 std::string* out) const {
181 switch (type) {
182 case TRACE_VALUE_TYPE_BOOL:
183 *out += this->as_bool ? "true" : "false";
184 break;
185 case TRACE_VALUE_TYPE_UINT:
186 StringAppendF(out, "%" PRIu64, static_cast<uint64_t>(this->as_uint));
187 break;
188 case TRACE_VALUE_TYPE_INT:
189 StringAppendF(out, "%" PRId64, static_cast<int64_t>(this->as_int));
190 break;
191 case TRACE_VALUE_TYPE_DOUBLE:
192 AppendDouble(this->as_double, as_json, out);
193 break;
194 case TRACE_VALUE_TYPE_POINTER: {
195 // JSON only supports double and int numbers.
196 // So as not to lose bits from a 64-bit pointer, output as a hex string.
197 // For consistency, do the same for non-JSON strings, but without the
198 // surrounding quotes.
199 const char* format_string = as_json ? "\"0x%" PRIx64 "\"" : "0x%" PRIx64;
200 StringAppendF(
201 out, format_string,
202 static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this->as_pointer)));
203 } break;
204 case TRACE_VALUE_TYPE_STRING:
205 case TRACE_VALUE_TYPE_COPY_STRING:
206 if (as_json)
207 EscapeJSONString(this->as_string ? this->as_string : "NULL", true, out);
208 else
209 *out += this->as_string ? this->as_string : "NULL";
210 break;
211 case TRACE_VALUE_TYPE_CONVERTABLE:
212 this->as_convertable->AppendAsTraceFormat(out);
213 break;
214 case TRACE_VALUE_TYPE_PROTO:
215 DCHECK(as_json);
216 // Typed protobuf arguments aren't representable in JSON.
217 *out += "\"Unsupported (crbug.com/1225176)\"";
218 break;
219 default:
220 NOTREACHED() << "Don't know how to print this value";
221 break;
222 }
223 }
224
operator =(TraceArguments && other)225 TraceArguments& TraceArguments::operator=(TraceArguments&& other) noexcept {
226 if (this != &other) {
227 this->~TraceArguments();
228 new (this) TraceArguments(std::move(other));
229 }
230 return *this;
231 }
232
TraceArguments(int num_args,const char * const * arg_names,const unsigned char * arg_types,const unsigned long long * arg_values)233 TraceArguments::TraceArguments(int num_args,
234 const char* const* arg_names,
235 const unsigned char* arg_types,
236 const unsigned long long* arg_values) {
237 if (num_args > static_cast<int>(kMaxSize))
238 num_args = static_cast<int>(kMaxSize);
239
240 size_ = static_cast<unsigned char>(num_args);
241 for (size_t n = 0; n < size_; ++n) {
242 types_[n] = arg_types[n];
243 names_[n] = arg_names[n];
244 values_[n].as_uint = arg_values[n];
245 }
246 }
247
Reset()248 void TraceArguments::Reset() {
249 for (size_t n = 0; n < size_; ++n) {
250 if (types_[n] == TRACE_VALUE_TYPE_CONVERTABLE)
251 delete values_[n].as_convertable;
252 }
253 size_ = 0;
254 }
255
CopyStringsTo(StringStorage * storage,bool copy_all_strings,const char ** extra_string1,const char ** extra_string2)256 void TraceArguments::CopyStringsTo(StringStorage* storage,
257 bool copy_all_strings,
258 const char** extra_string1,
259 const char** extra_string2) {
260 // First, compute total allocation size.
261 size_t alloc_size = 0;
262
263 if (copy_all_strings) {
264 alloc_size +=
265 GetAllocLength(*extra_string1) + GetAllocLength(*extra_string2);
266 for (size_t n = 0; n < size_; ++n)
267 alloc_size += GetAllocLength(names_[n]);
268 }
269 for (size_t n = 0; n < size_; ++n) {
270 if (copy_all_strings && types_[n] == TRACE_VALUE_TYPE_STRING)
271 types_[n] = TRACE_VALUE_TYPE_COPY_STRING;
272 if (types_[n] == TRACE_VALUE_TYPE_COPY_STRING)
273 alloc_size += GetAllocLength(values_[n].as_string);
274 }
275
276 if (alloc_size) {
277 storage->Reset(alloc_size);
278 char* ptr = storage->data();
279 const char* end = ptr + alloc_size;
280 if (copy_all_strings) {
281 CopyTraceEventParameter(&ptr, extra_string1, end);
282 CopyTraceEventParameter(&ptr, extra_string2, end);
283 for (size_t n = 0; n < size_; ++n)
284 CopyTraceEventParameter(&ptr, &names_[n], end);
285 }
286 for (size_t n = 0; n < size_; ++n) {
287 if (types_[n] == TRACE_VALUE_TYPE_COPY_STRING)
288 CopyTraceEventParameter(&ptr, &values_[n].as_string, end);
289 }
290 #if DCHECK_IS_ON()
291 DCHECK_EQ(end, ptr) << "Overrun by " << ptr - end;
292 if (copy_all_strings) {
293 if (extra_string1 && *extra_string1)
294 DCHECK(storage->Contains(*extra_string1));
295 if (extra_string2 && *extra_string2)
296 DCHECK(storage->Contains(*extra_string2));
297 for (size_t n = 0; n < size_; ++n)
298 DCHECK(storage->Contains(names_[n]));
299 }
300 for (size_t n = 0; n < size_; ++n) {
301 if (types_[n] == TRACE_VALUE_TYPE_COPY_STRING)
302 DCHECK(storage->Contains(values_[n].as_string));
303 }
304 #endif // DCHECK_IS_ON()
305 } else {
306 storage->Reset();
307 }
308 }
309
AppendDebugString(std::string * out)310 void TraceArguments::AppendDebugString(std::string* out) {
311 *out += "TraceArguments(";
312 for (size_t n = 0; n < size_; ++n) {
313 if (n > 0)
314 *out += ", ";
315 AppendValueDebugString(*this, n, out);
316 }
317 *out += ")";
318 }
319
320 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
Add(perfetto::protos::pbzero::DebugAnnotation * annotation) const321 void ConvertableToTraceFormat::Add(
322 perfetto::protos::pbzero::DebugAnnotation* annotation) const {
323 PerfettoProtoAppender proto_appender(annotation);
324 if (AppendToProto(&proto_appender)) {
325 return;
326 }
327
328 std::string json;
329 AppendAsTraceFormat(&json);
330 annotation->set_legacy_json_value(json);
331 }
332 #endif // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
333
334 } // namespace trace_event
335 } // namespace base
336