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(desc.message_type()->full_name(),
76 desc.message_type()->file()->package() + ".");
77 } else {
78 return MinimizeType(desc.message_type()->full_name(),
79 parent.full_name())
80 .value_or(desc.message_type()->name());
81 }
82 case google::protobuf::FieldDescriptor::TYPE_ENUM:
83 if (packageless_type) {
84 return base::StripPrefix(desc.enum_type()->full_name(),
85 desc.enum_type()->file()->package() + ".");
86 } else {
87 return MinimizeType(desc.enum_type()->full_name(), parent.full_name())
88 .value_or(desc.enum_type()->name());
89 }
90 default:
91 return kTypeToName[desc.type()];
92 }
93 }
94
FieldTypeFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::FieldDescriptor & desc,bool packageless_type)95 std::string FieldTypeFromDescriptor(
96 const google::protobuf::Descriptor& parent,
97 const google::protobuf::FieldDescriptor& desc,
98 bool packageless_type) {
99 if (!desc.is_map())
100 return SimpleFieldTypeFromDescriptor(parent, desc, packageless_type);
101
102 std::string field_type;
103 field_type += "map<";
104 field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(0),
105 packageless_type);
106 field_type += ",";
107 field_type += FieldTypeFromDescriptor(parent, *desc.message_type()->field(1),
108 packageless_type);
109 field_type += ">";
110 return field_type;
111 }
112
NormalizeOptionsMessage(const google::protobuf::DescriptorPool & pool,google::protobuf::DynamicMessageFactory * factory,const google::protobuf::Message & message)113 std::unique_ptr<google::protobuf::Message> NormalizeOptionsMessage(
114 const google::protobuf::DescriptorPool& pool,
115 google::protobuf::DynamicMessageFactory* factory,
116 const google::protobuf::Message& message) {
117 const auto* option_descriptor =
118 pool.FindMessageTypeByName(message.GetDescriptor()->full_name());
119 if (!option_descriptor)
120 return nullptr;
121
122 std::unique_ptr<google::protobuf::Message> dynamic_options(
123 factory->GetPrototype(option_descriptor)->New());
124 PERFETTO_CHECK(dynamic_options->ParseFromString(message.SerializeAsString()));
125 return dynamic_options;
126 }
127
OptionsFromMessage(const google::protobuf::DescriptorPool & pool,const google::protobuf::Message & raw_message)128 std::vector<ProtoFile::Option> OptionsFromMessage(
129 const google::protobuf::DescriptorPool& pool,
130 const google::protobuf::Message& raw_message) {
131 google::protobuf::DynamicMessageFactory factory;
132
133 auto normalized = NormalizeOptionsMessage(pool, &factory, raw_message);
134 const auto* message = normalized ? normalized.get() : &raw_message;
135 const auto* reflection = message->GetReflection();
136
137 std::vector<const google::protobuf::FieldDescriptor*> fields;
138 reflection->ListFields(*message, &fields);
139
140 std::vector<ProtoFile::Option> options;
141 for (size_t i = 0; i < fields.size(); i++) {
142 int count = 1;
143 bool repeated = false;
144 if (fields[i]->is_repeated()) {
145 count = reflection->FieldSize(*message, fields[i]);
146 repeated = true;
147 }
148 for (int j = 0; j < count; j++) {
149 std::string name;
150 if (fields[i]->is_extension()) {
151 name = "(" + fields[i]->full_name() + ")";
152 } else {
153 name = fields[i]->name();
154 }
155
156 std::string fieldval;
157 if (fields[i]->cpp_type() ==
158 google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
159 std::string tmp;
160 google::protobuf::TextFormat::Printer printer;
161 printer.PrintFieldValueToString(*message, fields[i], repeated ? j : -1,
162 &tmp);
163 fieldval.append("{\n");
164 fieldval.append(tmp);
165 fieldval.append("}");
166 } else {
167 google::protobuf::TextFormat::PrintFieldValueToString(
168 *message, fields[i], repeated ? j : -1, &fieldval);
169 }
170 options.push_back(ProtoFile::Option{name, fieldval});
171 }
172 }
173 return options;
174 }
175
176 template <typename Output, typename Descriptor>
InitFromDescriptor(const Descriptor & desc)177 Output InitFromDescriptor(const Descriptor& desc) {
178 google::protobuf::SourceLocation source_loc;
179 if (!desc.GetSourceLocation(&source_loc))
180 return {};
181
182 Output out;
183 out.leading_comments = base::SplitString(source_loc.leading_comments, "\n");
184 out.trailing_comments = base::SplitString(source_loc.trailing_comments, "\n");
185 return out;
186 }
187
FieldFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::FieldDescriptor & desc)188 ProtoFile::Field FieldFromDescriptor(
189 const google::protobuf::Descriptor& parent,
190 const google::protobuf::FieldDescriptor& desc) {
191 auto field = InitFromDescriptor<ProtoFile::Field>(desc);
192 field.is_repeated = desc.is_repeated();
193 field.packageless_type = FieldTypeFromDescriptor(parent, desc, true);
194 field.type = FieldTypeFromDescriptor(parent, desc, false);
195 field.name = desc.name();
196 field.number = desc.number();
197 field.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
198
199 // Protobuf editions: packed fields are no longer an option, but have the same
200 // syntax as far as writing the merged .proto file is concerned.
201 if (desc.is_packed()) {
202 field.options.push_back(
203 ProtoFile::Option{"features.repeated_field_encoding", "PACKED"});
204 }
205
206 return field;
207 }
208
EnumValueFromDescriptor(const google::protobuf::EnumValueDescriptor & desc)209 ProtoFile::Enum::Value EnumValueFromDescriptor(
210 const google::protobuf::EnumValueDescriptor& desc) {
211 auto value = InitFromDescriptor<ProtoFile::Enum::Value>(desc);
212 value.name = desc.name();
213 value.number = desc.number();
214 value.options = OptionsFromMessage(*desc.file()->pool(), desc.options());
215 return value;
216 }
217
EnumFromDescriptor(const google::protobuf::EnumDescriptor & desc)218 ProtoFile::Enum EnumFromDescriptor(
219 const google::protobuf::EnumDescriptor& desc) {
220 auto en = InitFromDescriptor<ProtoFile::Enum>(desc);
221 en.name = desc.name();
222 for (int i = 0; i < desc.value_count(); ++i) {
223 en.values.emplace_back(EnumValueFromDescriptor(*desc.value(i)));
224 }
225 return en;
226 }
227
OneOfFromDescriptor(const google::protobuf::Descriptor & parent,const google::protobuf::OneofDescriptor & desc)228 ProtoFile::Oneof OneOfFromDescriptor(
229 const google::protobuf::Descriptor& parent,
230 const google::protobuf::OneofDescriptor& desc) {
231 auto oneof = InitFromDescriptor<ProtoFile::Oneof>(desc);
232 oneof.name = desc.name();
233 for (int i = 0; i < desc.field_count(); ++i) {
234 oneof.fields.emplace_back(FieldFromDescriptor(parent, *desc.field(i)));
235 }
236 return oneof;
237 }
238
MessageFromDescriptor(const google::protobuf::Descriptor & desc)239 ProtoFile::Message MessageFromDescriptor(
240 const google::protobuf::Descriptor& desc) {
241 auto message = InitFromDescriptor<ProtoFile::Message>(desc);
242 message.name = desc.name();
243 for (int i = 0; i < desc.enum_type_count(); ++i) {
244 message.enums.emplace_back(EnumFromDescriptor(*desc.enum_type(i)));
245 }
246 for (int i = 0; i < desc.nested_type_count(); ++i) {
247 message.nested_messages.emplace_back(
248 MessageFromDescriptor(*desc.nested_type(i)));
249 }
250 for (int i = 0; i < desc.oneof_decl_count(); ++i) {
251 message.oneofs.emplace_back(OneOfFromDescriptor(desc, *desc.oneof_decl(i)));
252 }
253 for (int i = 0; i < desc.field_count(); ++i) {
254 auto* field = desc.field(i);
255 if (field->containing_oneof())
256 continue;
257 message.fields.emplace_back(FieldFromDescriptor(desc, *field));
258 }
259 return message;
260 }
261
262 } // namespace
263
ProtoFileFromDescriptor(std::string preamble,const google::protobuf::FileDescriptor & desc)264 ProtoFile ProtoFileFromDescriptor(
265 std::string preamble,
266 const google::protobuf::FileDescriptor& desc) {
267 ProtoFile file;
268 file.preamble = std::move(preamble);
269 for (int i = 0; i < desc.enum_type_count(); ++i) {
270 file.enums.push_back(EnumFromDescriptor(*desc.enum_type(i)));
271 }
272 for (int i = 0; i < desc.message_type_count(); ++i) {
273 file.messages.push_back(MessageFromDescriptor(*desc.message_type(i)));
274 }
275 return file;
276 }
277
278 } // namespace proto_merger
279 } // namespace perfetto
280