diff options
Diffstat (limited to 'objectivec/GPBMessage.m')
-rw-r--r-- | objectivec/GPBMessage.m | 83 |
1 files changed, 72 insertions, 11 deletions
diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m index 37cff6ce..db5d3b60 100644 --- a/objectivec/GPBMessage.m +++ b/objectivec/GPBMessage.m @@ -32,6 +32,7 @@ #import <objc/runtime.h> #import <objc/message.h> +#import <stdatomic.h> #import "GPBArray_PackagePrivate.h" #import "GPBCodedInputStream_PackagePrivate.h" @@ -77,6 +78,20 @@ static NSString *const kGPBDataCoderKey = @"GPBData"; GPBMessage *autocreator_; GPBFieldDescriptor *autocreatorField_; GPBExtensionDescriptor *autocreatorExtension_; + + // A lock to provide mutual exclusion from internal data that can be modified + // by *read* operations such as getters (autocreation of message fields and + // message extensions, not setting of values). Used to guarantee thread safety + // for concurrent reads on the message. + // NOTE: OSSpinLock may seem like a good fit here but Apple engineers have + // pointed out that they are vulnerable to live locking on iOS in cases of + // priority inversion: + // http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/ + // https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html + // Use of readOnlySemaphore_ must be prefaced by a call to + // GPBPrepareReadOnlySemaphore to ensure it has been created. This allows + // readOnlySemaphore_ to be only created when actually needed. + _Atomic(dispatch_semaphore_t) readOnlySemaphore_; } @end @@ -742,16 +757,22 @@ void GPBClearMessageAutocreator(GPBMessage *self) { void GPBPrepareReadOnlySemaphore(GPBMessage *self) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // Create the semaphore on demand (rather than init) as developers might not cause them // to be needed, and the heap usage can add up. The atomic swap is used to avoid needing // another lock around creating it. if (self->readOnlySemaphore_ == nil) { dispatch_semaphore_t worker = dispatch_semaphore_create(1); - if (!OSAtomicCompareAndSwapPtrBarrier(NULL, worker, (void * volatile *)&(self->readOnlySemaphore_))) { + dispatch_semaphore_t expected = nil; + if (!atomic_compare_exchange_strong(&self->readOnlySemaphore_, &expected, worker)) { dispatch_release(worker); } +#if defined(__clang_analyzer__) + // The Xcode 9.2 (and 9.3 beta) static analyzer thinks worker is leaked + // (doesn't seem to know about atomic_compare_exchange_strong); so just + // for the analyzer, let it think worker is also released in this case. + else { dispatch_release(worker); } +#endif } #pragma clang diagnostic pop @@ -969,7 +990,8 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) { newValue = [value copyWithZone:zone]; } } else { - if (field.mapKeyDataType == GPBDataTypeString) { + if ((field.mapKeyDataType == GPBDataTypeString) && + GPBFieldDataTypeIsObject(field)) { // NSDictionary newValue = [value mutableCopyWithZone:zone]; } else { @@ -2026,7 +2048,12 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) { [newInput release]; } else { GPBUnknownFieldSet *unknownFields = GetOrMakeUnknownFields(self); - [unknownFields mergeMessageSetMessage:typeId data:rawBytes]; + // rawBytes was created via a NoCopy, so it can be reusing a + // subrange of another NSData that might go out of scope as things + // unwind, so a copy is needed to ensure what is saved in the + // unknown fields stays valid. + NSData *cloned = [NSData dataWithData:rawBytes]; + [unknownFields mergeMessageSetMessage:typeId data:cloned]; } } } @@ -2591,13 +2618,14 @@ static void MergeRepeatedNotPackedFieldFromCodedInputStream( if (other == self) { return YES; } - if (![other isKindOfClass:[self class]] && - ![self isKindOfClass:[other class]]) { + if (![other isKindOfClass:[GPBMessage class]]) { return NO; } - GPBMessage *otherMsg = other; GPBDescriptor *descriptor = [[self class] descriptor]; + if ([[otherMsg class] descriptor] != descriptor) { + return NO; + } uint8_t *selfStorage = (uint8_t *)messageStorage_; uint8_t *otherStorage = (uint8_t *)otherMsg->messageStorage_; @@ -2997,7 +3025,10 @@ typedef struct ResolveIvarAccessorMethodResult { SEL encodingSelector; } ResolveIvarAccessorMethodResult; -static void ResolveIvarGet(GPBFieldDescriptor *field, +// |field| can be __unsafe_unretained because they are created at startup +// and are essentially global. No need to pay for retain/release when +// they are captured in blocks. +static void ResolveIvarGet(__unsafe_unretained GPBFieldDescriptor *field, ResolveIvarAccessorMethodResult *result) { GPBDataType fieldDataType = GPBGetFieldDataType(field); switch (fieldDataType) { @@ -3039,7 +3070,8 @@ static void ResolveIvarGet(GPBFieldDescriptor *field, } } -static void ResolveIvarSet(GPBFieldDescriptor *field, +// See comment about __unsafe_unretained on ResolveIvarGet. +static void ResolveIvarSet(__unsafe_unretained GPBFieldDescriptor *field, GPBFileSyntax syntax, ResolveIvarAccessorMethodResult *result) { GPBDataType fieldDataType = GPBGetFieldDataType(field); @@ -3083,9 +3115,10 @@ static void ResolveIvarSet(GPBFieldDescriptor *field, // NOTE: hasOrCountSel_/setHasSel_ will be NULL if the field for the given // message should not have has support (done in GPBDescriptor.m), so there is // no need for checks here to see if has*/setHas* are allowed. - ResolveIvarAccessorMethodResult result = {NULL, NULL}; - for (GPBFieldDescriptor *field in descriptor->fields_) { + + // See comment about __unsafe_unretained on ResolveIvarGet. + for (__unsafe_unretained GPBFieldDescriptor *field in descriptor->fields_) { BOOL isMapOrArray = GPBFieldIsMapOrArray(field); if (!isMapOrArray) { // Single fields. @@ -3253,4 +3286,32 @@ id GPBGetMessageMapField(GPBMessage *self, GPBFieldDescriptor *field) { return GetOrCreateMapIvarWithField(self, field, syntax); } +id GPBGetObjectIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) { + NSCAssert(!GPBFieldIsMapOrArray(field), @"Shouldn't get here"); + if (GPBGetHasIvarField(self, field)) { + uint8_t *storage = (uint8_t *)self->messageStorage_; + id *typePtr = (id *)&storage[field->description_->offset]; + return *typePtr; + } + // Not set... + + // Non messages (string/data), get their default. + if (!GPBFieldDataTypeIsMessage(field)) { + return field.defaultValue.valueMessage; + } + + GPBPrepareReadOnlySemaphore(self); + dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER); + GPBMessage *result = GPBGetObjectIvarWithFieldNoAutocreate(self, field); + if (!result) { + // For non repeated messages, create the object, set it and return it. + // This object will not initially be visible via GPBGetHasIvar, so + // we save its creator so it can become visible if it's mutated later. + result = GPBCreateMessageWithAutocreator(field.msgClass, self, field); + GPBSetAutocreatedRetainedObjectIvarWithField(self, field, result); + } + dispatch_semaphore_signal(self->readOnlySemaphore_); + return result; +} + #pragma clang diagnostic pop |