blob: 8f595e0d176a1769515fb0ddc5267c9175e31fb1 [file] [log] [blame]
/*
* Copyright (C) 2021 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/tools/proto_merger/proto_file.h"
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/text_format.h>
#include "perfetto/ext/base/string_utils.h"
namespace perfetto {
namespace proto_merger {
namespace {
const char* const
kTypeToName[google::protobuf::FieldDescriptor::Type::MAX_TYPE + 1] = {
"ERROR", // 0 is reserved for errors
"double", // TYPE_DOUBLE
"float", // TYPE_FLOAT
"int64", // TYPE_INT64
"uint64", // TYPE_UINT64
"int32", // TYPE_INT32
"fixed64", // TYPE_FIXED64
"fixed32", // TYPE_FIXED32
"bool", // TYPE_BOOL
"string", // TYPE_STRING
"group", // TYPE_GROUP
"message", // TYPE_MESSAGE
"bytes", // TYPE_BYTES
"uint32", // TYPE_UINT32
"enum", // TYPE_ENUM
"sfixed32", // TYPE_SFIXED32
"sfixed64", // TYPE_SFIXED64
"sint32", // TYPE_SINT32
"sint64", // TYPE_SINT64
};
const char* const
kLabelToName[google::protobuf::FieldDescriptor::MAX_LABEL + 1] = {
"ERROR", // 0 is reserved for errors
"optional", // LABEL_OPTIONAL
"required", // LABEL_REQUIRED
"repeated", // LABEL_REPEATED
};
std::optional<std::string> MinimizeType(const std::string& a,
const std::string& b) {
auto a_pieces = base::SplitString(a, ".");
auto b_pieces = base::SplitString(b, ".");
size_t skip = 0;
for (size_t i = 0; i < std::min(a_pieces.size(), b_pieces.size()); ++i) {
if (a_pieces[i] != b_pieces[i])
return a.substr(skip);
skip += a_pieces[i].size() + 1;
}
return std::nullopt;
}
std::string SimpleFieldTypeFromDescriptor(
const google::protobuf::Descriptor& parent,
const google::protobuf::FieldDescriptor& desc,
bool packageless_type) {
switch (desc.type()) {
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (packageless_type) {
return base::StripPrefix(desc.message_type()->full_name(),
desc.message_type()->file()->package() + ".");
} else {
return MinimizeType(desc.message_type()->full_name(),
parent.full_name())
.value_or(desc.message_type()->name());
}
case google::protobuf::FieldDescriptor::TYPE_ENUM:
if (packageless_type) {
return base::StripPrefix(desc.enum_type()->full_name(),
desc.enum_type()->file()->package() + ".");
} else {
return MinimizeType(desc.enum_type()->full_name(), parent.full_name())
.value_or(desc.enum_type()->name());
}
default:
return kTypeToName[desc.type()];
}
}
std::string FieldTypeFromDescriptor(
const google::protobuf::Descriptor& parent,
const google::protobuf::FieldDescriptor& desc,
bool packageless_type) {
if (!desc.is_map())
return SimpleFieldTypeFromDescriptor(parent, desc, packageless_type);
std::string field_type;
field_type += "map<";
field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(0),
packageless_type);
field_type += ",";
field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(1),
packageless_type);
field_type += ">";
return field_type;
}
std::unique_ptr<google::protobuf::Message> NormalizeOptionsMessage(
const google::protobuf::DescriptorPool& pool,
google::protobuf::DynamicMessageFactory* factory,
const google::protobuf::Message& message) {
const auto* option_descriptor =
pool.FindMessageTypeByName(message.GetDescriptor()->full_name());
if (!option_descriptor)
return nullptr;
std::unique_ptr<google::protobuf::Message> dynamic_options(
factory->GetPrototype(option_descriptor)->New());
PERFETTO_CHECK(dynamic_options->ParseFromString(message.SerializeAsString()));
return dynamic_options;
}
std::vector<ProtoFile::Option> OptionsFromMessage(
const google::protobuf::DescriptorPool& pool,
const google::protobuf::Message& raw_message) {
google::protobuf::DynamicMessageFactory factory;
auto normalized = NormalizeOptionsMessage(pool, &factory, raw_message);
const auto* message = normalized ? normalized.get() : &raw_message;
const auto* reflection = message->GetReflection();
std::vector<const google::protobuf::FieldDescriptor*> fields;
reflection->ListFields(*message, &fields);
std::vector<ProtoFile::Option> options;
for (size_t i = 0; i < fields.size(); i++) {
int count = 1;
bool repeated = false;
if (fields[i]->is_repeated()) {
count = reflection->FieldSize(*message, fields[i]);
repeated = true;
}
for (int j = 0; j < count; j++) {
std::string name;
if (fields[i]->is_extension()) {
name = "(" + fields[i]->full_name() + ")";
} else {
name = fields[i]->name();
}
std::string fieldval;
if (fields[i]->cpp_type() ==
google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
std::string tmp;
google::protobuf::TextFormat::Printer printer;
printer.PrintFieldValueToString(*message, fields[i], repeated ? j : -1,
&tmp);
fieldval.append("{\n");
fieldval.append(tmp);
fieldval.append("}");
} else {
google::protobuf::TextFormat::PrintFieldValueToString(
*message, fields[i], repeated ? j : -1, &fieldval);
}
options.push_back(ProtoFile::Option{name, fieldval});
}
}
return options;
}
template <typename Output, typename Descriptor>
Output InitFromDescriptor(const Descriptor& desc) {
google::protobuf::SourceLocation source_loc;
if (!desc.GetSourceLocation(&source_loc))
return {};
Output out;
out.leading_comments = base::SplitString(source_loc.leading_comments, "\n");
out.trailing_comments = base::SplitString(source_loc.trailing_comments, "\n");
return out;
}
ProtoFile::Field FieldFromDescriptor(
const google::protobuf::Descriptor& parent,
const google::protobuf::FieldDescriptor& desc) {
auto field = InitFromDescriptor<ProtoFile::Field>(desc);
// Map fields have label repeated but are actually emitted without a label
// in practice.
field.label = desc.is_map() ? "" : kLabelToName[desc.label()];
field.packageless_type = FieldTypeFromDescriptor(parent, desc, true);
field.type = FieldTypeFromDescriptor(parent, desc, false);
field.name = desc.name();
field.number = desc.number();
field.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
return field;
}
ProtoFile::Enum::Value EnumValueFromDescriptor(
const google::protobuf::EnumValueDescriptor& desc) {
auto value = InitFromDescriptor<ProtoFile::Enum::Value>(desc);
value.name = desc.name();
value.number = desc.number();
value.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
return value;
}
ProtoFile::Enum EnumFromDescriptor(
const google::protobuf::EnumDescriptor& desc) {
auto en = InitFromDescriptor<ProtoFile::Enum>(desc);
en.name = desc.name();
for (int i = 0; i < desc.value_count(); ++i) {
en.values.emplace_back(EnumValueFromDescriptor(*desc.value(i)));
}
return en;
}
ProtoFile::Oneof OneOfFromDescriptor(
const google::protobuf::Descriptor& parent,
const google::protobuf::OneofDescriptor& desc) {
auto oneof = InitFromDescriptor<ProtoFile::Oneof>(desc);
oneof.name = desc.name();
for (int i = 0; i < desc.field_count(); ++i) {
oneof.fields.emplace_back(FieldFromDescriptor(parent, *desc.field(i)));
}
return oneof;
}
ProtoFile::Message MessageFromDescriptor(
const google::protobuf::Descriptor& desc) {
auto message = InitFromDescriptor<ProtoFile::Message>(desc);
message.name = desc.name();
for (int i = 0; i < desc.enum_type_count(); ++i) {
message.enums.emplace_back(EnumFromDescriptor(*desc.enum_type(i)));
}
for (int i = 0; i < desc.nested_type_count(); ++i) {
message.nested_messages.emplace_back(
MessageFromDescriptor(*desc.nested_type(i)));
}
for (int i = 0; i < desc.oneof_decl_count(); ++i) {
message.oneofs.emplace_back(OneOfFromDescriptor(desc, *desc.oneof_decl(i)));
}
for (int i = 0; i < desc.field_count(); ++i) {
auto* field = desc.field(i);
if (field->containing_oneof())
continue;
message.fields.emplace_back(FieldFromDescriptor(desc, *field));
}
return message;
}
} // namespace
ProtoFile ProtoFileFromDescriptor(
std::string preamble,
const google::protobuf::FileDescriptor& desc) {
ProtoFile file;
file.preamble = std::move(preamble);
for (int i = 0; i < desc.enum_type_count(); ++i) {
file.enums.push_back(EnumFromDescriptor(*desc.enum_type(i)));
}
for (int i = 0; i < desc.message_type_count(); ++i) {
file.messages.push_back(MessageFromDescriptor(*desc.message_type(i)));
}
return file;
}
} // namespace proto_merger
} // namespace perfetto