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