From 4908fae3d57f68694cf006e89fd7761f45003447 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 30 Apr 2021 14:56:27 +0200 Subject: [PATCH] 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. --- Lib/test/test_embed.py | 56 ++++++++++++++++++++++++++++++++++++++++++ Objects/fileobject.c | 31 +++-------------------- Python/sysmodule.c | 2 +- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 646cd0632ed..23cf297d4ab 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1473,11 +1473,67 @@ class AuditingTests(EmbeddingTestsMixin, unittest.TestCase): timeout=support.SHORT_TIMEOUT, returncode=1) + class MiscTests(EmbeddingTestsMixin, unittest.TestCase): def test_unicode_id_init(self): # bpo-42882: Test that _PyUnicode_FromId() works # when Python is initialized multiples times. self.run_embedded_interpreter("test_unicode_id_init") + +class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase): + # Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr(): + # "Set up a preliminary stderr printer until we have enough + # infrastructure for the io module in place." + + def get_stdout_fd(self): + return sys.__stdout__.fileno() + + def create_printer(self, fd): + ctypes = import_helper.import_module('ctypes') + PyFile_NewStdPrinter = ctypes.pythonapi.PyFile_NewStdPrinter + PyFile_NewStdPrinter.argtypes = (ctypes.c_int,) + PyFile_NewStdPrinter.restype = ctypes.py_object + return PyFile_NewStdPrinter(fd) + + def test_write(self): + message = "unicode:\xe9-\u20ac-\udc80!\n" + + stdout_fd = self.get_stdout_fd() + stdout_fd_copy = os.dup(stdout_fd) + self.addCleanup(os.close, stdout_fd_copy) + + rfd, wfd = os.pipe() + self.addCleanup(os.close, rfd) + self.addCleanup(os.close, wfd) + try: + # PyFile_NewStdPrinter() only accepts fileno(stdout) + # or fileno(stderr) file descriptor. + os.dup2(wfd, stdout_fd) + + printer = self.create_printer(stdout_fd) + printer.write(message) + finally: + os.dup2(stdout_fd_copy, stdout_fd) + + data = os.read(rfd, 100) + self.assertEqual(data, message.encode('utf8', 'backslashreplace')) + + def test_methods(self): + fd = self.get_stdout_fd() + printer = self.create_printer(fd) + self.assertEqual(printer.fileno(), fd) + self.assertEqual(printer.isatty(), os.isatty(fd)) + printer.flush() # noop + printer.close() # noop + + def test_disallow_instantiation(self): + fd = self.get_stdout_fd() + printer = self.create_printer(fd) + PyStdPrinter_Type = type(printer) + with self.assertRaises(TypeError): + PyStdPrinter_Type(fd) + + if __name__ == "__main__": unittest.main() diff --git a/Objects/fileobject.c b/Objects/fileobject.c index 9b89448006e..5a2816f5524 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -325,29 +325,6 @@ typedef struct { int fd; } PyStdPrinter_Object; -static PyObject * -stdprinter_new(PyTypeObject *type, PyObject *args, PyObject *kews) -{ - PyStdPrinter_Object *self; - - assert(type != NULL && type->tp_alloc != NULL); - - self = (PyStdPrinter_Object *) type->tp_alloc(type, 0); - if (self != NULL) { - self->fd = -1; - } - - return (PyObject *) self; -} - -static int -stdprinter_init(PyObject *self, PyObject *args, PyObject *kwds) -{ - PyErr_SetString(PyExc_TypeError, - "cannot create 'stderrprinter' instances"); - return -1; -} - PyObject * PyFile_NewStdPrinter(int fd) { @@ -390,7 +367,7 @@ stdprinter_write(PyStdPrinter_Object *self, PyObject *args) return NULL; } - /* Encode Unicode to UTF-8/surrogateescape */ + /* Encode Unicode to UTF-8/backslashreplace */ str = PyUnicode_AsUTF8AndSize(unicode, &n); if (str == NULL) { PyErr_Clear(); @@ -507,7 +484,7 @@ PyTypeObject PyStdPrinter_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -523,9 +500,9 @@ PyTypeObject PyStdPrinter_Type = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - stdprinter_init, /* tp_init */ + 0, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ - stdprinter_new, /* tp_new */ + 0, /* tp_new */ PyObject_Del, /* tp_free */ }; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 36297ff82e1..2190bbf37fd 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3007,7 +3007,7 @@ err_occurred: /* Set up a preliminary stderr printer until we have enough infrastructure for the io module in place. - Use UTF-8/surrogateescape and ignore EAGAIN errors. */ + Use UTF-8/backslashreplace and ignore EAGAIN errors. */ static PyStatus _PySys_SetPreliminaryStderr(PyObject *sysdict) {