// Protocol Buffers - Google's data interchange format // Copyright 2008 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/compiler/objectivec/enum.h" #include #include #include "absl/container/flat_hash_set.h" #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "google/protobuf/compiler/objectivec/helpers.h" #include "google/protobuf/compiler/objectivec/names.h" #include "google/protobuf/compiler/objectivec/text_format_decode_data.h" #include "google/protobuf/io/printer.h" namespace google { namespace protobuf { namespace compiler { namespace objectivec { namespace { std::string SafelyPrintIntToCode(int v) { if (v == std::numeric_limits::min()) { // Some compilers try to parse -2147483648 as two tokens and then get spicy // about the fact that +2147483648 cannot be represented as an int. return absl::StrCat(v + 1, " - 1"); } else { return absl::StrCat(v); } } } // namespace EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor) : descriptor_(descriptor), name_(EnumName(descriptor_)) { // Track the names for the enum values, and if an alias overlaps a base // value, skip making a name for it. Likewise if two alias overlap, the // first one wins. // The one gap in this logic is if two base values overlap, but for that // to happen you have to have "Foo" and "FOO" or "FOO_BAR" and "FooBar", // and if an enum has that, it is already going to be confusing and a // compile error is just fine. // The values are still tracked to support the reflection apis and // TextFormat handing since they are different there. absl::flat_hash_set value_names; for (int i = 0; i < descriptor_->value_count(); i++) { const EnumValueDescriptor* value = descriptor_->value(i); const EnumValueDescriptor* canonical_value = descriptor_->FindValueByNumber(value->number()); if (value == canonical_value) { base_values_.push_back(value); value_names.insert(EnumValueName(value)); } else { if (!value_names.insert(EnumValueName(value)).second) { alias_values_to_skip_.insert(value); } } all_values_.push_back(value); } } void EnumGenerator::GenerateHeader(io::Printer* printer) const { // Swift 5 included SE0192 "Handling Future Enum Cases" // https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md // Since a .proto file can get new values added to an enum at any time, they // are effectively "non-frozen". Even in a proto3 syntax file where there is // support for the unknown value, an edit to the file can always add a new // value moving something from unknown to known. Since Swift is now ABI // stable, it also means a binary could contain Swift compiled against one // version of the .pbobjc.h file, but finally linked against an enum with // more cases. So the Swift code will always have to treat ObjC Proto Enums // as "non-frozen". The default behavior in SE0192 is for all objc enums to // be "non-frozen" unless marked as otherwise, so this means this generation // doesn't have to bother with the `enum_extensibility` attribute, as the // default will be what is needed. printer->Emit( { {"enum_name", name_}, {"enum_comments", [&] { EmitCommentsString(printer, descriptor_); }}, {"enum_deprecated_attribute", GetOptionalDeprecatedAttribute(descriptor_, descriptor_->file())}, {"maybe_unknown_value", [&] { if (descriptor_->is_closed()) return; // Include the unknown value. printer->Emit(R"objc( /** * Value used if any message's field encounters a value that is not defined * by this enum. The message will also have C functions to get/set the rawValue * of the field. **/ $enum_name$_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue, )objc"); }}, {"enum_values", [&] { CommentStringFlags comment_flags = CommentStringFlags::kNone; for (const auto* v : all_values_) { if (alias_values_to_skip_.contains(v)) continue; printer->Emit( { {"name", EnumValueName(v)}, {"comments", [&] { EmitCommentsString(printer, v, comment_flags); }}, {"deprecated_attribute", GetOptionalDeprecatedAttribute(v)}, {"value", SafelyPrintIntToCode(v->number())}, }, R"objc( $comments$ $name$$deprecated_attribute$ = $value$, )objc"); comment_flags = CommentStringFlags::kAddLeadingNewline; } }}, }, R"objc( #pragma mark - Enum $enum_name$ $enum_comments$ typedef$enum_deprecated_attribute$ GPB_ENUM($enum_name$) { $maybe_unknown_value$ $enum_values$ }; GPBEnumDescriptor *$enum_name$_EnumDescriptor(void); /** * Checks to see if the given value is defined by the enum or was not known at * the time this source was generated. **/ BOOL $enum_name$_IsValidValue(int32_t value); )objc"); printer->Emit("\n"); } void EnumGenerator::GenerateSource(io::Printer* printer) const { // Note: For the TextFormat decode info, we can't use the enum value as // the key because protocol buffer enums have 'allow_alias', which lets // a value be used more than once. Instead, the index into the list of // enum value descriptions is used. Note: start with -1 so the first one // will be zero. TextFormatDecodeData text_format_decode_data; int enum_value_description_key = -1; std::string text_blob; for (const auto* v : all_values_) { ++enum_value_description_key; std::string short_name(EnumValueShortName(v)); text_blob += short_name + '\0'; if (UnCamelCaseEnumShortName(short_name) != v->name()) { text_format_decode_data.AddString(enum_value_description_key, short_name, v->name()); } } printer->Emit( {{"name", name_}, {"values_name_blob", [&] { static const int kBytesPerLine = 40; // allow for escaping for (size_t i = 0; i < text_blob.size(); i += kBytesPerLine) { printer->Emit({{"data", EscapeTrigraphs(absl::CEscape( text_blob.substr(i, kBytesPerLine)))}, {"ending_semi", (i + kBytesPerLine) < text_blob.size() ? "" : ";"}}, R"objc( "$data$"$ending_semi$ )objc"); } }}, {"values", [&] { for (const auto* v : all_values_) { printer->Emit({{"value_name", EnumValueName(v)}}, R"objc( $value_name$, )objc"); } }}, {"maybe_extra_text_format_decl", [&] { if (text_format_decode_data.num_entries()) { printer->Emit({{"extraTextFormatInfo", absl::CEscape(text_format_decode_data.Data())}}, R"objc( static const char *extraTextFormatInfo = "$extraTextFormatInfo$"; )objc"); } }}, {"maybe_extraTextFormatInfo", // Could not find a better way to get this extra line inserted and // correctly formatted. (text_format_decode_data.num_entries() == 0 ? "" : "\n " "extraTextFormatInfo:extraTextFormatInfo")}, {"enum_flags", descriptor_->is_closed() ? "GPBEnumDescriptorInitializationFlag_IsClosed" : "GPBEnumDescriptorInitializationFlag_None"}, {"enum_cases", [&] { for (const auto* v : base_values_) { printer->Emit({{"case_name", EnumValueName(v)}}, R"objc( case $case_name$: )objc"); } }}}, R"objc( #pragma mark - Enum $name$ GPBEnumDescriptor *$name$_EnumDescriptor(void) { static _Atomic(GPBEnumDescriptor*) descriptor = nil; if (!descriptor) { GPB_DEBUG_CHECK_RUNTIME_VERSIONS(); static const char *valueNames = $values_name_blob$ static const int32_t values[] = { $values$ }; $maybe_extra_text_format_decl$ GPBEnumDescriptor *worker = [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol($name$) valueNames:valueNames values:values count:(uint32_t)(sizeof(values) / sizeof(int32_t)) enumVerifier:$name$_IsValidValue flags:$enum_flags$$maybe_extraTextFormatInfo$]; GPBEnumDescriptor *expected = nil; if (!atomic_compare_exchange_strong(&descriptor, &expected, worker)) { [worker release]; } } return descriptor; } BOOL $name$_IsValidValue(int32_t value__) { switch (value__) { $enum_cases$ return YES; default: return NO; } } )objc"); printer->Emit("\n"); } } // namespace objectivec } // namespace compiler } // namespace protobuf } // namespace google