Skip to content

Commit 2d68aa1

Browse files
radoeringSeanCline
authored andcommitted
Fix for managed dicts with a resolved PyDictObject instead of separate values
1 parent e57ea4f commit 2d68aa1

5 files changed

Lines changed: 39 additions & 16 deletions

File tree

include/PyDictObject.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ namespace PyExt::Remote {
2929
{
3030

3131
public: // Construction/Destruction.
32-
explicit PyManagedDict(RemoteType::Offset keysPtr, RemoteType::Offset valuesPtrPtr);
32+
explicit PyManagedDict(RemoteType::Offset keysPtr, RemoteType::Offset valuesPtr);
3333

3434
public: // Members.
3535
auto pairValues() const->std::vector<std::pair<std::unique_ptr<PyObject>, std::unique_ptr<PyObject>>> override;
3636

3737
private:
3838
RemoteType::Offset keysPtr;
39-
RemoteType::Offset valuesPtrPtr;
39+
RemoteType::Offset valuesPtr;
4040

4141
};
4242

src/objects/PyDictObject.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ namespace PyExt::Remote {
8787
}
8888

8989

90-
PyManagedDict::PyManagedDict(RemoteType::Offset keysPtr, RemoteType::Offset valuesPtrPtr)
91-
: keysPtr(keysPtr), valuesPtrPtr(valuesPtrPtr)
90+
PyManagedDict::PyManagedDict(RemoteType::Offset keysPtr, RemoteType::Offset valuesPtr)
91+
: keysPtr(keysPtr), valuesPtr(valuesPtr)
9292
{
9393
}
9494

@@ -100,14 +100,14 @@ namespace PyExt::Remote {
100100
auto keys = make_unique<PyDictKeysObject>(keysPtr);
101101
auto table = keys->getEntriesTable();
102102
auto tableSize = keys->getEntriesTableSize();
103-
auto valuesPtr = ExtRemoteTyped("(PyObject***)@$extin", valuesPtrPtr).Dereference().GetPtr();
103+
auto nextValue = valuesPtr;
104104
auto ptrSize = utils::getPointerSize();
105105

106-
for (auto i = 0; i < tableSize; ++i, valuesPtr += ptrSize) {
106+
for (auto i = 0; i < tableSize; ++i, nextValue += ptrSize) {
107107
auto dictEntry = table.ArrayElement(i);
108108

109109
auto keyPtr = dictEntry.Field("me_key").GetPtr();
110-
auto valuePtr = ExtRemoteTyped("(PyObject**)@$extin", valuesPtr).Dereference().GetPtr();
110+
auto valuePtr = ExtRemoteTyped("(PyObject**)@$extin", nextValue).Dereference().GetPtr();
111111

112112
if (keyPtr == 0 || valuePtr == 0) //< The hash bucket might be empty.
113113
continue;

src/objects/PyObject.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,16 @@ namespace PyExt::Remote {
6565
{
6666
if (type().isManagedDict()) {
6767
// Python >= 3.11, see PyObject_GenericGetDict
68+
auto pointerSize = utils::getPointerSize();
69+
auto valuesPtrPtr = offset() - 4 * pointerSize;
70+
auto valuesPtr = ExtRemoteTyped("(PyObject***)@$extin", valuesPtrPtr).Dereference().GetPtr();
71+
if (valuesPtr == 0) {
72+
auto dictPtr = ExtRemoteTyped("(PyDictObject**)@$extin", valuesPtrPtr + pointerSize).Dereference().GetPtr();
73+
return dictPtr ? make_unique<PyDictObject>(dictPtr) : nullptr;
74+
}
6875
auto ht = ExtRemoteTyped("PyHeapTypeObject", type().offset(), true);
6976
auto cachedKeys = ht.Field("ht_cached_keys");
70-
return make_unique<PyManagedDict>(cachedKeys.GetPtr(), offset() - 4 * utils::getPointerSize());
77+
return make_unique<PyManagedDict>(cachedKeys.GetPtr(), valuesPtr);
7178
}
7279

7380
// see https://docs.python.org/3.10/c-api/typeobj.html#c.PyTypeObject.tp_dictoffset

test/PyExtTest/ObjectDetailsTest.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ TEST_CASE("object_details.py has a stack frame with expected locals.", "[integra
5656

5757
vector<vector<string>> expectations{
5858
// Regex only necessary due to Python 2 (dicts not sorted)
59-
{ "d" , "D" , R"(dict: \{\n\t'(d1': 1,\n\t'd2': 2,|d2': 2,\n\t'd1': 1,)\n\})" },
60-
{ "s" , "S" , R"(slots: \{\n\tslot1: 1,\n\tslot2: 2,\n\})" },
61-
{ "dsubd" , "DsubD" , R"(dict: \{\n(\t('d1': 1|'d2': 2|'d3': 3),\n){3}\})" },
62-
{ "ssubs" , "SsubS" , R"(slots: \{\n\tslot3: 3,\n\tslot1: 1,\n\tslot2: 2,\n\})" },
63-
{ "dsubs" , "DsubS" , R"(slots: \{\n\tslot1: 1,\n\tslot2: 2,\n\}\ndict: \{\n\t'd3': 3,\n\})" },
64-
{ "ssubd" , "SsubD" , R"(slots: \{\n\tslot3: 3,\n\}\ndict: \{\n(\t('d1': 1|'d2': 2),\n){2}\})" },
65-
{ "ssubds" , "SsubDS" , R"(slots: \{\n\tslot3: 5,\n\tslot1: 3,\n\tslot2: 4,\n\}\ndict: \{\n(\t('d1': 1|'d2': 2),\n){2}\})" },
66-
{ "negDictOffset", "NegDictOffset", R"(tuple repr: \(1, 2, 3\)\ndict: \{\n\t'attr': 'test',\n\})" },
59+
{ "d" , "D" , R"(dict: \{\n\t'(d1': 1,\n\t'd2': 2,|d2': 2,\n\t'd1': 1,)\n\})" },
60+
{ "s" , "S" , R"(slots: \{\n\tslot1: 1,\n\tslot2: 2,\n\})" },
61+
{ "dsubd" , "DsubD" , R"(dict: \{\n(\t('d1': 1|'d2': 2|'d3': 3),\n){3}\})" },
62+
{ "ssubs" , "SsubS" , R"(slots: \{\n\tslot3: 3,\n\tslot1: 1,\n\tslot2: 2,\n\})" },
63+
{ "dsubs" , "DsubS" , R"(slots: \{\n\tslot1: 1,\n\tslot2: 2,\n\}\ndict: \{\n\t'd3': 3,\n\})" },
64+
{ "ssubd" , "SsubD" , R"(slots: \{\n\tslot3: 3,\n\}\ndict: \{\n(\t('d1': 1|'d2': 2),\n){2}\})" },
65+
{ "ssubds" , "SsubDS" , R"(slots: \{\n\tslot3: 5,\n\tslot1: 3,\n\tslot2: 4,\n\}\ndict: \{\n(\t('d1': 1|'d2': 2),\n){2}\})" },
66+
{ "negDictOffset", "NegDictOffset" , R"(tuple repr: \(1, 2, 3\)\ndict: \{\n\t'attr': 'test',\n\})" },
67+
{ "manDictRes" , "ManagedDictResolved", R"(dict: \{(\n\t'a\d+': \d+,){32}\n\})" },
6768
};
6869

6970
for (auto& objExp : expectations) {

test/scripts/object_details.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ def __init__(self, tupleValue):
7171
self.attr = 'test'
7272

7373

74+
class ManagedDictResolved(object):
75+
"""managed dict has two modes:
76+
- only values
77+
- fully resolved PyDictObject
78+
79+
We are trying to trigger the second one with this class.
80+
"""
81+
def __init__(self):
82+
# Observation: The more attributes an object has, the more likely the dict is not stored
83+
# as values only but as PyDictObject, which triggers another branch in the code.
84+
for i in range(32):
85+
setattr(self, 'a%s' % i, i)
86+
87+
7488
d = D(1, 2)
7589
s = S(1, 2)
7690
dsubd = DsubD(1, 2, 3)
@@ -79,4 +93,5 @@ def __init__(self, tupleValue):
7993
ssubd = SsubD(1, 2, 3)
8094
ssubds = SsubDS(1, 2, 3, 4, 5)
8195
negDictOffset = NegDictOffset((1, 2, 3))
96+
manDictRes = ManagedDictResolved()
8297
win32debug.dump_process("object_details.dmp")

0 commit comments

Comments
 (0)