Issue #20037: Avoid crashes when doing text I/O late at interpreter shutdown.

This commit is contained in:
Antoine Pitrou 2013-12-21 15:51:54 +01:00
parent 5255b86fba
commit 712cb734bd
9 changed files with 96 additions and 8 deletions

View file

@ -36,6 +36,7 @@ import _testcapi
from collections import deque, UserList from collections import deque, UserList
from itertools import cycle, count from itertools import cycle, count
from test import support from test import support
from test.script_helper import assert_python_ok
import codecs import codecs
import io # C implementation of io import io # C implementation of io
@ -2589,8 +2590,46 @@ class TextIOWrapperTest(unittest.TestCase):
encoding='quopri_codec') encoding='quopri_codec')
self.assertRaises(TypeError, t.read) self.assertRaises(TypeError, t.read)
def _check_create_at_shutdown(self, **kwargs):
# Issue #20037: creating a TextIOWrapper at shutdown
# shouldn't crash the interpreter.
iomod = self.io.__name__
code = """if 1:
import codecs
import {iomod} as io
# Avoid looking up codecs at shutdown
codecs.lookup('utf-8')
class C:
def __init__(self):
self.buf = io.BytesIO()
def __del__(self):
io.TextIOWrapper(self.buf, **{kwargs})
print("ok")
c = C()
""".format(iomod=iomod, kwargs=kwargs)
return assert_python_ok("-c", code)
def test_create_at_shutdown_without_encoding(self):
rc, out, err = self._check_create_at_shutdown()
if err:
# Can error out with a RuntimeError if the module state
# isn't found.
self.assertIn("RuntimeError: could not find io module state",
err.decode())
else:
self.assertEqual("ok", out.decode().strip())
def test_create_at_shutdown_with_encoding(self):
rc, out, err = self._check_create_at_shutdown(encoding='utf-8',
errors='strict')
self.assertFalse(err)
self.assertEqual("ok", out.decode().strip())
class CTextIOWrapperTest(TextIOWrapperTest): class CTextIOWrapperTest(TextIOWrapperTest):
io = io
def test_initialization(self): def test_initialization(self):
r = self.BytesIO(b"\xc3\xa9\n\n") r = self.BytesIO(b"\xc3\xa9\n\n")
@ -2634,7 +2673,7 @@ class CTextIOWrapperTest(TextIOWrapperTest):
class PyTextIOWrapperTest(TextIOWrapperTest): class PyTextIOWrapperTest(TextIOWrapperTest):
pass io = pyio
class IncrementalNewlineDecoderTest(unittest.TestCase): class IncrementalNewlineDecoderTest(unittest.TestCase):

View file

@ -41,6 +41,7 @@ import socket
import struct import struct
import sys import sys
import tempfile import tempfile
from test.script_helper import assert_python_ok
from test.support import (captured_stdout, run_with_locale, run_unittest, from test.support import (captured_stdout, run_with_locale, run_unittest,
patch, requires_zlib, TestHandler, Matcher) patch, requires_zlib, TestHandler, Matcher)
import textwrap import textwrap
@ -3397,6 +3398,25 @@ class ModuleLevelMiscTest(BaseTest):
logging.setLoggerClass(logging.Logger) logging.setLoggerClass(logging.Logger)
self.assertEqual(logging.getLoggerClass(), logging.Logger) self.assertEqual(logging.getLoggerClass(), logging.Logger)
def test_logging_at_shutdown(self):
# Issue #20037
code = """if 1:
import logging
class A:
def __del__(self):
try:
raise ValueError("some error")
except Exception:
logging.exception("exception in __del__")
a = A()"""
rc, out, err = assert_python_ok("-c", code)
err = err.decode()
self.assertIn("exception in __del__", err)
self.assertIn("ValueError: some error", err)
class LogRecordTest(BaseTest): class LogRecordTest(BaseTest):
def test_str_rep(self): def test_str_rep(self):
r = logging.makeLogRecord({}) r = logging.makeLogRecord({})

View file

@ -44,6 +44,9 @@ Core and Builtins
Library Library
------- -------
- Issue #20037: Avoid crashes when opening a text file late at interpreter
shutdown.
- Issue #19967: Thanks to the PEP 442, asyncio.Future now uses a - Issue #19967: Thanks to the PEP 442, asyncio.Future now uses a
destructor to log uncaught exceptions, instead of the dedicated destructor to log uncaught exceptions, instead of the dedicated
_TracebackLogger class. _TracebackLogger class.

View file

@ -539,6 +539,20 @@ _PyIO_ConvertSsize_t(PyObject *obj, void *result) {
} }
_PyIO_State *
_PyIO_get_module_state(void)
{
PyObject *mod = PyState_FindModule(&_PyIO_Module);
_PyIO_State *state;
if (mod == NULL || (state = IO_MOD_STATE(mod)) == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"could not find io module state "
"(interpreter shutdown?)");
return NULL;
}
return state;
}
PyObject * PyObject *
_PyIO_get_locale_module(_PyIO_State *state) _PyIO_get_locale_module(_PyIO_State *state)
{ {

View file

@ -135,8 +135,9 @@ typedef struct {
} _PyIO_State; } _PyIO_State;
#define IO_MOD_STATE(mod) ((_PyIO_State *)PyModule_GetState(mod)) #define IO_MOD_STATE(mod) ((_PyIO_State *)PyModule_GetState(mod))
#define IO_STATE IO_MOD_STATE(PyState_FindModule(&_PyIO_Module)) #define IO_STATE() _PyIO_get_module_state()
extern _PyIO_State *_PyIO_get_module_state(void);
extern PyObject *_PyIO_get_locale_module(_PyIO_State *); extern PyObject *_PyIO_get_locale_module(_PyIO_State *);
extern PyObject *_PyIO_str_close; extern PyObject *_PyIO_str_close;

View file

@ -91,7 +91,9 @@ bufferediobase_readinto(PyObject *self, PyObject *args)
static PyObject * static PyObject *
bufferediobase_unsupported(const char *message) bufferediobase_unsupported(const char *message)
{ {
PyErr_SetString(IO_STATE->unsupported_operation, message); _PyIO_State *state = IO_STATE();
if (state != NULL)
PyErr_SetString(state->unsupported_operation, message);
return NULL; return NULL;
} }

View file

@ -493,7 +493,9 @@ err_closed(void)
static PyObject * static PyObject *
err_mode(char *action) err_mode(char *action)
{ {
PyErr_Format(IO_STATE->unsupported_operation, _PyIO_State *state = IO_STATE();
if (state != NULL)
PyErr_Format(state->unsupported_operation,
"File not open for %s", action); "File not open for %s", action);
return NULL; return NULL;
} }

View file

@ -69,7 +69,9 @@ _Py_IDENTIFIER(read);
static PyObject * static PyObject *
iobase_unsupported(const char *message) iobase_unsupported(const char *message)
{ {
PyErr_SetString(IO_STATE->unsupported_operation, message); _PyIO_State *state = IO_STATE();
if (state != NULL)
PyErr_SetString(state->unsupported_operation, message);
return NULL; return NULL;
} }

View file

@ -45,7 +45,9 @@ PyDoc_STRVAR(textiobase_doc,
static PyObject * static PyObject *
_unsupported(const char *message) _unsupported(const char *message)
{ {
PyErr_SetString(IO_STATE->unsupported_operation, message); _PyIO_State *state = IO_STATE();
if (state != NULL)
PyErr_SetString(state->unsupported_operation, message);
return NULL; return NULL;
} }
@ -852,7 +854,7 @@ textiowrapper_init(textio *self, PyObject *args, PyObject *kwds)
char *errors = NULL; char *errors = NULL;
char *newline = NULL; char *newline = NULL;
int line_buffering = 0, write_through = 0; int line_buffering = 0, write_through = 0;
_PyIO_State *state = IO_STATE; _PyIO_State *state = NULL;
PyObject *res; PyObject *res;
int r; int r;
@ -891,6 +893,9 @@ textiowrapper_init(textio *self, PyObject *args, PyObject *kwds)
if (encoding == NULL) { if (encoding == NULL) {
/* Try os.device_encoding(fileno) */ /* Try os.device_encoding(fileno) */
PyObject *fileno; PyObject *fileno;
state = IO_STATE();
if (state == NULL)
goto error;
fileno = _PyObject_CallMethodId(buffer, &PyId_fileno, NULL); fileno = _PyObject_CallMethodId(buffer, &PyId_fileno, NULL);
/* Ignore only AttributeError and UnsupportedOperation */ /* Ignore only AttributeError and UnsupportedOperation */
if (fileno == NULL) { if (fileno == NULL) {