Skip to content

Commit 4e07043

Browse files
committed
fix(JSExternalStrings): fix reference counting on JSExternalStrings
1 parent bbcb6c6 commit 4e07043

File tree

1 file changed

+35
-16
lines changed

1 file changed

+35
-16
lines changed

src/jsTypeFactory.cc

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,46 +49,64 @@ static PyObjectProxyHandler pyObjectProxyHandler;
4949
static PyListProxyHandler pyListProxyHandler;
5050
static PyIterableProxyHandler pyIterableProxyHandler;
5151

52-
std::unordered_map<const char16_t *, PyObject *> ucs2ToPyObjectMap; // a map of char16_t (UCS-2) buffers to their corresponding PyObjects, used when finalizing JSExternalStrings
53-
std::unordered_map<const JS::Latin1Char *, PyObject *> latin1ToPyObjectMap; // a map of Latin-1 char buffers to their corresponding PyObjects, used when finalizing JSExternalStrings
52+
std::unordered_map<PyObject *, size_t> jsExternalStringPyObjects;// a map of python string objects to the number of JSExternalStrings that depend on it, used when finalizing JSExternalStrings
5453

5554
PyObject *PythonExternalString::getPyString(const char16_t *chars)
5655
{
57-
return ucs2ToPyObjectMap[chars];
56+
for (auto it: jsExternalStringPyObjects) {
57+
if (PyUnicode_DATA(it.first) == (void *)chars) {
58+
return it.first;
59+
}
60+
}
61+
62+
return NULL; // this shouldn't be reachable
5863
}
5964

6065
PyObject *PythonExternalString::getPyString(const JS::Latin1Char *chars)
6166
{
62-
return latin1ToPyObjectMap[chars];
67+
68+
return PythonExternalString::getPyString((const char16_t *)chars);
6369
}
6470

6571
void PythonExternalString::finalize(char16_t *chars) const
6672
{
6773
// We cannot call Py_DECREF here when shutting down as the thread state is gone.
6874
// Then, when shutting down, there is only on reference left, and we don't need
6975
// to free the object since the entire process memory is being released.
70-
PyObject *object = ucs2ToPyObjectMap[chars];
71-
if (Py_REFCNT(object) > 1) {
72-
Py_DECREF(object);
76+
if (_Py_IsFinalizing()) { return; }
77+
78+
for (auto it = jsExternalStringPyObjects.cbegin(), next_it = it; it != jsExternalStringPyObjects.cend(); it = next_it) {
79+
next_it++;
80+
if (PyUnicode_DATA(it->first) == (void *)chars) {
81+
Py_DECREF(it->first);
82+
jsExternalStringPyObjects[it->first] = jsExternalStringPyObjects[it->first] - 1;
83+
84+
if (jsExternalStringPyObjects[it->first] == 0) {
85+
jsExternalStringPyObjects.erase(it);
86+
}
87+
}
7388
}
7489
}
7590

7691
void PythonExternalString::finalize(JS::Latin1Char *chars) const
7792
{
78-
PyObject *object = latin1ToPyObjectMap[chars];
79-
if (Py_REFCNT(object) > 1) {
80-
Py_DECREF(object);
81-
}
93+
PythonExternalString::finalize((char16_t *)chars);
8294
}
8395

8496
size_t PythonExternalString::sizeOfBuffer(const char16_t *chars, mozilla::MallocSizeOf mallocSizeOf) const
8597
{
86-
return PyUnicode_GetLength(ucs2ToPyObjectMap[chars]);
98+
for (auto it: jsExternalStringPyObjects) {
99+
if (PyUnicode_DATA(it.first) == (void *)chars) {
100+
return PyUnicode_GetLength(it.first);
101+
}
102+
}
103+
104+
return 0; // // this shouldn't be reachable
87105
}
88106

89107
size_t PythonExternalString::sizeOfBuffer(const JS::Latin1Char *chars, mozilla::MallocSizeOf mallocSizeOf) const
90108
{
91-
return PyUnicode_GetLength(latin1ToPyObjectMap[chars]);
109+
return PythonExternalString::sizeOfBuffer((const char16_t *)chars, mallocSizeOf);
92110
}
93111

94112
PythonExternalString PythonExternalStringCallbacks = {};
@@ -151,21 +169,22 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) {
151169
break;
152170
}
153171
case (PyUnicode_2BYTE_KIND): {
154-
ucs2ToPyObjectMap[(char16_t *)PyUnicode_2BYTE_DATA(object)] = object;
172+
jsExternalStringPyObjects[object] = jsExternalStringPyObjects[object] + 1;
173+
Py_INCREF(object);
155174
JSString *str = JS_NewExternalUCString(cx, (char16_t *)PyUnicode_2BYTE_DATA(object), PyUnicode_GET_LENGTH(object), &PythonExternalStringCallbacks);
156175
returnType.setString(str);
157176
break;
158177
}
159178
case (PyUnicode_1BYTE_KIND): {
160-
latin1ToPyObjectMap[(JS::Latin1Char *)PyUnicode_1BYTE_DATA(object)] = object;
179+
jsExternalStringPyObjects[object] = jsExternalStringPyObjects[object] + 1;
180+
Py_INCREF(object);
161181
JSString *str = JS_NewExternalStringLatin1(cx, (JS::Latin1Char *)PyUnicode_1BYTE_DATA(object), PyUnicode_GET_LENGTH(object), &PythonExternalStringCallbacks);
162182
// JSExternalString can now be properly treated as either one-byte or two-byte strings when GCed
163183
// see https://hg.mozilla.org/releases/mozilla-esr128/file/tip/js/src/vm/StringType-inl.h#l785
164184
returnType.setString(str);
165185
break;
166186
}
167187
}
168-
Py_INCREF(object);
169188
}
170189
else if (PyMethod_Check(object) || PyFunction_Check(object) || PyCFunction_Check(object)) {
171190
// can't determine number of arguments for PyCFunctions, so just assume potentially unbounded

0 commit comments

Comments
 (0)