• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/trace_processor/util/protozero_to_text.h"
18 
19 #include <cinttypes>
20 #include <cstdint>
21 #include <optional>
22 #include <string>
23 #include <vector>
24 
25 #include "perfetto/base/logging.h"
26 #include "perfetto/ext/base/string_utils.h"
27 #include "perfetto/ext/base/string_view.h"
28 #include "perfetto/protozero/field.h"
29 #include "perfetto/protozero/proto_decoder.h"
30 #include "perfetto/protozero/proto_utils.h"
31 #include "protos/perfetto/common/descriptor.pbzero.h"
32 #include "src/trace_processor/util/descriptors.h"
33 
34 namespace perfetto::trace_processor::protozero_to_text {
35 
36 namespace {
37 
38 using protozero::proto_utils::ProtoWireType;
39 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
40 
41 // This function matches the implementation of TextFormatEscaper.escapeBytes
42 // from the Java protobuf library.
QuoteAndEscapeTextProtoString(base::StringView raw)43 std::string QuoteAndEscapeTextProtoString(base::StringView raw) {
44   std::string ret;
45   for (char c : raw) {
46     switch (c) {
47       case '\a':
48         ret += "\\a";
49         break;
50       case '\b':
51         ret += "\\b";
52         break;
53       case '\f':
54         ret += "\\f";
55         break;
56       case '\n':
57         ret += "\\n";
58         break;
59       case '\r':
60         ret += "\\r";
61         break;
62       case '\t':
63         ret += "\\t";
64         break;
65       case '\v':
66         ret += "\\v";
67         break;
68       case '\\':
69         ret += "\\\\";
70         break;
71       case '\'':
72         ret += "\\\'";
73         break;
74       case '"':
75         ret += "\\\"";
76         break;
77       default:
78         // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are
79         // printable; other byte values are escaped with 3-character octal
80         // codes.
81         if (c >= 0x20 && c <= 0x7e) {
82           ret += c;
83         } else {
84           ret += '\\';
85 
86           // Cast to unsigned char to make the right shift unsigned as well.
87           auto uc = static_cast<unsigned char>(c);
88           ret += ('0' + ((uc >> 6) & 3));
89           ret += ('0' + ((uc >> 3) & 7));
90           ret += ('0' + (uc & 7));
91         }
92         break;
93     }
94   }
95   return '"' + ret + '"';
96 }
97 
98 // Append |to_add| which is something string like to |out|.
99 template <typename T>
StrAppend(std::string * out,const T & to_add)100 void StrAppend(std::string* out, const T& to_add) {
101   out->append(to_add);
102 }
103 
104 template <typename T, typename... strings>
StrAppend(std::string * out,const T & first,strings...values)105 void StrAppend(std::string* out, const T& first, strings... values) {
106   StrAppend(out, first);
107   StrAppend(out, values...);
108 }
109 
IncreaseIndents(std::string * out)110 void IncreaseIndents(std::string* out) {
111   StrAppend(out, "  ");
112 }
113 
DecreaseIndents(std::string * out)114 void DecreaseIndents(std::string* out) {
115   PERFETTO_DCHECK(out->size() >= 2);
116   out->erase(out->size() - 2);
117 }
118 
PrintUnknownVarIntField(uint32_t id,int64_t value,std::string * out)119 void PrintUnknownVarIntField(uint32_t id, int64_t value, std::string* out) {
120   StrAppend(out, std::to_string(id), ": ", std::to_string(value));
121 }
122 
PrintEnumField(const FieldDescriptor & fd,const DescriptorPool & pool,uint32_t id,int32_t enum_value,std::string * out)123 void PrintEnumField(const FieldDescriptor& fd,
124                     const DescriptorPool& pool,
125                     uint32_t id,
126                     int32_t enum_value,
127                     std::string* out) {
128   auto opt_enum_descriptor_idx =
129       pool.FindDescriptorIdx(fd.resolved_type_name());
130   if (!opt_enum_descriptor_idx) {
131     PrintUnknownVarIntField(id, enum_value, out);
132     return;
133   }
134   auto opt_enum_string =
135       pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
136   // If the enum value is unknown, treat it like a completely unknown field.
137   if (!opt_enum_string) {
138     PrintUnknownVarIntField(id, enum_value, out);
139     return;
140   }
141   StrAppend(out, fd.name(), ": ", *opt_enum_string);
142 }
143 
FormattedFieldDescriptorName(const FieldDescriptor & field_descriptor)144 std::string FormattedFieldDescriptorName(
145     const FieldDescriptor& field_descriptor) {
146   if (field_descriptor.is_extension()) {
147     // Libprotobuf formatter always formats extension field names as fully
148     // qualified names.
149     // TODO(b/197625974): Assuming for now all our extensions will belong to the
150     // perfetto.protos package. Update this if we ever want to support extendees
151     // in different package.
152     return "[perfetto.protos." + field_descriptor.name() + "]";
153   }
154   return field_descriptor.name();
155 }
156 
PrintVarIntField(const FieldDescriptor * fd,const protozero::Field & field,const DescriptorPool & pool,std::string * out)157 void PrintVarIntField(const FieldDescriptor* fd,
158                       const protozero::Field& field,
159                       const DescriptorPool& pool,
160                       std::string* out) {
161   uint32_t type = fd ? fd->type() : 0;
162   switch (type) {
163     case FieldDescriptorProto::TYPE_INT32:
164       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
165       return;
166     case FieldDescriptorProto::TYPE_SINT32:
167       StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint32()));
168       return;
169     case FieldDescriptorProto::TYPE_UINT32:
170       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
171       return;
172     case FieldDescriptorProto::TYPE_INT64:
173       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
174       return;
175     case FieldDescriptorProto::TYPE_SINT64:
176       StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint64()));
177       return;
178     case FieldDescriptorProto::TYPE_UINT64:
179       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
180       return;
181     case FieldDescriptorProto::TYPE_BOOL:
182       StrAppend(out, fd->name(), ": ", field.as_bool() ? "true" : "false");
183       return;
184     case FieldDescriptorProto::TYPE_ENUM:
185       PrintEnumField(*fd, pool, field.id(), field.as_int32(), out);
186       return;
187     case 0:
188     default:
189       PrintUnknownVarIntField(field.id(), field.as_int64(), out);
190       return;
191   }
192 }
193 
PrintFixed32Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)194 void PrintFixed32Field(const FieldDescriptor* fd,
195                        const protozero::Field& field,
196                        std::string* out) {
197   uint32_t type = fd ? fd->type() : 0;
198   switch (type) {
199     case FieldDescriptorProto::TYPE_SFIXED32:
200       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
201       break;
202     case FieldDescriptorProto::TYPE_FIXED32:
203       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
204       break;
205     case FieldDescriptorProto::TYPE_FLOAT:
206       StrAppend(out, fd->name(), ": ", std::to_string(field.as_float()));
207       break;
208     case 0:
209     default:
210       base::StackString<12> padded_hex("0x%08" PRIx32, field.as_uint32());
211       StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
212       break;
213   }
214 }
215 
PrintFixed64Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)216 void PrintFixed64Field(const FieldDescriptor* fd,
217                        const protozero::Field& field,
218                        std::string* out) {
219   uint32_t type = fd ? fd->type() : 0;
220   switch (type) {
221     case FieldDescriptorProto::TYPE_SFIXED64:
222       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
223       break;
224     case FieldDescriptorProto::TYPE_FIXED64:
225       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
226       break;
227     case FieldDescriptorProto::TYPE_DOUBLE:
228       StrAppend(out, fd->name(), ": ", std::to_string(field.as_double()));
229       break;
230     case 0:
231     default:
232       base::StackString<20> padded_hex("0x%016" PRIx64, field.as_uint64());
233       StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
234       break;
235   }
236 }
237 
238 void ProtozeroToTextInternal(const std::string& type,
239                              protozero::ConstBytes protobytes,
240                              NewLinesMode new_lines_mode,
241                              const DescriptorPool& pool,
242                              std::string* indents,
243                              std::string* output);
244 
245 template <protozero::proto_utils::ProtoWireType wire_type, typename T>
PrintPackedField(const FieldDescriptor & fd,const protozero::Field & field,NewLinesMode new_lines_mode,const std::string & indents,const DescriptorPool & pool,std::string * out)246 void PrintPackedField(const FieldDescriptor& fd,
247                       const protozero::Field& field,
248                       NewLinesMode new_lines_mode,
249                       const std::string& indents,
250                       const DescriptorPool& pool,
251                       std::string* out) {
252   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
253   bool err = false;
254   bool first_output = true;
255   for (protozero::PackedRepeatedFieldIterator<wire_type, T> it(
256            field.data(), field.size(), &err);
257        it; it++) {
258     T value = *it;
259     if (!first_output) {
260       if (include_new_lines) {
261         StrAppend(out, "\n", indents);
262       } else {
263         StrAppend(out, " ");
264       }
265     }
266     std::string serialized_value;
267     if (fd.type() == FieldDescriptorProto::TYPE_ENUM) {
268       PrintEnumField(fd, pool, field.id(), static_cast<int32_t>(value), out);
269     } else {
270       StrAppend(out, fd.name(), ": ", std::to_string(value));
271     }
272     first_output = false;
273   }
274 
275   if (err) {
276     if (!first_output) {
277       if (include_new_lines) {
278         StrAppend(out, "\n", indents);
279       } else {
280         StrAppend(out, " ");
281       }
282     }
283     StrAppend(out, "# Packed decoding failure for field ", fd.name(), "\n");
284   }
285 }
286 
PrintLengthDelimitedField(const FieldDescriptor * fd,const protozero::Field & field,NewLinesMode new_lines_mode,std::string * indents,const DescriptorPool & pool,std::string * out)287 void PrintLengthDelimitedField(const FieldDescriptor* fd,
288                                const protozero::Field& field,
289                                NewLinesMode new_lines_mode,
290                                std::string* indents,
291                                const DescriptorPool& pool,
292                                std::string* out) {
293   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
294   uint32_t type = fd ? fd->type() : 0;
295   switch (type) {
296     case FieldDescriptorProto::TYPE_BYTES:
297     case FieldDescriptorProto::TYPE_STRING: {
298       std::string value = QuoteAndEscapeTextProtoString(field.as_string());
299       StrAppend(out, fd->name(), ": ", value);
300       return;
301     }
302     case FieldDescriptorProto::TYPE_MESSAGE:
303       StrAppend(out, FormattedFieldDescriptorName(*fd), " {");
304       if (include_new_lines) {
305         IncreaseIndents(indents);
306       }
307       ProtozeroToTextInternal(fd->resolved_type_name(), field.as_bytes(),
308                               new_lines_mode, pool, indents, out);
309       if (include_new_lines) {
310         DecreaseIndents(indents);
311         StrAppend(out, "\n", *indents, "}");
312       } else {
313         StrAppend(out, " }");
314       }
315       return;
316     case FieldDescriptorProto::TYPE_DOUBLE:
317       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, double>(
318           *fd, field, new_lines_mode, *indents, pool, out);
319       return;
320     case FieldDescriptorProto::TYPE_FLOAT:
321       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, float>(
322           *fd, field, new_lines_mode, *indents, pool, out);
323       return;
324     case FieldDescriptorProto::TYPE_INT64:
325       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int64_t>(
326           *fd, field, new_lines_mode, *indents, pool, out);
327       return;
328     case FieldDescriptorProto::TYPE_UINT64:
329       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
330                        uint64_t>(*fd, field, new_lines_mode, *indents, pool,
331                                  out);
332       return;
333     case FieldDescriptorProto::TYPE_INT32:
334       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
335           *fd, field, new_lines_mode, *indents, pool, out);
336       return;
337     case FieldDescriptorProto::TYPE_FIXED64:
338       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
339                        uint64_t>(*fd, field, new_lines_mode, *indents, pool,
340                                  out);
341       return;
342     case FieldDescriptorProto::TYPE_FIXED32:
343       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
344                        uint32_t>(*fd, field, new_lines_mode, *indents, pool,
345                                  out);
346       return;
347     case FieldDescriptorProto::TYPE_UINT32:
348       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
349                        uint32_t>(*fd, field, new_lines_mode, *indents, pool,
350                                  out);
351       return;
352     case FieldDescriptorProto::TYPE_SFIXED32:
353       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
354                        int32_t>(*fd, field, new_lines_mode, *indents, pool,
355                                 out);
356       return;
357     case FieldDescriptorProto::TYPE_SFIXED64:
358       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
359                        int64_t>(*fd, field, new_lines_mode, *indents, pool,
360                                 out);
361       return;
362     case FieldDescriptorProto::TYPE_ENUM:
363       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
364           *fd, field, new_lines_mode, *indents, pool, out);
365       return;
366     // Our protoc plugin cannot generate code for packed repeated fields with
367     // these types. Output a comment and then fall back to the raw field_id:
368     // string representation.
369     case FieldDescriptorProto::TYPE_BOOL:
370     case FieldDescriptorProto::TYPE_SINT32:
371     case FieldDescriptorProto::TYPE_SINT64:
372       StrAppend(out, "# Packed type ", std::to_string(type),
373                 " not supported. Printing raw string.", "\n", *indents);
374       break;
375     case 0:
376     default:
377       break;
378   }
379   std::string value = QuoteAndEscapeTextProtoString(field.as_string());
380   StrAppend(out, std::to_string(field.id()), ": ", value);
381 }
382 
383 // Recursive case function, Will parse |protobytes| assuming it is a proto of
384 // |type| and will use |pool| to look up the |type|. All output will be placed
385 // in |output|, using |new_lines_mode| to separate fields. When called for
386 // |indents| will be increased by 2 spaces to improve readability.
ProtozeroToTextInternal(const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,const DescriptorPool & pool,std::string * indents,std::string * output)387 void ProtozeroToTextInternal(const std::string& type,
388                              protozero::ConstBytes protobytes,
389                              NewLinesMode new_lines_mode,
390                              const DescriptorPool& pool,
391                              std::string* indents,
392                              std::string* output) {
393   std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
394   const ProtoDescriptor* opt_proto_descriptor =
395       opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr;
396   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
397 
398   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
399   for (auto field = decoder.ReadField(); field.valid();
400        field = decoder.ReadField()) {
401     if (!output->empty()) {
402       if (include_new_lines) {
403         StrAppend(output, "\n", *indents);
404       } else {
405         StrAppend(output, " ", *indents);
406       }
407     } else {
408       StrAppend(output, *indents);
409     }
410     const auto* opt_field_descriptor =
411         opt_proto_descriptor ? opt_proto_descriptor->FindFieldByTag(field.id())
412                              : nullptr;
413     switch (field.type()) {
414       case ProtoWireType::kVarInt:
415         PrintVarIntField(opt_field_descriptor, field, pool, output);
416         break;
417       case ProtoWireType::kLengthDelimited:
418         PrintLengthDelimitedField(opt_field_descriptor, field, new_lines_mode,
419                                   indents, pool, output);
420         break;
421       case ProtoWireType::kFixed32:
422         PrintFixed32Field(opt_field_descriptor, field, output);
423         break;
424       case ProtoWireType::kFixed64:
425         PrintFixed64Field(opt_field_descriptor, field, output);
426         break;
427     }
428   }
429   if (decoder.bytes_left() != 0) {
430     if (!output->empty()) {
431       if (include_new_lines) {
432         StrAppend(output, "\n", *indents);
433       } else {
434         StrAppend(output, " ", *indents);
435       }
436     }
437     StrAppend(
438         output, "# Extra bytes: ",
439         QuoteAndEscapeTextProtoString(base::StringView(
440             reinterpret_cast<const char*>(decoder.end() - decoder.bytes_left()),
441             decoder.bytes_left())),
442         "\n");
443   }
444 }
445 
446 }  // namespace
447 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,uint32_t initial_indent_depth)448 std::string ProtozeroToText(const DescriptorPool& pool,
449                             const std::string& type,
450                             protozero::ConstBytes protobytes,
451                             NewLinesMode new_lines_mode,
452                             uint32_t initial_indent_depth) {
453   std::string indent = std::string(2lu * initial_indent_depth, ' ');
454   std::string final_result;
455   ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
456                           &final_result);
457   return final_result;
458 }
459 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,const std::vector<uint8_t> & protobytes,NewLinesMode new_lines_mode)460 std::string ProtozeroToText(const DescriptorPool& pool,
461                             const std::string& type,
462                             const std::vector<uint8_t>& protobytes,
463                             NewLinesMode new_lines_mode) {
464   return ProtozeroToText(
465       pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
466       new_lines_mode);
467 }
468 
469 }  // namespace perfetto::trace_processor::protozero_to_text
470