[ObjC] Error when appearing to use newer generated code.

There already are explicit checks in debug builds, but this adds an addition
check within all builds to catch when unknown flags are being passed to the
runtime, which is an indication that the source generation is from a newer
version of the library.

PiperOrigin-RevId: 506091257
diff --git a/objectivec/GPBDescriptor.m b/objectivec/GPBDescriptor.m
index dcfbe63..130073b 100644
--- a/objectivec/GPBDescriptor.m
+++ b/objectivec/GPBDescriptor.m
@@ -140,23 +140,47 @@
                              fieldCount:(uint32_t)fieldCount
                             storageSize:(uint32_t)storageSize
                                   flags:(GPBDescriptorInitializationFlags)flags {
+  // Compute the unknown flags by this version of the runtime and then check the passed in flags
+  // (from the generated code) to detect when sources from a newer version are being used with an
+  // older runtime.
+  GPBDescriptorInitializationFlags unknownFlags =
+      ~(GPBDescriptorInitializationFlag_FieldsWithDefault |
+        GPBDescriptorInitializationFlag_WireFormat | GPBDescriptorInitializationFlag_UsesClassRefs |
+        GPBDescriptorInitializationFlag_Proto3OptionalKnown |
+        GPBDescriptorInitializationFlag_ClosedEnumSupportKnown);
+  if ((flags & unknownFlags) != 0) {
+    GPBRuntimeMatchFailure();
+  }
   NSMutableArray *fields =
       (fieldCount ? [[NSMutableArray alloc] initWithCapacity:fieldCount] : nil);
   BOOL fieldsIncludeDefault = (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0;
 
   void *desc;
+  GPBFieldFlags mergedFieldFlags = GPBFieldNone;
   for (uint32_t i = 0; i < fieldCount; ++i) {
     // Need correctly typed pointer for array indexing below to work.
     if (fieldsIncludeDefault) {
       desc = &(((GPBMessageFieldDescriptionWithDefault *)fieldDescriptions)[i]);
+      mergedFieldFlags |=
+          (((GPBMessageFieldDescriptionWithDefault *)fieldDescriptions)[i]).core.flags;
     } else {
       desc = &(((GPBMessageFieldDescription *)fieldDescriptions)[i]);
+      mergedFieldFlags |= (((GPBMessageFieldDescription *)fieldDescriptions)[i]).flags;
     }
     GPBFieldDescriptor *fieldDescriptor =
         [[GPBFieldDescriptor alloc] initWithFieldDescription:desc file:file descriptorFlags:flags];
     [fields addObject:fieldDescriptor];
     [fieldDescriptor release];
   }
+  // No real value in checking all the fields individually, just check the combined flags at the
+  // end.
+  GPBFieldFlags unknownFieldFlags =
+      ~(GPBFieldRequired | GPBFieldRepeated | GPBFieldPacked | GPBFieldOptional |
+        GPBFieldHasDefaultValue | GPBFieldClearHasIvarOnZero | GPBFieldTextFormatNameCustom |
+        GPBFieldHasEnumDescriptor | GPBFieldMapKeyMask | GPBFieldClosedEnum);
+  if ((mergedFieldFlags & unknownFieldFlags) != 0) {
+    GPBRuntimeMatchFailure();
+  }
 
   BOOL wireFormat = (flags & GPBDescriptorInitializationFlag_WireFormat) != 0;
   GPBDescriptor *descriptor = [[self alloc] initWithClass:messageClass
@@ -834,6 +858,14 @@
                                  count:(uint32_t)valueCount
                           enumVerifier:(GPBEnumValidationFunc)enumVerifier
                                  flags:(GPBEnumDescriptorInitializationFlags)flags {
+  // Compute the unknown flags by this version of the runtime and then check the passed in flags
+  // (from the generated code) to detect when sources from a newer version are being used with an
+  // older runtime.
+  GPBEnumDescriptorInitializationFlags unknownFlags =
+      ~(GPBEnumDescriptorInitializationFlag_IsClosed);
+  if ((flags & unknownFlags) != 0) {
+    GPBRuntimeMatchFailure();
+  }
   GPBEnumDescriptor *descriptor = [[self alloc] initWithName:name
                                                   valueNames:valueNames
                                                       values:values
@@ -1072,6 +1104,15 @@
 
 - (instancetype)initWithExtensionDescription:(GPBExtensionDescription *)desc
                                usesClassRefs:(BOOL)usesClassRefs {
+  // Compute the unknown options by this version of the runtime and then check the passed in
+  // descriptor's options (from the generated code) to detect when sources from a newer version are
+  // being used with an older runtime.
+  GPBExtensionOptions unknownOptions =
+      ~(GPBExtensionRepeated | GPBExtensionPacked | GPBExtensionSetWireFormat);
+  if ((desc->options & unknownOptions) != 0) {
+    GPBRuntimeMatchFailure();
+  }
+
   if ((self = [super init])) {
     description_ = desc;
     if (!usesClassRefs) {
diff --git a/objectivec/GPBDescriptor_PackagePrivate.h b/objectivec/GPBDescriptor_PackagePrivate.h
index fb443ff..0fadaf5 100644
--- a/objectivec/GPBDescriptor_PackagePrivate.h
+++ b/objectivec/GPBDescriptor_PackagePrivate.h
@@ -263,6 +263,8 @@
 typedef NS_OPTIONS(uint32_t, GPBEnumDescriptorInitializationFlags) {
   GPBEnumDescriptorInitializationFlag_None = 0,
 
+  // Available: 1 << 0
+
   // Marks this enum as a closed enum.
   GPBEnumDescriptorInitializationFlag_IsClosed = 1 << 1,
 };
diff --git a/objectivec/GPBUtilities.m b/objectivec/GPBUtilities.m
index 4111c92..4aa421e 100644
--- a/objectivec/GPBUtilities.m
+++ b/objectivec/GPBUtilities.m
@@ -228,6 +228,13 @@
   }
 }
 
+void GPBRuntimeMatchFailure(void) {
+  [NSException raise:NSInternalInconsistencyException
+              format:@"Proto generation source appears to have been from a"
+                     @" version newer that this runtime (%d).",
+                     GOOGLE_PROTOBUF_OBJC_VERSION];
+}
+
 // This api is no longer used for version checks. 30001 is the last version
 // using this old versioning model. When that support is removed, this function
 // can be removed (along with the declaration in GPBUtilities_PackagePrivate.h).
diff --git a/objectivec/GPBUtilities_PackagePrivate.h b/objectivec/GPBUtilities_PackagePrivate.h
index c9884cd..a5bde54 100644
--- a/objectivec/GPBUtilities_PackagePrivate.h
+++ b/objectivec/GPBUtilities_PackagePrivate.h
@@ -66,6 +66,12 @@
 #endif
 }
 
+// Helper called within the library when the runtime detects something that
+// indicates a older runtime is being used with newer generated code. Normally
+// GPB_DEBUG_CHECK_RUNTIME_VERSIONS() gates this with a better message; this
+// is just a final safety net to prevent otherwise hard to diagnose errors.
+void GPBRuntimeMatchFailure(void);
+
 // Legacy version of the checks, remove when GOOGLE_PROTOBUF_OBJC_GEN_VERSION
 // goes away (see more info in GPBBootstrap.h).
 void GPBCheckRuntimeVersionInternal(int32_t version);