Optimize gencode `<clinit>` handling of `FileDescriptorProto` and extensions

PiperOrigin-RevId: 803108052
diff --git a/java/core/src/main/java/com/google/protobuf/BuiltinExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/BuiltinExtensionRegistry.java
new file mode 100644
index 0000000..b7ef322
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/BuiltinExtensionRegistry.java
@@ -0,0 +1,26 @@
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+final class BuiltinExtensionRegistry {
+
+  private static volatile ExtensionRegistry instance = null;
+
+  static ExtensionRegistry getInstance() {
+    ExtensionRegistry instance = BuiltinExtensionRegistry.instance;
+    if (instance == null) {
+      synchronized (BuiltinExtensionRegistry.class) {
+        instance = BuiltinExtensionRegistry.instance;
+        if (instance == null) {
+          instance = ExtensionRegistry.newInstance();
+          instance.add(checkNotNull(JavaFeaturesProto.java_));
+          instance = instance.getUnmodifiable();
+          BuiltinExtensionRegistry.instance = instance;
+        }
+      }
+    }
+    return instance;
+  }
+
+  private BuiltinExtensionRegistry() {}
+}
diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java
index 1a0c39c..8cd7dd3 100644
--- a/java/core/src/main/java/com/google/protobuf/Descriptors.java
+++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java
@@ -30,6 +30,7 @@
 import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
 import com.google.protobuf.DescriptorProtos.ServiceOptions;
 import com.google.protobuf.JavaFeaturesProto.JavaFeatures;
+import java.io.IOException;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -494,17 +495,26 @@
       return descriptors.toArray(new FileDescriptor[0]);
     }
 
+    @Deprecated
+    public static FileDescriptor internalBuildGeneratedFileFrom(
+        final String[] descriptorDataParts, final FileDescriptor[] dependencies) {
+      return internalBuildGeneratedFileFrom(
+          descriptorDataParts, dependencies, ExtensionRegistry.getEmptyRegistry());
+    }
+
     /**
      * This method is to be called by generated code only. It is equivalent to {@code buildFrom}
      * except that the {@code FileDescriptorProto} is encoded in protocol buffer wire format.
      */
     public static FileDescriptor internalBuildGeneratedFileFrom(
-        final String[] descriptorDataParts, final FileDescriptor[] dependencies) {
+        final String[] descriptorDataParts,
+        final FileDescriptor[] dependencies,
+        ExtensionRegistry registry) {
       final byte[] descriptorBytes = latin1Cat(descriptorDataParts);
 
       FileDescriptorProto proto;
       try {
-        proto = FileDescriptorProto.parseFrom(descriptorBytes);
+        proto = FileDescriptorProto.parseFrom(descriptorBytes, registry);
       } catch (InvalidProtocolBufferException e) {
         throw new IllegalArgumentException(
             "Failed to parse protocol buffer descriptor for generated code.", e);
@@ -541,13 +551,11 @@
      */
     public static void internalUpdateFileDescriptor(
         FileDescriptor descriptor, ExtensionRegistry registry) {
-      ByteString bytes = descriptor.proto.toByteString();
       try {
-        FileDescriptorProto proto = FileDescriptorProto.parseFrom(bytes, registry);
-        descriptor.setProto(proto);
-      } catch (InvalidProtocolBufferException e) {
+        descriptor.promoteUnknownExtensions(registry);
+      } catch (DescriptorValidationException e) {
         throw new IllegalArgumentException(
-            "Failed to parse protocol buffer descriptor for generated code.", e);
+            "Invalid features in \"" + descriptor.getName() + "\".", e);
       }
     }
 
@@ -765,37 +773,35 @@
       }
     }
 
-    /**
-     * Replace our {@link FileDescriptorProto} with the given one, which is identical except that it
-     * might contain extensions that weren't present in the original. This method is needed for
-     * bootstrapping when a file defines custom options. The options may be defined in the file
-     * itself, so we can't actually parse them until we've constructed the descriptors, but to
-     * construct the descriptors we have to have parsed the descriptor protos. So, we have to parse
-     * the descriptor protos a second time after constructing the descriptors.
-     */
-    private synchronized void setProto(final FileDescriptorProto proto) {
-      this.proto = proto;
-      this.options = null;
-      try {
-        resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      FileOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      FileOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      FileOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
 
-        for (int i = 0; i < messageTypes.length; i++) {
-          messageTypes[i].setProto(proto.getMessageType(i));
-        }
-
-        for (int i = 0; i < enumTypes.length; i++) {
-          enumTypes[i].setProto(proto.getEnumType(i));
-        }
-
-        for (int i = 0; i < services.length; i++) {
-          services[i].setProto(proto.getService(i));
-        }
-
-        for (int i = 0; i < extensions.length; i++) {
-          extensions[i].setProto(proto.getExtension(i));
-        }
-      } catch (DescriptorValidationException e) {
-        throw new IllegalArgumentException("Invalid features for \"" + proto.getName() + "\".", e);
+      for (Descriptor messageType : messageTypes) {
+        messageType.promoteUnknownExtensions(registry);
+      }
+      for (EnumDescriptor enumType : enumTypes) {
+        enumType.promoteUnknownExtensions(registry);
+      }
+      for (ServiceDescriptor service : services) {
+        service.promoteUnknownExtensions(registry);
+      }
+      for (FieldDescriptor extension : extensions) {
+        extension.promoteUnknownExtensions(registry);
       }
     }
   }
@@ -1306,30 +1312,38 @@
       }
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final DescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
-
-      for (int i = 0; i < nestedTypes.length; i++) {
-        nestedTypes[i].setProto(proto.getNestedType(i));
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      MessageOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      MessageOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      MessageOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
       }
 
-      for (int i = 0; i < oneofs.length; i++) {
-        oneofs[i].setProto(proto.getOneofDecl(i));
+      for (Descriptor nestedType : nestedTypes) {
+        nestedType.promoteUnknownExtensions(registry);
       }
-
-      for (int i = 0; i < enumTypes.length; i++) {
-        enumTypes[i].setProto(proto.getEnumType(i));
+      for (OneofDescriptor oneof : oneofs) {
+        oneof.promoteUnknownExtensions(registry);
       }
-
-      for (int i = 0; i < fields.length; i++) {
-        fields[i].setProto(proto.getField(i));
+      for (EnumDescriptor enumType : enumTypes) {
+        enumType.promoteUnknownExtensions(registry);
       }
-
-      for (int i = 0; i < extensions.length; i++) {
-        extensions[i].setProto(proto.getExtension(i));
+      for (FieldDescriptor field : fields) {
+        field.promoteUnknownExtensions(registry);
+      }
+      for (FieldDescriptor extension : extensions) {
+        extension.promoteUnknownExtensions(registry);
       }
     }
   }
@@ -2275,11 +2289,23 @@
       }
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final FieldDescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      FieldOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      FieldOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      FieldOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
     }
 
     @Override
@@ -2569,14 +2595,26 @@
       }
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final EnumDescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      EnumOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      EnumOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      EnumOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
 
-      for (int i = 0; i < values.length; i++) {
-        values[i].setProto(proto.getValue(i));
+      for (EnumValueDescriptor value : values) {
+        value.promoteUnknownExtensions(registry);
       }
     }
   }
@@ -2718,12 +2756,23 @@
       resolveFeatures(proto.getOptions().getFeatures());
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final EnumValueDescriptorProto proto)
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
         throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+      EnumValueOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      EnumValueOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      EnumValueOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
     }
   }
 
@@ -2858,14 +2907,26 @@
       }
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final ServiceDescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      ServiceOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      ServiceOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      ServiceOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
 
-      for (int i = 0; i < methods.length; i++) {
-        methods[i].setProto(proto.getMethod(i));
+      for (MethodDescriptor method : methods) {
+        method.promoteUnknownExtensions(registry);
       }
     }
   }
@@ -3009,11 +3070,23 @@
       outputType = (Descriptor) output;
     }
 
-    /** See {@link FileDescriptor#setProto}. */
-    private void setProto(final MethodDescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      MethodOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      MethodOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      MethodOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
     }
   }
 
@@ -3133,6 +3206,33 @@
     }
 
     volatile FeatureSet features;
+
+    @CanIgnoreReturnValue
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    protected static <T extends GeneratedMessage.ExtendableMessage> T promoteUnknownExtensions(
+        T message, ExtensionRegistry registry) {
+      if (!message.hasUnknownFields()) {
+        return message;
+      }
+      // We could iterate the unknown fields directly, lookup the extension, and set the extensions.
+      // However that is more work (though probably more performant), and this is the easier route.
+      UnknownFieldSet oldUnknownFields = message.getUnknownFields();
+      ByteString serializedUnknownFields = oldUnknownFields.toByteString();
+      GeneratedMessage.ExtendableMessage.Builder builder =
+          (GeneratedMessage.ExtendableMessage.Builder) message.toBuilder();
+      builder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
+      try {
+        builder.mergeFrom(serializedUnknownFields, registry);
+      } catch (IOException e) {
+        throw new RuntimeException(
+            "Failed to reparse " + builder.getDescriptorForType().getFullName(), e);
+      }
+      if (builder.getUnknownFieldCount() == oldUnknownFields.getFieldCount()) {
+        // No promotion was successful. Throw everything away and just return the original.
+        return message;
+      }
+      return (T) builder.build();
+    }
   }
 
   /** Thrown when building descriptors fails because the source DescriptorProtos are not valid. */
@@ -3612,10 +3712,23 @@
       resolveFeatures(proto.getOptions().getFeatures());
     }
 
-    private void setProto(final OneofDescriptorProto proto) throws DescriptorValidationException {
-      this.proto = proto;
-      this.options = null;
-      resolveFeatures(proto.getOptions().getFeatures());
+    private void promoteUnknownExtensions(ExtensionRegistry registry)
+        throws DescriptorValidationException {
+      OneofOptions oldOptions = proto.getOptions();
+      FeatureSet oldFeatures = oldOptions.getFeatures();
+      FeatureSet newFeatures = promoteUnknownExtensions(oldFeatures, registry);
+      OneofOptions midOptions;
+      if (oldFeatures != newFeatures) {
+        midOptions = oldOptions.toBuilder().setFeatures(newFeatures).build();
+      } else {
+        midOptions = oldOptions;
+      }
+      OneofOptions newOptions = promoteUnknownExtensions(midOptions, registry);
+      if (oldOptions != newOptions) {
+        this.options = null;
+        this.proto = proto.toBuilder().setOptions(newOptions).build();
+        resolveFeatures(newFeatures);
+      }
     }
 
     private OneofDescriptor(
diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java
index d59d504..8d6486b 100644
--- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java
+++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java
@@ -76,6 +76,10 @@
     return EMPTY_REGISTRY;
   }
 
+  public static ExtensionRegistry getBuiltinRegistry() {
+    return BuiltinExtensionRegistry.getInstance();
+  }
+
   /** Returns an unmodifiable view of the registry. */
   @Override
   public ExtensionRegistry getUnmodifiable() {
diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java
index 7201024..335a219 100644
--- a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java
+++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java
@@ -287,6 +287,16 @@
     return unknownFields;
   }
 
+  @Override
+  public final boolean hasUnknownFields() {
+    return !unknownFields.isEmpty();
+  }
+
+  @Override
+  public final int getUnknownFieldCount() {
+    return unknownFields.getFieldCount();
+  }
+
   // TODO: This should go away when Schema classes cannot modify immutable
   // GeneratedMessage objects anymore.
   void setUnknownFields(UnknownFieldSet unknownFields) {
@@ -821,6 +831,24 @@
       }
     }
 
+    @Override
+    public final boolean hasUnknownFields() {
+      if (unknownFieldsOrBuilder instanceof UnknownFieldSet) {
+        return !((UnknownFieldSet) unknownFieldsOrBuilder).isEmpty();
+      } else {
+        return !((UnknownFieldSet.Builder) unknownFieldsOrBuilder).isEmpty();
+      }
+    }
+
+    @Override
+    public final int getUnknownFieldCount() {
+      if (unknownFieldsOrBuilder instanceof UnknownFieldSet) {
+        return ((UnknownFieldSet) unknownFieldsOrBuilder).getFieldCount();
+      } else {
+        return ((UnknownFieldSet.Builder) unknownFieldsOrBuilder).getFieldCount();
+      }
+    }
+
     /**
      * Called by generated subclasses to parse an unknown field.
      *
diff --git a/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java
index 2ae1081..abc18e4 100644
--- a/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java
+++ b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java
@@ -112,4 +112,14 @@
 
   /** Get the {@link UnknownFieldSet} for this message. */
   UnknownFieldSet getUnknownFields();
+
+  /** Determines whether this message has unknown fields in its unknown field set. */
+  default boolean hasUnknownFields() {
+    return !getUnknownFields().isEmpty();
+  }
+
+  /** Counts the number of unknown fields in this message. */
+  default int getUnknownFieldCount() {
+    return getUnknownFields().getFieldCount();
+  }
 }
diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
index de50583..e796dd9 100644
--- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
+++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
@@ -108,6 +108,10 @@
     return (result == null) ? Field.getDefaultInstance() : result;
   }
 
+  public int getFieldCount() {
+    return fields.size();
+  }
+
   /** Serializes the set and writes it to {@code output}. */
   @Override
   public void writeTo(CodedOutputStream output) throws IOException {
@@ -675,6 +679,14 @@
       // initialized.
       return true;
     }
+
+    public boolean isEmpty() {
+      return fieldBuilders.isEmpty();
+    }
+
+    public int getFieldCount() {
+      return fieldBuilders.size();
+    }
   }
 
   /**
diff --git a/src/google/protobuf/compiler/java/generator.cc b/src/google/protobuf/compiler/java/generator.cc
index b95df32..45486da 100644
--- a/src/google/protobuf/compiler/java/generator.cc
+++ b/src/google/protobuf/compiler/java/generator.cc
@@ -74,6 +74,8 @@
       file_options.annotation_list_file = option.second;
     } else if (option.first == "experimental_strip_nonfunctional_codegen") {
       file_options.strip_nonfunctional_codegen = true;
+    } else if (option.first == "bootstrap") {
+      file_options.bootstrap = true;
     } else {
       *error = absl::StrCat("Unknown generator option: ", option.first);
       return false;
diff --git a/src/google/protobuf/compiler/java/options.h b/src/google/protobuf/compiler/java/options.h
index ab40967..899a69f 100644
--- a/src/google/protobuf/compiler/java/options.h
+++ b/src/google/protobuf/compiler/java/options.h
@@ -55,6 +55,9 @@
   // If true, the generated DSL code will only utilize concrete types, never
   // referring to the OrBuilder interfaces.
   bool dsl_use_concrete_types;
+
+  // Used by protobuf itself and not supported for direct use by users.
+  bool bootstrap = false;
 };
 
 }  // namespace java
diff --git a/src/google/protobuf/compiler/java/shared_code_generator.cc b/src/google/protobuf/compiler/java/shared_code_generator.cc
index 113b2b0..960ee8f 100644
--- a/src/google/protobuf/compiler/java/shared_code_generator.cc
+++ b/src/google/protobuf/compiler/java/shared_code_generator.cc
@@ -197,7 +197,11 @@
     }
   }
 
-  printer->Print("    });\n");
+  printer->Print(
+      "    }, $registry$);\n", "registry",
+      options_.bootstrap
+          ? "com.google.protobuf.ExtensionRegistry.getEmptyRegistry()"
+          : "com.google.protobuf.ExtensionRegistry.getBuiltinRegistry()");
 }
 
 }  // namespace java