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