Skip to content

Commit 16ab410

Browse files
committed
Support mongo::StringData becoming a thin wrapper for std::string_view.
1 parent 34df8e8 commit 16ab410

File tree

2 files changed

+105
-12
lines changed

2 files changed

+105
-12
lines changed

CHANGELOG.rst

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Changelog
55
-------------------
66

77
* Support dumping LockManager from core dump of MongoDB 8.0.
8+
* Fix mongo::StringData pretty printer and mongo::BSONObj pretty printer which consumes it.
89

910
0.14.0 (2023-09-30)
1011
-------------------

gdbmongo/string_data_printer.py

+104-12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import gdb
2525

2626
from gdbmongo import stdlib_printers
27+
from gdbmongo.gdbutil import gdb_lookup_value
2728
from gdbmongo.printer_protocol import LazyString, SupportsDisplayHint, SupportsToString
2829

2930

@@ -93,9 +94,74 @@ class c_size_t(ctypes.c_size_t):
9394

9495

9596
@dataclasses.dataclass
96-
class MongoStringData(ctypes.Structure):
97+
class MongoStringDataLayoutStdStringView(ctypes.Structure):
9798
"""Structure with a memory layout compatible with that of mongo::StringData.
9899
100+
It corresponds to the memory layout after mongo::StringData became a thin wrapper over
101+
std::string_view as part of SERVER-82604 in MongoDB 7.3. It is equivalent to the following
102+
C struct:
103+
104+
.. code-block:: c
105+
106+
struct {
107+
size_t size;
108+
char* data;
109+
};
110+
"""
111+
112+
size: c_size_t
113+
data: c_char_p
114+
115+
116+
setattr(MongoStringDataLayoutStdStringView, "_fields_",
117+
[(field.name, field.type)
118+
for field in dataclasses.fields(MongoStringDataLayoutStdStringView)])
119+
120+
121+
@dataclasses.dataclass
122+
class MongoStringDataLayoutPre73(ctypes.Structure):
123+
"""Structure with a memory layout compatible with that of mongo::StringData.
124+
125+
It corresponds to the memory layout prior to mongo::StringData being made a thin wrapper over
126+
std::string_view as part of SERVER-82604 in MongoDB 7.3. It is equivalent to the following
127+
C struct:
128+
129+
.. code-block:: c
130+
131+
struct {
132+
char* data;
133+
size_t size;
134+
};
135+
"""
136+
137+
data: c_char_p
138+
size: c_size_t
139+
140+
141+
setattr(MongoStringDataLayoutPre73, "_fields_",
142+
[(field.name, field.type) for field in dataclasses.fields(MongoStringDataLayoutPre73)])
143+
144+
145+
class MongoStringData(ctypes.Union):
146+
"""Object with a memory layout compatible with that of mongo::StringData.
147+
148+
It is implemented as a ctypes.Union to accommodate the memory layout of mongo::StringData
149+
changing between MongoDB Server versions. It is equivalent to the following C union:
150+
151+
.. code-block:: c
152+
153+
union {
154+
struct {
155+
size_t size;
156+
char* data;
157+
} layout_string_view;
158+
159+
struct {
160+
char* data;
161+
size_t size;
162+
} layout_pre73;
163+
};
164+
99165
This class is useful for constructing gdb.Value objects of type mongo::StringData out of
100166
selected portions of a buffer read with gdb.Inferior.read_memory(). These synthetic gdb.Values
101167
can then be formatted by StringDataPrinter like normal.
@@ -107,14 +173,24 @@ class MongoStringData(ctypes.Structure):
107173
yield (f"{i}", string_data.to_value())
108174
"""
109175

110-
data: c_char_p
111-
size: c_size_t
176+
layout_string_view: MongoStringDataLayoutStdStringView
177+
layout_pre73: MongoStringDataLayoutPre73
178+
179+
# dataclasses.dataclass doesn't appear to be compatible with ctypes.Union. We enumerate
180+
# `MongoStringData._fields_` explicitly instead of relying on the type annotations.
181+
_fields_ = [("layout_string_view", MongoStringDataLayoutStdStringView),
182+
("layout_pre73", MongoStringDataLayoutPre73)]
112183

113184
def __init__(self, *, data: int, size: int) -> None:
114185
if size < 0:
115186
raise ValueError("size argument must be a non-negative integer")
116187

117-
super().__init__(data=c_char_p(data), size=c_size_t(size))
188+
if StringDataPrinter.is_wrapping_std_string_view():
189+
super().__init__(layout_string_view=MongoStringDataLayoutStdStringView(
190+
data=c_char_p(data), size=c_size_t(size)))
191+
else:
192+
super().__init__(
193+
layout_pre73=MongoStringDataLayoutPre73(data=c_char_p(data), size=c_size_t(size)))
118194

119195
@classmethod
120196
def from_cstring(cls, val: gdb.Value, /, *, maxsize: int) -> "MongoStringData":
@@ -132,33 +208,49 @@ def from_pascalstring(cls, val: gdb.Value, /, *, view: memoryview) -> "MongoStri
132208
"""Read a length-prefixed string starting from the beginning of the given buffer."""
133209
fmt = "<i"
134210
(size, ) = struct.unpack_from(fmt, view)
211+
135212
return cls(data=int(val + struct.calcsize(fmt)), size=size)
136213

214+
@property
215+
def data(self) -> c_char_p:
216+
"""Return the pointer to the first character in the string."""
217+
return (self.layout_string_view.data
218+
if StringDataPrinter.is_wrapping_std_string_view() else self.layout_pre73.data)
219+
220+
@property
221+
def size(self) -> c_size_t:
222+
"""Return the number of characters in the string."""
223+
return (self.layout_string_view.size
224+
if StringDataPrinter.is_wrapping_std_string_view() else self.layout_pre73.size)
225+
137226
def to_value(self) -> gdb.Value:
138227
"""Convert the structure to a gdb.Value of type mongo::StringData."""
139228
typ = gdb.lookup_type("mongo::StringData")
140229
return gdb.Value(memoryview(self), typ)
141230

142231

143-
setattr(MongoStringData, "_fields_",
144-
[(field.name, field.type) for field in dataclasses.fields(MongoStringData)])
145-
146-
147232
class StringDataPrinter(SupportsDisplayHint, ValueAsPythonStringMixin):
148233
# pylint: disable=missing-function-docstring
149234
"""Pretty-printer for mongo::StringData."""
150235

151236
def __init__(self, val: gdb.Value, /) -> None:
152237
self.val = val
153-
self.size = val["_size"]
154-
self.data = val["_data"]
155238

156239
@staticmethod
157240
def display_hint() -> typing.Literal["string"]:
158241
return "string"
159242

160-
def to_string(self) -> LazyString:
161-
return self.data.lazy_string(length=int(self.size))
243+
def to_string(self) -> typing.Union[gdb.Value, LazyString]:
244+
if StringDataPrinter.is_wrapping_std_string_view():
245+
return self.val["_sv"]
246+
247+
return self.val["_data"].lazy_string(length=int(self.val["_size"]))
248+
249+
@staticmethod
250+
def is_wrapping_std_string_view() -> bool:
251+
# The StringData class was changed to be a thin wrapper over std::string_view as part of
252+
# SERVER-82604 in MongoDB 7.3.
253+
return gdb_lookup_value("mongo::StringData::npos") is not None
162254

163255

164256
def add_printers(pretty_printer: gdb.printing.RegexpCollectionPrettyPrinter, /) -> None:

0 commit comments

Comments
 (0)