// Protocol Buffers - Google's data interchange format // Copyright 2023 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "google/protobuf/feature_resolver.h" #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/descriptor.pb.h" #include "google/protobuf/dynamic_message.h" #include "google/protobuf/message.h" #include "google/protobuf/reflection_ops.h" #include "google/protobuf/text_format.h" // Must be included last. #include "google/protobuf/port_def.inc" #define RETURN_IF_ERROR(expr) \ do { \ const absl::Status _status = (expr); \ if (PROTOBUF_PREDICT_FALSE(!_status.ok())) return _status; \ } while (0) namespace google { namespace protobuf { namespace { template absl::Status Error(Args... args) { return absl::FailedPreconditionError(absl::StrCat(args...)); } bool IsNonFeatureField(const FieldDescriptor& field) { return field.containing_type() && field.containing_type()->full_name() == "google.protobuf.FeatureSet" && field.name() == "raw_features"; } bool EditionsLessThan(absl::string_view a, absl::string_view b) { std::vector as = absl::StrSplit(a, '.'); std::vector bs = absl::StrSplit(b, '.'); size_t min_size = std::min(as.size(), bs.size()); for (size_t i = 0; i < min_size; ++i) { if (as[i].size() != bs[i].size()) { return as[i].size() < bs[i].size(); } else if (as[i] != bs[i]) { return as[i] < bs[i]; } } // Both strings are equal up until an extra element, which makes that string // more recent. return as.size() < bs.size(); } absl::Status ValidateDescriptor(absl::string_view edition, const Descriptor& descriptor) { if (descriptor.oneof_decl_count() > 0) { return Error("Type ", descriptor.full_name(), " contains unsupported oneof feature fields."); } for (int i = 0; i < descriptor.field_count(); ++i) { const FieldDescriptor& field = *descriptor.field(i); if (IsNonFeatureField(field)) continue; if (field.is_required()) { return Error("Feature field ", field.full_name(), " is an unsupported required field."); } if (field.is_repeated()) { return Error("Feature field ", field.full_name(), " is an unsupported repeated field."); } if (field.options().targets().empty()) { return Error("Feature field ", field.full_name(), " has no target specified."); } } return absl::OkStatus(); } absl::Status FillDefaults(absl::string_view edition, Message& msg) { const Descriptor& descriptor = *msg.GetDescriptor(); auto comparator = [](const FieldOptions::EditionDefault& a, const FieldOptions::EditionDefault& b) { return EditionsLessThan(a.edition(), b.edition()); }; FieldOptions::EditionDefault edition_lookup; edition_lookup.set_edition(edition); for (int i = 0; i < descriptor.field_count(); ++i) { const FieldDescriptor& field = *descriptor.field(i); if (IsNonFeatureField(field)) continue; msg.GetReflection()->ClearField(&msg, &field); ABSL_CHECK(!field.is_repeated()); std::vector defaults{ field.options().edition_defaults().begin(), field.options().edition_defaults().end()}; absl::c_sort(defaults, comparator); auto first_nonmatch = absl::c_upper_bound(defaults, edition_lookup, comparator); if (first_nonmatch == defaults.begin()) { return Error("No valid default found for edition ", edition, " in feature field ", field.full_name()); } if (field.cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { for (auto it = defaults.begin(); it != first_nonmatch; ++it) { if (!TextFormat::MergeFromString( it->value(), msg.GetReflection()->MutableMessage(&msg, &field))) { return Error("Parsing error in edition_defaults for feature field ", field.full_name(), ". Could not parse: ", it->value()); } } } else { const std::string& def = std::prev(first_nonmatch)->value(); if (!TextFormat::ParseFieldValueFromString(def, &field, &msg)) { return Error("Parsing error in edition_defaults for feature field ", field.full_name(), ". Could not parse: ", def); } } } return absl::OkStatus(); } absl::Status ValidateMergedFeatures(const Message& msg) { const Descriptor& descriptor = *msg.GetDescriptor(); const Reflection& reflection = *msg.GetReflection(); for (int i = 0; i < descriptor.field_count(); ++i) { const FieldDescriptor& field = *descriptor.field(i); // Validate enum features. if (field.enum_type() != nullptr) { ABSL_DCHECK(reflection.HasField(msg, &field)); int int_value = reflection.GetEnumValue(msg, &field); const EnumValueDescriptor* value = field.enum_type()->FindValueByNumber(int_value); ABSL_DCHECK(value != nullptr); if (value->number() == 0) { return Error("Feature field ", field.full_name(), " must resolve to a known value, found ", value->name()); } } } return absl::OkStatus(); } } // namespace absl::StatusOr FeatureResolver::Create( absl::string_view edition, const Descriptor* descriptor) { if (descriptor == nullptr) { return Error( "Unable to find definition of google.protobuf.FeatureSet in descriptor pool."); } RETURN_IF_ERROR(ValidateDescriptor(edition, *descriptor)); auto message_factory = absl::make_unique(); auto defaults = absl::WrapUnique(message_factory->GetPrototype(descriptor)->New()); RETURN_IF_ERROR(FillDefaults(edition, *defaults)); return FeatureResolver(edition, *descriptor, std::move(message_factory), std::move(defaults)); } absl::Status FeatureResolver::RegisterExtension( const FieldDescriptor& extension) { if (!extension.is_extension() || extension.containing_type() != &descriptor_ || extensions_.contains(&extension)) { // These are valid but irrelevant extensions, just return ok. return absl::OkStatus(); } ABSL_CHECK(descriptor_.IsExtensionNumber(extension.number())); if (extension.message_type() == nullptr) { return Error("FeatureSet extension ", extension.full_name(), " is not of message type. Feature extensions should " "always use messages to allow for evolution."); } if (extension.is_repeated()) { return Error( "Only singular features extensions are supported. Found " "repeated extension ", extension.full_name()); } if (extension.message_type()->extension_count() > 0 || extension.message_type()->extension_range_count() > 0) { return Error("Nested extensions in feature extension ", extension.full_name(), " are not supported."); } RETURN_IF_ERROR(ValidateDescriptor(edition_, *extension.message_type())); Message* msg = defaults_->GetReflection()->MutableMessage(defaults_.get(), &extension); ABSL_CHECK(msg != nullptr); RETURN_IF_ERROR(FillDefaults(edition_, *msg)); extensions_.insert(&extension); return absl::OkStatus(); } absl::Status FeatureResolver::RegisterExtensions(const FileDescriptor& file) { for (int i = 0; i < file.extension_count(); ++i) { RETURN_IF_ERROR(RegisterExtension(*file.extension(i))); } for (int i = 0; i < file.message_type_count(); ++i) { RETURN_IF_ERROR(RegisterExtensions(*file.message_type(i))); } return absl::OkStatus(); } absl::Status FeatureResolver::RegisterExtensions(const Descriptor& message) { for (int i = 0; i < message.extension_count(); ++i) { RETURN_IF_ERROR(RegisterExtension(*message.extension(i))); } for (int i = 0; i < message.nested_type_count(); ++i) { RETURN_IF_ERROR(RegisterExtensions(*message.nested_type(i))); } return absl::OkStatus(); } absl::StatusOr FeatureResolver::MergeFeatures( const FeatureSet& merged_parent, const FeatureSet& unmerged_child) const { FeatureSet merged; ABSL_CHECK(merged.ParseFromString(defaults_->SerializeAsString())); merged.MergeFrom(merged_parent); merged.MergeFrom(unmerged_child); RETURN_IF_ERROR(ValidateMergedFeatures(merged)); return merged; } } // namespace protobuf } // namespace google #include "google/protobuf/port_undef.inc"