/* * 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 "tools/proto_merger/proto_merger.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/optional.h" namespace perfetto { namespace proto_merger { namespace { template base::Optional FindInMap(const std::map& map, const Key& key) { auto it = map.find(key); return it == map.end() ? base::nullopt : base::make_optional(it->second); } // Finds the given 'name' in the vector by comparing against // the field named 'name' for each item in the vector. // T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message. template const T* FindByName(const std::vector& items, const std::string& name) { for (const auto& item : items) { if (item.name == name) return &item; } return nullptr; } // Compute the items present in the |input| vector but deleted in // the |upstream| vector by looking at the field |name|. // T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message. template std::vector ComputeDeletedByName(const std::vector& input, const std::vector& upstream) { std::vector deleted; std::set seen; for (const auto& upstream_item : upstream) { auto* input_item = FindByName(input, upstream_item.name); if (!input_item) continue; seen.insert(input_item->name); } for (const auto& input_item : input) { if (seen.count(input_item.name)) continue; deleted.emplace_back(input_item); } return deleted; } // Finds the given 'number' in the vector by comparing against // the field named 'number for each item in the vector. // T is ProtoFile::EnumValue or ProtoFile::Field. template const T* FindByNumber(const std::vector& items, int number) { for (const auto& item : items) { if (item.number == number) return &item; } return nullptr; } // Compute the items present in the |input| vector but deleted in // the |upstream| vector by looking at the field |number|. // T is ProtoFile::EnumValue or ProtoFile::Field. template std::vector ComputeDeletedByNumber(const std::vector& input, const std::vector& upstream) { std::vector deleted; std::set seen; for (const auto& upstream_item : upstream) { auto* input_item = FindByNumber(input, upstream_item.number); if (!input_item) continue; seen.insert(input_item->number); } for (const auto& input_item : input) { if (seen.count(input_item.number)) continue; deleted.emplace_back(input_item); } return deleted; } ProtoFile::Enum::Value MergeEnumValue(const ProtoFile::Enum::Value& input, const ProtoFile::Enum::Value& upstream) { PERFETTO_CHECK(input.number == upstream.number); ProtoFile::Enum::Value out; out.name = upstream.name; // Get the comments from the source of truth. out.leading_comments = upstream.leading_comments; out.trailing_comments = upstream.trailing_comments; // Get everything else from the input. out.number = input.number; out.options = input.options; return out; } ProtoFile::Enum MergeEnum(const ProtoFile::Enum& input, const ProtoFile::Enum& upstream) { PERFETTO_CHECK(input.name == upstream.name); ProtoFile::Enum out; out.name = upstream.name; // Get the comments from the source of truth. out.leading_comments = upstream.leading_comments; out.trailing_comments = upstream.trailing_comments; for (const auto& upstream_value : upstream.values) { // If an enum is allowlisted, we implicitly assume that all its // values are also allowed. Therefore, if the value doesn't exist // in the input, just take it from the source of truth. auto* input_value = FindByNumber(input.values, upstream_value.number); auto out_value = input_value ? MergeEnumValue(*input_value, upstream_value) : upstream_value; out.values.emplace_back(std::move(out_value)); } // Compute all the values present in the input but deleted in the // source of truth. out.deleted_values = ComputeDeletedByNumber(input.values, upstream.values); return out; } std::vector MergeEnums( const std::vector& input, const std::vector& upstream, const std::set& allowlist) { std::vector out; for (const auto& upstream_enum : upstream) { auto* input_enum = FindByName(input, upstream_enum.name); if (!input_enum) { // If the enum is missing from the input but is present // in the allowlist, take the whole enum from the // source of truth. if (allowlist.count(upstream_enum.name)) out.emplace_back(upstream_enum); continue; } // Otherwise, merge the enums from the input and source of truth. out.emplace_back(MergeEnum(*input_enum, upstream_enum)); } return out; } base::Status MergeField(const ProtoFile::Field& input, const ProtoFile::Field& upstream, ProtoFile::Field& out) { PERFETTO_CHECK(input.number == upstream.number); if (input.packageless_type != upstream.packageless_type) { return base::ErrStatus( "The type of field with id %d and name %s (source of truth name: %s) " "changed from %s to %s. Please resolve conflict manually before " "rerunning.", input.number, input.name.c_str(), upstream.name.c_str(), input.packageless_type.c_str(), upstream.packageless_type.c_str()); } // If the packageless type mathces, the type should also match. PERFETTO_CHECK(input.type == upstream.type); // Get the comments, label and the name from the source of truth. out.leading_comments = upstream.leading_comments; out.trailing_comments = upstream.trailing_comments; out.label = upstream.label; out.name = upstream.name; // Get everything else from the input. out.number = input.number; out.options = input.options; out.packageless_type = input.packageless_type; out.type = input.type; return base::OkStatus(); } base::Status MergeFields(const std::vector& input, const std::vector& upstream, const std::set& allowlist, std::vector& out) { for (const auto& upstream_field : upstream) { auto* input_field = FindByNumber(input, upstream_field.number); if (!input_field) { // If the field is missing from the input but is present // in the allowlist, take the whole field from the // source of truth. if (allowlist.count(upstream_field.number)) out.emplace_back(upstream_field); continue; } // Otherwise, merge the fields from the input and source of truth. ProtoFile::Field out_field; base::Status status = MergeField(*input_field, upstream_field, out_field); if (!status.ok()) return status; out.emplace_back(std::move(out_field)); } return base::OkStatus(); } // We call both of these just "Merge" so that |MergeRecursive| below can // reference them with the same name. base::Status Merge(const ProtoFile::Oneof& input, const ProtoFile::Oneof& upstream, const Allowlist::Oneof& allowlist, ProtoFile::Oneof& out); base::Status Merge(const ProtoFile::Message& input, const ProtoFile::Message& upstream, const Allowlist::Message& allowlist, ProtoFile::Message& out); template base::Status MergeRecursive( const std::vector& input, const std::vector& upstream, const std::map& allowlist_map, std::vector& out) { for (const auto& upstream_item : upstream) { auto opt_allowlist = FindInMap(allowlist_map, upstream_item.name); auto* input_item = FindByName(input, upstream_item.name); // If the value is not present in the input and the allowlist doesn't // exist either, this field is not approved so should not be included // in the output. if (!input_item && !opt_allowlist) continue; // If the input value doesn't exist, create a fake "input" that we can pass // to the merge functon. This basically has the effect that the upstream // item is taken but *not* recrusively; i.e. any fields which are inside the // message/oneof are checked against the allowlist individually. If we just // took the whole upstream here, we could add fields which were not // allowlisted. T input_or_fake; if (input_item) { input_or_fake = *input_item; } else { input_or_fake.name = upstream_item.name; } auto allowlist = opt_allowlist.value_or(AllowlistType{}); T out_item; auto status = Merge(input_or_fake, upstream_item, allowlist, out_item); if (!status.ok()) return status; out.emplace_back(std::move(out_item)); } return base::OkStatus(); } base::Status Merge(const ProtoFile::Oneof& input, const ProtoFile::Oneof& upstream, const Allowlist::Oneof& allowlist, ProtoFile::Oneof& out) { PERFETTO_CHECK(input.name == upstream.name); out.name = input.name; // Get the comments from the source of truth. out.leading_comments = upstream.leading_comments; out.trailing_comments = upstream.trailing_comments; // Compute all the fields present in the input but deleted in the // source of truth. out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields); // Finish by merging the list of fields. return MergeFields(input.fields, upstream.fields, allowlist, out.fields); } base::Status Merge(const ProtoFile::Message& input, const ProtoFile::Message& upstream, const Allowlist::Message& allowlist, ProtoFile::Message& out) { PERFETTO_CHECK(input.name == upstream.name); out.name = input.name; // Get the comments from the source of truth. out.leading_comments = upstream.leading_comments; out.trailing_comments = upstream.trailing_comments; // Compute all the values present in the input but deleted in the // source of truth. out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums); out.deleted_nested_messages = ComputeDeletedByName(input.nested_messages, upstream.nested_messages); out.deleted_oneofs = ComputeDeletedByName(input.oneofs, upstream.oneofs); out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields); // Merge any nested enum types. out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums); // Merge any nested message types. auto status = MergeRecursive(input.nested_messages, upstream.nested_messages, allowlist.nested_messages, out.nested_messages); if (!status.ok()) return status; // Merge any oneofs. status = MergeRecursive(input.oneofs, upstream.oneofs, allowlist.oneofs, out.oneofs); if (!status.ok()) return status; // Finish by merging the list of fields. return MergeFields(input.fields, upstream.fields, allowlist.fields, out.fields); } } // namespace base::Status MergeProtoFiles(const ProtoFile& input, const ProtoFile& upstream, const Allowlist& allowlist, ProtoFile& out) { // Compute all the enums and messages present in the input but deleted in the // source of truth. out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums); out.deleted_messages = ComputeDeletedByName(input.messages, upstream.messages); // Merge the top-level enums. out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums); // Finish by merging the top-level messages. return MergeRecursive(input.messages, upstream.messages, allowlist.messages, out.messages); } } // namespace proto_merger } // namespace perfetto