From c2299860fdd8df55c2790d6c803e9d13d7f7669f Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Mon, 8 Jul 2024 15:12:47 -0700 Subject: [PATCH] [ObjC] Add initializing a `GPBUnknownFields` from a message. PiperOrigin-RevId: 650388684 --- objectivec/GPBUnknownFields.h | 11 +++ objectivec/GPBUnknownFields.m | 118 ++++++++++++++++++++++++ objectivec/Tests/GPBUnknownFieldsTest.m | 30 ++++++ 3 files changed, 159 insertions(+) diff --git a/objectivec/GPBUnknownFields.h b/objectivec/GPBUnknownFields.h index 07c4dbf8ca..1ee50576a4 100644 --- a/objectivec/GPBUnknownFields.h +++ b/objectivec/GPBUnknownFields.h @@ -7,6 +7,7 @@ #import +@class GPBMessage; @class GPBUnknownField; NS_ASSUME_NONNULL_BEGIN @@ -26,6 +27,16 @@ NS_ASSUME_NONNULL_BEGIN __attribute__((objc_subclassing_restricted)) @interface GPBUnknownFields : NSObject +/** + * Initializes a new instance with the data from the unknown fields from the given + * message. + * + * Note: The instance is not linked to the message, any change will not be + * reflected on the message the changes have to be pushed back to the message + * with `-[GPBMessage mergeUnknownFields:error:]`. + **/ +- (instancetype)initFromMessage:(nonnull GPBMessage *)message; + /** * Initializes a new empty instance. **/ diff --git a/objectivec/GPBUnknownFields.m b/objectivec/GPBUnknownFields.m index 8856ac551f..ee33de1884 100644 --- a/objectivec/GPBUnknownFields.m +++ b/objectivec/GPBUnknownFields.m @@ -6,15 +6,28 @@ // https://developers.google.com/open-source/licenses/bsd #import "GPBUnknownFields.h" + +#import + +#import "GPBCodedInputStream_PackagePrivate.h" #import "GPBCodedOutputStream.h" #import "GPBCodedOutputStream_PackagePrivate.h" +#import "GPBMessage.h" +#import "GPBUnknownField.h" +#import "GPBUnknownFieldSet_PackagePrivate.h" #import "GPBUnknownField_PackagePrivate.h" +#import "GPBUnknownFields_PackagePrivate.h" +#import "GPBWireFormat.h" #define CHECK_FIELD_NUMBER(number) \ if (number <= 0) { \ [NSException raise:NSInvalidArgumentException format:@"Not a valid field number."]; \ } +@interface GPBUnknownFields () +- (BOOL)mergeFromInputStream:(nonnull GPBCodedInputStream *)input endTag:(uint32_t)endTag; +@end + @implementation GPBUnknownFields { @package NSMutableArray *fields_; @@ -26,6 +39,29 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdirect-ivar-access" +- (instancetype)initFromMessage:(nonnull GPBMessage *)message { + self = [super init]; + if (self) { + fields_ = [[NSMutableArray alloc] init]; + // TODO: b/349146447 - Move off the legacy class and directly to the data once Message is + // updated. + GPBUnknownFieldSet *legacyUnknownFields = [message unknownFields]; + if (legacyUnknownFields) { + GPBCodedInputStream *input = + [[GPBCodedInputStream alloc] initWithData:[legacyUnknownFields data]]; + // Parse until the end of the data (tag will be zero). + if (![self mergeFromInputStream:input endTag:0]) { + [input release]; + [self release]; + [NSException raise:NSInternalInconsistencyException + format:@"Internal error: Unknown field data from message was malformed."]; + } + [input release]; + } + } + return self; +} + - (instancetype)init { self = [super init]; if (self) { @@ -177,6 +213,88 @@ return data; } +- (BOOL)mergeFromInputStream:(nonnull GPBCodedInputStream *)input endTag:(uint32_t)endTag { +#if defined(DEBUG) && DEBUG + NSAssert(endTag == 0 || GPBWireFormatGetTagWireType(endTag) == GPBWireFormatEndGroup, + @"Internal error:Invalid end tag: %u", endTag); +#endif + GPBCodedInputStreamState *state = &input->state_; + @try { + while (YES) { + uint32_t tag = GPBCodedInputStreamReadTag(state); + if (tag == endTag) { + return YES; + } + if (tag == 0) { + // Reached end of input without finding the end tag. + return NO; + } + GPBWireFormat wireType = GPBWireFormatGetTagWireType(tag); + int32_t fieldNumber = GPBWireFormatGetTagFieldNumber(tag); + switch (wireType) { + case GPBWireFormatVarint: { + uint64_t value = GPBCodedInputStreamReadInt64(state); + GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber + varint:value]; + [fields_ addObject:field]; + [field release]; + break; + } + case GPBWireFormatFixed32: { + uint32_t value = GPBCodedInputStreamReadFixed32(state); + GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber + fixed32:value]; + [fields_ addObject:field]; + [field release]; + break; + } + case GPBWireFormatFixed64: { + uint64_t value = GPBCodedInputStreamReadFixed64(state); + GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber + fixed64:value]; + [fields_ addObject:field]; + [field release]; + break; + } + case GPBWireFormatLengthDelimited: { + NSData *data = GPBCodedInputStreamReadRetainedBytes(state); + GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber + lengthDelimited:data]; + [fields_ addObject:field]; + [field release]; + [data release]; + break; + } + case GPBWireFormatStartGroup: { + GPBUnknownFields *group = [[GPBUnknownFields alloc] init]; + GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber group:group]; + [fields_ addObject:field]; + [field release]; + [group release]; // Still will be held in the field/fields_. + uint32_t endGroupTag = GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup); + if ([group mergeFromInputStream:input endTag:endGroupTag]) { + GPBCodedInputStreamCheckLastTagWas(state, endGroupTag); + } else { + [NSException + raise:NSInternalInconsistencyException + format:@"Internal error: Unknown field data for nested group was malformed."]; + } + break; + } + case GPBWireFormatEndGroup: + [NSException raise:NSInternalInconsistencyException + format:@"Unexpected end group tag: %u", tag]; + break; + } + } + } @catch (NSException *exception) { +#if defined(DEBUG) && DEBUG + NSLog(@"%@: Internal exception while parsing unknown data, this shouldn't happen!: %@", + [self class], exception); +#endif + } +} + @end @implementation GPBUnknownFields (AccessHelpers) diff --git a/objectivec/Tests/GPBUnknownFieldsTest.m b/objectivec/Tests/GPBUnknownFieldsTest.m index 01f36689db..d6affa4074 100644 --- a/objectivec/Tests/GPBUnknownFieldsTest.m +++ b/objectivec/Tests/GPBUnknownFieldsTest.m @@ -566,6 +566,8 @@ [ufs addFieldNumber:TestAllTypes_FieldNumber_OptionalBytes lengthDelimited:DataFromCStr("foo")]; GPBUnknownFields* group = [ufs addGroupWithFieldNumber:TestAllTypes_FieldNumber_OptionalGroup]; [group addFieldNumber:TestAllTypes_OptionalGroup_FieldNumber_A varint:55]; + [ufs addFieldNumber:123456 varint:4321]; + [group addFieldNumber:123456 varint:5432]; TestAllTypes* msg = [TestAllTypes message]; [msg mergeUnknownFields:ufs extensionRegistry:nil]; @@ -574,6 +576,34 @@ XCTAssertEqual(msg.optionalFixed64, 300); XCTAssertEqualObjects(msg.optionalBytes, DataFromCStr("foo")); XCTAssertEqual(msg.optionalGroup.a, 55); + GPBUnknownFields* ufs2 = [[[GPBUnknownFields alloc] initFromMessage:msg] autorelease]; + XCTAssertEqual(ufs2.count, 1); // The unknown at the root + uint64_t varint = 0; + XCTAssertTrue([ufs2 getFirst:123456 varint:&varint]); + XCTAssertEqual(varint, 4321); + GPBUnknownFields* ufs2group = + [[[GPBUnknownFields alloc] initFromMessage:msg.optionalGroup] autorelease]; + XCTAssertEqual(ufs2group.count, 1); // The unknown at in group + XCTAssertTrue([ufs2group getFirst:123456 varint:&varint]); + XCTAssertEqual(varint, 5432); + + TestEmptyMessage* emptyMessage = [TestEmptyMessage message]; + [emptyMessage mergeUnknownFields:ufs extensionRegistry:nil]; + GPBUnknownFields* ufs3 = [[[GPBUnknownFields alloc] initFromMessage:emptyMessage] autorelease]; + XCTAssertEqualObjects(ufs3, ufs); // Round trip through an empty message got us same fields back. + XCTAssertTrue(ufs3 != ufs); // But they are different objects. +} + +- (void)testRoundTripLotsOfFields { + // Usage a message with everything, into an empty message to get a lot of unknown fields, + // and confirm it comes back to match. + TestAllTypes* allFields = [self allSetRepeatedCount:kGPBDefaultRepeatCount]; + NSData* allFieldsData = [allFields data]; + TestEmptyMessage* emptyMessage = [TestEmptyMessage parseFromData:allFieldsData error:NULL]; + GPBUnknownFields* ufs = [[[GPBUnknownFields alloc] initFromMessage:emptyMessage] autorelease]; + TestAllTypes* allFields2 = [TestAllTypes message]; + [allFields2 mergeUnknownFields:ufs extensionRegistry:nil]; + XCTAssertEqualObjects(allFields2, allFields); } @end