| #include "google/protobuf/symbol_checker.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "absl/strings/str_cat.h" |
| #include "google/protobuf/descriptor.h" |
| #include "google/protobuf/descriptor.pb.h" |
| #include "google/protobuf/descriptor_visitor.h" |
| |
| // Must be included last. |
| #include "google/protobuf/port_def.inc" |
| |
| namespace google { |
| namespace protobuf { |
| |
| std::string SymbolCheckerError::message() const { |
| switch (type_) { |
| case NESTED_MESSAGE_STRICT_VIOLATION: |
| return absl::StrCat( |
| "\"", symbol_name_, |
| "\" is a nested message and cannot be `export` with STRICT " |
| "default_symbol_visibility. It must be moved to top-level, " |
| "ideally " |
| "in its own file in order to be `export`."); |
| case NESTED_ENUM_STRICT_VIOLATION: |
| return absl::StrCat( |
| "\"", symbol_name_, |
| "\" is a nested enum and cannot be marked `export` with " |
| "STRICT " |
| "default_symbol_visibility. It must be moved to " |
| "top-level, ideally " |
| "in its own file in order to be `export`. For C++ " |
| "namespacing of enums in a messages use: `local " |
| "message <OuterNamespace> { export enum ", |
| symbol_name_, " {...} reserved 1 to max; }`"); |
| default: |
| return ""; |
| } |
| } |
| |
| namespace { |
| |
| // Populate VisibilityCheckerState for messages. |
| void VisitType(const Descriptor& message, const DescriptorProto& proto, |
| internal::SymbolCheckerState* state) { |
| if (message.containing_type() != nullptr) { |
| state->nested_messages.push_back( |
| internal::MessageDescriptorAndProto{&message, &proto}); |
| } |
| } |
| |
| void VisitType(const EnumDescriptor& enm, const EnumDescriptorProto& proto, |
| internal::SymbolCheckerState* state) { |
| if (enm.containing_type() != nullptr) { |
| if (SymbolChecker::IsNamespacedEnum(enm)) { |
| state->namespaced_enums.push_back( |
| internal::EnumDescriptorAndProto{&enm, &proto}); |
| } else { |
| state->nested_enums.push_back( |
| internal::EnumDescriptorAndProto{&enm, &proto}); |
| } |
| } |
| } |
| void VisitType(const FileDescriptor& file, const FileDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const FieldDescriptor& field, const FieldDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const EnumValueDescriptor& value, |
| const EnumValueDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const OneofDescriptor& one, const OneofDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const Descriptor::ExtensionRange&, |
| const DescriptorProto::ExtensionRange& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const MethodDescriptor& method, |
| const MethodDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| void VisitType(const ServiceDescriptor& service, |
| const ServiceDescriptorProto& proto, |
| internal::SymbolCheckerState* state) {} |
| |
| } // namespace |
| |
| SymbolChecker::SymbolChecker(const FileDescriptor* file, |
| const FileDescriptorProto& proto) |
| : initialized_(false), descriptor_(file), proto_(proto) {} |
| |
| bool SymbolChecker::IsEnumNamespaceMessage(const Descriptor& container) { |
| const FeatureSet::VisibilityFeature::DefaultSymbolVisibility |
| default_visibility = |
| container.file()->features().default_symbol_visibility(); |
| |
| // Only allowed for top-level messages |
| if (container.containing_type() != nullptr) { |
| return false; |
| } |
| |
| bool default_to_local = |
| default_visibility == FeatureSet::VisibilityFeature::STRICT || |
| default_visibility == FeatureSet::VisibilityFeature::LOCAL_ALL; |
| |
| bool is_container_local = |
| container.visibility_keyword() == SymbolVisibility::VISIBILITY_LOCAL || |
| (container.visibility_keyword() == SymbolVisibility::VISIBILITY_UNSET && |
| default_to_local); |
| |
| // the containing message must either be marked local, or unset with file |
| // default making it local |
| if (!is_container_local) { |
| return false; |
| } |
| |
| // We also require a single reserved range of 1 to max. |
| if (container.reserved_range_count() != 1) { |
| return false; |
| } |
| |
| // Always not-null due to reserved_range_count check above. |
| const Descriptor::ReservedRange* range = container.reserved_range(0); |
| |
| // The reserved range must be 1 to max. Max is exclusive so results in |
| // kMaxNumber + 1 |
| return range->start == 1 && range->end == (FieldDescriptor::kMaxNumber + 1); |
| } |
| |
| bool SymbolChecker::IsNamespacedEnum(const EnumDescriptor& enm) { |
| // If the enum is top-level, it definitely isn't namespaced. |
| if (enm.containing_type() == nullptr) { |
| return false; |
| } |
| |
| const FeatureSet::VisibilityFeature::DefaultSymbolVisibility |
| default_visibility = enm.file()->features().default_symbol_visibility(); |
| |
| bool default_to_local = |
| default_visibility == FeatureSet::VisibilityFeature::STRICT || |
| default_visibility == FeatureSet::VisibilityFeature::LOCAL_ALL; |
| |
| // This check only cares if the enum is explicitly marked export, the |
| // containing message can be local or unset with the file default making it |
| // local. |
| bool is_exported = |
| !default_to_local || |
| enm.visibility_keyword() == SymbolVisibility::VISIBILITY_EXPORT; |
| return is_exported && IsEnumNamespaceMessage(*enm.containing_type()); |
| } |
| |
| void SymbolChecker::Initialize() { |
| if (initialized_) { |
| return; |
| } |
| |
| internal::VisitDescriptors(*descriptor_, proto_, |
| [&](const auto& descriptor, const auto& proto) { |
| VisitType(descriptor, proto, &state_); |
| }); |
| |
| initialized_ = true; |
| } |
| |
| // Enforce File-wide visibility and future co-location rules. |
| std::vector<SymbolCheckerError> SymbolChecker::CheckSymbolVisibilityRules() { |
| std::vector<SymbolCheckerError> errors; |
| |
| Initialize(); |
| |
| // Check DefaultSymbolVisibility first. |
| // |
| // For Edition 2024: |
| // If DefaultSymbolVisibility is STRICT enforce it with caveats for: |
| |
| // Build our state object so we can apply rules based on type. |
| |
| // In edition 2024 we only enforce STRICT visibility rules. There are possibly |
| // more rules to come in future editions, but for now just apply the rule for |
| // enforcing nested symbol local visibility. There is a single caveat for, |
| // allowing nested enums to have visibility set only when |
| // |
| // local msg { export enum {} reserved 1 to max; } |
| for (auto& nested : state_.nested_messages) { |
| if (nested.descriptor->visibility_keyword() == |
| SymbolVisibility::VISIBILITY_EXPORT && |
| nested.descriptor->features().default_symbol_visibility() == |
| FeatureSet::VisibilityFeature::STRICT) { |
| errors.push_back(SymbolCheckerError(nested.descriptor->full_name(), |
| *nested.proto, |
| NESTED_MESSAGE_STRICT_VIOLATION)); |
| } |
| } |
| |
| for (auto& nested : state_.nested_enums) { |
| if (nested.descriptor->visibility_keyword() == |
| SymbolVisibility::VISIBILITY_EXPORT && |
| nested.descriptor->features().default_symbol_visibility() == |
| FeatureSet::VisibilityFeature::STRICT) { |
| // This list contains only enums not considered 'namespaced' by |
| // IsEnumNamespaceMessage |
| errors.push_back(SymbolCheckerError(nested.descriptor->full_name(), |
| *nested.proto, |
| NESTED_ENUM_STRICT_VIOLATION)); |
| } |
| } |
| |
| // Enforce Future rules here: |
| return errors; |
| } |
| |
| } // namespace protobuf |
| } // namespace google |
| |
| #include "google/protobuf/port_undef.inc" |