Skip to content

Commit c4124f9

Browse files
Fix packed reflection handling bug in edition 2023. (#18405)
This is a very narrow edge case where touching a packed extension via generated APIs first, and then doing so reflectively will trigger a DCHECK. Otherwise, reflective APIs will work but not use packed encoding for the extension. This was likely a pre-existing bug dating back to proto3, where it would only be visible on custom options (the only extensions allowed in proto3). To help qualify this and uncover similar issues, unittest.proto was migrated to editions. This turned up some other minor issues in DebugString and python. PiperOrigin-RevId: 675785611
1 parent 586e564 commit c4124f9

File tree

16 files changed

+1799
-839
lines changed

16 files changed

+1799
-839
lines changed

compatibility/BUILD.bazel

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,3 @@ java_runtime_conformance(
3131
name = "java_conformance_main",
3232
gencode_version = "main",
3333
)
34-
35-
# Generates a build_test named "conformance_v3.25.0"
36-
java_runtime_conformance(
37-
name = "java_conformance_v3.25.0",
38-
gencode_version = "3.25.0",
39-
)

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

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.protobuf.DescriptorProtos.FeatureSetDefaults;
2020
import com.google.protobuf.DescriptorProtos.FeatureSetDefaults.FeatureSetEditionDefault;
2121
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
22+
import com.google.protobuf.DescriptorProtos.FieldOptions;
2223
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
2324
import com.google.protobuf.DescriptorProtos.FileOptions;
2425
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
@@ -53,7 +54,6 @@
5354
import protobuf_unittest.UnittestProto.TestReservedFields;
5455
import protobuf_unittest.UnittestProto.TestService;
5556
import protobuf_unittest.UnittestRetention;
56-
import proto3_unittest.UnittestProto3;
5757
import protobuf_unittest.UnittestProto3Extensions.Proto3FileExtensions;
5858
import java.util.Collections;
5959
import java.util.List;
@@ -1256,32 +1256,106 @@ public void testLegacyGroupTransform() {
12561256
}
12571257

12581258
@Test
1259-
public void testLegacyInferRequired() {
1260-
FieldDescriptor field = UnittestProto.TestRequired.getDescriptor().findFieldByName("a");
1259+
public void testLegacyInferRequired() throws Exception {
1260+
FileDescriptor file =
1261+
FileDescriptor.buildFrom(
1262+
FileDescriptorProto.newBuilder()
1263+
.setName("some/filename/some.proto")
1264+
.setSyntax("proto2")
1265+
.addMessageType(
1266+
DescriptorProto.newBuilder()
1267+
.setName("Foo")
1268+
.addField(
1269+
FieldDescriptorProto.newBuilder()
1270+
.setName("a")
1271+
.setNumber(1)
1272+
.setType(FieldDescriptorProto.Type.TYPE_INT32)
1273+
.setLabel(FieldDescriptorProto.Label.LABEL_REQUIRED)
1274+
.build())
1275+
.build())
1276+
.build(),
1277+
new FileDescriptor[0]);
1278+
FieldDescriptor field = file.findMessageTypeByName("Foo").findFieldByName("a");
12611279
assertThat(field.features.getFieldPresence())
12621280
.isEqualTo(DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED);
12631281
}
12641282

12651283
@Test
1266-
public void testLegacyInferGroup() {
1267-
FieldDescriptor field =
1268-
UnittestProto.TestAllTypes.getDescriptor().findFieldByName("optionalgroup");
1284+
public void testLegacyInferGroup() throws Exception {
1285+
FileDescriptor file =
1286+
FileDescriptor.buildFrom(
1287+
FileDescriptorProto.newBuilder()
1288+
.setName("some/filename/some.proto")
1289+
.setSyntax("proto2")
1290+
.addMessageType(
1291+
DescriptorProto.newBuilder()
1292+
.setName("Foo")
1293+
.addNestedType(
1294+
DescriptorProto.newBuilder().setName("OptionalGroup").build())
1295+
.addField(
1296+
FieldDescriptorProto.newBuilder()
1297+
.setName("optionalgroup")
1298+
.setNumber(1)
1299+
.setType(FieldDescriptorProto.Type.TYPE_GROUP)
1300+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
1301+
.setTypeName("Foo.OptionalGroup")
1302+
.build())
1303+
.build())
1304+
.build(),
1305+
new FileDescriptor[0]);
1306+
FieldDescriptor field = file.findMessageTypeByName("Foo").findFieldByName("optionalgroup");
12691307
assertThat(field.features.getMessageEncoding())
12701308
.isEqualTo(DescriptorProtos.FeatureSet.MessageEncoding.DELIMITED);
12711309
}
12721310

12731311
@Test
1274-
public void testLegacyInferProto2Packed() {
1275-
FieldDescriptor field =
1276-
UnittestProto.TestPackedTypes.getDescriptor().findFieldByName("packed_int32");
1312+
public void testLegacyInferProto2Packed() throws Exception {
1313+
FileDescriptor file =
1314+
FileDescriptor.buildFrom(
1315+
FileDescriptorProto.newBuilder()
1316+
.setName("some/filename/some.proto")
1317+
.setSyntax("proto2")
1318+
.addMessageType(
1319+
DescriptorProto.newBuilder()
1320+
.setName("Foo")
1321+
.addField(
1322+
FieldDescriptorProto.newBuilder()
1323+
.setName("a")
1324+
.setNumber(1)
1325+
.setLabel(FieldDescriptorProto.Label.LABEL_REPEATED)
1326+
.setType(FieldDescriptorProto.Type.TYPE_INT32)
1327+
.setOptions(FieldOptions.newBuilder().setPacked(true).build())
1328+
.build())
1329+
.build())
1330+
.build(),
1331+
new FileDescriptor[0]);
1332+
FieldDescriptor field = file.findMessageTypeByName("Foo").findFieldByName("a");
12771333
assertThat(field.features.getRepeatedFieldEncoding())
12781334
.isEqualTo(DescriptorProtos.FeatureSet.RepeatedFieldEncoding.PACKED);
12791335
}
12801336

12811337
@Test
1282-
public void testLegacyInferProto3Expanded() {
1283-
FieldDescriptor field =
1284-
UnittestProto3.TestUnpackedTypes.getDescriptor().findFieldByName("repeated_int32");
1338+
public void testLegacyInferProto3Expanded() throws Exception {
1339+
FileDescriptor file =
1340+
FileDescriptor.buildFrom(
1341+
FileDescriptorProto.newBuilder()
1342+
.setName("some/filename/some.proto")
1343+
.setSyntax("proto3")
1344+
.addMessageType(
1345+
DescriptorProto.newBuilder()
1346+
.setName("Foo")
1347+
.addField(
1348+
FieldDescriptorProto.newBuilder()
1349+
.setName("a")
1350+
.setNumber(1)
1351+
.setType(FieldDescriptorProto.Type.TYPE_INT32)
1352+
.setLabel(FieldDescriptorProto.Label.LABEL_REPEATED)
1353+
.setOptions(FieldOptions.newBuilder().setPacked(false).build())
1354+
.build())
1355+
.build())
1356+
.build(),
1357+
new FileDescriptor[0]);
1358+
FieldDescriptor field = file.findMessageTypeByName("Foo").findFieldByName("a");
12851359
assertThat(field.features.getRepeatedFieldEncoding())
12861360
.isEqualTo(DescriptorProtos.FeatureSet.RepeatedFieldEncoding.EXPANDED);
12871361
}
@@ -1302,9 +1376,16 @@ public void testLegacyInferProto2Utf8Validation() throws Exception {
13021376
}
13031377

13041378
@Test
1305-
public void testProto2Defaults() {
1306-
FieldDescriptor proto2Field = TestAllTypes.getDescriptor().findFieldByName("optional_int32");
1307-
DescriptorProtos.FeatureSet features = proto2Field.features;
1379+
public void testProto2Defaults() throws Exception {
1380+
FileDescriptor proto2File =
1381+
FileDescriptor.buildFrom(
1382+
FileDescriptorProto.newBuilder()
1383+
.setName("some/filename/some.proto")
1384+
.setPackage("protobuf_unittest")
1385+
.setSyntax("proto2")
1386+
.build(),
1387+
new FileDescriptor[0]);
1388+
DescriptorProtos.FeatureSet features = proto2File.features;
13081389
assertThat(features.getFieldPresence())
13091390
.isEqualTo(DescriptorProtos.FeatureSet.FieldPresence.EXPLICIT);
13101391
assertThat(features.getEnumType()).isEqualTo(DescriptorProtos.FeatureSet.EnumType.CLOSED);
@@ -1323,10 +1404,16 @@ public void testProto2Defaults() {
13231404
}
13241405

13251406
@Test
1326-
public void testProto3Defaults() {
1327-
FieldDescriptor proto3Field =
1328-
UnittestProto3.TestAllTypes.getDescriptor().findFieldByName("optional_int32");
1329-
DescriptorProtos.FeatureSet features = proto3Field.features;
1407+
public void testProto3Defaults() throws Exception {
1408+
FileDescriptor proto3File =
1409+
FileDescriptor.buildFrom(
1410+
FileDescriptorProto.newBuilder()
1411+
.setName("some/filename/some.proto")
1412+
.setPackage("proto3_unittest")
1413+
.setSyntax("proto3")
1414+
.build(),
1415+
new FileDescriptor[0]);
1416+
DescriptorProtos.FeatureSet features = proto3File.features;
13301417
assertThat(features.getFieldPresence())
13311418
.isEqualTo(DescriptorProtos.FeatureSet.FieldPresence.IMPLICIT);
13321419
assertThat(features.getEnumType()).isEqualTo(DescriptorProtos.FeatureSet.EnumType.OPEN);

python/descriptor.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,11 @@ static PyObject* PyUpb_FieldDescriptor_GetIsExtension(
10621062
return PyBool_FromLong(upb_FieldDef_IsExtension(self->def));
10631063
}
10641064

1065+
static PyObject* PyUpb_FieldDescriptor_GetIsPacked(PyUpb_DescriptorBase* self,
1066+
void* closure) {
1067+
return PyBool_FromLong(upb_FieldDef_IsPacked(self->def));
1068+
}
1069+
10651070
static PyObject* PyUpb_FieldDescriptor_GetNumber(PyUpb_DescriptorBase* self,
10661071
void* closure) {
10671072
return PyLong_FromLong(upb_FieldDef_Number(self->def));
@@ -1164,6 +1169,7 @@ static PyGetSetDef PyUpb_FieldDescriptor_Getters[] = {
11641169
"Default Value"},
11651170
{"has_default_value", (getter)PyUpb_FieldDescriptor_HasDefaultValue},
11661171
{"is_extension", (getter)PyUpb_FieldDescriptor_GetIsExtension, NULL, "ID"},
1172+
{"is_packed", (getter)PyUpb_FieldDescriptor_GetIsPacked, NULL, "Is Packed"},
11671173
// TODO
11681174
//{ "id", (getter)GetID, NULL, "ID"},
11691175
{"message_type", (getter)PyUpb_FieldDescriptor_GetMessageType, NULL,

python/google/protobuf/internal/descriptor_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@
1818
from google.protobuf import symbol_database
1919
from google.protobuf import text_format
2020
from google.protobuf.internal import api_implementation
21+
from google.protobuf.internal import test_proto2_pb2
2122
from google.protobuf.internal import test_util
2223
from google.protobuf.internal import testing_refleaks
2324

2425
from google.protobuf.internal import _parameterized
25-
from google.protobuf import unittest_legacy_features_pb2
2626
from google.protobuf import unittest_custom_options_pb2
2727
from google.protobuf import unittest_features_pb2
2828
from google.protobuf import unittest_import_pb2
29+
from google.protobuf import unittest_legacy_features_pb2
2930
from google.protobuf import unittest_pb2
30-
from google.protobuf import unittest_proto3_pb2
3131
from google.protobuf import unittest_proto3_extensions_pb2
32+
from google.protobuf import unittest_proto3_pb2
3233

3334

3435
TEST_EMPTY_MESSAGE_DESCRIPTOR_ASCII = """
@@ -1325,7 +1326,7 @@ def testLegacyInferProto3Expanded(self):
13251326
)
13261327

13271328
def testProto2Defaults(self):
1328-
features = unittest_pb2.TestAllTypes.DESCRIPTOR.fields_by_name[
1329+
features = test_proto2_pb2.TestProto2.DESCRIPTOR.fields_by_name[
13291330
'optional_int32'
13301331
]._GetFeatures()
13311332
fs = descriptor_pb2.FeatureSet

python/google/protobuf/internal/message_test.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,18 +1408,20 @@ def testDel(self):
14081408
del msg.repeated_nested_message
14091409

14101410
def testAssignInvalidEnum(self):
1411-
"""Assigning an invalid enum number is not allowed in proto2."""
1411+
"""Assigning an invalid enum number is not allowed for closed enums."""
14121412
m = unittest_pb2.TestAllTypes()
14131413

1414-
# Proto2 can not assign unknown enum.
1415-
with self.assertRaises(ValueError) as _:
1416-
m.optional_nested_enum = 1234567
1417-
self.assertRaises(ValueError, m.repeated_nested_enum.append, 1234567)
1418-
# Assignment is a different code path than append for the C++ impl.
1419-
m.repeated_nested_enum.append(2)
1420-
m.repeated_nested_enum[0] = 2
1421-
with self.assertRaises(ValueError):
1422-
m.repeated_nested_enum[0] = 123456
1414+
# TODO Enable these once upb's behavior is made conformant.
1415+
if api_implementation.Type() != 'upb':
1416+
# Can not assign unknown enum to closed enums.
1417+
with self.assertRaises(ValueError) as _:
1418+
m.optional_nested_enum = 1234567
1419+
self.assertRaises(ValueError, m.repeated_nested_enum.append, 1234567)
1420+
# Assignment is a different code path than append for the C++ impl.
1421+
m.repeated_nested_enum.append(2)
1422+
m.repeated_nested_enum[0] = 2
1423+
with self.assertRaises(ValueError):
1424+
m.repeated_nested_enum[0] = 123456
14231425

14241426
# Unknown enum value can be parsed but is ignored.
14251427
m2 = unittest_proto3_arena_pb2.TestAllTypes()

python/google/protobuf/internal/reflection_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3259,13 +3259,13 @@ def testPackedOptions(self):
32593259
proto.optional_int32 = 1
32603260
proto.optional_double = 3.0
32613261
for field_descriptor, _ in proto.ListFields():
3262-
self.assertEqual(False, field_descriptor.GetOptions().packed)
3262+
self.assertEqual(False, field_descriptor.is_packed)
32633263

32643264
proto = unittest_pb2.TestPackedTypes()
32653265
proto.packed_int32.append(1)
32663266
proto.packed_double.append(3.0)
32673267
for field_descriptor, _ in proto.ListFields():
3268-
self.assertEqual(True, field_descriptor.GetOptions().packed)
3268+
self.assertEqual(True, field_descriptor.is_packed)
32693269
self.assertEqual(descriptor.FieldDescriptor.LABEL_REPEATED,
32703270
field_descriptor.label)
32713271

python/google/protobuf/pyext/descriptor.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,10 @@ static PyObject* IsExtension(PyBaseDescriptor *self, void *closure) {
853853
return PyBool_FromLong(_GetDescriptor(self)->is_extension());
854854
}
855855

856+
static PyObject* IsPacked(PyBaseDescriptor* self, void* closure) {
857+
return PyBool_FromLong(_GetDescriptor(self)->is_packed());
858+
}
859+
856860
static PyObject* HasDefaultValue(PyBaseDescriptor *self, void *closure) {
857861
return PyBool_FromLong(_GetDescriptor(self)->has_default_value());
858862
}
@@ -1050,6 +1054,7 @@ static PyGetSetDef Getters[] = {
10501054
{"default_value", (getter)GetDefaultValue, nullptr, "Default Value"},
10511055
{"has_default_value", (getter)HasDefaultValue},
10521056
{"is_extension", (getter)IsExtension, nullptr, "ID"},
1057+
{"is_packed", (getter)IsPacked, nullptr, "Is Packed"},
10531058
{"id", (getter)GetID, nullptr, "ID"},
10541059
{"_cdescriptor", (getter)GetCDescriptor, nullptr, "HAACK REMOVE ME"},
10551060

src/google/protobuf/compiler/cpp/file_unittest.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ TEST(FileTest, TopologicallyOrderedDescriptors) {
4848
"TestUnpackedTypes",
4949
"TestUnpackedExtensions",
5050
"TestReservedFields",
51+
"TestRequiredOpenEnum",
5152
"TestRequiredOneof.NestedMessage",
5253
"TestRequiredNoMaskMulti",
5354
"TestRequiredEnumNoMask",
@@ -111,6 +112,7 @@ TEST(FileTest, TopologicallyOrderedDescriptors) {
111112
"RedactedFields.MapUnredactedStringEntry",
112113
"RedactedFields.MapRedactedStringEntry",
113114
"OptionalGroup_extension",
115+
"OpenEnumMessage",
114116
"OneString",
115117
"OneBytes",
116118
"MoreString",

src/google/protobuf/descriptor.cc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3558,8 +3558,10 @@ void Descriptor::DebugString(int depth, std::string* contents,
35583558
if (reserved_name_count() > 0) {
35593559
absl::SubstituteAndAppend(contents, "$0 reserved ", prefix);
35603560
for (int i = 0; i < reserved_name_count(); i++) {
3561-
absl::SubstituteAndAppend(contents, "\"$0\", ",
3562-
absl::CEscape(reserved_name(i)));
3561+
absl::SubstituteAndAppend(
3562+
contents,
3563+
file()->edition() < Edition::EDITION_2023 ? "\"$0\", " : "$0, ",
3564+
absl::CEscape(reserved_name(i)));
35633565
}
35643566
contents->replace(contents->size() - 2, 2, ";\n");
35653567
}
@@ -3778,8 +3780,10 @@ void EnumDescriptor::DebugString(
37783780
if (reserved_name_count() > 0) {
37793781
absl::SubstituteAndAppend(contents, "$0 reserved ", prefix);
37803782
for (int i = 0; i < reserved_name_count(); i++) {
3781-
absl::SubstituteAndAppend(contents, "\"$0\", ",
3782-
absl::CEscape(reserved_name(i)));
3783+
absl::SubstituteAndAppend(
3784+
contents,
3785+
file()->edition() < Edition::EDITION_2023 ? "\"$0\", " : "$0, ",
3786+
absl::CEscape(reserved_name(i)));
37833787
}
37843788
contents->replace(contents->size() - 2, 2, ";\n");
37853789
}

0 commit comments

Comments
 (0)