1 /*
2 * Copyright (C) 2021 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/tools/proto_merger/proto_file.h"
18
19 #include <google/protobuf/descriptor.h>
20 #include <google/protobuf/descriptor.pb.h>
21 #include <google/protobuf/dynamic_message.h>
22 #include <google/protobuf/text_format.h>
23
24 #include "perfetto/ext/base/string_utils.h"
25
26 namespace perfetto {
27 namespace proto_merger {
28 namespace {
29
30 const char* const
31 kTypeToName[google::protobuf::FieldDescriptor::Type::MAX_TYPE + 1] = {
32 "ERROR", // 0 is reserved for errors
33
34 "double", // TYPE_DOUBLE
35 "float", // TYPE_FLOAT
36 "int64", // TYPE_INT64
37 "uint64", // TYPE_UINT64
38 "int32", // TYPE_INT32
39 "fixed64", // TYPE_FIXED64
40 "fixed32", // TYPE_FIXED32
41 "bool", // TYPE_BOOL
42 "string", // TYPE_STRING
43 "group", // TYPE_GROUP
44 "message", // TYPE_MESSAGE
45 "bytes", // TYPE_BYTES
46 "uint32", // TYPE_UINT32
47 "enum", // TYPE_ENUM
48 "sfixed32", // TYPE_SFIXED32
49 "sfixed64", // TYPE_SFIXED64
50 "sint32", // TYPE_SINT32
51 "sint64", // TYPE_SINT64
52 };
53
MinimizeType(const std::string & a,const std::string & b)54 std::optional<std::string> MinimizeType(const std::string& a,
55 const std::string& b) {
56 auto a_pieces = base::SplitString(a, ".");
57 auto b_pieces = base::SplitString(b, ".");
58
59 size_t skip = 0;
60 for (size_t i = 0; i < std::min(a_pieces.size(), b_pieces.size()); ++i) {
61 if (a_pieces[i] != b_pieces[i])
62 return a.substr(skip);
63 skip += a_pieces[i].size() + 1;
64 }
65 return std::nullopt;
66 }
67
SimpleFieldTypeFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::FieldDescriptor & desc,bool packageless_type)68 std::string SimpleFieldTypeFromDescriptor(
69 const google::protobuf::Descriptor& parent,
70 const google::protobuf::FieldDescriptor& desc,
71 bool packageless_type) {
72 switch (desc.type()) {
73 case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
74 if (packageless_type) {
75 return base::StripPrefix(
76 std::string(desc.message_type()->full_name()),
77 std::string(desc.message_type()->file()->package()) + ".");
78 } else {
79 return MinimizeType(std::string(desc.message_type()->full_name()),
80 std::string(parent.full_name()))
81 .value_or(std::string(desc.message_type()->name()));
82 }
83 case google::protobuf::FieldDescriptor::TYPE_ENUM:
84 if (packageless_type) {
85 return base::StripPrefix(
86 std::string(desc.enum_type()->full_name()),
87 std::string(desc.enum_type()->file()->package()) + ".");
88 } else {
89 return MinimizeType(std::string(desc.enum_type()->full_name()),
90 std::string(parent.full_name()))
91 .value_or(std::string(desc.enum_type()->name()));
92 }
93 default:
94 return kTypeToName[desc.type()];
95 }
96 }
97
FieldTypeFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::FieldDescriptor & desc,bool packageless_type)98 std::string FieldTypeFromDescriptor(
99 const google::protobuf::Descriptor& parent,
100 const google::protobuf::FieldDescriptor& desc,
101 bool packageless_type) {
102 if (!desc.is_map())
103 return SimpleFieldTypeFromDescriptor(parent, desc, packageless_type);
104
105 std::string field_type;
106 field_type += "map<";
107 field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(0),
108 packageless_type);
109 field_type += ",";
110 field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(1),
111 packageless_type);
112 field_type += ">";
113 return field_type;
114 }
115
NormalizeOptionsMessage(const google::protobuf::DescriptorPool & pool,google::protobuf::DynamicMessageFactory * factory,const google::protobuf::Message & message)116 std::unique_ptr<google::protobuf::Message> NormalizeOptionsMessage(
117 const google::protobuf::DescriptorPool& pool,
118 google::protobuf::DynamicMessageFactory* factory,
119 const google::protobuf::Message& message) {
120 const auto* option_descriptor =
121 pool.FindMessageTypeByName(message.GetDescriptor()->full_name());
122 if (!option_descriptor)
123 return nullptr;
124
125 std::unique_ptr<google::protobuf::Message> dynamic_options(
126 factory->GetPrototype(option_descriptor)->New());
127 PERFETTO_CHECK(dynamic_options->ParseFromString(message.SerializeAsString()));
128 return dynamic_options;
129 }
130
OptionsFromMessage(const google::protobuf::DescriptorPool & pool,const google::protobuf::Message & raw_message)131 std::vector<ProtoFile::Option> OptionsFromMessage(
132 const google::protobuf::DescriptorPool& pool,
133 const google::protobuf::Message& raw_message) {
134 google::protobuf::DynamicMessageFactory factory;
135
136 auto normalized = NormalizeOptionsMessage(pool, &factory, raw_message);
137 const auto* message = normalized ? normalized.get() : &raw_message;
138 const auto* reflection = message->GetReflection();
139
140 std::vector<const google::protobuf::FieldDescriptor*> fields;
141 reflection->ListFields(*message, &fields);
142
143 std::vector<ProtoFile::Option> options;
144 for (size_t i = 0; i < fields.size(); i++) {
145 int count = 1;
146 bool repeated = false;
147 if (fields[i]->is_repeated()) {
148 count = reflection->FieldSize(*message, fields[i]);
149 repeated = true;
150 }
151 for (int j = 0; j < count; j++) {
152 std::string name;
153 if (fields[i]->is_extension()) {
154 name = "(" + std::string(fields[i]->full_name()) + ")";
155 } else {
156 name = fields[i]->name();
157 }
158
159 std::string fieldval;
160 if (fields[i]->cpp_type() ==
161 google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
162 std::string tmp;
163 google::protobuf::TextFormat::Printer printer;
164 printer.PrintFieldValueToString(*message, fields[i], repeated ? j : -1,
165 &tmp);
166 fieldval.append("{\n");
167 fieldval.append(tmp);
168 fieldval.append("}");
169 } else {
170 google::protobuf::TextFormat::PrintFieldValueToString(
171 *message, fields[i], repeated ? j : -1, &fieldval);
172 }
173 options.push_back(ProtoFile::Option{name, fieldval});
174 }
175 }
176 return options;
177 }
178
179 template <typename Output, typename Descriptor>
InitFromDescriptor(const Descriptor & desc)180 Output InitFromDescriptor(const Descriptor& desc) {
181 google::protobuf::SourceLocation source_loc;
182 if (!desc.GetSourceLocation(&source_loc))
183 return {};
184
185 Output out;
186 out.leading_comments = base::SplitString(source_loc.leading_comments, "\n");
187 out.trailing_comments = base::SplitString(source_loc.trailing_comments, "\n");
188 return out;
189 }
190
FieldFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::FieldDescriptor & desc)191 ProtoFile::Field FieldFromDescriptor(
192 const google::protobuf::Descriptor& parent,
193 const google::protobuf::FieldDescriptor& desc) {
194 auto field = InitFromDescriptor<ProtoFile::Field>(desc);
195 field.is_repeated = desc.is_repeated();
196 field.packageless_type = FieldTypeFromDescriptor(parent, desc, true);
197 field.type = FieldTypeFromDescriptor(parent, desc, false);
198 field.name = desc.name();
199 field.number = desc.number();
200 field.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
201
202 // Protobuf editions: packed fields are no longer an option, but have the same
203 // syntax as far as writing the merged .proto file is concerned.
204 if (desc.is_packed()) {
205 field.options.push_back(
206 ProtoFile::Option{"features.repeated_field_encoding", "PACKED"});
207 }
208
209 return field;
210 }
211
EnumValueFromDescriptor(const google::protobuf::EnumValueDescriptor & desc)212 ProtoFile::Enum::Value EnumValueFromDescriptor(
213 const google::protobuf::EnumValueDescriptor& desc) {
214 auto value = InitFromDescriptor<ProtoFile::Enum::Value>(desc);
215 value.name = desc.name();
216 value.number = desc.number();
217 value.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
218 return value;
219 }
220
EnumFromDescriptor(const google::protobuf::EnumDescriptor & desc)221 ProtoFile::Enum EnumFromDescriptor(
222 const google::protobuf::EnumDescriptor& desc) {
223 auto en = InitFromDescriptor<ProtoFile::Enum>(desc);
224 en.name = desc.name();
225 for (int i = 0; i < desc.value_count(); ++i) {
226 en.values.emplace_back(EnumValueFromDescriptor(*desc.value(i)));
227 }
228 return en;
229 }
230
OneOfFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::OneofDescriptor & desc)231 ProtoFile::Oneof OneOfFromDescriptor(
232 const google::protobuf::Descriptor& parent,
233 const google::protobuf::OneofDescriptor& desc) {
234 auto oneof = InitFromDescriptor<ProtoFile::Oneof>(desc);
235 oneof.name = desc.name();
236 for (int i = 0; i < desc.field_count(); ++i) {
237 oneof.fields.emplace_back(FieldFromDescriptor(parent, *desc.field(i)));
238 }
239 return oneof;
240 }
241
MessageFromDescriptor(const google::protobuf::Descriptor & desc)242 ProtoFile::Message MessageFromDescriptor(
243 const google::protobuf::Descriptor& desc) {
244 auto message = InitFromDescriptor<ProtoFile::Message>(desc);
245 message.name = desc.name();
246 for (int i = 0; i < desc.enum_type_count(); ++i) {
247 message.enums.emplace_back(EnumFromDescriptor(*desc.enum_type(i)));
248 }
249 for (int i = 0; i < desc.nested_type_count(); ++i) {
250 message.nested_messages.emplace_back(
251 MessageFromDescriptor(*desc.nested_type(i)));
252 }
253 for (int i = 0; i < desc.oneof_decl_count(); ++i) {
254 message.oneofs.emplace_back(OneOfFromDescriptor(desc, *desc.oneof_decl(i)));
255 }
256 for (int i = 0; i < desc.field_count(); ++i) {
257 auto* field = desc.field(i);
258 if (field->containing_oneof())
259 continue;
260 message.fields.emplace_back(FieldFromDescriptor(desc, *field));
261 }
262 return message;
263 }
264
265 } // namespace
266
ProtoFileFromDescriptor(std::string preamble,const google::protobuf::FileDescriptor & desc)267 ProtoFile ProtoFileFromDescriptor(
268 std::string preamble,
269 const google::protobuf::FileDescriptor& desc) {
270 ProtoFile file;
271 file.preamble = std::move(preamble);
272 for (int i = 0; i < desc.enum_type_count(); ++i) {
273 file.enums.push_back(EnumFromDescriptor(*desc.enum_type(i)));
274 }
275 for (int i = 0; i < desc.message_type_count(); ++i) {
276 file.messages.push_back(MessageFromDescriptor(*desc.message_type(i)));
277 }
278 return file;
279 }
280
281 } // namespace proto_merger
282 } // namespace perfetto
283