Expose a common helper for visiting descriptors.

This can be used immediately to simplify some of our options logic during DescriptorPool builds.

PiperOrigin-RevId: 533265315
pull/12837/head
Mike Kruskal 2 years ago committed by Copybara-Service
parent 4ac36141db
commit 8ff658a09d
  1. 26
      src/google/protobuf/BUILD.bazel
  2. 165
      src/google/protobuf/compiler/command_line_interface.cc
  3. 93
      src/google/protobuf/descriptor.cc
  4. 180
      src/google/protobuf/descriptor_visitor.h
  5. 148
      src/google/protobuf/descriptor_visitor_test.cc

@ -457,6 +457,7 @@ cc_library(
"descriptor.pb.h", "descriptor.pb.h",
"descriptor_database.h", "descriptor_database.h",
"descriptor_legacy.h", "descriptor_legacy.h",
"descriptor_visitor.h",
"dynamic_message.h", "dynamic_message.h",
"field_access_listener.h", "field_access_listener.h",
"generated_enum_reflection.h", "generated_enum_reflection.h",
@ -550,6 +551,19 @@ cc_library(
], ],
) )
cc_library(
name = "descriptor_visitor",
hdrs = ["descriptor_visitor.h"],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
visibility = ["//:__subpackages__"],
deps = [
":port_def",
":protobuf_nowkt",
],
)
filegroup( filegroup(
name = "well_known_type_protos", name = "well_known_type_protos",
srcs = [ srcs = [
@ -1435,6 +1449,18 @@ cc_test(
], ],
) )
cc_test(
name = "descriptor_visitor_test",
srcs = ["descriptor_visitor_test.cc"],
deps = [
":protobuf",
":cc_test_protos",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
################################################################################ ################################################################################
# Helper targets for Kotlin tests # Helper targets for Kotlin tests
################################################################################ ################################################################################

@ -40,6 +40,7 @@
#include "absl/types/span.h" #include "absl/types/span.h"
#include "google/protobuf/compiler/allowlists/allowlists.h" #include "google/protobuf/compiler/allowlists/allowlists.h"
#include "google/protobuf/descriptor_legacy.h" #include "google/protobuf/descriptor_legacy.h"
#include "google/protobuf/descriptor_visitor.h"
#include "google/protobuf/stubs/platform_macros.h" #include "google/protobuf/stubs/platform_macros.h"
@ -1025,100 +1026,6 @@ bool ContainsProto3Optional(const FileDescriptor* file) {
return false; return false;
} }
template <typename Visitor>
struct VisitImpl {
Visitor visitor;
void Visit(const FieldDescriptor* descriptor) { visitor(descriptor); }
void Visit(const EnumValueDescriptor* descriptor) { visitor(descriptor); }
void Visit(const EnumDescriptor* descriptor) {
visitor(descriptor);
for (int i = 0; i < descriptor->value_count(); i++) {
Visit(descriptor->value(i));
}
}
void Visit(const Descriptor::ExtensionRange* descriptor) {
visitor(descriptor);
}
void Visit(const OneofDescriptor* descriptor) { visitor(descriptor); }
void Visit(const Descriptor* descriptor) {
visitor(descriptor);
for (int i = 0; i < descriptor->enum_type_count(); i++) {
Visit(descriptor->enum_type(i));
}
for (int i = 0; i < descriptor->field_count(); i++) {
Visit(descriptor->field(i));
}
for (int i = 0; i < descriptor->nested_type_count(); i++) {
Visit(descriptor->nested_type(i));
}
for (int i = 0; i < descriptor->extension_count(); i++) {
Visit(descriptor->extension(i));
}
for (int i = 0; i < descriptor->extension_range_count(); i++) {
Visit(descriptor->extension_range(i));
}
for (int i = 0; i < descriptor->oneof_decl_count(); i++) {
Visit(descriptor->oneof_decl(i));
}
}
void Visit(const MethodDescriptor* method) { visitor(method); }
void Visit(const ServiceDescriptor* descriptor) {
visitor(descriptor);
for (int i = 0; i < descriptor->method_count(); i++) {
Visit(descriptor->method(i));
}
}
void Visit(absl::Span<const FileDescriptor*> descriptors) {
for (const FileDescriptor* descriptor : descriptors) {
visitor(descriptor);
for (int i = 0; i < descriptor->message_type_count(); i++) {
Visit(descriptor->message_type(i));
}
for (int i = 0; i < descriptor->enum_type_count(); i++) {
Visit(descriptor->enum_type(i));
}
for (int i = 0; i < descriptor->extension_count(); i++) {
Visit(descriptor->extension(i));
}
for (int i = 0; i < descriptor->service_count(); i++) {
Visit(descriptor->service(i));
}
}
}
};
// Visit every node in the descriptors calling `visitor(node)`.
// The visitor does not need to handle all possible node types. Types that are
// not visitable via `visitor` will be ignored.
template <typename Visitor>
void VisitDescriptors(absl::Span<const FileDescriptor*> descriptors,
Visitor visitor) {
// Provide a fallback to ignore all the nodes that are not interesting to the
// input visitor.
struct VisitorImpl : Visitor {
explicit VisitorImpl(Visitor visitor) : Visitor(visitor) {}
using Visitor::operator();
// Honeypot to ignore all inputs that Visitor does not take.
void operator()(const void*) const {}
};
VisitImpl<VisitorImpl>{VisitorImpl(visitor)}.Visit(descriptors);
}
bool HasReservedFieldNumber(const FieldDescriptor* field) { bool HasReservedFieldNumber(const FieldDescriptor* field) {
if (field->number() >= FieldDescriptor::kFirstReservedNumber && if (field->number() >= FieldDescriptor::kFirstReservedNumber &&
field->number() <= FieldDescriptor::kLastReservedNumber) { field->number() <= FieldDescriptor::kLastReservedNumber) {
@ -1365,49 +1272,47 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
bool validation_error = false; // Defer exiting so we log more warnings. bool validation_error = false; // Defer exiting so we log more warnings.
VisitDescriptors( for (auto& file : parsed_files) {
absl::Span<const FileDescriptor*>(parsed_files.data(), google::protobuf::internal::VisitDescriptors(
parsed_files.size()), *file, [&](const FieldDescriptor& field) {
[&](const FieldDescriptor* field) { if (HasReservedFieldNumber(&field)) {
if (HasReservedFieldNumber(field)) { const char* error_link = nullptr;
const char* error_link = nullptr; validation_error = true;
validation_error = true; std::string error;
std::string error; if (field.number() >= FieldDescriptor::kFirstReservedNumber &&
if (field->number() >= FieldDescriptor::kFirstReservedNumber && field.number() <= FieldDescriptor::kLastReservedNumber) {
field->number() <= FieldDescriptor::kLastReservedNumber) { error = absl::Substitute(
error = absl::Substitute( "Field numbers $0 through $1 are reserved "
"Field numbers $0 through $1 are reserved " "for the protocol buffer library implementation.",
"for the protocol buffer library implementation.", FieldDescriptor::kFirstReservedNumber,
FieldDescriptor::kFirstReservedNumber, FieldDescriptor::kLastReservedNumber);
FieldDescriptor::kLastReservedNumber); } else {
} else { error = absl::Substitute(
error = absl::Substitute( "Field number $0 is reserved for specific purposes.",
"Field number $0 is reserved for specific purposes.", field.number());
field->number()); }
} if (error_link) {
if (error_link) { absl::StrAppend(&error, "(See ", error_link, ")");
absl::StrAppend(&error, "(See ", error_link, ")"); }
static_cast<DescriptorPool::ErrorCollector*>(error_collector.get())
->RecordError(field.file()->name(), field.full_name(), nullptr,
DescriptorPool::ErrorCollector::NUMBER, error);
} }
static_cast<DescriptorPool::ErrorCollector*>(error_collector.get()) });
->RecordError(field->file()->name(), field->full_name(), nullptr, }
DescriptorPool::ErrorCollector::NUMBER, error);
}
});
// We visit one file at a time because we need to provide the file name for // We visit one file at a time because we need to provide the file name for
// error messages. Usually we can get the file name from any descriptor with // error messages. Usually we can get the file name from any descriptor with
// something like descriptor->file()->name(), but ExtensionRange does not // something like descriptor->file()->name(), but ExtensionRange does not
// support this. // support this.
for (const google::protobuf::FileDescriptor* file : parsed_files) { for (const google::protobuf::FileDescriptor* file : parsed_files) {
VisitDescriptors( google::protobuf::internal::VisitDescriptors(*file, [&](const auto& descriptor) {
absl::Span<const FileDescriptor*>(&file, 1), if (!ValidateTargetConstraints(descriptor.options(), *descriptor_pool,
[&](const auto* descriptor) { *error_collector, file->name(),
if (!ValidateTargetConstraints( GetTargetType(&descriptor))) {
descriptor->options(), *descriptor_pool, *error_collector, validation_error = true;
file->name(), GetTargetType(descriptor))) { }
validation_error = true; });
}
});
} }

@ -74,6 +74,7 @@
#include "google/protobuf/descriptor.pb.h" #include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor_database.h" #include "google/protobuf/descriptor_database.h"
#include "google/protobuf/descriptor_legacy.h" #include "google/protobuf/descriptor_legacy.h"
#include "google/protobuf/descriptor_visitor.h"
#include "google/protobuf/dynamic_message.h" #include "google/protobuf/dynamic_message.h"
#include "google/protobuf/generated_message_util.h" #include "google/protobuf/generated_message_util.h"
#include "google/protobuf/io/strtod.h" #include "google/protobuf/io/strtod.h"
@ -4207,20 +4208,21 @@ class DescriptorBuilder {
// descriptors, which are the ones that have been interpreted. The const // descriptors, which are the ones that have been interpreted. The const
// proto references are passed in only so they can be provided to calls to // proto references are passed in only so they can be provided to calls to
// AddError(). Do not look at their options, which have not been interpreted. // AddError(). Do not look at their options, which have not been interpreted.
void ValidateFileOptions(const FileDescriptor* file, void ValidateOptions(const FileDescriptor* file,
const FileDescriptorProto& proto); const FileDescriptorProto& proto);
void ValidateMessageOptions(const Descriptor* message, void ValidateOptions(const Descriptor* message, const DescriptorProto& proto);
const DescriptorProto& proto); void ValidateOptions(const OneofDescriptor* oneof,
void ValidateOneofOptions(const OneofDescriptor* oneof, const OneofDescriptorProto& proto);
const OneofDescriptorProto& proto); void ValidateOptions(const FieldDescriptor* field,
void ValidateFieldOptions(const FieldDescriptor* field, const FieldDescriptorProto& proto);
const FieldDescriptorProto& proto);
void ValidateFieldFeatures(const FieldDescriptor* field, void ValidateFieldFeatures(const FieldDescriptor* field,
const FieldDescriptorProto& proto); const FieldDescriptorProto& proto);
void ValidateEnumOptions(const EnumDescriptor* enm, void ValidateOptions(const EnumDescriptor* enm,
const EnumDescriptorProto& proto); const EnumDescriptorProto& proto);
void ValidateEnumValueOptions(const EnumValueDescriptor* enum_value, void ValidateOptions(const EnumValueDescriptor* enum_value,
const EnumValueDescriptorProto& proto); const EnumValueDescriptorProto& proto);
void ValidateOptions(const Descriptor::ExtensionRange* range,
const DescriptorProto::ExtensionRange& proto) {}
void ValidateExtensionRangeOptions(const DescriptorProto& proto, void ValidateExtensionRangeOptions(const DescriptorProto& proto,
const Descriptor& message); const Descriptor& message);
void ValidateExtensionDeclaration( void ValidateExtensionDeclaration(
@ -4228,10 +4230,10 @@ class DescriptorBuilder {
const RepeatedPtrField<ExtensionRangeOptions_Declaration>& declarations, const RepeatedPtrField<ExtensionRangeOptions_Declaration>& declarations,
const DescriptorProto_ExtensionRange& proto, const DescriptorProto_ExtensionRange& proto,
absl::flat_hash_set<absl::string_view>& full_name_set); absl::flat_hash_set<absl::string_view>& full_name_set);
void ValidateServiceOptions(const ServiceDescriptor* service, void ValidateOptions(const ServiceDescriptor* service,
const ServiceDescriptorProto& proto); const ServiceDescriptorProto& proto);
void ValidateMethodOptions(const MethodDescriptor* method, void ValidateOptions(const MethodDescriptor* method,
const MethodDescriptorProto& proto); const MethodDescriptorProto& proto);
void ValidateProto3(const FileDescriptor* file, void ValidateProto3(const FileDescriptor* file,
const FileDescriptorProto& proto); const FileDescriptorProto& proto);
void ValidateProto3Message(const Descriptor* message, void ValidateProto3Message(const Descriptor* message,
@ -5478,7 +5480,10 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
// Validate options. See comments at InternalSetLazilyBuildDependencies about // Validate options. See comments at InternalSetLazilyBuildDependencies about
// error checking and lazy import building. // error checking and lazy import building.
if (!had_errors_ && !pool_->lazily_build_dependencies_) { if (!had_errors_ && !pool_->lazily_build_dependencies_) {
ValidateFileOptions(result, proto); internal::VisitDescriptors(*result, proto,
[&](const auto& descriptor, const auto& proto) {
ValidateOptions(&descriptor, proto);
});
} }
// Additional naming conflict check for map entry types. Only need to check // Additional naming conflict check for map entry types. Only need to check
@ -7103,12 +7108,6 @@ void DescriptorBuilder::SuggestFieldNumbers(FileDescriptor* file,
// ------------------------------------------------------------------- // -------------------------------------------------------------------
#define VALIDATE_OPTIONS_FROM_ARRAY(descriptor, array_name, type) \
for (int i = 0; i < descriptor->array_name##_count(); ++i) { \
Validate##type##Options(descriptor->array_name##s_ + i, \
proto.array_name(i)); \
}
// Determine if the file uses optimize_for = LITE_RUNTIME, being careful to // Determine if the file uses optimize_for = LITE_RUNTIME, being careful to
// avoid problems that exist at init time. // avoid problems that exist at init time.
static bool IsLite(const FileDescriptor* file) { static bool IsLite(const FileDescriptor* file) {
@ -7119,13 +7118,8 @@ static bool IsLite(const FileDescriptor* file) {
file->options().optimize_for() == FileOptions::LITE_RUNTIME; file->options().optimize_for() == FileOptions::LITE_RUNTIME;
} }
void DescriptorBuilder::ValidateFileOptions(const FileDescriptor* file, void DescriptorBuilder::ValidateOptions(const FileDescriptor* file,
const FileDescriptorProto& proto) { const FileDescriptorProto& proto) {
VALIDATE_OPTIONS_FROM_ARRAY(file, message_type, Message);
VALIDATE_OPTIONS_FROM_ARRAY(file, enum_type, Enum);
VALIDATE_OPTIONS_FROM_ARRAY(file, service, Service);
VALIDATE_OPTIONS_FROM_ARRAY(file, extension, Field);
// Lite files can only be imported by other Lite files. // Lite files can only be imported by other Lite files.
if (!IsLite(file)) { if (!IsLite(file)) {
for (int i = 0; i < file->dependency_count(); i++) { for (int i = 0; i < file->dependency_count(); i++) {
@ -7235,26 +7229,19 @@ void DescriptorBuilder::ValidateProto3Enum(const EnumDescriptor* enm,
} }
} }
void DescriptorBuilder::ValidateMessageOptions(const Descriptor* message, void DescriptorBuilder::ValidateOptions(const Descriptor* message,
const DescriptorProto& proto) { const DescriptorProto& proto) {
VALIDATE_OPTIONS_FROM_ARRAY(message, field, Field);
VALIDATE_OPTIONS_FROM_ARRAY(message, nested_type, Message);
VALIDATE_OPTIONS_FROM_ARRAY(message, enum_type, Enum);
VALIDATE_OPTIONS_FROM_ARRAY(message, extension, Field);
CheckFieldJsonNameUniqueness(proto, message); CheckFieldJsonNameUniqueness(proto, message);
ValidateExtensionRangeOptions(proto, *message); ValidateExtensionRangeOptions(proto, *message);
for (int i = 0; i < message->real_oneof_decl_count(); ++i) {
ValidateOneofOptions(message->oneof_decl(i), proto.oneof_decl(i));
}
} }
void DescriptorBuilder::ValidateOneofOptions( void DescriptorBuilder::ValidateOptions(const OneofDescriptor* /*oneof*/,
const OneofDescriptor* /*oneof*/, const OneofDescriptorProto& /*proto*/) {} const OneofDescriptorProto& /*proto*/) {
}
void DescriptorBuilder::ValidateFieldOptions( void DescriptorBuilder::ValidateOptions(const FieldDescriptor* field,
const FieldDescriptor* field, const FieldDescriptorProto& proto) { const FieldDescriptorProto& proto) {
if (pool_->lazily_build_dependencies_ && (!field || !field->message_type())) { if (pool_->lazily_build_dependencies_ && (!field || !field->message_type())) {
return; return;
} }
@ -7345,10 +7332,8 @@ void DescriptorBuilder::ValidateFieldFeatures(
const FieldDescriptor* field, const FieldDescriptorProto& proto) { const FieldDescriptor* field, const FieldDescriptorProto& proto) {
} }
void DescriptorBuilder::ValidateEnumOptions(const EnumDescriptor* enm, void DescriptorBuilder::ValidateOptions(const EnumDescriptor* enm,
const EnumDescriptorProto& proto) { const EnumDescriptorProto& proto) {
VALIDATE_OPTIONS_FROM_ARRAY(enm, value, EnumValue);
CheckEnumValueUniqueness(proto, enm); CheckEnumValueUniqueness(proto, enm);
if (!enm->options().has_allow_alias() || !enm->options().allow_alias()) { if (!enm->options().has_allow_alias() || !enm->options().allow_alias()) {
@ -7390,7 +7375,7 @@ void DescriptorBuilder::ValidateEnumOptions(const EnumDescriptor* enm,
} }
} }
void DescriptorBuilder::ValidateEnumValueOptions( void DescriptorBuilder::ValidateOptions(
const EnumValueDescriptor* /* enum_value */, const EnumValueDescriptor* /* enum_value */,
const EnumValueDescriptorProto& /* proto */) { const EnumValueDescriptorProto& /* proto */) {
// Nothing to do so far. // Nothing to do so far.
@ -7531,8 +7516,8 @@ void DescriptorBuilder::ValidateExtensionRangeOptions(
} }
} }
void DescriptorBuilder::ValidateServiceOptions( void DescriptorBuilder::ValidateOptions(const ServiceDescriptor* service,
const ServiceDescriptor* service, const ServiceDescriptorProto& proto) { const ServiceDescriptorProto& proto) {
if (IsLite(service->file()) && if (IsLite(service->file()) &&
(service->file()->options().cc_generic_services() || (service->file()->options().cc_generic_services() ||
service->file()->options().java_generic_services())) { service->file()->options().java_generic_services())) {
@ -7541,11 +7526,9 @@ void DescriptorBuilder::ValidateServiceOptions(
"unless you set both options cc_generic_services and " "unless you set both options cc_generic_services and "
"java_generic_services to false."); "java_generic_services to false.");
} }
VALIDATE_OPTIONS_FROM_ARRAY(service, method, Method);
} }
void DescriptorBuilder::ValidateMethodOptions( void DescriptorBuilder::ValidateOptions(
const MethodDescriptor* /* method */, const MethodDescriptor* /* method */,
const MethodDescriptorProto& /* proto */) { const MethodDescriptorProto& /* proto */) {
// Nothing to do so far. // Nothing to do so far.
@ -7725,8 +7708,6 @@ void DescriptorBuilder::ValidateJSType(const FieldDescriptor* field,
} }
} }
#undef VALIDATE_OPTIONS_FROM_ARRAY
// ------------------------------------------------------------------- // -------------------------------------------------------------------
DescriptorBuilder::OptionInterpreter::OptionInterpreter( DescriptorBuilder::OptionInterpreter::OptionInterpreter(

@ -0,0 +1,180 @@
// 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.
#ifndef GOOGLE_PROTOBUF_DESCRIPTOR_VISITOR_H__
#define GOOGLE_PROTOBUF_DESCRIPTOR_VISITOR_H__
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/generated_message_reflection.h"
namespace google {
namespace protobuf {
namespace internal {
// Visit every node in the descriptors calling `visitor(node, proto)`.
// The visitor does not need to handle all possible node types. Types that are
// not visitable via `visitor` will be ignored.
template <typename Visitor>
void VisitDescriptors(const FileDescriptor& file,
const FileDescriptorProto& proto, Visitor visitor);
// Visit just the descriptors, without a corresponding proto tree.
template <typename Visitor>
void VisitDescriptors(const FileDescriptor& file, Visitor visitor);
template <typename Visitor>
struct VisitImpl {
Visitor visitor;
template <typename... Proto>
void Visit(const FieldDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
}
template <typename... Proto>
void Visit(const EnumValueDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
}
template <typename... Proto>
void Visit(const EnumDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
for (int i = 0; i < descriptor.value_count(); i++) {
Visit(*descriptor.value(i), proto.value(i)...);
}
}
template <typename... Proto>
void Visit(const Descriptor::ExtensionRange& descriptor,
const Proto&... proto) {
visitor(descriptor, proto...);
}
template <typename... Proto>
void Visit(const OneofDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
}
template <typename... Proto>
void Visit(const Descriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
for (int i = 0; i < descriptor.enum_type_count(); i++) {
Visit(*descriptor.enum_type(i), proto.enum_type(i)...);
}
for (int i = 0; i < descriptor.oneof_decl_count(); i++) {
Visit(*descriptor.oneof_decl(i), proto.oneof_decl(i)...);
}
for (int i = 0; i < descriptor.field_count(); i++) {
Visit(*descriptor.field(i), proto.field(i)...);
}
for (int i = 0; i < descriptor.nested_type_count(); i++) {
Visit(*descriptor.nested_type(i), proto.nested_type(i)...);
}
for (int i = 0; i < descriptor.extension_count(); i++) {
Visit(*descriptor.extension(i), proto.extension(i)...);
}
for (int i = 0; i < descriptor.extension_range_count(); i++) {
Visit(*descriptor.extension_range(i), proto.extension_range(i)...);
}
}
template <typename... Proto>
void Visit(const MethodDescriptor& method, const Proto&... proto) {
visitor(method, proto...);
}
template <typename... Proto>
void Visit(const ServiceDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
for (int i = 0; i < descriptor.method_count(); i++) {
Visit(*descriptor.method(i), proto.method(i)...);
}
}
template <typename... Proto>
void Visit(const FileDescriptor& descriptor, const Proto&... proto) {
visitor(descriptor, proto...);
for (int i = 0; i < descriptor.message_type_count(); i++) {
Visit(*descriptor.message_type(i), proto.message_type(i)...);
}
for (int i = 0; i < descriptor.enum_type_count(); i++) {
Visit(*descriptor.enum_type(i), proto.enum_type(i)...);
}
for (int i = 0; i < descriptor.extension_count(); i++) {
Visit(*descriptor.extension(i), proto.extension(i)...);
}
for (int i = 0; i < descriptor.service_count(); i++) {
Visit(*descriptor.service(i), proto.service(i)...);
}
}
};
// Provide a fallback to ignore all the nodes that are not interesting to the
// input visitor.
template <typename Visitor>
struct VisitorImpl : Visitor {
explicit VisitorImpl(Visitor visitor) : Visitor(visitor) {}
// Pull in all of the supplied callbacks.
using Visitor::operator();
// Honeypots to ignore all inputs that Visitor does not take.
struct DescriptorEater {
template <typename T>
DescriptorEater(T&&) {} // NOLINT
};
void operator()(DescriptorEater, DescriptorEater) const {}
void operator()(DescriptorEater) const {}
};
template <typename Visitor>
void VisitDescriptors(const FileDescriptor& file,
const FileDescriptorProto& proto, Visitor visitor) {
using VisitorImpl = internal::VisitorImpl<Visitor>;
internal::VisitImpl<VisitorImpl>{VisitorImpl(visitor)}.Visit(file, proto);
}
template <typename Visitor>
void VisitDescriptors(const FileDescriptor& file, Visitor visitor) {
using VisitorImpl = internal::VisitorImpl<Visitor>;
internal::VisitImpl<VisitorImpl>{VisitorImpl(visitor)}.Visit(file);
}
} // namespace internal
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_DESCRIPTOR_VISITOR_H__

@ -0,0 +1,148 @@
// 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/descriptor_visitor.h"
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
#include "google/protobuf/unittest.pb.h"
namespace google {
namespace protobuf {
namespace {
using ::testing::Contains;
using ::testing::IsSupersetOf;
using ::testing::Not;
constexpr absl::string_view kUnittestProtoFile =
"google/protobuf/unittest.proto";
TEST(VisitDescriptorsTest, SingleTypeNoProto) {
const FileDescriptor& file =
*protobuf_unittest::TestAllTypes::GetDescriptor()->file();
std::vector<std::string> descriptors;
VisitDescriptors(file, [&](const Descriptor& descriptor) {
descriptors.push_back(descriptor.full_name());
});
EXPECT_THAT(descriptors,
IsSupersetOf({"protobuf_unittest.TestAllTypes",
"protobuf_unittest.TestAllTypes.NestedMessage"}));
}
TEST(VisitDescriptorsTest, SingleTypeWithProto) {
const FileDescriptor& file =
*protobuf_unittest::TestAllTypes::GetDescriptor()->file();
FileDescriptorProto proto;
file.CopyTo(&proto);
std::vector<std::string> descriptors;
VisitDescriptors(
file, proto,
[&](const Descriptor& descriptor, const DescriptorProto& proto) {
descriptors.push_back(descriptor.full_name());
EXPECT_EQ(descriptor.name(), proto.name());
});
EXPECT_THAT(descriptors,
IsSupersetOf({"protobuf_unittest.TestAllTypes",
"protobuf_unittest.TestAllTypes.NestedMessage"}));
}
TEST(VisitDescriptorsTest, AllTypesDeduce) {
const FileDescriptor& file =
*protobuf_unittest::TestAllTypes::GetDescriptor()->file();
std::vector<std::string> descriptors;
VisitDescriptors(file, [&](const auto& descriptor) {
descriptors.push_back(descriptor.name());
});
EXPECT_THAT(descriptors, Contains(kUnittestProtoFile));
EXPECT_THAT(descriptors, IsSupersetOf({"TestAllTypes", "TestSparseEnum",
"SPARSE_C", "optional_int32",
"oneof_nested_message", "oneof_field",
"optional_nested_message_extension"}));
}
TEST(VisitDescriptorsTest, AllTypesDeduceSelective) {
const FileDescriptor& file =
*protobuf_unittest::TestAllTypes::GetDescriptor()->file();
std::vector<std::string> descriptors;
VisitDescriptors(
file,
// Only select on descriptors with a full_name method.
[&](const auto& descriptor)
-> std::enable_if_t<
!std::is_void<decltype(descriptor.full_name())>::value> {
descriptors.push_back(descriptor.full_name());
});
// FileDescriptor doesn't have a full_name method.
EXPECT_THAT(descriptors, Not(Contains(kUnittestProtoFile)));
EXPECT_THAT(descriptors,
IsSupersetOf(
{"protobuf_unittest.TestAllTypes",
"protobuf_unittest.TestSparseEnum", "protobuf_unittest.SPARSE_C",
"protobuf_unittest.TestAllTypes.optional_int32",
"protobuf_unittest.TestAllTypes.oneof_nested_message",
"protobuf_unittest.TestAllTypes.oneof_field",
"protobuf_unittest.optional_nested_message_extension"}));
}
void TestHandle(const Descriptor& message, const DescriptorProto& proto,
std::vector<std::string>* result) {
if (result != nullptr) result->push_back(message.full_name());
EXPECT_EQ(message.name(), proto.name());
}
void TestHandle(const EnumDescriptor& enm, const EnumDescriptorProto& proto,
std::vector<std::string>* result) {
if (result != nullptr) result->push_back(enm.full_name());
EXPECT_EQ(enm.name(), proto.name());
}
TEST(VisitDescriptorsTest, AllTypesDeduceDelegate) {
const FileDescriptor& file =
*protobuf_unittest::TestAllTypes::GetDescriptor()->file();
FileDescriptorProto proto;
file.CopyTo(&proto);
std::vector<std::string> descriptors;
VisitDescriptors(file, proto,
[&](const auto& descriptor, const auto& proto)
-> decltype(TestHandle(descriptor, proto, nullptr)) {
TestHandle(descriptor, proto, &descriptors);
});
EXPECT_THAT(descriptors, IsSupersetOf({"protobuf_unittest.TestAllTypes",
"protobuf_unittest.TestSparseEnum"}));
}
} // namespace
} // namespace protobuf
} // namespace google
Loading…
Cancel
Save