| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/trace_processor/util/protozero_to_json.h" |
| |
| #include <optional> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/base/string_view.h" |
| #include "perfetto/protozero/field.h" |
| #include "perfetto/protozero/proto_decoder.h" |
| #include "perfetto/protozero/proto_utils.h" |
| #include "protos/perfetto/common/descriptor.pbzero.h" |
| #include "src/trace_processor/util/descriptors.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace protozero_to_json { |
| |
| namespace { |
| |
| using protos::pbzero::FieldDescriptorProto; |
| using protozero::PackedRepeatedFieldIterator; |
| using protozero::proto_utils::ProtoWireType; |
| |
| class JsonBuilder { |
| public: |
| explicit JsonBuilder(int flags) : flags_(flags) {} |
| |
| void OpenObject() { |
| if (is_array_scope()) { |
| if (!is_empty_scope()) { |
| Append(","); |
| } |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| Append("{"); |
| stack_.push_back(Scope{ScopeContext::kObject}); |
| } |
| |
| void CloseObject() { |
| bool needs_newline = !is_empty_scope(); |
| stack_.pop_back(); |
| if (needs_newline) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| |
| MarkScopeAsNonEmpty(); |
| Append("}"); |
| } |
| |
| void OpenArray() { |
| Append("["); |
| stack_.push_back(Scope{ScopeContext::kArray}); |
| } |
| |
| void CloseArray() { |
| bool needs_newline = !is_empty_scope(); |
| stack_.pop_back(); |
| if (needs_newline) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| Append("]"); |
| if (is_array_scope() && !is_empty_scope()) { |
| Append(","); |
| } |
| } |
| |
| void Key(const std::string& key) { |
| if (is_object_scope() && !is_empty_scope()) { |
| Append(","); |
| } |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| Append(EscapeString(base::StringView(key))); |
| Append(":"); |
| MaybeAppendSpace(); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| template <typename T> |
| void NumberValue(T v) { |
| AppendValue(std::to_string(v)); |
| } |
| |
| void BoolValue(bool v) { AppendValue(v ? "true" : "false"); } |
| |
| void FloatValue(float v) { NumberValue(v); } |
| |
| void DoubleValue(double v) { NumberValue(v); } |
| |
| void StringValue(base::StringView v) { AppendValue(EscapeString(v)); } |
| |
| void AddError(const std::string& s) { errors_.push_back(s); } |
| |
| std::string ToString() { return base::Join(parts_, ""); } |
| |
| bool is_empty_scope() { return !stack_.empty() && stack_.back().is_empty; } |
| |
| bool is_pretty() const { return flags_ & Flags::kPretty; } |
| |
| bool is_inline_errors() const { return flags_ & Flags::kInlineErrors; } |
| |
| const std::vector<std::string>& errors() const { return errors_; } |
| |
| private: |
| enum class ScopeContext { |
| kObject, |
| kArray, |
| }; |
| |
| struct Scope { |
| ScopeContext ctx; |
| bool is_empty = true; |
| }; |
| |
| int flags_; |
| std::vector<std::string> parts_; |
| std::vector<Scope> stack_; |
| std::vector<std::string> errors_; |
| |
| bool is_object_scope() { |
| return !stack_.empty() && stack_.back().ctx == ScopeContext::kObject; |
| } |
| |
| bool is_array_scope() { |
| return !stack_.empty() && stack_.back().ctx == ScopeContext::kArray; |
| } |
| |
| void MarkScopeAsNonEmpty() { |
| if (!stack_.empty()) { |
| stack_.back().is_empty = false; |
| } |
| } |
| |
| void MaybeAppendSpace() { |
| if (is_pretty()) { |
| Append(" "); |
| } |
| } |
| |
| void MaybeAppendIndent() { |
| if (is_pretty()) { |
| Append(std::string(stack_.size() * 2, ' ')); |
| } |
| } |
| |
| void MaybeAppendNewline() { |
| if (is_pretty()) { |
| Append("\n"); |
| } |
| } |
| |
| void AppendValue(const std::string& s) { |
| if (is_array_scope() && !is_empty_scope()) { |
| Append(","); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| Append(s); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void Append(const std::string& s) { parts_.push_back(s); } |
| |
| std::string EscapeString(base::StringView raw) { |
| std::string result; |
| result.reserve(raw.size() + 2); |
| result += "\""; |
| for (size_t i = 0; i < raw.size(); ++i) { |
| char c = *(raw.begin() + i); |
| switch (c) { |
| case '"': |
| case '\\': |
| result += '\\'; |
| result += c; |
| break; |
| case '\n': |
| result += R"(\n)"; |
| break; |
| case '\b': |
| result += R"(\b)"; |
| break; |
| case '\f': |
| result += R"(\f)"; |
| break; |
| case '\r': |
| result += R"(\r)"; |
| break; |
| case '\t': |
| result += R"(\t)"; |
| break; |
| default: |
| // ASCII characters between 0x20 (space) and 0x7e (tilde) are |
| // inserted directly. All others are escaped. |
| if (c >= 0x20 && c <= 0x7e) { |
| result += c; |
| } else { |
| unsigned char uc = static_cast<unsigned char>(c); |
| uint32_t codepoint = 0; |
| |
| // Compute the number of bytes: |
| size_t extra = 1 + (uc >= 0xc0u) + (uc >= 0xe0u) + (uc >= 0xf0u); |
| |
| // We want to consume |extra| bytes but also need to not |
| // read out of bounds: |
| size_t stop = std::min(raw.size(), i + extra); |
| |
| // Manually insert the bits from first byte: |
| codepoint |= uc & (0xff >> (extra + 1)); |
| |
| // Insert remaining bits: |
| for (size_t j = i + 1; j < stop; ++j) { |
| uc = static_cast<unsigned char>(*(raw.begin() + j)); |
| codepoint = (codepoint << 6) | (uc & 0x3f); |
| } |
| |
| // Update i to show the consumed chars: |
| i = stop - 1; |
| |
| static const char hex_chars[] = "0123456789abcdef"; |
| // JSON does not have proper utf-8 escapes. Instead you |
| // have to use utf-16 codes. For the low codepoints |
| // \uXXXX and for the high codepoints a surrogate pair: |
| // \uXXXX\uYYYY |
| if (codepoint <= 0xffff) { |
| result += R"(\u)"; |
| result += hex_chars[(codepoint >> 12) & 0xf]; |
| result += hex_chars[(codepoint >> 8) & 0xf]; |
| result += hex_chars[(codepoint >> 4) & 0xf]; |
| result += hex_chars[(codepoint >> 0) & 0xf]; |
| } else { |
| uint32_t high = ((codepoint - 0x10000) >> 10) + 0xD800; |
| uint32_t low = (codepoint & 0x4fff) + 0xDC00; |
| result += R"(\u)"; |
| result += hex_chars[(high >> 12) & 0xf]; |
| result += hex_chars[(high >> 8) & 0xf]; |
| result += hex_chars[(high >> 4) & 0xf]; |
| result += hex_chars[(high >> 0) & 0xf]; |
| result += R"(\u)"; |
| result += hex_chars[(low >> 12) & 0xf]; |
| result += hex_chars[(low >> 8) & 0xf]; |
| result += hex_chars[(low >> 4) & 0xf]; |
| result += hex_chars[(low >> 0) & 0xf]; |
| } |
| } |
| break; |
| } |
| } |
| result += "\""; |
| return result; |
| } |
| }; |
| |
| bool HasFieldOptions(const FieldDescriptor& field_desc) { |
| return !field_desc.options().empty(); |
| } |
| |
| std::string FulllyQualifiedFieldName(const ProtoDescriptor& desc, |
| const FieldDescriptor& field_desc) { |
| return desc.package_name().substr(1) + "." + field_desc.name(); |
| } |
| |
| bool IsTypeMatch(ProtoWireType wire, uint32_t type) { |
| switch (wire) { |
| case ProtoWireType::kVarInt: |
| switch (type) { |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_SINT32: |
| case FieldDescriptorProto::TYPE_UINT32: |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_SINT64: |
| case FieldDescriptorProto::TYPE_UINT64: |
| case FieldDescriptorProto::TYPE_BOOL: |
| case FieldDescriptorProto::TYPE_ENUM: |
| return true; |
| default: |
| return false; |
| } |
| case ProtoWireType::kLengthDelimited: |
| switch (type) { |
| case FieldDescriptorProto::TYPE_BYTES: |
| case FieldDescriptorProto::TYPE_MESSAGE: |
| case FieldDescriptorProto::TYPE_STRING: |
| // The normal case. |
| return true; |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_SINT32: |
| case FieldDescriptorProto::TYPE_UINT32: |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_SINT64: |
| case FieldDescriptorProto::TYPE_UINT64: |
| case FieldDescriptorProto::TYPE_BOOL: |
| case FieldDescriptorProto::TYPE_ENUM: |
| case FieldDescriptorProto::TYPE_FIXED32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FLOAT: |
| case FieldDescriptorProto::TYPE_FIXED64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| // Packed repeated fields. |
| return true; |
| default: |
| return false; |
| } |
| case ProtoWireType::kFixed32: |
| switch (type) { |
| case FieldDescriptorProto::TYPE_FIXED32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FLOAT: |
| return true; |
| default: |
| return false; |
| } |
| case ProtoWireType::kFixed64: |
| switch (type) { |
| case FieldDescriptorProto::TYPE_FIXED64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| PERFETTO_FATAL("For GCC"); |
| } |
| |
| bool IsNumericFieldType(uint32_t type) { |
| switch (type) { |
| case FieldDescriptorProto::TYPE_BYTES: |
| case FieldDescriptorProto::TYPE_MESSAGE: |
| case FieldDescriptorProto::TYPE_STRING: |
| return false; |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_SINT32: |
| case FieldDescriptorProto::TYPE_UINT32: |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_SINT64: |
| case FieldDescriptorProto::TYPE_UINT64: |
| case FieldDescriptorProto::TYPE_BOOL: |
| case FieldDescriptorProto::TYPE_ENUM: |
| case FieldDescriptorProto::TYPE_FIXED32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FLOAT: |
| case FieldDescriptorProto::TYPE_FIXED64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| default: |
| return true; |
| } |
| } |
| |
| void MessageField(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| bool fully_qualify_extensions, |
| JsonBuilder* out); |
| void EnumField(const DescriptorPool& pool, |
| const FieldDescriptor& fd, |
| int32_t value, |
| JsonBuilder* out); |
| |
| template <ProtoWireType W, typename T> |
| void PackedField(const DescriptorPool& pool, |
| const FieldDescriptor& fd, |
| const protozero::Field& field, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| bool e = false; |
| for (PackedRepeatedFieldIterator<W, T> it(field.data(), field.size(), &e); it; |
| it++) { |
| T value = *it; |
| if (fd.type() == FieldDescriptorProto::TYPE_ENUM) { |
| EnumField(pool, fd, static_cast<int32_t>(value), out); |
| } else { |
| out->NumberValue<T>(value); |
| } |
| } |
| out->CloseArray(); |
| if (e) { |
| out->AddError( |
| std::string("Decoding failure for field '" + fd.name() + "'")); |
| } |
| } |
| |
| template <ProtoWireType W> |
| void PackedBoolField(const DescriptorPool&, |
| const FieldDescriptor& fd, |
| const protozero::Field& field, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| bool e = false; |
| for (PackedRepeatedFieldIterator<W, int32_t> it(field.data(), field.size(), |
| &e); |
| it; it++) { |
| bool value = *it; |
| out->BoolValue(value); |
| } |
| out->CloseArray(); |
| if (e) { |
| out->AddError( |
| std::string("Decoding failure for field '" + fd.name() + "'")); |
| } |
| } |
| |
| void LengthField(const DescriptorPool& pool, |
| const FieldDescriptor* fd, |
| const protozero::Field& field, |
| bool fully_qualify_extensions, |
| JsonBuilder* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_BYTES: |
| out->StringValue(field.as_string()); |
| return; |
| case FieldDescriptorProto::TYPE_STRING: |
| out->StringValue(field.as_string()); |
| return; |
| case FieldDescriptorProto::TYPE_MESSAGE: |
| MessageField(pool, fd->resolved_type_name(), field.as_bytes(), |
| fully_qualify_extensions, out); |
| return; |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| PackedField<ProtoWireType::kFixed64, double>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_FLOAT: |
| PackedField<ProtoWireType::kFixed32, float>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_FIXED32: |
| PackedField<ProtoWireType::kFixed32, uint32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| PackedField<ProtoWireType::kFixed32, int32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_INT32: |
| PackedField<ProtoWireType::kVarInt, int32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_SINT32: |
| PackedField<ProtoWireType::kVarInt, int32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_UINT32: |
| PackedField<ProtoWireType::kVarInt, uint32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_FIXED64: |
| PackedField<ProtoWireType::kFixed64, uint64_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| PackedField<ProtoWireType::kFixed64, int64_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_INT64: |
| PackedField<ProtoWireType::kVarInt, int64_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_SINT64: |
| PackedField<ProtoWireType::kVarInt, int64_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_UINT64: |
| PackedField<ProtoWireType::kVarInt, uint64_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_ENUM: |
| PackedField<ProtoWireType::kVarInt, int32_t>(pool, *fd, field, out); |
| return; |
| case FieldDescriptorProto::TYPE_BOOL: |
| PackedBoolField<ProtoWireType::kVarInt>(pool, *fd, field, out); |
| return; |
| case 0: |
| default: |
| // In the absence of specific information display bytes. |
| out->StringValue(field.as_string()); |
| return; |
| } |
| } |
| |
| void EnumField(const DescriptorPool& pool, |
| const FieldDescriptor& fd, |
| int32_t value, |
| JsonBuilder* out) { |
| auto opt_enum_descriptor_idx = |
| pool.FindDescriptorIdx(fd.resolved_type_name()); |
| if (!opt_enum_descriptor_idx) { |
| out->NumberValue(value); |
| return; |
| } |
| auto opt_enum_string = |
| pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(value); |
| // If the enum value is unknown, treat it like a completely unknown field. |
| if (!opt_enum_string) { |
| out->NumberValue(value); |
| return; |
| } |
| |
| out->StringValue(base::StringView(*opt_enum_string)); |
| } |
| |
| void VarIntField(const DescriptorPool& pool, |
| const FieldDescriptor* fd, |
| const protozero::Field& field, |
| JsonBuilder* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_INT32: |
| out->NumberValue(field.as_int32()); |
| return; |
| case FieldDescriptorProto::TYPE_SINT32: |
| out->NumberValue(field.as_sint32()); |
| return; |
| case FieldDescriptorProto::TYPE_UINT32: |
| out->NumberValue(field.as_uint32()); |
| return; |
| case FieldDescriptorProto::TYPE_INT64: |
| out->NumberValue(field.as_int64()); |
| return; |
| case FieldDescriptorProto::TYPE_SINT64: |
| out->NumberValue(field.as_sint64()); |
| return; |
| case FieldDescriptorProto::TYPE_UINT64: |
| out->NumberValue(field.as_uint64()); |
| return; |
| case FieldDescriptorProto::TYPE_BOOL: |
| out->BoolValue(field.as_bool()); |
| return; |
| case FieldDescriptorProto::TYPE_ENUM: |
| EnumField(pool, *fd, field.as_int32(), out); |
| return; |
| case 0: |
| default: |
| out->NumberValue(field.as_int64()); |
| return; |
| } |
| } |
| |
| void Fixed32Field(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| JsonBuilder* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| out->NumberValue(field.as_int32()); |
| break; |
| case FieldDescriptorProto::TYPE_FIXED32: |
| out->NumberValue(field.as_uint32()); |
| break; |
| case FieldDescriptorProto::TYPE_FLOAT: |
| out->FloatValue(field.as_float()); |
| break; |
| case 0: |
| default: |
| out->NumberValue(field.as_uint32()); |
| break; |
| } |
| } |
| |
| void Fixed64Field(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| JsonBuilder* out) { |
| uint64_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| out->NumberValue(field.as_int64()); |
| break; |
| case FieldDescriptorProto::TYPE_FIXED64: |
| out->NumberValue(field.as_uint64()); |
| break; |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| out->DoubleValue(field.as_double()); |
| break; |
| case 0: |
| default: |
| out->NumberValue(field.as_uint64()); |
| break; |
| } |
| } |
| |
| void RepeatedVarInt(const DescriptorPool& pool, |
| protozero::ConstBytes protobytes, |
| const FieldDescriptor* fd, |
| uint32_t id, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| if (field.id() == id) { |
| VarIntField(pool, fd, field, out); |
| } |
| } |
| out->CloseArray(); |
| } |
| |
| void RepeatedLengthField(const DescriptorPool& pool, |
| protozero::ConstBytes protobytes, |
| const FieldDescriptor* fd, |
| uint32_t id, |
| bool fully_qualify_extensions, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| if (field.id() == id) { |
| LengthField(pool, fd, field, fully_qualify_extensions, out); |
| } |
| } |
| out->CloseArray(); |
| } |
| |
| void RepeatedFixed64(protozero::ConstBytes protobytes, |
| const FieldDescriptor* fd, |
| uint32_t id, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| if (field.id() == id) { |
| Fixed64Field(fd, field, out); |
| } |
| } |
| out->CloseArray(); |
| } |
| |
| void RepeatedFixed32(protozero::ConstBytes protobytes, |
| const FieldDescriptor* fd, |
| uint32_t id, |
| JsonBuilder* out) { |
| out->OpenArray(); |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| if (field.id() == id) { |
| Fixed32Field(fd, field, out); |
| } |
| } |
| out->CloseArray(); |
| } |
| |
| void InnerMessageField(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| bool fully_qualify_extensions, |
| JsonBuilder* out) { |
| std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type); |
| const ProtoDescriptor* opt_proto_descriptor = |
| opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr; |
| |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| std::unordered_set<uint32_t> fields_seen; |
| |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| auto* opt_field_descriptor = |
| opt_proto_descriptor ? opt_proto_descriptor->FindFieldByTag(field.id()) |
| : nullptr; |
| bool is_repeated = false; |
| if (opt_field_descriptor && |
| IsTypeMatch(field.type(), opt_field_descriptor->type())) { |
| is_repeated = opt_field_descriptor->is_repeated(); |
| // The first time we see a repeated field we consume them all: |
| if (fields_seen.count(field.id())) { |
| continue; |
| } |
| if (opt_field_descriptor->is_extension() && fully_qualify_extensions) { |
| out->Key(FulllyQualifiedFieldName(*opt_proto_descriptor, |
| *opt_field_descriptor)); |
| } else { |
| out->Key(opt_field_descriptor->name()); |
| } |
| } else { |
| out->Key(std::to_string(field.id())); |
| } |
| if (is_repeated) { |
| fields_seen.insert(field.id()); |
| |
| switch (field.type()) { |
| case ProtoWireType::kVarInt: |
| RepeatedVarInt(pool, protobytes, opt_field_descriptor, field.id(), |
| out); |
| break; |
| case ProtoWireType::kLengthDelimited: |
| if (opt_field_descriptor && |
| IsNumericFieldType(opt_field_descriptor->type())) { |
| // wire_type = length + field_type in |
| // {u,s,}int{32,64}, float, double etc means this is the |
| // packed case: |
| LengthField(pool, opt_field_descriptor, field, |
| fully_qualify_extensions, out); |
| } else { |
| RepeatedLengthField(pool, protobytes, opt_field_descriptor, |
| field.id(), fully_qualify_extensions, out); |
| } |
| break; |
| case ProtoWireType::kFixed32: |
| RepeatedFixed32(protobytes, opt_field_descriptor, field.id(), out); |
| break; |
| case ProtoWireType::kFixed64: |
| RepeatedFixed64(protobytes, opt_field_descriptor, field.id(), out); |
| break; |
| } |
| } else { |
| switch (field.type()) { |
| case ProtoWireType::kVarInt: |
| VarIntField(pool, opt_field_descriptor, field, out); |
| break; |
| case ProtoWireType::kLengthDelimited: |
| LengthField(pool, opt_field_descriptor, field, |
| fully_qualify_extensions, out); |
| break; |
| case ProtoWireType::kFixed32: |
| Fixed32Field(opt_field_descriptor, field, out); |
| break; |
| case ProtoWireType::kFixed64: |
| Fixed64Field(opt_field_descriptor, field, out); |
| break; |
| } |
| } |
| } |
| |
| if (decoder.bytes_left() != 0) { |
| out->AddError(std::to_string(decoder.bytes_left()) + " extra bytes"); |
| } |
| } |
| |
| void MessageField(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| bool fully_qualify_extensions, |
| JsonBuilder* out) { |
| out->OpenObject(); |
| InnerMessageField(pool, type, protobytes, fully_qualify_extensions, out); |
| out->CloseObject(); |
| } |
| |
| // Prints all field options for non-empty fields of a message. Example: |
| // --- Message definitions --- |
| // FooMessage { |
| // repeated int64 foo = 1 [op1 = val1, op2 = val2]; |
| // optional BarMessage bar = 2 [op3 = val3]; |
| // } |
| // |
| // BarMessage { |
| // optional int64 baz = 1 [op4 = val4]; |
| // } |
| // --- MessageInstance --- |
| // foo_msg = { // (As JSON) |
| // foo: [23, 24, 25], |
| // bar: { |
| // baz: 42 |
| // } |
| // } |
| // --- Output of MessageFieldOptionsToJson(foo_msg) --- |
| // foo: { |
| // __field_options: { |
| // op1: val1, |
| // op2: val2, |
| // }, |
| // __repeated: true |
| // } |
| // bar: { |
| // __field_options: { |
| // op3 = val3, |
| // }, |
| // baz: { |
| // __field_options: { |
| // op4 = val4 |
| // }, |
| // } |
| // } |
| void MessageFieldOptionsToJson( |
| const DescriptorPool& pool, |
| const std::string& type, |
| const std::string& field_prefix, |
| const std::unordered_set<std::string>& allowed_fields, |
| JsonBuilder* out) { |
| std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type); |
| if (!opt_proto_desc_idx) { |
| return; |
| } |
| const ProtoDescriptor& desc = pool.descriptors()[*opt_proto_desc_idx]; |
| for (const auto& id_and_field : desc.fields()) { |
| const FieldDescriptor& field_desc = id_and_field.second; |
| std::string full_field_name = field_prefix + field_desc.name(); |
| if (allowed_fields.find(full_field_name) == allowed_fields.end()) { |
| continue; |
| } |
| if (field_desc.is_extension()) { |
| out->Key(FulllyQualifiedFieldName(desc, field_desc)); |
| } else { |
| out->Key(field_desc.name()); |
| } |
| out->OpenObject(); |
| if (HasFieldOptions(field_desc)) { |
| out->Key("__field_options"); |
| MessageField(pool, ".google.protobuf.FieldOptions", |
| protozero::ConstBytes{field_desc.options().data(), |
| field_desc.options().size()}, |
| false, out); |
| } |
| if (field_desc.type() == FieldDescriptorProto::Type::TYPE_MESSAGE) { |
| MessageFieldOptionsToJson(pool, field_desc.resolved_type_name(), |
| full_field_name + ".", allowed_fields, out); |
| } |
| if (field_desc.is_repeated()) { |
| out->Key("__repeated"); |
| out->BoolValue(true); |
| } |
| out->CloseObject(); |
| } |
| } |
| |
| bool PopulateAllowedFieldOptionsSet( |
| const DescriptorPool& pool, |
| const std::string& type, |
| const std::string& field_prefix, |
| protozero::ConstBytes protobytes, |
| std::unordered_set<std::string>& allowed_fields) { |
| std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type); |
| if (!opt_proto_desc_idx) { |
| return false; |
| } |
| const ProtoDescriptor& desc = pool.descriptors()[*opt_proto_desc_idx]; |
| protozero::ProtoDecoder decoder(protobytes); |
| bool allowed = false; |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| auto* opt_field_descriptor = desc.FindFieldByTag(field.id()); |
| if (!opt_field_descriptor) { |
| continue; |
| } |
| std::string full_field_name = field_prefix + opt_field_descriptor->name(); |
| bool nested = false; |
| if (opt_field_descriptor->type() == |
| protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) { |
| nested = PopulateAllowedFieldOptionsSet( |
| pool, opt_field_descriptor->resolved_type_name(), |
| full_field_name + ".", field.as_bytes(), allowed_fields); |
| } |
| if (nested || HasFieldOptions(*opt_field_descriptor)) { |
| allowed_fields.emplace(full_field_name); |
| allowed = true; |
| } |
| } |
| return allowed; |
| } |
| |
| } // namespace |
| |
| std::string ProtozeroToJson(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| int flags) { |
| JsonBuilder builder(flags); |
| builder.OpenObject(); |
| InnerMessageField(pool, type, protobytes, true, &builder); |
| if (builder.is_inline_errors() && !builder.errors().empty()) { |
| builder.Key("__error"); |
| builder.StringValue(base::StringView(base::Join(builder.errors(), "\n"))); |
| } |
| if (flags & kInlineAnnotations) { |
| std::unordered_set<std::string> allowed_fields; |
| PopulateAllowedFieldOptionsSet(pool, type, "", protobytes, allowed_fields); |
| if (!allowed_fields.empty()) { |
| builder.Key("__annotations"); |
| builder.OpenObject(); |
| MessageFieldOptionsToJson(pool, type, "", allowed_fields, &builder); |
| builder.CloseObject(); |
| } |
| } |
| builder.CloseObject(); |
| return builder.ToString(); |
| } |
| |
| std::string ProtozeroToJson(const DescriptorPool& pool, |
| const std::string& type, |
| const std::vector<uint8_t>& protobytes, |
| int flags) { |
| return ProtozeroToJson( |
| pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()}, |
| flags); |
| } |
| |
| } // namespace protozero_to_json |
| } // namespace trace_processor |
| } // namespace perfetto |