Skip to content

Commit 4908fae

Browse files
authored
bpo-43916: PyStdPrinter_Type uses Py_TPFLAGS_DISALLOW_INSTANTIATION (GH-25749)
The PyStdPrinter_Type type now uses the Py_TPFLAGS_DISALLOW_INSTANTIATION flag to disallow instantiation, rather than seting a tp_init method which always fail. Write also unit tests for PyStdPrinter_Type.
1 parent 0cad068 commit 4908fae

File tree

3 files changed

+61
-28
lines changed

3 files changed

+61
-28
lines changed

Lib/test/test_embed.py

+56
Original file line numberDiff line numberDiff line change
@@ -1473,11 +1473,67 @@ def test_audit_run_stdin(self):
14731473
timeout=support.SHORT_TIMEOUT,
14741474
returncode=1)
14751475

1476+
14761477
class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
14771478
def test_unicode_id_init(self):
14781479
# bpo-42882: Test that _PyUnicode_FromId() works
14791480
# when Python is initialized multiples times.
14801481
self.run_embedded_interpreter("test_unicode_id_init")
14811482

1483+
1484+
class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
1485+
# Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
1486+
# "Set up a preliminary stderr printer until we have enough
1487+
# infrastructure for the io module in place."
1488+
1489+
def get_stdout_fd(self):
1490+
return sys.__stdout__.fileno()
1491+
1492+
def create_printer(self, fd):
1493+
ctypes = import_helper.import_module('ctypes')
1494+
PyFile_NewStdPrinter = ctypes.pythonapi.PyFile_NewStdPrinter
1495+
PyFile_NewStdPrinter.argtypes = (ctypes.c_int,)
1496+
PyFile_NewStdPrinter.restype = ctypes.py_object
1497+
return PyFile_NewStdPrinter(fd)
1498+
1499+
def test_write(self):
1500+
message = "unicode:\xe9-\u20ac-\udc80!\n"
1501+
1502+
stdout_fd = self.get_stdout_fd()
1503+
stdout_fd_copy = os.dup(stdout_fd)
1504+
self.addCleanup(os.close, stdout_fd_copy)
1505+
1506+
rfd, wfd = os.pipe()
1507+
self.addCleanup(os.close, rfd)
1508+
self.addCleanup(os.close, wfd)
1509+
try:
1510+
# PyFile_NewStdPrinter() only accepts fileno(stdout)
1511+
# or fileno(stderr) file descriptor.
1512+
os.dup2(wfd, stdout_fd)
1513+
1514+
printer = self.create_printer(stdout_fd)
1515+
printer.write(message)
1516+
finally:
1517+
os.dup2(stdout_fd_copy, stdout_fd)
1518+
1519+
data = os.read(rfd, 100)
1520+
self.assertEqual(data, message.encode('utf8', 'backslashreplace'))
1521+
1522+
def test_methods(self):
1523+
fd = self.get_stdout_fd()
1524+
printer = self.create_printer(fd)
1525+
self.assertEqual(printer.fileno(), fd)
1526+
self.assertEqual(printer.isatty(), os.isatty(fd))
1527+
printer.flush() # noop
1528+
printer.close() # noop
1529+
1530+
def test_disallow_instantiation(self):
1531+
fd = self.get_stdout_fd()
1532+
printer = self.create_printer(fd)
1533+
PyStdPrinter_Type = type(printer)
1534+
with self.assertRaises(TypeError):
1535+
PyStdPrinter_Type(fd)
1536+
1537+
14821538
if __name__ == "__main__":
14831539
unittest.main()

Objects/fileobject.c

+4-27
Original file line numberDiff line numberDiff line change
@@ -325,29 +325,6 @@ typedef struct {
325325
int fd;
326326
} PyStdPrinter_Object;
327327

328-
static PyObject *
329-
stdprinter_new(PyTypeObject *type, PyObject *args, PyObject *kews)
330-
{
331-
PyStdPrinter_Object *self;
332-
333-
assert(type != NULL && type->tp_alloc != NULL);
334-
335-
self = (PyStdPrinter_Object *) type->tp_alloc(type, 0);
336-
if (self != NULL) {
337-
self->fd = -1;
338-
}
339-
340-
return (PyObject *) self;
341-
}
342-
343-
static int
344-
stdprinter_init(PyObject *self, PyObject *args, PyObject *kwds)
345-
{
346-
PyErr_SetString(PyExc_TypeError,
347-
"cannot create 'stderrprinter' instances");
348-
return -1;
349-
}
350-
351328
PyObject *
352329
PyFile_NewStdPrinter(int fd)
353330
{
@@ -390,7 +367,7 @@ stdprinter_write(PyStdPrinter_Object *self, PyObject *args)
390367
return NULL;
391368
}
392369

393-
/* Encode Unicode to UTF-8/surrogateescape */
370+
/* Encode Unicode to UTF-8/backslashreplace */
394371
str = PyUnicode_AsUTF8AndSize(unicode, &n);
395372
if (str == NULL) {
396373
PyErr_Clear();
@@ -507,7 +484,7 @@ PyTypeObject PyStdPrinter_Type = {
507484
PyObject_GenericGetAttr, /* tp_getattro */
508485
0, /* tp_setattro */
509486
0, /* tp_as_buffer */
510-
Py_TPFLAGS_DEFAULT, /* tp_flags */
487+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, /* tp_flags */
511488
0, /* tp_doc */
512489
0, /* tp_traverse */
513490
0, /* tp_clear */
@@ -523,9 +500,9 @@ PyTypeObject PyStdPrinter_Type = {
523500
0, /* tp_descr_get */
524501
0, /* tp_descr_set */
525502
0, /* tp_dictoffset */
526-
stdprinter_init, /* tp_init */
503+
0, /* tp_init */
527504
PyType_GenericAlloc, /* tp_alloc */
528-
stdprinter_new, /* tp_new */
505+
0, /* tp_new */
529506
PyObject_Del, /* tp_free */
530507
};
531508

Python/sysmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -3007,7 +3007,7 @@ _PySys_UpdateConfig(PyThreadState *tstate)
30073007
/* Set up a preliminary stderr printer until we have enough
30083008
infrastructure for the io module in place.
30093009
3010-
Use UTF-8/surrogateescape and ignore EAGAIN errors. */
3010+
Use UTF-8/backslashreplace and ignore EAGAIN errors. */
30113011
static PyStatus
30123012
_PySys_SetPreliminaryStderr(PyObject *sysdict)
30133013
{

0 commit comments

Comments
 (0)