Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 063dae8

Browse files
committed
Don't abort on FT2Font weakref.
Currently, import weakref, matplotlib.ft2font weakref.ref(matplotlib.ft2font.FT2Font("/usr/share/fonts/TTF/DejaVuSans.ttf")) aborts Py3 with terminate called after throwing an instance of 'char const*' Fatal Python error: Aborted whereas on Py2 a normal `TypeError: cannot create weak reference to 'matplotlib.ft2font.FT2Font' object` is raised. This is because the following happens: - Py3 sets the TypeError for failure to create a weakref. - The FT2Font object gets GC'd as nothing is referring to it (yes, creating a weakref to a temporary is a bit silly). - The FT2Font destructor calls close_file_callback, which calls mpl_PyFile_DupClose, which calls PyObject_AsFileDescriptor... which, on Py3, calls the fileno() method on the file object. - PyObject_AsFileDescriptor fails because the originally set TypeError has not been cleared, so mpl_PyFile_DupClose likewise fails, causing close_file_callback to throw a C++ exception. Instead, carefully stash and restore the current exception state, both in mpl_PyFile_DupClose, and likewise below, in mpl_PyFile_CloseFile (the latter is needed because otherwise PyObject_CallMethod will clear the exception, cf comment in CPython's Objects/abstract.c: /* PyObject_Call() must not be called with an exception set, because it may clear it (directly or indirectly) and so the caller loses its exception */ Note that if an exception *actually* gets raised by mpl_PyFile_DupClose or mpl_PyFile_CloseFile, it will overwrite the TypeError. Strictly speaking, on Py3, it may be preferrable to chain the exceptions, but this seems a bit overkill (mostly, I just want to avoid aborting the whole Python process).
1 parent a3d1c46 commit 063dae8

File tree

1 file changed

+25
-5
lines changed

1 file changed

+25
-5
lines changed

src/file_compat.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ static NPY_INLINE FILE *mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *or
126126
*/
127127
static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_t orig_pos)
128128
{
129+
PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL;
130+
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
131+
129132
int fd;
130133
PyObject *ret;
131134
mpl_off_t position;
@@ -136,25 +139,33 @@ static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_
136139
fclose(handle);
137140

138141
/* Restore original file handle position, in order to not confuse
139-
Python-side data structures */
142+
Python-side data structures. Note that this would fail if an exception
143+
is currently set, which can happen as this function is called in cleanup
144+
code, so we need to carefully fetch and restore the exception state. */
140145
fd = PyObject_AsFileDescriptor(file);
141146
if (fd == -1) {
142-
return -1;
147+
goto fail;
143148
}
144149
if (mpl_lseek(fd, orig_pos, SEEK_SET) != -1) {
145150
if (position == -1) {
146151
PyErr_SetString(PyExc_IOError, "obtaining file position failed");
147-
return -1;
152+
goto fail;
148153
}
149154

150155
/* Seek Python-side handle to the FILE* handle position */
151156
ret = PyObject_CallMethod(file, (char *)"seek", (char *)(MPL_OFF_T_PYFMT "i"), position, 0);
152157
if (ret == NULL) {
153-
return -1;
158+
goto fail;
154159
}
155160
Py_DECREF(ret);
156161
}
162+
PyErr_Restore(exc_type, exc_value, exc_tb);
157163
return 0;
164+
fail:
165+
Py_XDECREF(exc_type);
166+
Py_XDECREF(exc_value);
167+
Py_XDECREF(exc_tb);
168+
return -1;
158169
}
159170

160171
static NPY_INLINE int mpl_PyFile_Check(PyObject *file)
@@ -200,14 +211,23 @@ static NPY_INLINE PyObject *mpl_PyFile_OpenFile(PyObject *filename, const char *
200211

201212
static NPY_INLINE int mpl_PyFile_CloseFile(PyObject *file)
202213
{
214+
PyObject *type, *value, *tb;
215+
PyErr_Fetch(&type, &value, &tb);
216+
203217
PyObject *ret;
204218

205219
ret = PyObject_CallMethod(file, (char *)"close", NULL);
206220
if (ret == NULL) {
207-
return -1;
221+
goto fail;
208222
}
209223
Py_DECREF(ret);
224+
PyErr_Restore(type, value, tb);
210225
return 0;
226+
fail:
227+
Py_XDECREF(type);
228+
Py_XDECREF(value);
229+
Py_XDECREF(tb);
230+
return -1;
211231
}
212232

213233
#ifdef __cplusplus

0 commit comments

Comments
 (0)