// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd #import "GPBDescriptor_PackagePrivate.h" #import #import "GPBMessage_PackagePrivate.h" #import "GPBUtilities_PackagePrivate.h" #import "GPBWireFormat.h" @interface GPBDescriptor () - (instancetype)initWithClass:(Class)messageClass messageName:(NSString *)messageName fileDescription:(GPBFileDescription *)fileDescription fields:(NSArray *)fields storageSize:(uint32_t)storage wireFormat:(BOOL)wireFormat; @end @interface GPBFieldDescriptor () // Single initializer // description has to be long lived, it is held as a raw pointer. - (instancetype)initWithFieldDescription:(void *)description descriptorFlags:(GPBDescriptorInitializationFlags)descriptorFlags; @end @interface GPBEnumDescriptor () - (instancetype)initWithName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier flags:(GPBEnumDescriptorInitializationFlags)flags; @end // Direct access is use for speed, to avoid even internally declaring things // read/write, etc. The warning is enabled in the project to ensure code calling // protos can turn on -Wdirect-ivar-access without issues. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdirect-ivar-access" // The addresses of these variables are used as keys for objc_getAssociatedObject. static const char kTextFormatExtraValueKey = 0; static const char kParentClassValueKey = 0; static const char kClassNameSuffixKey = 0; static const char kFileDescriptorCacheKey = 0; // Utility function to generate selectors on the fly. static SEL SelFromStrings(const char *prefix, const char *middle, const char *suffix, BOOL takesArg) { if (prefix == NULL && suffix == NULL && !takesArg) { return sel_getUid(middle); } const size_t prefixLen = prefix != NULL ? strlen(prefix) : 0; const size_t middleLen = strlen(middle); const size_t suffixLen = suffix != NULL ? strlen(suffix) : 0; size_t totalLen = prefixLen + middleLen + suffixLen + 1; // include space for null on end. if (takesArg) { totalLen += 1; } char buffer[totalLen]; if (prefix != NULL) { memcpy(buffer, prefix, prefixLen); memcpy(buffer + prefixLen, middle, middleLen); buffer[prefixLen] = (char)toupper(buffer[prefixLen]); } else { memcpy(buffer, middle, middleLen); } if (suffix != NULL) { memcpy(buffer + prefixLen + middleLen, suffix, suffixLen); } if (takesArg) { buffer[totalLen - 2] = ':'; } // Always null terminate it. buffer[totalLen - 1] = 0; SEL result = sel_getUid(buffer); return result; } static NSArray *NewFieldsArrayForHasIndex(int hasIndex, NSArray *allMessageFields) __attribute__((ns_returns_retained)); static NSArray *NewFieldsArrayForHasIndex(int hasIndex, NSArray *allMessageFields) { NSMutableArray *result = [[NSMutableArray alloc] init]; for (GPBFieldDescriptor *fieldDesc in allMessageFields) { if (fieldDesc->description_->hasIndex == hasIndex) { [result addObject:fieldDesc]; } } return result; } @implementation GPBDescriptor { Class messageClass_; NSString *messageName_; const GPBFileDescription *fileDescription_; BOOL wireFormat_; } @synthesize messageClass = messageClass_; @synthesize fields = fields_; @synthesize oneofs = oneofs_; @synthesize extensionRanges = extensionRanges_; @synthesize extensionRangesCount = extensionRangesCount_; @synthesize wireFormat = wireFormat_; + (instancetype)allocDescriptorForClass:(Class)messageClass messageName:(NSString *)messageName fileDescription:(GPBFileDescription *)fileDescription fields:(void *)fieldDescriptions fieldCount:(uint32_t)fieldCount storageSize:(uint32_t)storageSize flags:(GPBDescriptorInitializationFlags)flags { // Compute the unknown flags by this version of the runtime and then check the passed in flags // (from the generated code) to detect when sources from a newer version are being used with an // older runtime. GPBDescriptorInitializationFlags unknownFlags = ~(GPBDescriptorInitializationFlag_FieldsWithDefault | GPBDescriptorInitializationFlag_WireFormat | GPBDescriptorInitializationFlag_UsesClassRefs | GPBDescriptorInitializationFlag_Proto3OptionalKnown | GPBDescriptorInitializationFlag_ClosedEnumSupportKnown); if ((flags & unknownFlags) != 0) { GPBRuntimeMatchFailure(); } #if defined(DEBUG) && DEBUG NSAssert((flags & GPBDescriptorInitializationFlag_UsesClassRefs) != 0, @"Internal error: all fields should have class refs"); NSAssert((flags & GPBDescriptorInitializationFlag_Proto3OptionalKnown) != 0, @"Internal error: proto3 optional should be known"); NSAssert((flags & GPBDescriptorInitializationFlag_ClosedEnumSupportKnown) != 0, @"Internal error: close enum should be known"); // `messageName` and `fileDescription` should both be set or both be unset depending on if this is // being called from current code generation or legacy code generation. NSAssert((messageName == nil) == (fileDescription == NULL), @"name and fileDescription should always be provided together"); #endif NSMutableArray *fields = (fieldCount ? [[NSMutableArray alloc] initWithCapacity:fieldCount] : nil); BOOL fieldsIncludeDefault = (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0; void *desc; GPBFieldFlags mergedFieldFlags = GPBFieldNone; for (uint32_t i = 0; i < fieldCount; ++i) { // Need correctly typed pointer for array indexing below to work. if (fieldsIncludeDefault) { desc = &(((GPBMessageFieldDescriptionWithDefault *)fieldDescriptions)[i]); mergedFieldFlags |= (((GPBMessageFieldDescriptionWithDefault *)fieldDescriptions)[i]).core.flags; } else { desc = &(((GPBMessageFieldDescription *)fieldDescriptions)[i]); mergedFieldFlags |= (((GPBMessageFieldDescription *)fieldDescriptions)[i]).flags; } GPBFieldDescriptor *fieldDescriptor = [[GPBFieldDescriptor alloc] initWithFieldDescription:desc descriptorFlags:flags]; [fields addObject:fieldDescriptor]; [fieldDescriptor release]; } // No real value in checking all the fields individually, just check the combined flags at the // end. GPBFieldFlags unknownFieldFlags = ~(GPBFieldRequired | GPBFieldRepeated | GPBFieldPacked | GPBFieldOptional | GPBFieldHasDefaultValue | GPBFieldClearHasIvarOnZero | GPBFieldTextFormatNameCustom | GPBFieldHasEnumDescriptor | GPBFieldMapKeyMask | GPBFieldClosedEnum); if ((mergedFieldFlags & unknownFieldFlags) != 0) { GPBRuntimeMatchFailure(); } BOOL wireFormat = (flags & GPBDescriptorInitializationFlag_WireFormat) != 0; GPBDescriptor *descriptor = [[self alloc] initWithClass:messageClass messageName:messageName fileDescription:fileDescription fields:fields storageSize:storageSize wireFormat:wireFormat]; [fields release]; return descriptor; } + (instancetype)allocDescriptorForClass:(Class)messageClass file:(GPBFileDescriptor *)file fields:(void *)fieldDescriptions fieldCount:(uint32_t)fieldCount storageSize:(uint32_t)storageSize flags:(GPBDescriptorInitializationFlags)flags { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30006, time_to_remove_this_old_version_shim); BOOL fixClassRefs = (flags & GPBDescriptorInitializationFlag_UsesClassRefs) == 0; GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30003, time_to_remove_non_class_ref_support); BOOL fixProto3Optional = (flags & GPBDescriptorInitializationFlag_Proto3OptionalKnown) == 0; GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30004, time_to_remove_proto3_optional_fallback); BOOL fixClosedEnums = (flags & GPBDescriptorInitializationFlag_ClosedEnumSupportKnown) == 0; GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30005, time_to_remove_closed_enum_fallback); if (fixClassRefs || fixProto3Optional || fixClosedEnums) { BOOL fieldsIncludeDefault = (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" GPBFileSyntax fileSyntax = file.syntax; #pragma clang diagnostic pop for (uint32_t i = 0; i < fieldCount; ++i) { GPBMessageFieldDescription *coreDesc; if (fieldsIncludeDefault) { coreDesc = &((((GPBMessageFieldDescriptionWithDefault *)fieldDescriptions)[i]).core); } else { coreDesc = &(((GPBMessageFieldDescription *)fieldDescriptions)[i]); } if (fixClassRefs && GPBDataTypeIsMessage(coreDesc->dataType)) { const char *className = coreDesc->dataTypeSpecific.className; Class msgClass = objc_getClass(className); NSAssert(msgClass, @"Class %s not defined", className); coreDesc->dataTypeSpecific.clazz = msgClass; } if (fixProto3Optional) { // If it was... // - proto3 syntax // - not repeated/map // - not in a oneof (negative has index) // - not a message (the flag doesn't make sense for messages) BOOL clearOnZero = ((fileSyntax == GPBFileSyntaxProto3) && ((coreDesc->flags & (GPBFieldRepeated | GPBFieldMapKeyMask)) == 0) && (coreDesc->hasIndex >= 0) && !GPBDataTypeIsMessage(coreDesc->dataType)); if (clearOnZero) { coreDesc->flags |= GPBFieldClearHasIvarOnZero; } } if (fixClosedEnums) { // NOTE: This isn't correct, it is using the syntax of the file that // declared the field, not the syntax of the file that declared the // enum; but for older generated code, that's all we have and that happens // to be what the runtime was doing (even though it was wrong). This is // only wrong in the rare cases an enum is declared in a proto3 syntax // file but used for a field in the proto2 syntax file. BOOL isClosedEnum = (coreDesc->dataType == GPBDataTypeEnum && fileSyntax == GPBFileSyntaxProto2); if (isClosedEnum) { coreDesc->flags |= GPBFieldClosedEnum; } } } flags |= (GPBDescriptorInitializationFlag_UsesClassRefs | GPBDescriptorInitializationFlag_Proto3OptionalKnown | GPBDescriptorInitializationFlag_ClosedEnumSupportKnown); } GPBDescriptor *result = [self allocDescriptorForClass:messageClass messageName:nil fileDescription:NULL fields:fieldDescriptions fieldCount:fieldCount storageSize:storageSize flags:flags]; objc_setAssociatedObject(result, &kFileDescriptorCacheKey, file, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return result; } + (instancetype)allocDescriptorForClass:(Class)messageClass rootClass:(__unused Class)rootClass file:(GPBFileDescriptor *)file fields:(void *)fieldDescriptions fieldCount:(uint32_t)fieldCount storageSize:(uint32_t)storageSize flags:(GPBDescriptorInitializationFlags)flags { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30006, time_to_remove_this_old_version_shim); // The rootClass is no longer used, but it is passed as [ROOT class] to // ensure it was started up during initialization also when the message // scopes extensions. return [self allocDescriptorForClass:messageClass file:file fields:fieldDescriptions fieldCount:fieldCount storageSize:storageSize flags:flags]; } - (instancetype)initWithClass:(Class)messageClass messageName:(NSString *)messageName fileDescription:(GPBFileDescription *)fileDescription fields:(NSArray *)fields storageSize:(uint32_t)storageSize wireFormat:(BOOL)wireFormat { if ((self = [super init])) { messageClass_ = messageClass; messageName_ = [messageName copy]; fileDescription_ = fileDescription; fields_ = [fields retain]; storageSize_ = storageSize; wireFormat_ = wireFormat; } return self; } - (void)dealloc { [messageName_ release]; [fields_ release]; [oneofs_ release]; [super dealloc]; } // No need to provide -hash/-isEqual: as the instances are singletons and the // default from NSObject is fine. - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } - (void)setupOneofs:(const char **)oneofNames count:(uint32_t)count firstHasIndex:(int32_t)firstHasIndex { NSCAssert(firstHasIndex < 0, @"Should always be <0"); NSMutableArray *oneofs = [[NSMutableArray alloc] initWithCapacity:count]; for (uint32_t i = 0, hasIndex = firstHasIndex; i < count; ++i, --hasIndex) { const char *name = oneofNames[i]; NSArray *fieldsForOneof = NewFieldsArrayForHasIndex(hasIndex, fields_); NSCAssert(fieldsForOneof.count > 0, @"No fields for this oneof? (%s:%d)", name, hasIndex); GPBOneofDescriptor *oneofDescriptor = [[GPBOneofDescriptor alloc] initWithName:name fields:fieldsForOneof]; [oneofs addObject:oneofDescriptor]; [oneofDescriptor release]; [fieldsForOneof release]; } oneofs_ = oneofs; } - (void)setupExtraTextInfo:(const char *)extraTextFormatInfo { // Extra info is a compile time option, so skip the work if not needed. if (extraTextFormatInfo) { NSValue *extraInfoValue = [NSValue valueWithPointer:extraTextFormatInfo]; for (GPBFieldDescriptor *fieldDescriptor in fields_) { if (fieldDescriptor->description_->flags & GPBFieldTextFormatNameCustom) { objc_setAssociatedObject(fieldDescriptor, &kTextFormatExtraValueKey, extraInfoValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } } } - (void)setupExtensionRanges:(const GPBExtensionRange *)ranges count:(int32_t)count { extensionRanges_ = ranges; extensionRangesCount_ = count; } - (void)setupContainingMessageClass:(Class)messageClass { objc_setAssociatedObject(self, &kParentClassValueKey, messageClass, OBJC_ASSOCIATION_ASSIGN); } - (void)setupContainingMessageClassName:(const char *)msgClassName { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30003, time_to_remove_this_old_version_shim); // Note: Only fetch the class here, can't send messages to it because // that could cause cycles back to this class within +initialize if // two messages have each other in fields (i.e. - they build a graph). Class clazz = objc_getClass(msgClassName); NSAssert(clazz, @"Class %s not defined", msgClassName); [self setupContainingMessageClass:clazz]; } - (void)setupMessageClassNameSuffix:(NSString *)suffix { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30007, time_to_remove_this_old_version_shim); if (suffix.length) { objc_setAssociatedObject(self, &kClassNameSuffixKey, suffix, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } - (NSString *)name { return NSStringFromClass(messageClass_); } - (GPBFileDescriptor *)file { @synchronized(self) { GPBFileDescriptor *result = objc_getAssociatedObject(self, &kFileDescriptorCacheKey); if (!result) { #if defined(DEBUG) && DEBUG NSAssert(fileDescription_ != NULL, @"Internal error in generation/startup"); #endif // `package` and `prefix` can both be NULL if there wasn't one for the file. NSString *package = fileDescription_->package ? @(fileDescription_->package) : @""; if (fileDescription_->prefix) { result = [[GPBFileDescriptor alloc] initWithPackage:package objcPrefix:@(fileDescription_->prefix) syntax:fileDescription_->syntax]; } else { result = [[GPBFileDescriptor alloc] initWithPackage:package syntax:fileDescription_->syntax]; } objc_setAssociatedObject(result, &kFileDescriptorCacheKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return result; } } - (GPBDescriptor *)containingType { Class parentClass = objc_getAssociatedObject(self, &kParentClassValueKey); return [parentClass descriptor]; } - (NSString *)fullName { GPBDescriptor *parent = self.containingType; if (messageName_) { if (parent) { return [NSString stringWithFormat:@"%@.%@", parent.fullName, messageName_]; } if (fileDescription_->package) { return [NSString stringWithFormat:@"%s.%@", fileDescription_->package, messageName_]; } return messageName_; } GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30007, time_to_remove_this_old_approach); // NOTE: When this code path is removed, this also means this api can't return nil any more but // that would be a breaking code change (not longer a Swift optional), so changing that will be // harder. NSString *className = NSStringFromClass(self.messageClass); GPBFileDescriptor *file = self.file; NSString *objcPrefix = file.objcPrefix; if (objcPrefix && ![className hasPrefix:objcPrefix]) { NSAssert(0, @"Class didn't have correct prefix? (%@ - %@)", className, objcPrefix); return nil; } NSString *name = nil; if (parent) { NSString *parentClassName = NSStringFromClass(parent.messageClass); // The generator will add _Class to avoid reserved words, drop it. NSString *suffix = objc_getAssociatedObject(parent, &kClassNameSuffixKey); if (suffix) { if (![parentClassName hasSuffix:suffix]) { NSAssert(0, @"ParentMessage class didn't have correct suffix? (%@ - %@)", className, suffix); return nil; } parentClassName = [parentClassName substringToIndex:(parentClassName.length - suffix.length)]; } NSString *parentPrefix = [parentClassName stringByAppendingString:@"_"]; if (![className hasPrefix:parentPrefix]) { NSAssert(0, @"Class didn't have the correct parent name prefix? (%@ - %@)", parentPrefix, className); return nil; } name = [className substringFromIndex:parentPrefix.length]; } else { name = [className substringFromIndex:objcPrefix.length]; } // The generator will add _Class to avoid reserved words, drop it. NSString *suffix = objc_getAssociatedObject(self, &kClassNameSuffixKey); if (suffix) { if (![name hasSuffix:suffix]) { NSAssert(0, @"Message class didn't have correct suffix? (%@ - %@)", name, suffix); return nil; } name = [name substringToIndex:(name.length - suffix.length)]; } NSString *prefix = (parent != nil ? parent.fullName : file.package); NSString *result; if (prefix.length > 0) { result = [NSString stringWithFormat:@"%@.%@", prefix, name]; } else { result = name; } return result; } - (GPBFieldDescriptor *)fieldWithNumber:(uint32_t)fieldNumber { for (GPBFieldDescriptor *descriptor in fields_) { if (GPBFieldNumber(descriptor) == fieldNumber) { return descriptor; } } return nil; } - (GPBFieldDescriptor *)fieldWithName:(NSString *)name { for (GPBFieldDescriptor *descriptor in fields_) { if ([descriptor.name isEqual:name]) { return descriptor; } } return nil; } - (GPBOneofDescriptor *)oneofWithName:(NSString *)name { for (GPBOneofDescriptor *descriptor in oneofs_) { if ([descriptor.name isEqual:name]) { return descriptor; } } return nil; } @end @implementation GPBFileDescriptor { NSString *package_; NSString *objcPrefix_; GPBFileSyntax syntax_; } @synthesize package = package_; @synthesize objcPrefix = objcPrefix_; @synthesize syntax = syntax_; - (instancetype)initWithPackage:(NSString *)package objcPrefix:(NSString *)objcPrefix syntax:(GPBFileSyntax)syntax { self = [super init]; if (self) { package_ = [package copy]; objcPrefix_ = [objcPrefix copy]; syntax_ = syntax; } return self; } - (instancetype)initWithPackage:(NSString *)package syntax:(GPBFileSyntax)syntax { self = [super init]; if (self) { package_ = [package copy]; syntax_ = syntax; } return self; } - (void)dealloc { [package_ release]; [objcPrefix_ release]; [super dealloc]; } - (BOOL)isEqual:(id)other { if (other == self) { return YES; } if (![other isKindOfClass:[GPBFileDescriptor class]]) { return NO; } GPBFileDescriptor *otherFile = other; // objcPrefix can be nil, otherwise, straight up compare. return (syntax_ == otherFile->syntax_ && [package_ isEqual:otherFile->package_] && (objcPrefix_ == otherFile->objcPrefix_ || (otherFile->objcPrefix_ && [objcPrefix_ isEqual:otherFile->objcPrefix_]))); } - (NSUInteger)hash { // The prefix is recommended to be the same for a given package, so just hash // the package. return [package_ hash]; } - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } @end @implementation GPBOneofDescriptor @synthesize fields = fields_; - (instancetype)initWithName:(const char *)name fields:(NSArray *)fields { self = [super init]; if (self) { name_ = name; fields_ = [fields retain]; for (GPBFieldDescriptor *fieldDesc in fields) { fieldDesc->containingOneof_ = self; } caseSel_ = SelFromStrings(NULL, name, "OneOfCase", NO); } return self; } - (void)dealloc { [fields_ release]; [super dealloc]; } // No need to provide -hash/-isEqual: as the instances are singletons and the // default from NSObject is fine. - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } - (NSString *)name { return (NSString *_Nonnull)@(name_); } - (GPBFieldDescriptor *)fieldWithNumber:(uint32_t)fieldNumber { for (GPBFieldDescriptor *descriptor in fields_) { if (GPBFieldNumber(descriptor) == fieldNumber) { return descriptor; } } return nil; } - (GPBFieldDescriptor *)fieldWithName:(NSString *)name { for (GPBFieldDescriptor *descriptor in fields_) { if ([descriptor.name isEqual:name]) { return descriptor; } } return nil; } @end uint32_t GPBFieldTag(GPBFieldDescriptor *self) { GPBMessageFieldDescription *description = self->description_; GPBWireFormat format; if ((description->flags & GPBFieldMapKeyMask) != 0) { // Maps are repeated messages on the wire. format = GPBWireFormatForType(GPBDataTypeMessage, NO); } else { format = GPBWireFormatForType(description->dataType, ((description->flags & GPBFieldPacked) != 0)); } return GPBWireFormatMakeTag(description->number, format); } uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) { GPBMessageFieldDescription *description = self->description_; NSCAssert((description->flags & GPBFieldRepeated) != 0, @"Only valid on repeated fields"); GPBWireFormat format = GPBWireFormatForType(description->dataType, ((description->flags & GPBFieldPacked) == 0)); return GPBWireFormatMakeTag(description->number, format); } @implementation GPBFieldDescriptor { GPBGenericValue defaultValue_; // Message ivars Class msgClass_; // Enum ivars. GPBEnumDescriptor *enumDescriptor_; } @synthesize msgClass = msgClass_; @synthesize containingOneof = containingOneof_; - (instancetype)initWithFieldDescription:(void *)description descriptorFlags:(GPBDescriptorInitializationFlags)descriptorFlags { if ((self = [super init])) { BOOL includesDefault = (descriptorFlags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0; GPBMessageFieldDescription *coreDesc; if (includesDefault) { coreDesc = &(((GPBMessageFieldDescriptionWithDefault *)description)->core); } else { coreDesc = description; } description_ = coreDesc; getSel_ = sel_getUid(coreDesc->name); setSel_ = SelFromStrings("set", coreDesc->name, NULL, YES); GPBDataType dataType = coreDesc->dataType; BOOL isMessage = GPBDataTypeIsMessage(dataType); BOOL isMapOrArray = GPBFieldIsMapOrArray(self); if (isMapOrArray) { // map<>/repeated fields get a *Count property (inplace of a has*) to // support checking if there are any entries without triggering // autocreation. hasOrCountSel_ = SelFromStrings(NULL, coreDesc->name, "_Count", NO); } else { // It is a single field; it gets has/setHas selectors if... // - not in a oneof (negative has index) // - not clearing on zero if ((coreDesc->hasIndex >= 0) && ((coreDesc->flags & GPBFieldClearHasIvarOnZero) == 0)) { hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO); setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES); } } // Extra type specific data. if (isMessage) { // Note: Only fetch the class here, can't send messages to it because // that could cause cycles back to this class within +initialize if // two messages have each other in fields (i.e. - they build a graph). msgClass_ = coreDesc->dataTypeSpecific.clazz; } else if (dataType == GPBDataTypeEnum) { enumDescriptor_ = coreDesc->dataTypeSpecific.enumDescFunc(); #if defined(DEBUG) && DEBUG NSAssert((coreDesc->flags & GPBFieldHasEnumDescriptor) != 0, @"Field must have GPBFieldHasEnumDescriptor set"); #endif // DEBUG } // Non map<>/repeated fields can have defaults in proto2 syntax. if (!isMapOrArray && includesDefault) { defaultValue_ = ((GPBMessageFieldDescriptionWithDefault *)description)->defaultValue; if (dataType == GPBDataTypeBytes) { // Data stored as a length prefixed (network byte order) c-string in // descriptor structure. const uint8_t *bytes = (const uint8_t *)defaultValue_.valueData; if (bytes) { uint32_t length; memcpy(&length, bytes, sizeof(length)); length = ntohl(length); bytes += sizeof(length); defaultValue_.valueData = [[NSData alloc] initWithBytes:bytes length:length]; } } } } return self; } - (void)dealloc { if (description_->dataType == GPBDataTypeBytes && !(description_->flags & GPBFieldRepeated)) { [defaultValue_.valueData release]; } [super dealloc]; } // No need to provide -hash/-isEqual: as the instances are singletons and the // default from NSObject is fine. - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } - (GPBDataType)dataType { return description_->dataType; } - (BOOL)hasDefaultValue { return (description_->flags & GPBFieldHasDefaultValue) != 0; } - (uint32_t)number { return description_->number; } - (NSString *)name { return (NSString *_Nonnull)@(description_->name); } - (BOOL)isRequired { return (description_->flags & GPBFieldRequired) != 0; } - (BOOL)isOptional { return (description_->flags & GPBFieldOptional) != 0; } - (GPBFieldType)fieldType { GPBFieldFlags flags = description_->flags; if ((flags & GPBFieldRepeated) != 0) { return GPBFieldTypeRepeated; } else if ((flags & GPBFieldMapKeyMask) != 0) { return GPBFieldTypeMap; } else { return GPBFieldTypeSingle; } } - (GPBDataType)mapKeyDataType { switch (description_->flags & GPBFieldMapKeyMask) { case GPBFieldMapKeyInt32: return GPBDataTypeInt32; case GPBFieldMapKeyInt64: return GPBDataTypeInt64; case GPBFieldMapKeyUInt32: return GPBDataTypeUInt32; case GPBFieldMapKeyUInt64: return GPBDataTypeUInt64; case GPBFieldMapKeySInt32: return GPBDataTypeSInt32; case GPBFieldMapKeySInt64: return GPBDataTypeSInt64; case GPBFieldMapKeyFixed32: return GPBDataTypeFixed32; case GPBFieldMapKeyFixed64: return GPBDataTypeFixed64; case GPBFieldMapKeySFixed32: return GPBDataTypeSFixed32; case GPBFieldMapKeySFixed64: return GPBDataTypeSFixed64; case GPBFieldMapKeyBool: return GPBDataTypeBool; case GPBFieldMapKeyString: return GPBDataTypeString; default: NSAssert(0, @"Not a map type"); return GPBDataTypeInt32; // For lack of anything better. } } - (BOOL)isPackable { return (description_->flags & GPBFieldPacked) != 0; } - (BOOL)isValidEnumValue:(int32_t)value { NSAssert(description_->dataType == GPBDataTypeEnum, @"Field Must be of type GPBDataTypeEnum"); return enumDescriptor_.enumVerifier(value); } - (GPBEnumDescriptor *)enumDescriptor { return enumDescriptor_; } - (GPBGenericValue)defaultValue { // Depends on the fact that defaultValue_ is initialized either to "0/nil" or // to an actual defaultValue in our initializer. GPBGenericValue value = defaultValue_; if (!(description_->flags & GPBFieldRepeated)) { // We special handle data and strings. If they are nil, we replace them // with empty string/empty data. GPBDataType type = description_->dataType; if (type == GPBDataTypeBytes && value.valueData == nil) { value.valueData = GPBEmptyNSData(); } else if (type == GPBDataTypeString && value.valueString == nil) { value.valueString = @""; } } return value; } - (NSString *)textFormatName { if ((description_->flags & GPBFieldTextFormatNameCustom) != 0) { NSValue *extraInfoValue = objc_getAssociatedObject(self, &kTextFormatExtraValueKey); // Support can be left out at generation time. if (!extraInfoValue) { return nil; } const uint8_t *extraTextFormatInfo = [extraInfoValue pointerValue]; return GPBDecodeTextFormatName(extraTextFormatInfo, GPBFieldNumber(self), self.name); } // The logic here has to match SetCommonFieldVariables() from // objectivec/field.cc in the proto compiler. NSString *name = self.name; NSUInteger len = [name length]; // Remove the "_p" added to reserved names. if ([name hasSuffix:@"_p"]) { name = [name substringToIndex:(len - 2)]; len = [name length]; } // Remove "Array" from the end for repeated fields. if (((description_->flags & GPBFieldRepeated) != 0) && [name hasSuffix:@"Array"]) { name = [name substringToIndex:(len - 5)]; len = [name length]; } // Groups vs. other fields. if (description_->dataType == GPBDataTypeGroup) { // Just capitalize the first letter. unichar firstChar = [name characterAtIndex:0]; if (firstChar >= 'a' && firstChar <= 'z') { NSString *firstCharString = [NSString stringWithFormat:@"%C", (unichar)(firstChar - 'a' + 'A')]; NSString *result = [name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstCharString]; return result; } return name; } else { // Undo the CamelCase. NSMutableString *result = [NSMutableString stringWithCapacity:len]; for (uint32_t i = 0; i < len; i++) { unichar c = [name characterAtIndex:i]; if (c >= 'A' && c <= 'Z') { if (i > 0) { [result appendFormat:@"_%C", (unichar)(c - 'A' + 'a')]; } else { [result appendFormat:@"%C", c]; } } else { [result appendFormat:@"%C", c]; } } return result; } } @end @implementation GPBEnumDescriptor { NSString *name_; // valueNames_ is a single c string with all of the value names appended // together, each null terminated. -calcValueNameOffsets fills in // nameOffsets_ with the offsets to allow quicker access to the individual // names. const char *valueNames_; const int32_t *values_; GPBEnumValidationFunc enumVerifier_; const uint8_t *extraTextFormatInfo_; uint32_t *nameOffsets_; uint32_t valueCount_; uint32_t flags_; } @synthesize name = name_; @synthesize enumVerifier = enumVerifier_; + (instancetype)allocDescriptorForName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier flags:(GPBEnumDescriptorInitializationFlags)flags { // Compute the unknown flags by this version of the runtime and then check the passed in flags // (from the generated code) to detect when sources from a newer version are being used with an // older runtime. GPBEnumDescriptorInitializationFlags unknownFlags = ~(GPBEnumDescriptorInitializationFlag_IsClosed); if ((flags & unknownFlags) != 0) { GPBRuntimeMatchFailure(); } GPBEnumDescriptor *descriptor = [[self alloc] initWithName:name valueNames:valueNames values:values count:valueCount enumVerifier:enumVerifier flags:flags]; return descriptor; } + (instancetype)allocDescriptorForName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier flags:(GPBEnumDescriptorInitializationFlags)flags extraTextFormatInfo:(const char *)extraTextFormatInfo { // Call the common case. GPBEnumDescriptor *descriptor = [self allocDescriptorForName:name valueNames:valueNames values:values count:valueCount enumVerifier:enumVerifier flags:flags]; // Set the extra info. descriptor->extraTextFormatInfo_ = (const uint8_t *)extraTextFormatInfo; return descriptor; } + (instancetype)allocDescriptorForName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30005, time_to_remove_this_old_version_shim); return [self allocDescriptorForName:name valueNames:valueNames values:values count:valueCount enumVerifier:enumVerifier flags:GPBEnumDescriptorInitializationFlag_None]; } + (instancetype)allocDescriptorForName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier extraTextFormatInfo:(const char *)extraTextFormatInfo { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30005, time_to_remove_this_old_version_shim); return [self allocDescriptorForName:name valueNames:valueNames values:values count:valueCount enumVerifier:enumVerifier flags:GPBEnumDescriptorInitializationFlag_None extraTextFormatInfo:extraTextFormatInfo]; } - (instancetype)initWithName:(NSString *)name valueNames:(const char *)valueNames values:(const int32_t *)values count:(uint32_t)valueCount enumVerifier:(GPBEnumValidationFunc)enumVerifier flags:(GPBEnumDescriptorInitializationFlags)flags { if ((self = [super init])) { name_ = [name copy]; valueNames_ = valueNames; values_ = values; valueCount_ = valueCount; enumVerifier_ = enumVerifier; flags_ = flags; } return self; } - (void)dealloc { [name_ release]; if (nameOffsets_) free(nameOffsets_); [super dealloc]; } // No need to provide -hash/-isEqual: as the instances are singletons and the // default from NSObject is fine. - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } - (BOOL)isClosed { return (flags_ & GPBEnumDescriptorInitializationFlag_IsClosed) != 0; } - (void)calcValueNameOffsets { @synchronized(self) { if (nameOffsets_ != NULL) { return; } uint32_t *offsets = malloc(valueCount_ * sizeof(uint32_t)); if (!offsets) return; const char *scan = valueNames_; for (uint32_t i = 0; i < valueCount_; ++i) { offsets[i] = (uint32_t)(scan - valueNames_); while (*scan != '\0') ++scan; ++scan; // Step over the null. } nameOffsets_ = offsets; } } - (NSString *)enumNameForValue:(int32_t)number { for (uint32_t i = 0; i < valueCount_; ++i) { if (values_[i] == number) { return [self getEnumNameForIndex:i]; } } return nil; } - (BOOL)getValue:(int32_t *)outValue forEnumName:(NSString *)name { // Must have the prefix. NSUInteger prefixLen = name_.length + 1; if ((name.length <= prefixLen) || ![name hasPrefix:name_] || ([name characterAtIndex:prefixLen - 1] != '_')) { return NO; } // Skip over the prefix. const char *nameAsCStr = [name UTF8String]; nameAsCStr += prefixLen; [self calcValueNameOffsets]; if (nameOffsets_ == NULL) return NO; // Find it. for (uint32_t i = 0; i < valueCount_; ++i) { const char *valueName = valueNames_ + nameOffsets_[i]; if (strcmp(nameAsCStr, valueName) == 0) { if (outValue) { *outValue = values_[i]; } return YES; } } return NO; } - (BOOL)getValue:(int32_t *)outValue forEnumTextFormatName:(NSString *)textFormatName { [self calcValueNameOffsets]; if (nameOffsets_ == NULL) return NO; for (uint32_t i = 0; i < valueCount_; ++i) { NSString *valueTextFormatName = [self getEnumTextFormatNameForIndex:i]; if ([valueTextFormatName isEqual:textFormatName]) { if (outValue) { *outValue = values_[i]; } return YES; } } return NO; } - (NSString *)textFormatNameForValue:(int32_t)number { // Find the EnumValue descriptor and its index. BOOL foundIt = NO; uint32_t valueDescriptorIndex; for (valueDescriptorIndex = 0; valueDescriptorIndex < valueCount_; ++valueDescriptorIndex) { if (values_[valueDescriptorIndex] == number) { foundIt = YES; break; } } if (!foundIt) { return nil; } return [self getEnumTextFormatNameForIndex:valueDescriptorIndex]; } - (uint32_t)enumNameCount { return valueCount_; } - (NSString *)getEnumNameForIndex:(uint32_t)index { [self calcValueNameOffsets]; if (nameOffsets_ == NULL) return nil; if (index >= valueCount_) { return nil; } const char *valueName = valueNames_ + nameOffsets_[index]; NSString *fullName = [NSString stringWithFormat:@"%@_%s", name_, valueName]; return fullName; } - (NSString *)getEnumTextFormatNameForIndex:(uint32_t)index { [self calcValueNameOffsets]; if (nameOffsets_ == NULL) return nil; if (index >= valueCount_) { return nil; } NSString *result = nil; // Naming adds an underscore between enum name and value name, skip that also. const char *valueName = valueNames_ + nameOffsets_[index]; NSString *shortName = @(valueName); // See if it is in the map of special format handling. if (extraTextFormatInfo_) { result = GPBDecodeTextFormatName(extraTextFormatInfo_, (int32_t)index, shortName); } // Logic here needs to match what objectivec/enum.cc does in the proto // compiler. if (result == nil) { NSUInteger len = [shortName length]; NSMutableString *worker = [NSMutableString stringWithCapacity:len]; for (NSUInteger i = 0; i < len; i++) { unichar c = [shortName characterAtIndex:i]; if (i > 0 && c >= 'A' && c <= 'Z') { [worker appendString:@"_"]; } [worker appendFormat:@"%c", toupper((char)c)]; } result = worker; } return result; } @end @implementation GPBExtensionDescriptor { GPBGenericValue defaultValue_; } - (instancetype)initWithExtensionDescription:(GPBExtensionDescription *)desc usesClassRefs:(BOOL)usesClassRefs { // Compute the unknown options by this version of the runtime and then check the passed in // descriptor's options (from the generated code) to detect when sources from a newer version are // being used with an older runtime. GPBExtensionOptions unknownOptions = ~(GPBExtensionRepeated | GPBExtensionPacked | GPBExtensionSetWireFormat); if ((desc->options & unknownOptions) != 0) { GPBRuntimeMatchFailure(); } #if defined(DEBUG) && DEBUG NSAssert(usesClassRefs, @"Internal error: all extensions should have class refs"); #endif if ((self = [super init])) { description_ = desc; GPBDataType type = description_->dataType; if (type == GPBDataTypeBytes) { // Data stored as a length prefixed c-string in descriptor records. const uint8_t *bytes = (const uint8_t *)description_->defaultValue.valueData; if (bytes) { uint32_t length; memcpy(&length, bytes, sizeof(length)); // The length is stored in network byte order. length = ntohl(length); bytes += sizeof(length); defaultValue_.valueData = [[NSData alloc] initWithBytes:bytes length:length]; } } else if (type == GPBDataTypeMessage || type == GPBDataTypeGroup) { // The default is looked up in -defaultValue instead since extensions // aren't common, we avoid the hit startup hit and it avoids initialization // order issues. } else { defaultValue_ = description_->defaultValue; } } return self; } - (instancetype)initWithExtensionDescription:(GPBExtensionDescription *)desc { GPBInternalCompileAssert(GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION <= 30003, time_to_remove_this_old_version_shim); const char *className = desc->messageOrGroupClass.name; if (className) { Class clazz = objc_lookUpClass(className); NSAssert(clazz != Nil, @"Class %s not defined", className); desc->messageOrGroupClass.clazz = clazz; } const char *extendedClassName = desc->extendedClass.name; if (extendedClassName) { Class clazz = objc_lookUpClass(extendedClassName); NSAssert(clazz, @"Class %s not defined", extendedClassName); desc->extendedClass.clazz = clazz; } return [self initWithExtensionDescription:desc usesClassRefs:YES]; } - (void)dealloc { if ((description_->dataType == GPBDataTypeBytes) && !GPBExtensionIsRepeated(description_)) { [defaultValue_.valueData release]; } [super dealloc]; } // No need to provide -hash/-isEqual: as the instances are singletons and the // default from NSObject is fine. - (instancetype)copyWithZone:(__unused NSZone *)zone { // Immutable. return [self retain]; } - (NSString *)singletonName { return (NSString *_Nonnull)@(description_->singletonName); } - (const char *)singletonNameC { return description_->singletonName; } - (uint32_t)fieldNumber { return description_->fieldNumber; } - (GPBDataType)dataType { return description_->dataType; } - (GPBWireFormat)wireType { return GPBWireFormatForType(description_->dataType, GPBExtensionIsPacked(description_)); } - (GPBWireFormat)alternateWireType { NSAssert(GPBExtensionIsRepeated(description_), @"Only valid on repeated extensions"); return GPBWireFormatForType(description_->dataType, !GPBExtensionIsPacked(description_)); } - (BOOL)isRepeated { return GPBExtensionIsRepeated(description_); } - (BOOL)isPackable { return GPBExtensionIsPacked(description_); } - (Class)msgClass { return description_->messageOrGroupClass.clazz; } - (Class)containingMessageClass { return description_->extendedClass.clazz; } - (GPBEnumDescriptor *)enumDescriptor { if (description_->dataType == GPBDataTypeEnum) { GPBEnumDescriptor *enumDescriptor = description_->enumDescriptorFunc(); return enumDescriptor; } return nil; } - (id)defaultValue { if (GPBExtensionIsRepeated(description_)) { return nil; } switch (description_->dataType) { case GPBDataTypeBool: return @(defaultValue_.valueBool); case GPBDataTypeFloat: return @(defaultValue_.valueFloat); case GPBDataTypeDouble: return @(defaultValue_.valueDouble); case GPBDataTypeInt32: case GPBDataTypeSInt32: case GPBDataTypeEnum: case GPBDataTypeSFixed32: return @(defaultValue_.valueInt32); case GPBDataTypeInt64: case GPBDataTypeSInt64: case GPBDataTypeSFixed64: return @(defaultValue_.valueInt64); case GPBDataTypeUInt32: case GPBDataTypeFixed32: return @(defaultValue_.valueUInt32); case GPBDataTypeUInt64: case GPBDataTypeFixed64: return @(defaultValue_.valueUInt64); case GPBDataTypeBytes: // Like message fields, the default is zero length data. return (defaultValue_.valueData ? defaultValue_.valueData : GPBEmptyNSData()); case GPBDataTypeString: // Like message fields, the default is zero length string. return (defaultValue_.valueString ? defaultValue_.valueString : @""); case GPBDataTypeGroup: case GPBDataTypeMessage: return nil; } } - (NSComparisonResult)compareByFieldNumber:(GPBExtensionDescriptor *)other { int32_t selfNumber = description_->fieldNumber; int32_t otherNumber = other->description_->fieldNumber; if (selfNumber < otherNumber) { return NSOrderedAscending; } else if (selfNumber == otherNumber) { return NSOrderedSame; } else { return NSOrderedDescending; } } @end #pragma clang diagnostic pop