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