[ObjC] Improve parsing validations

Pass through the expected end marker and validate it so calling code can't
forget to do the validations.

PiperOrigin-RevId: 651809006
diff --git a/objectivec/GPBCodedInputStream.m b/objectivec/GPBCodedInputStream.m
index ae1124c..c39648b 100644
--- a/objectivec/GPBCodedInputStream.m
+++ b/objectivec/GPBCodedInputStream.m
@@ -429,9 +429,9 @@
     extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry {
   CheckRecursionLimit(&state_);
   ++state_.recursionDepth;
-  [message mergeFromCodedInputStream:self extensionRegistry:extensionRegistry];
-  GPBCodedInputStreamCheckLastTagWas(&state_,
-                                     GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup));
+  [message mergeFromCodedInputStream:self
+                   extensionRegistry:extensionRegistry
+                           endingTag:GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup)];
   --state_.recursionDepth;
 }
 
@@ -452,8 +452,7 @@
   size_t length2 = (size_t)length;  // Cast safe on 32bit because of CheckFieldSize() above.
   size_t oldLimit = GPBCodedInputStreamPushLimit(&state_, length2);
   ++state_.recursionDepth;
-  [message mergeFromCodedInputStream:self extensionRegistry:extensionRegistry];
-  GPBCodedInputStreamCheckLastTagWas(&state_, 0);
+  [message mergeFromCodedInputStream:self extensionRegistry:extensionRegistry endingTag:0];
   --state_.recursionDepth;
   GPBCodedInputStreamPopLimit(&state_, oldLimit);
 }
diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m
index b2c3ba7..d29d0f3 100644
--- a/objectivec/GPBMessage.m
+++ b/objectivec/GPBMessage.m
@@ -746,7 +746,9 @@
         if (GPBExtensionIsWireFormat(description)) {
           // For MessageSet fields the message length will have already been
           // read.
-          [targetMessage mergeFromCodedInputStream:input extensionRegistry:extensionRegistry];
+          [targetMessage mergeFromCodedInputStream:input
+                                 extensionRegistry:extensionRegistry
+                                         endingTag:0];
         } else {
           [input readMessage:targetMessage extensionRegistry:extensionRegistry];
         }
@@ -1027,7 +1029,7 @@
                                    error:(NSError **)errorPtr {
   if ((self = [self init])) {
     @try {
-      [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry];
+      [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry endingTag:0];
       if (errorPtr) {
         *errorPtr = nil;
       }
@@ -2078,8 +2080,7 @@
 - (void)mergeFromData:(NSData *)data extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry {
   GPBCodedInputStream *input = [[GPBCodedInputStream alloc] initWithData:data];
   @try {
-    [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry];
-    [input checkLastTagWas:0];
+    [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry endingTag:0];
   } @finally {
     [input release];
   }
@@ -2090,7 +2091,7 @@
                 error:(NSError **)errorPtr {
   GPBCodedInputStream *input = [[GPBCodedInputStream alloc] initWithData:data];
   @try {
-    [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry];
+    [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry endingTag:0];
     [input checkLastTagWas:0];
     if (errorPtr) {
       *errorPtr = nil;
@@ -2227,38 +2228,36 @@
   }
 }
 
-- (BOOL)parseUnknownField:(GPBCodedInputStream *)input
+- (void)parseUnknownField:(GPBCodedInputStream *)input
         extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry
                       tag:(uint32_t)tag {
-  GPBWireFormat wireType = GPBWireFormatGetTagWireType(tag);
   int32_t fieldNumber = GPBWireFormatGetTagFieldNumber(tag);
-
   GPBDescriptor *descriptor = [self descriptor];
   GPBExtensionDescriptor *extension = [extensionRegistry extensionForDescriptor:descriptor
                                                                     fieldNumber:fieldNumber];
   if (extension == nil) {
     if (descriptor.wireFormat && GPBWireFormatMessageSetItemTag == tag) {
       [self parseMessageSet:input extensionRegistry:extensionRegistry];
-      return YES;
+      return;
     }
   } else {
+    GPBWireFormat wireType = GPBWireFormatGetTagWireType(tag);
     if (extension.wireType == wireType) {
       ExtensionMergeFromInputStream(extension, extension.packable, input, extensionRegistry, self);
-      return YES;
+      return;
     }
     // Primitive, repeated types can be packed on unpacked on the wire, and are
     // parsed either way.
     if ([extension isRepeated] && !GPBDataTypeIsObject(extension->description_->dataType) &&
         (extension.alternateWireType == wireType)) {
       ExtensionMergeFromInputStream(extension, !extension.packable, input, extensionRegistry, self);
-      return YES;
+      return;
     }
   }
-  if ([GPBUnknownFieldSet isFieldTag:tag]) {
-    GPBUnknownFieldSet *unknownFields = GetOrMakeUnknownFields(self);
-    return [unknownFields mergeFieldFrom:tag input:input];
-  } else {
-    return NO;
+  GPBUnknownFieldSet *unknownFields = GetOrMakeUnknownFields(self);
+  if (![unknownFields mergeFieldFrom:tag input:input]) {
+    [NSException raise:NSInternalInconsistencyException
+                format:@"Internal Error: Unable to parse unknown field %u", tag];
   }
 }
 
@@ -2465,7 +2464,12 @@
 }
 
 - (void)mergeFromCodedInputStream:(GPBCodedInputStream *)input
-                extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry {
+                extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry
+                        endingTag:(uint32_t)endingTag {
+#if defined(DEBUG) && DEBUG
+  NSAssert(endingTag == 0 || GPBWireFormatGetTagWireType(endingTag) == GPBWireFormatEndGroup,
+           @"endingTag should have been an endGroup tag");
+#endif  // DEBUG
   GPBDescriptor *descriptor = [self descriptor];
   GPBCodedInputStreamState *state = &input->state_;
   uint32_t tag = 0;
@@ -2475,8 +2479,11 @@
   while (YES) {
     BOOL merged = NO;
     tag = GPBCodedInputStreamReadTag(state);
-    if (tag == 0) {
-      break;  // Reached end.
+    if (tag == endingTag || tag == 0) {
+      // If we got to the end (tag zero), when we were expecting the end group, this will
+      // raise the error.
+      GPBCodedInputStreamCheckLastTagWas(state, endingTag);
+      return;
     }
     for (NSUInteger i = 0; i < numFields; ++i) {
       if (startingIndex >= numFields) startingIndex = 0;
@@ -2514,46 +2521,37 @@
       }
     }  // for(i < numFields)
 
-    if (!merged && (tag != 0)) {
-      // Primitive, repeated types can be packed on unpacked on the wire, and
-      // are parsed either way.  The above loop covered tag in the preferred
-      // for, so this need to check the alternate form.
-      for (NSUInteger i = 0; i < numFields; ++i) {
-        if (startingIndex >= numFields) startingIndex = 0;
-        GPBFieldDescriptor *fieldDescriptor = fields[startingIndex];
-        if ((fieldDescriptor.fieldType == GPBFieldTypeRepeated) &&
-            !GPBFieldDataTypeIsObject(fieldDescriptor) &&
-            (GPBFieldAlternateTag(fieldDescriptor) == tag)) {
-          BOOL alternateIsPacked = !fieldDescriptor.isPackable;
-          if (alternateIsPacked) {
-            MergeRepeatedPackedFieldFromCodedInputStream(self, fieldDescriptor, input);
-            // Well formed protos will only have a repeated field that is
-            // packed once, advance the starting index to the next field.
-            startingIndex += 1;
-          } else {
-            MergeRepeatedNotPackedFieldFromCodedInputStream(self, fieldDescriptor, input,
-                                                            extensionRegistry);
-          }
-          merged = YES;
-          break;
-        } else {
+    if (merged) continue;  // On to the next tag
+
+    // Primitive, repeated types can be packed or unpacked on the wire, and
+    // are parsed either way.  The above loop covered tag in the preferred
+    // for, so this need to check the alternate form.
+    for (NSUInteger i = 0; i < numFields; ++i) {
+      if (startingIndex >= numFields) startingIndex = 0;
+      GPBFieldDescriptor *fieldDescriptor = fields[startingIndex];
+      if ((fieldDescriptor.fieldType == GPBFieldTypeRepeated) &&
+          !GPBFieldDataTypeIsObject(fieldDescriptor) &&
+          (GPBFieldAlternateTag(fieldDescriptor) == tag)) {
+        BOOL alternateIsPacked = !fieldDescriptor.isPackable;
+        if (alternateIsPacked) {
+          MergeRepeatedPackedFieldFromCodedInputStream(self, fieldDescriptor, input);
+          // Well formed protos will only have a repeated field that is
+          // packed once, advance the starting index to the next field.
           startingIndex += 1;
+        } else {
+          MergeRepeatedNotPackedFieldFromCodedInputStream(self, fieldDescriptor, input,
+                                                          extensionRegistry);
         }
+        merged = YES;
+        break;
+      } else {
+        startingIndex += 1;
       }
     }
 
-    if (!merged) {
-      if (tag == 0) {
-        // zero signals EOF / limit reached
-        return;
-      } else {
-        if (![self parseUnknownField:input extensionRegistry:extensionRegistry tag:tag]) {
-          // it's an endgroup tag
-          return;
-        }
-      }
-    }  // if(!merged)
+    if (merged) continue;  // On to the next tag
 
+    [self parseUnknownField:input extensionRegistry:extensionRegistry tag:tag];
   }  // while(YES)
 }
 
@@ -3554,8 +3552,7 @@
     if (data.length) {
       GPBCodedInputStream *input = [[GPBCodedInputStream alloc] initWithData:data];
       @try {
-        [self mergeFromCodedInputStream:input extensionRegistry:nil];
-        [input checkLastTagWas:0];
+        [self mergeFromCodedInputStream:input extensionRegistry:nil endingTag:0];
       } @finally {
         [input release];
       }
diff --git a/objectivec/GPBMessage_PackagePrivate.h b/objectivec/GPBMessage_PackagePrivate.h
index e562e5d..65a1a7c 100644
--- a/objectivec/GPBMessage_PackagePrivate.h
+++ b/objectivec/GPBMessage_PackagePrivate.h
@@ -47,15 +47,15 @@
 // Parses a message of this type from the input and merges it with this
 // message.
 //
+// `endingTag` should be zero if expected to consume to the end of input, but if
+// the input is supposed to be a Group, it should be the endgroup tag to look for.
+//
 // Warning:  This does not verify that all required fields are present in
 // the input message.
-// Note:  The caller should call
-// -[CodedInputStream checkLastTagWas:] after calling this to
-// verify that the last tag seen was the appropriate end-group tag,
-// or zero for EOF.
 // NOTE: This will throw if there is an error while parsing.
 - (void)mergeFromCodedInputStream:(GPBCodedInputStream *)input
-                extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry;
+                extensionRegistry:(id<GPBExtensionRegistry>)extensionRegistry
+                        endingTag:(uint32_t)endingTag;
 
 - (void)addUnknownMapEntry:(int32_t)fieldNum value:(NSData *)data;
 
diff --git a/objectivec/GPBUnknownFieldSet.m b/objectivec/GPBUnknownFieldSet.m
index 7687340..5fe8ce0 100644
--- a/objectivec/GPBUnknownFieldSet.m
+++ b/objectivec/GPBUnknownFieldSet.m
@@ -212,10 +212,6 @@
   return data;
 }
 
-+ (BOOL)isFieldTag:(int32_t)tag {
-  return GPBWireFormatGetTagWireType(tag) != GPBWireFormatEndGroup;
-}
-
 - (void)addField:(GPBUnknownField *)field {
   int32_t number = [field number];
   checkNumber(number);
diff --git a/objectivec/GPBUnknownFieldSet_PackagePrivate.h b/objectivec/GPBUnknownFieldSet_PackagePrivate.h
index 37cb61b..7de20ed 100644
--- a/objectivec/GPBUnknownFieldSet_PackagePrivate.h
+++ b/objectivec/GPBUnknownFieldSet_PackagePrivate.h
@@ -14,8 +14,6 @@
 
 @interface GPBUnknownFieldSet ()
 
-+ (BOOL)isFieldTag:(int32_t)tag;
-
 - (NSData *)data;
 
 - (size_t)serializedSize;