• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "tools/proto_merger/proto_merger.h"
18 
19 #include "perfetto/base/logging.h"
20 #include "perfetto/base/status.h"
21 #include "perfetto/ext/base/optional.h"
22 
23 namespace perfetto {
24 namespace proto_merger {
25 namespace {
26 
27 template <typename Key, typename Value>
FindInMap(const std::map<Key,Value> & map,const Key & key)28 base::Optional<Value> FindInMap(const std::map<Key, Value>& map,
29                                 const Key& key) {
30   auto it = map.find(key);
31   return it == map.end() ? base::nullopt : base::make_optional(it->second);
32 }
33 
34 // Finds the given 'name' in the vector by comparing against
35 // the field named 'name' for each item in the vector.
36 // T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message.
37 template <typename T>
FindByName(const std::vector<T> & items,const std::string & name)38 const T* FindByName(const std::vector<T>& items, const std::string& name) {
39   for (const auto& item : items) {
40     if (item.name == name)
41       return &item;
42   }
43   return nullptr;
44 }
45 
46 // Compute the items present in the |input| vector but deleted in
47 // the |upstream| vector by looking at the field |name|.
48 // T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message.
49 template <typename T>
ComputeDeletedByName(const std::vector<T> & input,const std::vector<T> & upstream)50 std::vector<T> ComputeDeletedByName(const std::vector<T>& input,
51                                     const std::vector<T>& upstream) {
52   std::vector<T> deleted;
53   std::set<std::string> seen;
54   for (const auto& upstream_item : upstream) {
55     auto* input_item = FindByName(input, upstream_item.name);
56     if (!input_item)
57       continue;
58     seen.insert(input_item->name);
59   }
60 
61   for (const auto& input_item : input) {
62     if (seen.count(input_item.name))
63       continue;
64     deleted.emplace_back(input_item);
65   }
66   return deleted;
67 }
68 
69 // Finds the given 'number' in the vector by comparing against
70 // the field named 'number for each item in the vector.
71 // T is ProtoFile::EnumValue or ProtoFile::Field.
72 template <typename T>
FindByNumber(const std::vector<T> & items,int number)73 const T* FindByNumber(const std::vector<T>& items, int number) {
74   for (const auto& item : items) {
75     if (item.number == number)
76       return &item;
77   }
78   return nullptr;
79 }
80 
81 // Compute the items present in the |input| vector but deleted in
82 // the |upstream| vector by looking at the field |number|.
83 // T is ProtoFile::EnumValue or ProtoFile::Field.
84 template <typename T>
ComputeDeletedByNumber(const std::vector<T> & input,const std::vector<T> & upstream)85 std::vector<T> ComputeDeletedByNumber(const std::vector<T>& input,
86                                       const std::vector<T>& upstream) {
87   std::vector<T> deleted;
88   std::set<int> seen;
89   for (const auto& upstream_item : upstream) {
90     auto* input_item = FindByNumber(input, upstream_item.number);
91     if (!input_item)
92       continue;
93     seen.insert(input_item->number);
94   }
95 
96   for (const auto& input_item : input) {
97     if (seen.count(input_item.number))
98       continue;
99     deleted.emplace_back(input_item);
100   }
101   return deleted;
102 }
103 
MergeEnumValue(const ProtoFile::Enum::Value & input,const ProtoFile::Enum::Value & upstream)104 ProtoFile::Enum::Value MergeEnumValue(const ProtoFile::Enum::Value& input,
105                                       const ProtoFile::Enum::Value& upstream) {
106   PERFETTO_CHECK(input.number == upstream.number);
107 
108   ProtoFile::Enum::Value out;
109   out.name = upstream.name;
110 
111   // Get the comments from the source of truth.
112   out.leading_comments = upstream.leading_comments;
113   out.trailing_comments = upstream.trailing_comments;
114 
115   // Get everything else from the input.
116   out.number = input.number;
117   out.options = input.options;
118   return out;
119 }
120 
MergeEnum(const ProtoFile::Enum & input,const ProtoFile::Enum & upstream)121 ProtoFile::Enum MergeEnum(const ProtoFile::Enum& input,
122                           const ProtoFile::Enum& upstream) {
123   PERFETTO_CHECK(input.name == upstream.name);
124 
125   ProtoFile::Enum out;
126   out.name = upstream.name;
127 
128   // Get the comments from the source of truth.
129   out.leading_comments = upstream.leading_comments;
130   out.trailing_comments = upstream.trailing_comments;
131 
132   for (const auto& upstream_value : upstream.values) {
133     // If an enum is allowlisted, we implicitly assume that all its
134     // values are also allowed. Therefore, if the value doesn't exist
135     // in the input, just take it from the source of truth.
136     auto* input_value = FindByNumber(input.values, upstream_value.number);
137     auto out_value = input_value ? MergeEnumValue(*input_value, upstream_value)
138                                  : upstream_value;
139     out.values.emplace_back(std::move(out_value));
140   }
141 
142   // Compute all the values present in the input but deleted in the
143   // source of truth.
144   out.deleted_values = ComputeDeletedByNumber(input.values, upstream.values);
145   return out;
146 }
147 
MergeEnums(const std::vector<ProtoFile::Enum> & input,const std::vector<ProtoFile::Enum> & upstream,const std::set<std::string> & allowlist)148 std::vector<ProtoFile::Enum> MergeEnums(
149     const std::vector<ProtoFile::Enum>& input,
150     const std::vector<ProtoFile::Enum>& upstream,
151     const std::set<std::string>& allowlist) {
152   std::vector<ProtoFile::Enum> out;
153   for (const auto& upstream_enum : upstream) {
154     auto* input_enum = FindByName(input, upstream_enum.name);
155     if (!input_enum) {
156       // If the enum is missing from the input but is present
157       // in the allowlist, take the whole enum from the
158       // source of truth.
159       if (allowlist.count(upstream_enum.name))
160         out.emplace_back(upstream_enum);
161       continue;
162     }
163 
164     // Otherwise, merge the enums from the input and source of truth.
165     out.emplace_back(MergeEnum(*input_enum, upstream_enum));
166   }
167   return out;
168 }
169 
MergeField(const ProtoFile::Field & input,const ProtoFile::Field & upstream,ProtoFile::Field & out)170 base::Status MergeField(const ProtoFile::Field& input,
171                         const ProtoFile::Field& upstream,
172                         ProtoFile::Field& out) {
173   PERFETTO_CHECK(input.number == upstream.number);
174 
175   if (input.packageless_type != upstream.packageless_type) {
176     return base::ErrStatus(
177         "The type of field with id %d and name %s (source of truth name: %s) "
178         "changed from %s to %s. Please resolve conflict manually before "
179         "rerunning.",
180         input.number, input.name.c_str(), upstream.name.c_str(),
181         input.packageless_type.c_str(), upstream.packageless_type.c_str());
182   }
183 
184   // If the packageless type mathces, the type should also match.
185   PERFETTO_CHECK(input.type == upstream.type);
186 
187   // Get the comments, label and the name from the source of truth.
188   out.leading_comments = upstream.leading_comments;
189   out.trailing_comments = upstream.trailing_comments;
190   out.label = upstream.label;
191   out.name = upstream.name;
192 
193   // Get everything else from the input.
194   out.number = input.number;
195   out.options = input.options;
196   out.packageless_type = input.packageless_type;
197   out.type = input.type;
198 
199   return base::OkStatus();
200 }
201 
MergeFields(const std::vector<ProtoFile::Field> & input,const std::vector<ProtoFile::Field> & upstream,const std::set<int> & allowlist,std::vector<ProtoFile::Field> & out)202 base::Status MergeFields(const std::vector<ProtoFile::Field>& input,
203                          const std::vector<ProtoFile::Field>& upstream,
204                          const std::set<int>& allowlist,
205                          std::vector<ProtoFile::Field>& out) {
206   for (const auto& upstream_field : upstream) {
207     auto* input_field = FindByNumber(input, upstream_field.number);
208     if (!input_field) {
209       // If the field is missing from the input but is present
210       // in the allowlist, take the whole field from the
211       // source of truth.
212       if (allowlist.count(upstream_field.number))
213         out.emplace_back(upstream_field);
214       continue;
215     }
216 
217     // Otherwise, merge the fields from the input and source of truth.
218     ProtoFile::Field out_field;
219     base::Status status = MergeField(*input_field, upstream_field, out_field);
220     if (!status.ok())
221       return status;
222     out.emplace_back(std::move(out_field));
223   }
224   return base::OkStatus();
225 }
226 
227 // We call both of these just "Merge" so that |MergeRecursive| below can
228 // reference them with the same name.
229 base::Status Merge(const ProtoFile::Oneof& input,
230                    const ProtoFile::Oneof& upstream,
231                    const Allowlist::Oneof& allowlist,
232                    ProtoFile::Oneof& out);
233 
234 base::Status Merge(const ProtoFile::Message& input,
235                    const ProtoFile::Message& upstream,
236                    const Allowlist::Message& allowlist,
237                    ProtoFile::Message& out);
238 
239 template <typename T, typename AllowlistType>
MergeRecursive(const std::vector<T> & input,const std::vector<T> & upstream,const std::map<std::string,AllowlistType> & allowlist_map,std::vector<T> & out)240 base::Status MergeRecursive(
241     const std::vector<T>& input,
242     const std::vector<T>& upstream,
243     const std::map<std::string, AllowlistType>& allowlist_map,
244     std::vector<T>& out) {
245   for (const auto& upstream_item : upstream) {
246     auto opt_allowlist = FindInMap(allowlist_map, upstream_item.name);
247     auto* input_item = FindByName(input, upstream_item.name);
248 
249     // If the value is not present in the input and the allowlist doesn't
250     // exist either, this field is not approved so should not be included
251     // in the output.
252     if (!input_item && !opt_allowlist)
253       continue;
254 
255     // If the input value doesn't exist, create a fake "input" that we can pass
256     // to the merge functon. This basically has the effect that the upstream
257     // item is taken but *not* recrusively; i.e. any fields which are inside the
258     // message/oneof are checked against the allowlist individually. If we just
259     // took the whole upstream here, we could add fields which were not
260     // allowlisted.
261     T input_or_fake;
262     if (input_item) {
263       input_or_fake = *input_item;
264     } else {
265       input_or_fake.name = upstream_item.name;
266     }
267 
268     auto allowlist = opt_allowlist.value_or(AllowlistType{});
269     T out_item;
270     auto status = Merge(input_or_fake, upstream_item, allowlist, out_item);
271     if (!status.ok())
272       return status;
273     out.emplace_back(std::move(out_item));
274   }
275   return base::OkStatus();
276 }
277 
Merge(const ProtoFile::Oneof & input,const ProtoFile::Oneof & upstream,const Allowlist::Oneof & allowlist,ProtoFile::Oneof & out)278 base::Status Merge(const ProtoFile::Oneof& input,
279                    const ProtoFile::Oneof& upstream,
280                    const Allowlist::Oneof& allowlist,
281                    ProtoFile::Oneof& out) {
282   PERFETTO_CHECK(input.name == upstream.name);
283   out.name = input.name;
284 
285   // Get the comments from the source of truth.
286   out.leading_comments = upstream.leading_comments;
287   out.trailing_comments = upstream.trailing_comments;
288 
289   // Compute all the fields present in the input but deleted in the
290   // source of truth.
291   out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields);
292 
293   // Finish by merging the list of fields.
294   return MergeFields(input.fields, upstream.fields, allowlist, out.fields);
295 }
296 
Merge(const ProtoFile::Message & input,const ProtoFile::Message & upstream,const Allowlist::Message & allowlist,ProtoFile::Message & out)297 base::Status Merge(const ProtoFile::Message& input,
298                    const ProtoFile::Message& upstream,
299                    const Allowlist::Message& allowlist,
300                    ProtoFile::Message& out) {
301   PERFETTO_CHECK(input.name == upstream.name);
302   out.name = input.name;
303 
304   // Get the comments from the source of truth.
305   out.leading_comments = upstream.leading_comments;
306   out.trailing_comments = upstream.trailing_comments;
307 
308   // Compute all the values present in the input but deleted in the
309   // source of truth.
310   out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums);
311   out.deleted_nested_messages =
312       ComputeDeletedByName(input.nested_messages, upstream.nested_messages);
313   out.deleted_oneofs = ComputeDeletedByName(input.oneofs, upstream.oneofs);
314   out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields);
315 
316   // Merge any nested enum types.
317   out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums);
318 
319   // Merge any nested message types.
320   auto status = MergeRecursive(input.nested_messages, upstream.nested_messages,
321                                allowlist.nested_messages, out.nested_messages);
322   if (!status.ok())
323     return status;
324 
325   // Merge any oneofs.
326   status = MergeRecursive(input.oneofs, upstream.oneofs, allowlist.oneofs,
327                           out.oneofs);
328   if (!status.ok())
329     return status;
330 
331   // Finish by merging the list of fields.
332   return MergeFields(input.fields, upstream.fields, allowlist.fields,
333                      out.fields);
334 }
335 
336 }  // namespace
337 
MergeProtoFiles(const ProtoFile & input,const ProtoFile & upstream,const Allowlist & allowlist,ProtoFile & out)338 base::Status MergeProtoFiles(const ProtoFile& input,
339                              const ProtoFile& upstream,
340                              const Allowlist& allowlist,
341                              ProtoFile& out) {
342   // Compute all the enums and messages present in the input but deleted in the
343   // source of truth.
344   out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums);
345   out.deleted_messages =
346       ComputeDeletedByName(input.messages, upstream.messages);
347 
348   // Merge the top-level enums.
349   out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums);
350 
351   // Finish by merging the top-level messages.
352   return MergeRecursive(input.messages, upstream.messages, allowlist.messages,
353                         out.messages);
354 }
355 
356 }  // namespace proto_merger
357 }  // namespace perfetto
358