| /* |
| * Copyright (C) 2019 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/descriptors.h" |
| |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/base/string_view.h" |
| #include "perfetto/protozero/field.h" |
| #include "perfetto/protozero/scattered_heap_buffer.h" |
| #include "protos/perfetto/common/descriptor.pbzero.h" |
| #include "protos/perfetto/trace_processor/trace_processor.pbzero.h" |
| #include "src/trace_processor/util/status_macros.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| FieldDescriptor CreateFieldFromDecoder( |
| const protos::pbzero::FieldDescriptorProto::Decoder& f_decoder, |
| bool is_extension) { |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| std::string type_name = |
| f_decoder.has_type_name() |
| ? base::StringView(f_decoder.type_name()).ToStdString() |
| : ""; |
| // TODO(lalitm): add support for enums here. |
| uint32_t type = |
| f_decoder.has_type() |
| ? static_cast<uint32_t>(f_decoder.type()) |
| : static_cast<uint32_t>(FieldDescriptorProto::TYPE_MESSAGE); |
| protos::pbzero::FieldOptions::Decoder opt(f_decoder.options()); |
| return FieldDescriptor( |
| base::StringView(f_decoder.name()).ToStdString(), |
| static_cast<uint32_t>(f_decoder.number()), type, std::move(type_name), |
| f_decoder.label() == FieldDescriptorProto::LABEL_REPEATED, opt.packed(), |
| is_extension); |
| } |
| |
| std::optional<uint32_t> DescriptorPool::ResolveShortType( |
| const std::string& parent_path, |
| const std::string& short_type) { |
| PERFETTO_DCHECK(!short_type.empty()); |
| |
| std::string search_path = short_type[0] == '.' |
| ? parent_path + short_type |
| : parent_path + '.' + short_type; |
| auto opt_idx = FindDescriptorIdx(search_path); |
| if (opt_idx) |
| return opt_idx; |
| |
| if (parent_path.empty()) |
| return std::nullopt; |
| |
| auto parent_dot_idx = parent_path.rfind('.'); |
| auto parent_substr = parent_dot_idx == std::string::npos |
| ? "" |
| : parent_path.substr(0, parent_dot_idx); |
| return ResolveShortType(parent_substr, short_type); |
| } |
| |
| util::Status DescriptorPool::AddExtensionField( |
| const std::string& package_name, |
| protozero::ConstBytes field_desc_proto) { |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| FieldDescriptorProto::Decoder f_decoder(field_desc_proto); |
| auto field = CreateFieldFromDecoder(f_decoder, true); |
| |
| auto extendee_name = base::StringView(f_decoder.extendee()).ToStdString(); |
| if (extendee_name.empty()) { |
| return util::ErrStatus("Extendee name is empty"); |
| } |
| |
| if (extendee_name[0] != '.') { |
| // Only prepend if the extendee is not fully qualified |
| extendee_name = package_name + "." + extendee_name; |
| } |
| auto extendee = FindDescriptorIdx(extendee_name); |
| if (!extendee.has_value()) { |
| return util::ErrStatus("Extendee does not exist %s", extendee_name.c_str()); |
| } |
| descriptors_[extendee.value()].AddField(field); |
| return util::OkStatus(); |
| } |
| |
| util::Status DescriptorPool::AddNestedProtoDescriptors( |
| const std::string& file_name, |
| const std::string& package_name, |
| std::optional<uint32_t> parent_idx, |
| protozero::ConstBytes descriptor_proto, |
| std::vector<ExtensionInfo>* extensions, |
| bool merge_existing_messages) { |
| protos::pbzero::DescriptorProto::Decoder decoder(descriptor_proto); |
| |
| auto parent_name = |
| parent_idx ? descriptors_[*parent_idx].full_name() : package_name; |
| auto full_name = |
| parent_name + "." + base::StringView(decoder.name()).ToStdString(); |
| |
| auto idx = FindDescriptorIdx(full_name); |
| if (idx.has_value() && !merge_existing_messages) { |
| const auto& existing_descriptor = descriptors_[*idx]; |
| return util::ErrStatus("%s: %s was already defined in file %s", |
| file_name.c_str(), full_name.c_str(), |
| existing_descriptor.file_name().c_str()); |
| } |
| if (!idx.has_value()) { |
| ProtoDescriptor proto_descriptor(file_name, package_name, full_name, |
| ProtoDescriptor::Type::kMessage, |
| parent_idx); |
| idx = AddProtoDescriptor(std::move(proto_descriptor)); |
| } |
| ProtoDescriptor& proto_descriptor = descriptors_[*idx]; |
| if (proto_descriptor.type() != ProtoDescriptor::Type::kMessage) { |
| return util::ErrStatus("%s was enum, redefined as message", |
| full_name.c_str()); |
| } |
| |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| for (auto it = decoder.field(); it; ++it) { |
| FieldDescriptorProto::Decoder f_decoder(*it); |
| auto field = CreateFieldFromDecoder(f_decoder, /*is_extension=*/false); |
| auto existing_field = proto_descriptor.FindFieldByTag(field.number()); |
| if (!existing_field) { |
| proto_descriptor.AddField(std::move(field)); |
| } else { |
| if (field.type() != existing_field->type()) { |
| return util::ErrStatus("Field %s is re-introduced with different type", |
| field.name().c_str()); |
| } |
| if ((field.type() == FieldDescriptorProto::TYPE_MESSAGE || |
| field.type() == FieldDescriptorProto::TYPE_ENUM) && |
| field.raw_type_name() != existing_field->raw_type_name()) { |
| return util::ErrStatus( |
| "Field %s is re-introduced with different type %s (was %s)", |
| field.name().c_str(), field.raw_type_name().c_str(), |
| existing_field->raw_type_name().c_str()); |
| } |
| } |
| } |
| |
| for (auto it = decoder.enum_type(); it; ++it) { |
| RETURN_IF_ERROR(AddEnumProtoDescriptors(file_name, package_name, idx, *it, |
| merge_existing_messages)); |
| } |
| for (auto it = decoder.nested_type(); it; ++it) { |
| RETURN_IF_ERROR(AddNestedProtoDescriptors(file_name, package_name, idx, *it, |
| extensions, |
| merge_existing_messages)); |
| } |
| for (auto ext_it = decoder.extension(); ext_it; ++ext_it) { |
| extensions->emplace_back(package_name, *ext_it); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status DescriptorPool::AddEnumProtoDescriptors( |
| const std::string& file_name, |
| const std::string& package_name, |
| std::optional<uint32_t> parent_idx, |
| protozero::ConstBytes descriptor_proto, |
| bool merge_existing_messages) { |
| protos::pbzero::EnumDescriptorProto::Decoder decoder(descriptor_proto); |
| |
| auto parent_name = |
| parent_idx ? descriptors_[*parent_idx].full_name() : package_name; |
| auto full_name = |
| parent_name + "." + base::StringView(decoder.name()).ToStdString(); |
| |
| auto prev_idx = FindDescriptorIdx(full_name); |
| if (prev_idx.has_value() && !merge_existing_messages) { |
| const auto& existing_descriptor = descriptors_[*prev_idx]; |
| return util::ErrStatus("%s: %s was already defined in file %s", |
| file_name.c_str(), full_name.c_str(), |
| existing_descriptor.file_name().c_str()); |
| } |
| if (!prev_idx.has_value()) { |
| ProtoDescriptor proto_descriptor(file_name, package_name, full_name, |
| ProtoDescriptor::Type::kEnum, |
| std::nullopt); |
| prev_idx = AddProtoDescriptor(std::move(proto_descriptor)); |
| } |
| ProtoDescriptor& proto_descriptor = descriptors_[*prev_idx]; |
| if (proto_descriptor.type() != ProtoDescriptor::Type::kEnum) { |
| return util::ErrStatus("%s was message, redefined as enum", |
| full_name.c_str()); |
| } |
| |
| for (auto it = decoder.value(); it; ++it) { |
| protos::pbzero::EnumValueDescriptorProto::Decoder enum_value(it->data(), |
| it->size()); |
| proto_descriptor.AddEnumValue(enum_value.number(), |
| enum_value.name().ToStdString()); |
| } |
| |
| return util::OkStatus(); |
| } |
| |
| util::Status DescriptorPool::AddFromFileDescriptorSet( |
| const uint8_t* file_descriptor_set_proto, |
| size_t size, |
| const std::vector<std::string>& skip_prefixes, |
| bool merge_existing_messages) { |
| protos::pbzero::FileDescriptorSet::Decoder proto(file_descriptor_set_proto, |
| size); |
| std::vector<ExtensionInfo> extensions; |
| for (auto it = proto.file(); it; ++it) { |
| protos::pbzero::FileDescriptorProto::Decoder file(*it); |
| const std::string file_name = file.name().ToStdString(); |
| if (base::StartsWithAny(file_name, skip_prefixes)) |
| continue; |
| if (processed_files_.find(file_name) != processed_files_.end()) { |
| // This file has been loaded once already. Skip. |
| continue; |
| } |
| processed_files_.insert(file_name); |
| std::string package = "." + base::StringView(file.package()).ToStdString(); |
| for (auto message_it = file.message_type(); message_it; ++message_it) { |
| RETURN_IF_ERROR(AddNestedProtoDescriptors( |
| file_name, package, std::nullopt, *message_it, &extensions, |
| merge_existing_messages)); |
| } |
| for (auto enum_it = file.enum_type(); enum_it; ++enum_it) { |
| RETURN_IF_ERROR(AddEnumProtoDescriptors( |
| file_name, package, std::nullopt, *enum_it, merge_existing_messages)); |
| } |
| for (auto ext_it = file.extension(); ext_it; ++ext_it) { |
| extensions.emplace_back(package, *ext_it); |
| } |
| } |
| |
| // Second pass: Add extension fields to the real protos. |
| for (const auto& extension : extensions) { |
| auto status = AddExtensionField(extension.first, extension.second); |
| if (!status.ok()) |
| return status; |
| } |
| |
| // Third pass: resolve the types of all the fields to the correct indiices. |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| for (auto& descriptor : descriptors_) { |
| for (auto& entry : *descriptor.mutable_fields()) { |
| auto& field = entry.second; |
| if (!field.resolved_type_name().empty()) |
| continue; |
| |
| if (field.type() == FieldDescriptorProto::TYPE_MESSAGE || |
| field.type() == FieldDescriptorProto::TYPE_ENUM) { |
| auto opt_desc = |
| ResolveShortType(descriptor.full_name(), field.raw_type_name()); |
| if (!opt_desc.has_value()) { |
| return util::ErrStatus( |
| "Unable to find short type %s in field inside message %s", |
| field.raw_type_name().c_str(), descriptor.full_name().c_str()); |
| } |
| field.set_resolved_type_name( |
| descriptors_[opt_desc.value()].full_name()); |
| } |
| } |
| } |
| return util::OkStatus(); |
| } |
| |
| std::optional<uint32_t> DescriptorPool::FindDescriptorIdx( |
| const std::string& full_name) const { |
| auto it = full_name_to_descriptor_index_.find(full_name); |
| if (it == full_name_to_descriptor_index_.end()) { |
| return std::nullopt; |
| } |
| return it->second; |
| } |
| |
| std::vector<uint8_t> DescriptorPool::SerializeAsDescriptorSet() { |
| protozero::HeapBuffered<protos::pbzero::DescriptorSet> descs; |
| for (auto& desc : descriptors()) { |
| protos::pbzero::DescriptorProto* proto_descriptor = |
| descs->add_descriptors(); |
| proto_descriptor->set_name(desc.full_name()); |
| for (auto& entry : desc.fields()) { |
| auto& field = entry.second; |
| protos::pbzero::FieldDescriptorProto* field_descriptor = |
| proto_descriptor->add_field(); |
| field_descriptor->set_name(field.name()); |
| field_descriptor->set_number(static_cast<int32_t>(field.number())); |
| // We do not support required fields. They will show up as optional |
| // after serialization. |
| field_descriptor->set_label( |
| field.is_repeated() |
| ? protos::pbzero::FieldDescriptorProto::LABEL_REPEATED |
| : protos::pbzero::FieldDescriptorProto::LABEL_OPTIONAL); |
| field_descriptor->set_type_name(field.resolved_type_name()); |
| field_descriptor->set_type( |
| static_cast<protos::pbzero::FieldDescriptorProto_Type>(field.type())); |
| } |
| } |
| return descs.SerializeAsArray(); |
| } |
| |
| uint32_t DescriptorPool::AddProtoDescriptor(ProtoDescriptor descriptor) { |
| uint32_t idx = static_cast<uint32_t>(descriptors_.size()); |
| full_name_to_descriptor_index_[descriptor.full_name()] = idx; |
| descriptors_.emplace_back(std::move(descriptor)); |
| return idx; |
| } |
| |
| ProtoDescriptor::ProtoDescriptor(std::string file_name, |
| std::string package_name, |
| std::string full_name, |
| Type type, |
| std::optional<uint32_t> parent_id) |
| : file_name_(std::move(file_name)), |
| package_name_(std::move(package_name)), |
| full_name_(std::move(full_name)), |
| type_(type), |
| parent_id_(parent_id) {} |
| |
| FieldDescriptor::FieldDescriptor(std::string name, |
| uint32_t number, |
| uint32_t type, |
| std::string raw_type_name, |
| bool is_repeated, |
| bool is_packed, |
| bool is_extension) |
| : name_(std::move(name)), |
| number_(number), |
| type_(type), |
| raw_type_name_(std::move(raw_type_name)), |
| is_repeated_(is_repeated), |
| is_packed_(is_packed), |
| is_extension_(is_extension) {} |
| |
| } // namespace trace_processor |
| } // namespace perfetto |