Skip to content

Commit e5ddc45

Browse files
committed
Reparse unknown features using extension registry containing Java features.
This should not be needed for generated code, but may be needed for user calls to the public buildFrom method. FileDescriptorProto should really be parsed with java features in the extension registry (like other extensions), but we can handle this in Java runtime to ease editions adoption. PiperOrigin-RevId: 638715579
1 parent 86768b3 commit e5ddc45

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

java/core/src/main/java/com/google/protobuf/Descriptors.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,6 +2785,21 @@ private GenericDescriptor() {}
27852785
public abstract FileDescriptor getFile();
27862786

27872787
void resolveFeatures(FeatureSet unresolvedFeatures) throws DescriptorValidationException {
2788+
// Unknown java features may be passed by users via public buildFrom but should not occur from
2789+
// generated code.
2790+
if (!unresolvedFeatures.getUnknownFields().isEmpty()
2791+
&& unresolvedFeatures.getUnknownFields().hasField(JavaFeaturesProto.java_.getNumber())) {
2792+
ExtensionRegistry registry = ExtensionRegistry.newInstance();
2793+
registry.add(JavaFeaturesProto.java_);
2794+
ByteString bytes = unresolvedFeatures.toByteString();
2795+
try {
2796+
unresolvedFeatures = FeatureSet.parseFrom(bytes, registry);
2797+
} catch (InvalidProtocolBufferException e) {
2798+
throw new DescriptorValidationException(
2799+
this, "Failed to parse features with Java feature extension registry.", e);
2800+
}
2801+
}
2802+
27882803
if (this.parent != null
27892804
&& unresolvedFeatures.equals(FeatureSet.getDefaultInstance())
27902805
&& !hasInferredLegacyProtoFeatures()) {

java/core/src/test/java/com/google/protobuf/DescriptorsTest.java

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public void testFieldDescriptorDefault() throws Exception {
352352
}
353353

354354
@Test
355-
public void testFieldDescriptorLegacyEnumFieldTreatedAsClosed() throws Exception {
355+
public void testProto2FieldDescriptorLegacyEnumFieldTreatedAsClosed() throws Exception {
356356
// Make an open enum definition.
357357
FileDescriptorProto openEnumFile =
358358
FileDescriptorProto.newBuilder()
@@ -430,6 +430,111 @@ public void testFieldDescriptorLegacyEnumFieldTreatedAsClosed() throws Exception
430430
.isTrue();
431431
}
432432

433+
@Test
434+
public void testEditionFieldDescriptorLegacyEnumFieldTreatedAsClosed() throws Exception {
435+
// Make an open enum definition.
436+
FileDescriptorProto openEnumFile =
437+
FileDescriptorProto.newBuilder()
438+
.setName("open_enum.proto")
439+
.setSyntax("editions")
440+
.setEdition(Edition.EDITION_2023)
441+
.addEnumType(
442+
EnumDescriptorProto.newBuilder()
443+
.setName("TestEnumOpen")
444+
.addValue(
445+
EnumValueDescriptorProto.newBuilder()
446+
.setName("TestEnumOpen_VALUE0")
447+
.setNumber(0)
448+
.build())
449+
.build())
450+
.build();
451+
FileDescriptor openFileDescriptor =
452+
Descriptors.FileDescriptor.buildFrom(openEnumFile, new FileDescriptor[0]);
453+
EnumDescriptor openEnum = openFileDescriptor.getEnumTypes().get(0);
454+
assertThat(openEnum.isClosed()).isFalse();
455+
456+
// Create a message that treats enum fields as closed.
457+
FileDescriptorProto editionsClosedEnumFile =
458+
FileDescriptorProto.newBuilder()
459+
.setName("editions_closed_enum_field.proto")
460+
.addDependency("open_enum.proto")
461+
.setSyntax("editions")
462+
.setEdition(Edition.EDITION_2023)
463+
.setOptions(
464+
FileOptions.newBuilder()
465+
.setFeatures(
466+
DescriptorProtos.FeatureSet.newBuilder()
467+
.setEnumType(DescriptorProtos.FeatureSet.EnumType.CLOSED)
468+
.build())
469+
.build())
470+
.addEnumType(
471+
EnumDescriptorProto.newBuilder()
472+
.setName("TestEnum")
473+
.addValue(
474+
EnumValueDescriptorProto.newBuilder()
475+
.setName("TestEnum_VALUE0")
476+
.setNumber(0)
477+
.build())
478+
.build())
479+
.addMessageType(
480+
DescriptorProto.newBuilder()
481+
.setName("TestClosedEnumField")
482+
.addField(
483+
FieldDescriptorProto.newBuilder()
484+
.setName("int_field")
485+
.setNumber(1)
486+
.setType(FieldDescriptorProto.Type.TYPE_INT32)
487+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
488+
.build())
489+
.addField(
490+
FieldDescriptorProto.newBuilder()
491+
.setName("open_enum")
492+
.setNumber(2)
493+
.setType(FieldDescriptorProto.Type.TYPE_ENUM)
494+
.setTypeName("TestEnumOpen")
495+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
496+
.setOptions(
497+
DescriptorProtos.FieldOptions.newBuilder()
498+
.setFeatures(
499+
DescriptorProtos.FeatureSet.newBuilder()
500+
.setExtension(
501+
JavaFeaturesProto.java_,
502+
JavaFeaturesProto.JavaFeatures.newBuilder()
503+
.setLegacyClosedEnum(true)
504+
.build())
505+
.build())
506+
.build())
507+
.build())
508+
.addField(
509+
FieldDescriptorProto.newBuilder()
510+
.setName("closed_enum")
511+
.setNumber(3)
512+
.setType(FieldDescriptorProto.Type.TYPE_ENUM)
513+
.setTypeName("TestEnum")
514+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
515+
.build())
516+
.build())
517+
.build();
518+
// Ensure Java features are in unknown fields.
519+
editionsClosedEnumFile =
520+
FileDescriptorProto.parseFrom(
521+
editionsClosedEnumFile.toByteString(), ExtensionRegistry.getEmptyRegistry());
522+
Descriptor editionsClosedMessage =
523+
Descriptors.FileDescriptor.buildFrom(
524+
editionsClosedEnumFile, new FileDescriptor[] {openFileDescriptor})
525+
.getMessageTypes()
526+
.get(0);
527+
assertThat(
528+
editionsClosedMessage.findFieldByName("int_field").legacyEnumFieldTreatedAsClosed())
529+
.isFalse();
530+
assertThat(
531+
editionsClosedMessage.findFieldByName("closed_enum").legacyEnumFieldTreatedAsClosed())
532+
.isTrue();
533+
assertThat(
534+
editionsClosedMessage.findFieldByName("open_enum").legacyEnumFieldTreatedAsClosed())
535+
.isTrue();
536+
}
537+
433538
@Test
434539
public void testFieldDescriptorLegacyEnumFieldTreatedAsOpen() throws Exception {
435540
// Make an open enum definition and message that treats enum fields as open.

0 commit comments

Comments
 (0)