Skip to content

Commit 27d42c5

Browse files
mkruskal-googlecopybara-github
authored andcommitted
Fix a bug that strips options from descriptor.proto in Python.
This fixes Python/C++ and upb, and pushes the buggy behavior to pure python. There, it's very difficult to handle options on the bootstrapped proto with the current architecture. Future changes will attempt to address this more isolated issue. PiperOrigin-RevId: 559450900
1 parent 3bc507d commit 27d42c5

File tree

3 files changed

+110
-23
lines changed

3 files changed

+110
-23
lines changed

python/google/protobuf/descriptor.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ def _Deprecated(name):
115115
_internal_create_key = object()
116116

117117

118+
def _IsDescriptorBootstrapProto(file):
119+
"""Checks if the file descriptor corresponds to our bootstrapped descriptor.proto"""
120+
if file is None:
121+
return False
122+
return (
123+
file.name == 'net/proto2/proto/descriptor.proto'
124+
or file.name == 'google/protobuf/descriptor.proto'
125+
)
126+
127+
118128
class DescriptorBase(metaclass=DescriptorMetaclass):
119129

120130
"""Descriptors base class.
@@ -123,29 +133,35 @@ class DescriptorBase(metaclass=DescriptorMetaclass):
123133
related functionality.
124134
125135
Attributes:
126-
has_options: True if the descriptor has non-default options. Usually it
127-
is not necessary to read this -- just call GetOptions() which will
128-
happily return the default instance. However, it's sometimes useful
129-
for efficiency, and also useful inside the protobuf implementation to
130-
avoid some bootstrapping issues.
136+
has_options: True if the descriptor has non-default options. Usually it is
137+
not necessary to read this -- just call GetOptions() which will happily
138+
return the default instance. However, it's sometimes useful for
139+
efficiency, and also useful inside the protobuf implementation to avoid
140+
some bootstrapping issues.
141+
file (FileDescriptor): Reference to file info.
131142
"""
132143

133144
if _USE_C_DESCRIPTORS:
134145
# The class, or tuple of classes, that are considered as "virtual
135146
# subclasses" of this descriptor class.
136147
_C_DESCRIPTOR_CLASS = ()
137148

138-
def __init__(self, options, serialized_options, options_class_name):
149+
def __init__(self, file, options, serialized_options, options_class_name):
139150
"""Initialize the descriptor given its options message and the name of the
140151
class of the options message. The name of the class is required in case
141152
the options message is None and has to be created.
142153
"""
154+
self.file = file
143155
self._options = options
144156
self._options_class_name = options_class_name
145-
self._serialized_options = serialized_options
157+
self._serialized_options = (
158+
serialized_options if not _IsDescriptorBootstrapProto(file) else None
159+
)
146160

147161
# Does this descriptor have non-default options?
148-
self.has_options = (options is not None) or (serialized_options is not None)
162+
self.has_options = (self._options is not None) or (
163+
self._serialized_options is not None
164+
)
149165

150166
def _SetOptions(self, options, options_class_name):
151167
"""Sets the descriptor's options
@@ -195,14 +211,12 @@ def __init__(self, options, options_class_name, name, full_name,
195211
"""Constructor.
196212
197213
Args:
198-
options: Protocol message options or None
199-
to use default message options.
214+
options: Protocol message options or None to use default message options.
200215
options_class_name (str): The class name of the above options.
201216
name (str): Name of this protocol message type.
202-
full_name (str): Fully-qualified name of this protocol message type,
203-
which will include protocol "package" name and the name of any
204-
enclosing types.
205-
file (FileDescriptor): Reference to file info.
217+
full_name (str): Fully-qualified name of this protocol message type, which
218+
will include protocol "package" name and the name of any enclosing
219+
types.
206220
containing_type: if provided, this is a nested descriptor, with this
207221
descriptor as parent, otherwise None.
208222
serialized_start: The start index (inclusive) in block in the
@@ -212,13 +226,13 @@ def __init__(self, options, options_class_name, name, full_name,
212226
serialized_options: Protocol message serialized options or None.
213227
"""
214228
super(_NestedDescriptorBase, self).__init__(
215-
options, serialized_options, options_class_name)
229+
file, options, serialized_options, options_class_name
230+
)
216231

217232
self.name = name
218233
# TODO(falk): Add function to calculate full_name instead of having it in
219234
# memory?
220235
self.full_name = full_name
221-
self.file = file
222236
self.containing_type = containing_type
223237

224238
self._serialized_start = serialized_start
@@ -581,10 +595,10 @@ def __init__(self, name, full_name, index, number, type, cpp_type, label,
581595
_Deprecated('FieldDescriptor')
582596

583597
super(FieldDescriptor, self).__init__(
584-
options, serialized_options, 'FieldOptions')
598+
file, options, serialized_options, 'FieldOptions'
599+
)
585600
self.name = name
586601
self.full_name = full_name
587-
self.file = file
588602
self._camelcase_name = None
589603
if json_name is None:
590604
self.json_name = _ToJsonName(name)
@@ -732,6 +746,7 @@ def __init__(self, name, full_name, filename, values,
732746

733747
self.values = values
734748
for value in self.values:
749+
value.file = file
735750
value.type = self
736751
self.values_by_name = dict((v.name, v) for v in values)
737752
# Values are reversed to ensure that the first alias is retained.
@@ -808,7 +823,11 @@ def __init__(self, name, index, number,
808823
_Deprecated('EnumValueDescriptor')
809824

810825
super(EnumValueDescriptor, self).__init__(
811-
options, serialized_options, 'EnumValueOptions')
826+
type.file if type else None,
827+
options,
828+
serialized_options,
829+
'EnumValueOptions',
830+
)
812831
self.name = name
813832
self.index = index
814833
self.number = number
@@ -847,7 +866,11 @@ def __init__(
847866
_Deprecated('OneofDescriptor')
848867

849868
super(OneofDescriptor, self).__init__(
850-
options, serialized_options, 'OneofOptions')
869+
containing_type.file if containing_type else None,
870+
options,
871+
serialized_options,
872+
'OneofOptions',
873+
)
851874
self.name = name
852875
self.full_name = full_name
853876
self.index = index
@@ -907,6 +930,7 @@ def __init__(self, name, full_name, index, methods, options=None,
907930
self.methods_by_name = dict((m.name, m) for m in methods)
908931
# Set the containing service for each method in this service.
909932
for method in self.methods:
933+
method.file = self.file
910934
method.containing_service = self
911935

912936
def FindMethodByName(self, name):
@@ -992,7 +1016,11 @@ def __init__(self,
9921016
_Deprecated('MethodDescriptor')
9931017

9941018
super(MethodDescriptor, self).__init__(
995-
options, serialized_options, 'MethodOptions')
1019+
containing_service.file if containing_service else None,
1020+
options,
1021+
serialized_options,
1022+
'MethodOptions',
1023+
)
9961024
self.name = name
9971025
self.full_name = full_name
9981026
self.index = index
@@ -1076,7 +1104,8 @@ def __init__(self, name, package, options=None,
10761104
_Deprecated('FileDescriptor')
10771105

10781106
super(FileDescriptor, self).__init__(
1079-
options, serialized_options, 'FileOptions')
1107+
None, options, serialized_options, 'FileOptions'
1108+
)
10801109

10811110
if pool is None:
10821111
from google.protobuf import descriptor_pool

python/google/protobuf/internal/reflection_test.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,64 @@ def testFileDescriptorErrors(self):
20532053
# dependency on the C++ logging code.
20542054
self.assertIn('test_file_descriptor_errors.msg1', str(cm.exception))
20552055

2056+
@unittest.skipIf(
2057+
api_implementation.Type() == 'python',
2058+
'Options are not supported on descriptor.proto in pure python'
2059+
' (b/296476238).',
2060+
)
2061+
def testDescriptorProtoHasFileOptions(self):
2062+
self.assertTrue(descriptor_pb2.DESCRIPTOR.has_options)
2063+
self.assertEqual(
2064+
descriptor_pb2.DESCRIPTOR.GetOptions().java_package,
2065+
'com.google.protobuf',
2066+
)
2067+
2068+
@unittest.skipIf(
2069+
api_implementation.Type() == 'python',
2070+
'Options are not supported on descriptor.proto in pure python'
2071+
' (b/296476238).',
2072+
)
2073+
def testDescriptorProtoHasFieldOptions(self):
2074+
self.assertTrue(descriptor_pb2.DESCRIPTOR.has_options)
2075+
self.assertEqual(
2076+
descriptor_pb2.DESCRIPTOR.GetOptions().java_package,
2077+
'com.google.protobuf',
2078+
)
2079+
packed_desc = (
2080+
descriptor_pb2.SourceCodeInfo.DESCRIPTOR.nested_types_by_name.get(
2081+
'Location'
2082+
).fields_by_name.get('path')
2083+
)
2084+
self.assertTrue(packed_desc.has_options)
2085+
self.assertTrue(packed_desc.GetOptions().packed)
2086+
2087+
@unittest.skipIf(
2088+
api_implementation.Type() == 'python',
2089+
'Options are not supported on descriptor.proto in pure python'
2090+
' (b/296476238).',
2091+
)
2092+
def testDescriptorProtoHasFeatureOptions(self):
2093+
self.assertTrue(descriptor_pb2.DESCRIPTOR.has_options)
2094+
self.assertEqual(
2095+
descriptor_pb2.DESCRIPTOR.GetOptions().java_package,
2096+
'com.google.protobuf',
2097+
)
2098+
presence_desc = descriptor_pb2.FeatureSet.DESCRIPTOR.fields_by_name.get(
2099+
'field_presence'
2100+
)
2101+
self.assertTrue(presence_desc.has_options)
2102+
self.assertEqual(
2103+
presence_desc.GetOptions().retention,
2104+
descriptor_pb2.FieldOptions.OptionRetention.RETENTION_RUNTIME,
2105+
)
2106+
self.assertListsEqual(
2107+
presence_desc.GetOptions().targets,
2108+
[
2109+
descriptor_pb2.FieldOptions.OptionTargetType.TARGET_TYPE_FIELD,
2110+
descriptor_pb2.FieldOptions.OptionTargetType.TARGET_TYPE_FILE,
2111+
],
2112+
)
2113+
20562114
def testStringUTF8Serialization(self):
20572115
proto = message_set_extensions_pb2.TestMessageSet()
20582116
extension_message = message_set_extensions_pb2.TestMessageSetExtension2

src/google/protobuf/compiler/python/generator.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ void Generator::PrintEnumValueDescriptor(
10321032
// Returns a CEscaped string of serialized_options.
10331033
std::string Generator::OptionsValue(
10341034
absl::string_view serialized_options) const {
1035-
if (serialized_options.length() == 0 || GeneratingDescriptorProto()) {
1035+
if (serialized_options.length() == 0) {
10361036
return "None";
10371037
} else {
10381038
return absl::StrCat("b'", absl::CEscape(serialized_options), "'");

0 commit comments

Comments
 (0)