• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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