aboutsummaryrefslogtreecommitdiffhomepage
path: root/objectivec/GPBMessage.m
diff options
context:
space:
mode:
Diffstat (limited to 'objectivec/GPBMessage.m')
-rw-r--r--objectivec/GPBMessage.m76
1 files changed, 68 insertions, 8 deletions
diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m
index afe39c1e..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];
}
}
}
@@ -2998,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) {
@@ -3040,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);
@@ -3084,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.
@@ -3254,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