[ObjC] Runtime support for proto3 optional.

- Add a Descriptor flag to capture if the field should clear on being zeroed.
- Update the runtime to use the new clear on zero flag.
- Add a flag on message initialization to indicate the sources were generated
  with/without this support so the runtime can backfill the flag for older
  generated sources.
pull/7429/head
Thomas Van Lenten 5 years ago
parent e9b0c92659
commit 8d224b4c48
  1. 40
      objectivec/GPBDescriptor.m
  2. 19
      objectivec/GPBDescriptor_PackagePrivate.h
  3. 106
      objectivec/GPBUtilities.m

@ -128,6 +128,8 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
(flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0;
BOOL usesClassRefs =
(flags & GPBDescriptorInitializationFlag_UsesClassRefs) != 0;
BOOL proto3OptionalKnown =
(flags & GPBDescriptorInitializationFlag_Proto3OptionalKnown) != 0;
void *desc;
for (uint32_t i = 0; i < fieldCount; ++i) {
@ -146,6 +148,7 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
[[GPBFieldDescriptor alloc] initWithFieldDescription:desc
includesDefault:fieldsIncludeDefault
usesClassRefs:usesClassRefs
proto3OptionalKnown:proto3OptionalKnown
syntax:syntax];
[fields addObject:fieldDescriptor];
[fieldDescriptor release];
@ -488,6 +491,7 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
- (instancetype)initWithFieldDescription:(void *)description
includesDefault:(BOOL)includesDefault
usesClassRefs:(BOOL)usesClassRefs
proto3OptionalKnown:(BOOL)proto3OptionalKnown
syntax:(GPBFileSyntax)syntax {
if ((self = [super init])) {
GPBMessageFieldDescription *coreDesc;
@ -504,20 +508,33 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
BOOL isMessage = GPBDataTypeIsMessage(dataType);
BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
// If proto3 optionals weren't know, compute the flag for the rest of the runtime.
if (!proto3OptionalKnown) {
// 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 = ((syntax == GPBFileSyntaxProto3) &&
!isMapOrArray &&
(coreDesc->hasIndex >= 0) &&
!isMessage);
if (clearOnZero) {
coreDesc->flags |= GPBFieldClearHasIvarOnZero;
}
}
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 {
// If there is a positive hasIndex, then:
// - All fields types for proto2 messages get has* selectors.
// - Only message fields for proto3 messages get has* selectors.
// Note: the positive check is to handle oneOfs, we can't check
// containingOneof_ because it isn't set until after initialization.
// Know 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->hasIndex != GPBNoHasBit) &&
((syntax != GPBFileSyntaxProto3) || isMessage)) {
((coreDesc->flags & GPBFieldClearHasIvarOnZero) == 0)) {
hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO);
setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES);
}
@ -567,15 +584,6 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
return self;
}
- (instancetype)initWithFieldDescription:(void *)description
includesDefault:(BOOL)includesDefault
syntax:(GPBFileSyntax)syntax {
return [self initWithFieldDescription:description
includesDefault:includesDefault
usesClassRefs:NO
syntax:syntax];
}
- (void)dealloc {
if (description_->dataType == GPBDataTypeBytes &&
!(description_->flags & GPBFieldRepeated)) {

@ -45,6 +45,10 @@ typedef NS_OPTIONS(uint16_t, GPBFieldFlags) {
GPBFieldOptional = 1 << 3,
GPBFieldHasDefaultValue = 1 << 4,
// Indicate that the field should "clear" when set to zero value. This is the
// proto3 non optional behavior for singular data (ints, data, string, enum)
// fields.
GPBFieldClearHasIvarOnZero = 1 << 5,
// Indicates the field needs custom handling for the TextFormat name, if not
// set, the name can be derived from the ObjC name.
GPBFieldTextFormatNameCustom = 1 << 6,
@ -149,7 +153,13 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
// This is used as a stopgap as we move from using class names to class
// references. The runtime needs to support both until we allow a
// breaking change in the runtime.
GPBDescriptorInitializationFlag_UsesClassRefs = 1 << 2,
GPBDescriptorInitializationFlag_UsesClassRefs = 1 << 2,
// This flag is used to indicate that the generated sources already containg
// the `GPBFieldClearHasIvarOnZero` flag and it doesn't have to be computed
// at startup, this allows older generated code to still work with the
// current runtime library.
GPBDescriptorInitializationFlag_Proto3OptionalKnown = 1 << 3,
};
@interface GPBDescriptor () {
@ -225,14 +235,9 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
- (instancetype)initWithFieldDescription:(void *)description
includesDefault:(BOOL)includesDefault
usesClassRefs:(BOOL)usesClassRefs
proto3OptionalKnown:(BOOL)proto3OptionalKnown
syntax:(GPBFileSyntax)syntax;
// Deprecated. Equivalent to calling above with `usesClassRefs = NO`.
- (instancetype)initWithFieldDescription:(void *)description
includesDefault:(BOOL)includesDefault
syntax:(GPBFileSyntax)syntax;
@end
@interface GPBEnumDescriptor ()

@ -400,6 +400,7 @@ void GPBMaybeClearOneof(GPBMessage *self, GPBOneofDescriptor *oneof,
//% NAME$S GPBFieldDescriptor *field,
//% NAME$S TYPE value,
//% NAME$S GPBFileSyntax syntax) {
//%#pragma unused(syntax)
//% GPBOneofDescriptor *oneof = field->containingOneof_;
//% GPBMessageFieldDescription *fieldDesc = field->description_;
//% if (oneof) {
@ -416,11 +417,10 @@ void GPBMaybeClearOneof(GPBMessage *self, GPBOneofDescriptor *oneof,
//% uint8_t *storage = (uint8_t *)self->messageStorage_;
//% TYPE *typePtr = (TYPE *)&storage[fieldDesc->offset];
//% *typePtr = value;
//% // proto2: any value counts as having been set; proto3, it
//% // has to be a non zero value or be in a oneof.
//% BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
//% || (value != (TYPE)0)
//% || (field->containingOneof_ != NULL));
//% // If the value is zero, then we only count the field as "set" if the field
//% // shouldn't auto clear on zero.
//% BOOL hasValue = ((value != (TYPE)0)
//% || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
//% GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
//% GPBBecomeVisibleToAutocreator(self);
//%}
@ -548,6 +548,7 @@ void GPBSetObjectIvarWithFieldInternal(GPBMessage *self,
void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
id value, GPBFileSyntax syntax) {
#pragma unused(syntax)
NSCAssert(self->messageStorage_ != NULL,
@"%@: All messages should have storage (from init)",
[self class]);
@ -596,24 +597,15 @@ void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self,
}
// Clear "has" if they are being set to nil.
BOOL setHasValue = (value != nil);
// Under proto3, Bytes & String fields get cleared by resetting them to
// their default (empty) values, so if they are set to something of length
// zero, they are being cleared.
if ((syntax == GPBFileSyntaxProto3) && !fieldIsMessage &&
// If the field should clear on a "zero" value, then check if the string/data
// was zero length, and clear instead.
if (((fieldDesc->flags & GPBFieldClearHasIvarOnZero) != 0) &&
([value length] == 0)) {
// Except, if the field was in a oneof, then it still gets recorded as
// having been set so the state of the oneof can be serialized back out.
if (!oneof) {
setHasValue = NO;
}
if (setHasValue) {
NSCAssert(value != nil, @"Should never be setting has for nil");
} else {
// The value passed in was retained, it must be released since we
// aren't saving anything in the field.
[value release];
value = nil;
}
setHasValue = NO;
// The value passed in was retained, it must be released since we
// aren't saving anything in the field.
[value release];
value = nil;
}
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, setHasValue);
}
@ -802,6 +794,7 @@ void GPBSetBoolIvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
BOOL value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBMessageFieldDescription *fieldDesc = field->description_;
GPBOneofDescriptor *oneof = field->containingOneof_;
if (oneof) {
@ -814,11 +807,10 @@ void GPBSetBoolIvarWithFieldInternal(GPBMessage *self,
// the offset is never negative)
GPBSetHasIvar(self, (int32_t)(fieldDesc->offset), fieldDesc->number, value);
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (BOOL)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (BOOL)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -873,6 +865,7 @@ void GPBSetInt32IvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
int32_t value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -889,11 +882,10 @@ void GPBSetInt32IvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
int32_t *typePtr = (int32_t *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (int32_t)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (int32_t)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -949,6 +941,7 @@ void GPBSetUInt32IvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
uint32_t value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -965,11 +958,10 @@ void GPBSetUInt32IvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
uint32_t *typePtr = (uint32_t *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (uint32_t)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (uint32_t)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -1025,6 +1017,7 @@ void GPBSetInt64IvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
int64_t value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -1041,11 +1034,10 @@ void GPBSetInt64IvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
int64_t *typePtr = (int64_t *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (int64_t)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (int64_t)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -1101,6 +1093,7 @@ void GPBSetUInt64IvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
uint64_t value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -1117,11 +1110,10 @@ void GPBSetUInt64IvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
uint64_t *typePtr = (uint64_t *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (uint64_t)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (uint64_t)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -1177,6 +1169,7 @@ void GPBSetFloatIvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
float value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -1193,11 +1186,10 @@ void GPBSetFloatIvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
float *typePtr = (float *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (float)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (float)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}
@ -1253,6 +1245,7 @@ void GPBSetDoubleIvarWithFieldInternal(GPBMessage *self,
GPBFieldDescriptor *field,
double value,
GPBFileSyntax syntax) {
#pragma unused(syntax)
GPBOneofDescriptor *oneof = field->containingOneof_;
GPBMessageFieldDescription *fieldDesc = field->description_;
if (oneof) {
@ -1269,11 +1262,10 @@ void GPBSetDoubleIvarWithFieldInternal(GPBMessage *self,
uint8_t *storage = (uint8_t *)self->messageStorage_;
double *typePtr = (double *)&storage[fieldDesc->offset];
*typePtr = value;
// proto2: any value counts as having been set; proto3, it
// has to be a non zero value or be in a oneof.
BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
|| (value != (double)0)
|| (field->containingOneof_ != NULL));
// If the value is zero, then we only count the field as "set" if the field
// shouldn't auto clear on zero.
BOOL hasValue = ((value != (double)0)
|| ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
GPBBecomeVisibleToAutocreator(self);
}

Loading…
Cancel
Save