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