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