From 4d0f508a488ac1572e05b4c31a7ba6d8f43758e8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 20 Sep 2023 15:52:34 +0300 Subject: [PATCH 01/13] gh-108512: Add and use new replacements for PySys_GetObject() Add functions PySys_GetAttr(), PySys_GetAttrString(), PySys_GetOptionalAttr() and PySys_GetOptionalAttrString(). --- Doc/c-api/init_config.rst | 2 +- Doc/c-api/sys.rst | 49 +++++- Doc/whatsnew/3.13.rst | 6 + Include/internal/pycore_sysmodule.h | 3 - Include/sysmodule.h | 6 + ...-10-18-14-36-35.gh-issue-108512.fMZLfr.rst | 2 + Modules/_ctypes/callbacks.c | 26 +-- Modules/_cursesmodule.c | 14 +- Modules/_lsprof.c | 6 +- Modules/_pickle.c | 13 +- Modules/_threadmodule.c | 11 +- Modules/_tkinter.c | 12 +- Modules/faulthandler.c | 36 +++-- Modules/main.c | 9 +- Modules/syslogmodule.c | 26 +-- Python/_warnings.c | 7 +- Python/bltinmodule.c | 95 +++++++---- Python/errors.c | 20 ++- Python/import.c | 36 +++-- Python/initconfig.c | 9 +- Python/intrinsics.c | 11 +- Python/pylifecycle.c | 40 +++-- Python/pythonrun.c | 105 ++++++++---- Python/sysmodule.c | 153 +++++++++++++----- Python/traceback.c | 18 ++- 25 files changed, 487 insertions(+), 228 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 1d4e0fbb0d400f..3b8da30aa21781 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -1417,7 +1417,7 @@ initialization:: /* Specify sys.path explicitly */ /* If you want to modify the default set of paths, finish - initialization first and then use PySys_GetObject("path") */ + initialization first and then use PySys_GetAttr("path") */ config.module_search_paths_set = 1; status = PyWideStringList_Append(&config.module_search_paths, L"/path/to/stdlib"); diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index e3c54b075114ff..a55b0dd8345297 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -222,10 +222,55 @@ These are utility functions that make functionality from the :mod:`sys` module accessible to C code. They all work with the current interpreter thread's :mod:`sys` module's dict, which is contained in the internal thread state structure. +.. c:function:: PyObject *PySys_GetAttr(PyObject *name) + + Return the object *name* from the :mod:`sys` module or ``NULL`` on failure. + Set :exc:`RuntimeError` and return ``NULL`` if it does not exist. + + If the non-existing object should not be treated as a failure, you can use + :c:func:`PySys_GetOptionalAttr` instead. + + .. versionadded:: 3.13 + +.. c:function:: PyObject *PySys_GetAttrString(const char *name) + + This is the same as :c:func:`PySys_GetAttr`, but *name* is + specified as a :c:expr:`const char*` UTF-8 encoded bytes string, + rather than a :c:expr:`PyObject*`. + + If the non-existing object should not be treated as a failure, you can use + :c:func:`PySys_GetOptionalAttrString` instead. + + .. versionadded:: 3.13 + +.. c:function:: int PySys_GetOptionalAttr(PyObject *name, PyObject **result); + + Variant of :c:func:`PySys_GetAttr` which doesn't raise + exception if the object does not exist. + + If the object exists, set *\*result* to a new :term:`strong reference` + to the object and return ``1``. + If the object does not exist, set *\*result* to ``NULL`` and return ``0``, + without setting an exception. + If other error occurred, set an exception, set *\*result* to ``NULL`` and + return ``-1``. + + .. versionadded:: 3.13 + +.. c:function:: int PySys_GetOptionalAttrString(const char *name, PyObject **result); + + This is the same as :c:func:`PySys_GetOptionalAttr`, but *name* is + specified as a :c:expr:`const char*` UTF-8 encoded bytes string, + rather than a :c:expr:`PyObject*`. + + .. versionadded:: 3.13 + .. c:function:: PyObject *PySys_GetObject(const char *name) - Return the object *name* from the :mod:`sys` module or ``NULL`` if it does - not exist, without setting an exception. + Similar to :c:func:`PySys_GetAttrString`, but return a :term:`borrowed + reference` and return ``NULL`` *without* setting exception on failure. + + Preserves exception that was set before the call. .. c:function:: int PySys_SetObject(const char *name, PyObject *v) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 42e059386e2b8c..89800dda1a0071 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -988,6 +988,12 @@ New Features (Contributed by Serhiy Storchaka in :gh:`108511`.) +* Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, + :c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`. + They all are variants of :c:func:`PySys_GetObject` which return a new + :term:`strong reference` and can set an exception on failure. + (Contributed by Serhiy Storchaka in :gh:`108512`.) + * If Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and :c:func:`PyList_SET_ITEM` now check the index argument with an assertion. diff --git a/Include/internal/pycore_sysmodule.h b/Include/internal/pycore_sysmodule.h index 9b8eafd3d6cfd3..ffa823c3b9a37d 100644 --- a/Include/internal/pycore_sysmodule.h +++ b/Include/internal/pycore_sysmodule.h @@ -8,9 +8,6 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -// Export for '_pickle' shared extension -PyAPI_FUNC(PyObject*) _PySys_GetAttr(PyThreadState *tstate, PyObject *name); - // Export for '_pickle' shared extension PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *); diff --git a/Include/sysmodule.h b/Include/sysmodule.h index 7406513ec1439a..b4557596aadd40 100644 --- a/Include/sysmodule.h +++ b/Include/sysmodule.h @@ -8,6 +8,12 @@ extern "C" { #endif PyAPI_FUNC(PyObject *) PySys_GetObject(const char *); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000 +PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *); +PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *); +PyAPI_FUNC(int) PySys_GetOptionalAttr(PyObject *, PyObject **); +PyAPI_FUNC(int) PySys_GetOptionalAttrString(const char *, PyObject **); +#endif PyAPI_FUNC(int) PySys_SetObject(const char *, PyObject *); PyAPI_FUNC(void) PySys_WriteStdout(const char *format, ...) diff --git a/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst b/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst new file mode 100644 index 00000000000000..279e588f3adcb7 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst @@ -0,0 +1,2 @@ +Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, +:c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`. diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c index 1bd8fec97179e9..28a65c48334d5a 100644 --- a/Modules/_ctypes/callbacks.c +++ b/Modules/_ctypes/callbacks.c @@ -82,22 +82,6 @@ PyType_Spec cthunk_spec = { /**************************************************************/ -static void -PrintError(const char *msg, ...) -{ - char buf[512]; - PyObject *f = PySys_GetObject("stderr"); - va_list marker; - - va_start(marker, msg); - PyOS_vsnprintf(buf, sizeof(buf), msg, marker); - va_end(marker); - if (f != NULL && f != Py_None) - PyFile_WriteString(buf, f); - PyErr_Print(); -} - - #ifdef MS_WIN32 /* * We must call AddRef() on non-NULL COM pointers we receive as arguments @@ -116,7 +100,7 @@ TryAddRef(StgDictObject *dict, CDataObject *obj) int r = PyDict_Contains((PyObject *)dict, &_Py_ID(_needs_com_addref_)); if (r <= 0) { if (r < 0) { - PrintError("getting _needs_com_addref_"); + PySys_WriteStderr("getting _needs_com_addref_"); } return; } @@ -160,7 +144,7 @@ static void _CallPythonObject(void *mem, if (dict && dict->getfunc && !_ctypes_simple_instance(cnv)) { PyObject *v = dict->getfunc(*pArgs, dict->size); if (!v) { - PrintError("create argument %zd:\n", i); + PySys_WriteStderr("create argument %zd:\n", i); goto Done; } args[i] = v; @@ -173,12 +157,12 @@ static void _CallPythonObject(void *mem, /* Hm, shouldn't we use PyCData_AtAddress() or something like that instead? */ CDataObject *obj = (CDataObject *)_PyObject_CallNoArgs(cnv); if (!obj) { - PrintError("create argument %zd:\n", i); + PySys_WriteStderr("create argument %zd:\n", i); goto Done; } if (!CDataObject_Check(obj)) { Py_DECREF(obj); - PrintError("unexpected result of create argument %zd:\n", i); + PySys_WriteStderr("unexpected result of create argument %zd:\n", i); goto Done; } memcpy(obj->b_ptr, *pArgs, dict->size); @@ -189,7 +173,7 @@ static void _CallPythonObject(void *mem, } else { PyErr_SetString(PyExc_TypeError, "cannot build parameter"); - PrintError("Parsing argument %zd\n", i); + PySys_WriteStderr("Parsing argument %zd\n", i); goto Done; } /* XXX error handling! */ diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index d339a8aa798361..58683b2d3c6903 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3373,17 +3373,19 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd) if (fd == -1) { PyObject* sys_stdout; - sys_stdout = PySys_GetObject("stdout"); + sys_stdout = PySys_GetAttrString("stdout"); + if (sys_stdout == NULL) { + return NULL; + } - if (sys_stdout == NULL || sys_stdout == Py_None) { - PyErr_SetString( - PyCursesError, - "lost sys.stdout"); + if (sys_stdout == Py_None) { + PyErr_SetString(PyCursesError, "lost sys.stdout"); + Py_DECREF(sys_stdout); return NULL; } fd = PyObject_AsFileDescriptor(sys_stdout); - + Py_DECREF(sys_stdout); if (fd == -1) { return NULL; } diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index d23a756ace887d..75023bd3340bbb 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -716,7 +716,7 @@ profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds) return NULL; } - PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring"); + PyObject* monitoring = PySys_GetAttrString("monitoring"); if (!monitoring) { return NULL; } @@ -778,7 +778,7 @@ profiler_disable(ProfilerObject *self, PyObject* noarg) { if (self->flags & POF_ENABLED) { PyObject* result = NULL; - PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring"); + PyObject* monitoring = PySys_GetAttrString("monitoring"); if (!monitoring) { return NULL; @@ -880,7 +880,7 @@ profiler_init(ProfilerObject *pObj, PyObject *args, PyObject *kw) Py_XSETREF(pObj->externalTimer, Py_XNewRef(timer)); pObj->tool_id = PY_MONITORING_PROFILER_ID; - PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring"); + PyObject* monitoring = PySys_GetAttrString("monitoring"); if (!monitoring) { return -1; } diff --git a/Modules/_pickle.c b/Modules/_pickle.c index a3cf34699ba509..6ca63dcc3329af 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -17,7 +17,7 @@ #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_runtime.h" // _Py_ID() #include "pycore_setobject.h" // _PySet_NextEntry() -#include "pycore_sysmodule.h" // _PySys_GetAttr() +#include "pycore_sysmodule.h" // _PySys_GetSizeOf() #include // strtol() @@ -1986,19 +1986,19 @@ whichmodule(PyObject *global, PyObject *dotted_path) assert(module_name == NULL); /* Fallback on walking sys.modules */ - PyThreadState *tstate = _PyThreadState_GET(); - modules = _PySys_GetAttr(tstate, &_Py_ID(modules)); + modules = PySys_GetAttr(&_Py_ID(modules)); if (modules == NULL) { - PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules"); return NULL; } if (PyDict_CheckExact(modules)) { i = 0; while (PyDict_Next(modules, &i, &module_name, &module)) { if (_checkmodule(module_name, module, global, dotted_path) == 0) { + Py_DECREF(modules); return Py_NewRef(module_name); } if (PyErr_Occurred()) { + Py_DECREF(modules); return NULL; } } @@ -2006,6 +2006,7 @@ whichmodule(PyObject *global, PyObject *dotted_path) else { PyObject *iterator = PyObject_GetIter(modules); if (iterator == NULL) { + Py_DECREF(modules); return NULL; } while ((module_name = PyIter_Next(iterator))) { @@ -2013,22 +2014,26 @@ whichmodule(PyObject *global, PyObject *dotted_path) if (module == NULL) { Py_DECREF(module_name); Py_DECREF(iterator); + Py_DECREF(modules); return NULL; } if (_checkmodule(module_name, module, global, dotted_path) == 0) { Py_DECREF(module); Py_DECREF(iterator); + Py_DECREF(modules); return module_name; } Py_DECREF(module); Py_DECREF(module_name); if (PyErr_Occurred()) { Py_DECREF(iterator); + Py_DECREF(modules); return NULL; } } Py_DECREF(iterator); } + Py_DECREF(modules); /* If no module is found, use __main__. */ return &_Py_ID(__main__); diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 86bd560b92ba6b..c30d11dc6e69d6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -10,7 +10,6 @@ #include "pycore_pyerrors.h" // _PyErr_WriteUnraisableMsg() #include "pycore_pylifecycle.h" #include "pycore_pystate.h" // _PyThreadState_SetCurrent() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include // offsetof() @@ -1567,9 +1566,12 @@ thread_excepthook(PyObject *module, PyObject *args) PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2); PyObject *thread = PyStructSequence_GET_ITEM(args, 3); - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + PyObject *file; + if (PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) { + return NULL; + } if (file == NULL || file == Py_None) { + Py_XDECREF(file); if (thread == Py_None) { /* do nothing if sys.stderr is None and thread is None */ Py_RETURN_NONE; @@ -1586,9 +1588,6 @@ thread_excepthook(PyObject *module, PyObject *args) Py_RETURN_NONE; } } - else { - Py_INCREF(file); - } int res = thread_excepthook_file(file, exc_type, exc_value, exc_tb, thread); diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index f9a18644945c65..05ef6bec205f30 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -137,7 +137,7 @@ _get_tcl_lib_path(void) struct stat stat_buf; int stat_return_value; - PyObject *prefix = PySys_GetObject("prefix"); // borrowed reference + PyObject *prefix = PySys_GetAttrString("prefix"); if (prefix == NULL) { return NULL; } @@ -145,9 +145,11 @@ _get_tcl_lib_path(void) /* Check expected location for an installed Python first */ tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION); if (tcl_library_path == NULL) { + Py_DECREF(prefix); return NULL; } tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path); + Py_DECREF(prefix); if (tcl_library_path == NULL) { return NULL; } @@ -3269,9 +3271,10 @@ PyInit__tkinter(void) /* This helps the dynamic loader; in Unicode aware Tcl versions it also helps Tcl find its encodings. */ - uexe = PySys_GetObject("executable"); // borrowed reference + uexe = PySys_GetAttrString("executable"); if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None cexe = PyUnicode_EncodeFSDefault(uexe); + Py_DECREF(uexe); if (cexe) { #ifdef MS_WINDOWS int set_var = 0; @@ -3284,12 +3287,14 @@ PyInit__tkinter(void) if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { str_path = _get_tcl_lib_path(); if (str_path == NULL && PyErr_Occurred()) { + Py_DECREF(cexe); Py_DECREF(m); return NULL; } if (str_path != NULL) { wcs_path = PyUnicode_AsWideCharString(str_path, NULL); if (wcs_path == NULL) { + Py_DECREF(cexe); Py_DECREF(m); return NULL; } @@ -3310,6 +3315,9 @@ PyInit__tkinter(void) } Py_XDECREF(cexe); } + else { + Py_XDECREF(uexe); + } if (PyErr_Occurred()) { Py_DECREF(m); diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index a2e3c2300b3ce8..9220cfa95afad1 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -3,7 +3,6 @@ #include "pycore_pyerrors.h" // _Py_DumpExtensionModules #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_signal.h" // Py_NSIG -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_traceback.h" // _Py_DumpTracebackThreads #ifdef HAVE_UNISTD_H @@ -107,14 +106,13 @@ faulthandler_get_fileno(PyObject **file_ptr) PyObject *file = *file_ptr; if (file == NULL || file == Py_None) { - PyThreadState *tstate = _PyThreadState_GET(); - file = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + file = PySys_GetAttr(&_Py_ID(stderr)); if (file == NULL) { - PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); return -1; } if (file == Py_None) { PyErr_SetString(PyExc_RuntimeError, "sys.stderr is None"); + Py_DECREF(file); return -1; } } @@ -130,10 +128,15 @@ faulthandler_get_fileno(PyObject **file_ptr) *file_ptr = NULL; return fd; } + else { + Py_INCREF(file); + } result = PyObject_CallMethodNoArgs(file, &_Py_ID(fileno)); - if (result == NULL) + if (result == NULL) { + Py_DECREF(file); return -1; + } fd = -1; if (PyLong_Check(result)) { @@ -146,6 +149,7 @@ faulthandler_get_fileno(PyObject **file_ptr) if (fd == -1) { PyErr_SetString(PyExc_RuntimeError, "file.fileno() is not a valid file descriptor"); + Py_DECREF(file); return -1; } @@ -225,19 +229,23 @@ faulthandler_dump_traceback_py(PyObject *self, return NULL; tstate = get_thread_state(); - if (tstate == NULL) + if (tstate == NULL) { + Py_XDECREF(file); return NULL; + } if (all_threads) { errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate); if (errmsg != NULL) { PyErr_SetString(PyExc_RuntimeError, errmsg); + Py_XDECREF(file); return NULL; } } else { _Py_DumpTraceback(fd, tstate); } + Py_XDECREF(file); if (PyErr_CheckSignals()) return NULL; @@ -499,10 +507,11 @@ faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs) return NULL; tstate = get_thread_state(); - if (tstate == NULL) + if (tstate == NULL) { + Py_XDECREF(file); return NULL; + } - Py_XINCREF(file); Py_XSETREF(fatal_error.file, file); fatal_error.fd = fd; fatal_error.all_threads = all_threads; @@ -692,12 +701,14 @@ faulthandler_dump_traceback_later(PyObject *self, if (!thread.running) { thread.running = PyThread_allocate_lock(); if (!thread.running) { + Py_XDECREF(file); return PyErr_NoMemory(); } } if (!thread.cancel_event) { thread.cancel_event = PyThread_allocate_lock(); if (!thread.cancel_event || !thread.running) { + Py_XDECREF(file); return PyErr_NoMemory(); } @@ -709,6 +720,7 @@ faulthandler_dump_traceback_later(PyObject *self, /* format the timeout */ header = format_timeout(timeout_us); if (header == NULL) { + Py_XDECREF(file); return PyErr_NoMemory(); } header_len = strlen(header); @@ -716,7 +728,6 @@ faulthandler_dump_traceback_later(PyObject *self, /* Cancel previous thread, if running */ cancel_dump_traceback_later(); - Py_XINCREF(file); Py_XSETREF(thread.file, file); thread.fd = fd; /* the downcast is safe: we check that 0 < timeout_us < PY_TIMEOUT_MAX */ @@ -878,14 +889,17 @@ faulthandler_register_py(PyObject *self, if (user_signals == NULL) { user_signals = PyMem_Calloc(Py_NSIG, sizeof(user_signal_t)); - if (user_signals == NULL) + if (user_signals == NULL) { + Py_XDECREF(file); return PyErr_NoMemory(); + } } user = &user_signals[signum]; if (!user->enabled) { #ifdef FAULTHANDLER_USE_ALT_STACK if (faulthandler_allocate_stack() < 0) { + Py_XDECREF(file); return NULL; } #endif @@ -893,13 +907,13 @@ faulthandler_register_py(PyObject *self, err = faulthandler_register(signum, chain, &previous); if (err) { PyErr_SetFromErrno(PyExc_OSError); + Py_XDECREF(file); return NULL; } user->previous = previous; } - Py_XINCREF(file); Py_XSETREF(user->file, file); user->fd = fd; user->all_threads = all_threads; diff --git a/Modules/main.c b/Modules/main.c index b5ee34d0141daf..8be3fbf17cdfb1 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -444,16 +444,11 @@ pymain_run_startup(PyConfig *config, int *exitcode) static int pymain_run_interactive_hook(int *exitcode) { - PyObject *sys, *hook, *result; - sys = PyImport_ImportModule("sys"); - if (sys == NULL) { + PyObject *hook, *result; + if (PySys_GetOptionalAttrString("__interactivehook__", &hook) < 0) { goto error; } - - hook = PyObject_GetAttrString(sys, "__interactivehook__"); - Py_DECREF(sys); if (hook == NULL) { - PyErr_Clear(); return 0; } diff --git a/Modules/syslogmodule.c b/Modules/syslogmodule.c index 6a44850e291448..0bf69cf6d0e1c7 100644 --- a/Modules/syslogmodule.c +++ b/Modules/syslogmodule.c @@ -89,45 +89,51 @@ syslog_get_argv(void) Py_ssize_t argv_len, scriptlen; PyObject *scriptobj; Py_ssize_t slash; - PyObject *argv = PySys_GetObject("argv"); + PyObject *argv = PySys_GetAttrString("argv"); if (argv == NULL) { - return(NULL); + PyErr_Clear(); + return NULL; } argv_len = PyList_Size(argv); if (argv_len == -1) { PyErr_Clear(); - return(NULL); + Py_DECREF(argv); + return NULL; } if (argv_len == 0) { - return(NULL); + Py_DECREF(argv); + return NULL; } scriptobj = PyList_GetItem(argv, 0); + Py_XINCREF(scriptobj); + Py_DECREF(argv); if (scriptobj == NULL) { PyErr_Clear(); return NULL; } if (!PyUnicode_Check(scriptobj)) { - return(NULL); + Py_DECREF(scriptobj); + return NULL; } scriptlen = PyUnicode_GET_LENGTH(scriptobj); if (scriptlen == 0) { - return(NULL); + Py_DECREF(scriptobj); + return NULL; } slash = PyUnicode_FindChar(scriptobj, SEP, 0, scriptlen, -1); if (slash == -2) { PyErr_Clear(); + Py_DECREF(scriptobj); return NULL; } if (slash != -1) { - return PyUnicode_Substring(scriptobj, slash + 1, scriptlen); - } else { - Py_INCREF(scriptobj); - return(scriptobj); + Py_SETREF(scriptobj, PyUnicode_Substring(scriptobj, slash + 1, scriptlen)); } + return scriptobj; } diff --git a/Python/_warnings.c b/Python/_warnings.c index 4b7fb888247145..723de19638f729 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -5,7 +5,6 @@ #include "pycore_pyerrors.h" // _PyErr_Occurred() #include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_traceback.h" // _Py_DisplaySourceLine() #include "clinic/_warnings.c.h" @@ -496,7 +495,7 @@ static void show_warning(PyThreadState *tstate, PyObject *filename, int lineno, PyObject *text, PyObject *category, PyObject *sourceline) { - PyObject *f_stderr; + PyObject *f_stderr = NULL; PyObject *name; char lineno_str[128]; @@ -507,9 +506,8 @@ show_warning(PyThreadState *tstate, PyObject *filename, int lineno, goto error; } - f_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + f_stderr = PySys_GetAttr(&_Py_ID(stderr)); if (f_stderr == NULL) { - fprintf(stderr, "lost sys.stderr\n"); goto error; } @@ -558,6 +556,7 @@ show_warning(PyThreadState *tstate, PyObject *filename, int lineno, } error: + Py_XDECREF(f_stderr); Py_XDECREF(name); PyErr_Clear(); } diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index c373585c0986ce..511b61038936a5 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -12,7 +12,6 @@ #include "pycore_pyerrors.h" // _PyErr_NoMemory() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_pythonrun.h" // _Py_SourceAsString() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_tuple.h" // _PyTuple_FromArray() #include "clinic/bltinmodule.c.h" @@ -463,18 +462,16 @@ builtin_callable(PyObject *module, PyObject *obj) static PyObject * builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords) { - PyObject *hook = PySys_GetObject("breakpointhook"); - + PyObject *hook = PySys_GetAttrString("breakpointhook"); if (hook == NULL) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.breakpointhook"); return NULL; } if (PySys_Audit("builtins.breakpoint", "O", hook) < 0) { + Py_DECREF(hook); return NULL; } - Py_INCREF(hook); PyObject *retval = PyObject_Vectorcall(hook, args, nargs, keywords); Py_DECREF(hook); return retval; @@ -2027,18 +2024,20 @@ builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep, int i, err; if (file == Py_None) { - PyThreadState *tstate = _PyThreadState_GET(); - file = _PySys_GetAttr(tstate, &_Py_ID(stdout)); + file = PySys_GetAttr(&_Py_ID(stdout)); if (file == NULL) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout"); return NULL; } /* sys.stdout may be None when FILE* stdout isn't connected */ if (file == Py_None) { + Py_DECREF(file); Py_RETURN_NONE; } } + else { + Py_INCREF(file); + } if (sep == Py_None) { sep = NULL; @@ -2047,6 +2046,7 @@ builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep, PyErr_Format(PyExc_TypeError, "sep must be None or a string, not %.200s", Py_TYPE(sep)->tp_name); + Py_DECREF(file); return NULL; } if (end == Py_None) { @@ -2056,6 +2056,7 @@ builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep, PyErr_Format(PyExc_TypeError, "end must be None or a string, not %.200s", Py_TYPE(end)->tp_name); + Py_DECREF(file); return NULL; } @@ -2068,11 +2069,13 @@ builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep, err = PyFile_WriteObject(sep, file, Py_PRINT_RAW); } if (err) { + Py_DECREF(file); return NULL; } } err = PyFile_WriteObject(PyTuple_GET_ITEM(args, i), file, Py_PRINT_RAW); if (err) { + Py_DECREF(file); return NULL; } } @@ -2084,14 +2087,17 @@ builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep, err = PyFile_WriteObject(end, file, Py_PRINT_RAW); } if (err) { + Py_DECREF(file); return NULL; } if (flush) { if (_PyFile_Flush(file) < 0) { + Py_DECREF(file); return NULL; } } + Py_DECREF(file); Py_RETURN_NONE; } @@ -2116,36 +2122,41 @@ static PyObject * builtin_input_impl(PyObject *module, PyObject *prompt) /*[clinic end generated code: output=83db5a191e7a0d60 input=159c46d4ae40977e]*/ { - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *fin = _PySys_GetAttr( - tstate, &_Py_ID(stdin)); - PyObject *fout = _PySys_GetAttr( - tstate, &_Py_ID(stdout)); - PyObject *ferr = _PySys_GetAttr( - tstate, &_Py_ID(stderr)); + PyObject *fin = NULL; + PyObject *fout = NULL; + PyObject *ferr = NULL; PyObject *tmp; long fd; int tty; /* Check that stdin/out/err are intact */ - if (fin == NULL || fin == Py_None) { - PyErr_SetString(PyExc_RuntimeError, - "input(): lost sys.stdin"); - return NULL; + fin = PySys_GetAttr(&_Py_ID(stdin)); + if (fin == NULL) { + goto error; } - if (fout == NULL || fout == Py_None) { - PyErr_SetString(PyExc_RuntimeError, - "input(): lost sys.stdout"); - return NULL; + if (fin == Py_None) { + PyErr_SetString(PyExc_RuntimeError, "lost sys.stdin"); + goto error; } - if (ferr == NULL || ferr == Py_None) { - PyErr_SetString(PyExc_RuntimeError, - "input(): lost sys.stderr"); - return NULL; + fout = PySys_GetAttr(&_Py_ID(stdout)); + if (fout == NULL) { + goto error; + } + if (fout == Py_None) { + PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout"); + goto error; + } + ferr = PySys_GetAttr(&_Py_ID(stderr)); + if (ferr == NULL) { + goto error; + } + if (ferr == Py_None) { + PyErr_SetString(PyExc_RuntimeError, "lost sys.stderr"); + goto error; } if (PySys_Audit("builtins.input", "O", prompt ? prompt : Py_None) < 0) { - return NULL; + goto error; } /* First of all, flush stderr */ @@ -2164,8 +2175,9 @@ builtin_input_impl(PyObject *module, PyObject *prompt) else { fd = PyLong_AsLong(tmp); Py_DECREF(tmp); - if (fd < 0 && PyErr_Occurred()) - return NULL; + if (fd < 0 && PyErr_Occurred()) { + goto error; + } tty = fd == fileno(stdin) && isatty(fd); } if (tty) { @@ -2178,7 +2190,7 @@ builtin_input_impl(PyObject *module, PyObject *prompt) fd = PyLong_AsLong(tmp); Py_DECREF(tmp); if (fd < 0 && PyErr_Occurred()) - return NULL; + goto error; tty = fd == fileno(stdout) && isatty(fd); } } @@ -2301,10 +2313,13 @@ builtin_input_impl(PyObject *module, PyObject *prompt) if (result != NULL) { if (PySys_Audit("builtins.input/result", "O", result) < 0) { - return NULL; + goto error; } } + Py_DECREF(fin); + Py_DECREF(fout); + Py_DECREF(ferr); return result; _readline_errors: @@ -2314,7 +2329,7 @@ builtin_input_impl(PyObject *module, PyObject *prompt) Py_XDECREF(stdout_errors); Py_XDECREF(po); if (tty) - return NULL; + goto error; PyErr_Clear(); } @@ -2322,12 +2337,22 @@ builtin_input_impl(PyObject *module, PyObject *prompt) /* Fallback if we're not interactive */ if (prompt != NULL) { if (PyFile_WriteObject(prompt, fout, Py_PRINT_RAW) != 0) - return NULL; + goto error; } if (_PyFile_Flush(fout) < 0) { PyErr_Clear(); } - return PyFile_GetLine(fin, -1); + tmp = PyFile_GetLine(fin, -1); + Py_DECREF(fin); + Py_DECREF(fout); + Py_DECREF(ferr); + return tmp; + +error: + Py_XDECREF(fin); + Py_XDECREF(fout); + Py_XDECREF(ferr); + return NULL; } diff --git a/Python/errors.c b/Python/errors.c index 15af39b10dc07e..8ebccdfa55ecbd 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1521,14 +1521,15 @@ write_unraisable_exc(PyThreadState *tstate, PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb, PyObject *err_msg, PyObject *obj) { - PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + PyObject *file; + if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) { + return -1; + } if (file == NULL || file == Py_None) { + Py_XDECREF(file); return 0; } - /* Hold a strong reference to ensure that sys.stderr doesn't go away - while we use it */ - Py_INCREF(file); int res = write_unraisable_exc_file(tstate, exc_type, exc_value, exc_tb, err_msg, obj, file); Py_DECREF(file); @@ -1625,13 +1626,20 @@ _PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj) goto error; } - PyObject *hook = _PySys_GetAttr(tstate, &_Py_ID(unraisablehook)); + PyObject *hook; + if (PySys_GetOptionalAttr(&_Py_ID(unraisablehook), &hook) < 0) { + Py_DECREF(hook_args); + err_msg_str = NULL; + obj = NULL; + goto error; + } if (hook == NULL) { Py_DECREF(hook_args); goto default_hook; } if (_PySys_Audit(tstate, "sys.unraisablehook", "OO", hook, hook_args) < 0) { + Py_DECREF(hook); Py_DECREF(hook_args); err_msg_str = "Exception ignored in audit hook"; obj = NULL; @@ -1639,11 +1647,13 @@ _PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj) } if (hook == Py_None) { + Py_DECREF(hook); Py_DECREF(hook_args); goto default_hook; } PyObject *res = PyObject_CallOneArg(hook, hook_args); + Py_DECREF(hook); Py_DECREF(hook_args); if (res != NULL) { Py_DECREF(res); diff --git a/Python/import.c b/Python/import.c index cafdd834502224..74ef651cda0719 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2409,19 +2409,15 @@ PyObject * PyImport_GetImporter(PyObject *path) { PyThreadState *tstate = _PyThreadState_GET(); - PyObject *path_importer_cache = PySys_GetObject("path_importer_cache"); + PyObject *path_importer_cache = PySys_GetAttrString("path_importer_cache"); if (path_importer_cache == NULL) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.path_importer_cache"); return NULL; } - Py_INCREF(path_importer_cache); - PyObject *path_hooks = PySys_GetObject("path_hooks"); + PyObject *path_hooks = PySys_GetAttrString("path_hooks"); if (path_hooks == NULL) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.path_hooks"); Py_DECREF(path_importer_cache); return NULL; } - Py_INCREF(path_hooks); PyObject *importer = get_path_importer(tstate, path_importer_cache, path_hooks, path); Py_DECREF(path_hooks); Py_DECREF(path_importer_cache); @@ -2724,15 +2720,31 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) _PyTime_t t1 = 0, accumulated_copy = accumulated; - PyObject *sys_path = PySys_GetObject("path"); - PyObject *sys_meta_path = PySys_GetObject("meta_path"); - PyObject *sys_path_hooks = PySys_GetObject("path_hooks"); + PyObject *sys_path, *sys_meta_path, *sys_path_hooks; + if (PySys_GetOptionalAttrString("path", &sys_path) < 0) { + return NULL; + } + if (PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) { + Py_XDECREF(sys_path); + return NULL; + } + if (PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) { + Py_XDECREF(sys_meta_path); + Py_XDECREF(sys_path); + return NULL; + } if (_PySys_Audit(tstate, "import", "OOOOO", abs_name, Py_None, sys_path ? sys_path : Py_None, sys_meta_path ? sys_meta_path : Py_None, sys_path_hooks ? sys_path_hooks : Py_None) < 0) { + Py_XDECREF(sys_path_hooks); + Py_XDECREF(sys_meta_path); + Py_XDECREF(sys_path); return NULL; } + Py_XDECREF(sys_path_hooks); + Py_XDECREF(sys_meta_path); + Py_XDECREF(sys_path); /* XOptions is initialized after first some imports. @@ -3182,10 +3194,8 @@ _PyImport_FiniCore(PyInterpreterState *interp) static int init_zipimport(PyThreadState *tstate, int verbose) { - PyObject *path_hooks = PySys_GetObject("path_hooks"); + PyObject *path_hooks = PySys_GetAttrString("path_hooks"); if (path_hooks == NULL) { - _PyErr_SetString(tstate, PyExc_RuntimeError, - "unable to get sys.path_hooks"); return -1; } @@ -3205,12 +3215,14 @@ init_zipimport(PyThreadState *tstate, int verbose) int err = PyList_Insert(path_hooks, 0, zipimporter); Py_DECREF(zipimporter); if (err < 0) { + Py_DECREF(path_hooks); return -1; } if (verbose) { PySys_WriteStderr("# installed zipimport hook\n"); } } + Py_DECREF(path_hooks); return 0; } diff --git a/Python/initconfig.c b/Python/initconfig.c index e1199338f2a54f..f0014433520ff1 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -3168,10 +3168,13 @@ _Py_DumpPathConfig(PyThreadState *tstate) #define DUMP_SYS(NAME) \ do { \ - obj = PySys_GetObject(#NAME); \ PySys_FormatStderr(" sys.%s = ", #NAME); \ + if (PySys_GetOptionalAttrString(#NAME, &obj) < 0) { \ + PyErr_Clear(); \ + } \ if (obj != NULL) { \ PySys_FormatStderr("%A", obj); \ + Py_DECREF(obj); \ } \ else { \ PySys_WriteStderr("(not set)"); \ @@ -3189,7 +3192,8 @@ _Py_DumpPathConfig(PyThreadState *tstate) DUMP_SYS(exec_prefix); #undef DUMP_SYS - PyObject *sys_path = PySys_GetObject("path"); /* borrowed reference */ + PyObject *sys_path; + (void) PySys_GetOptionalAttrString("path", &sys_path); if (sys_path != NULL && PyList_Check(sys_path)) { PySys_WriteStderr(" sys.path = [\n"); Py_ssize_t len = PyList_GET_SIZE(sys_path); @@ -3199,6 +3203,7 @@ _Py_DumpPathConfig(PyThreadState *tstate) } PySys_WriteStderr(" ]\n"); } + Py_XDECREF(sys_path); _PyErr_SetRaisedException(tstate, exc); } diff --git a/Python/intrinsics.c b/Python/intrinsics.c index bbd79ec473f470..4e624af868829c 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -8,7 +8,6 @@ #include "pycore_intrinsics.h" // INTRINSIC_PRINT #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_runtime.h" // _Py_ID() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_typevarobject.h" // _Py_make_typevar() @@ -22,16 +21,16 @@ no_intrinsic1(PyThreadState* tstate, PyObject *unused) } static PyObject * -print_expr(PyThreadState* tstate, PyObject *value) +print_expr(PyThreadState* Py_UNUSED(ignored), PyObject *value) { - PyObject *hook = _PySys_GetAttr(tstate, &_Py_ID(displayhook)); + PyObject *hook = PySys_GetAttr(&_Py_ID(displayhook)); // Can't use ERROR_IF here. if (hook == NULL) { - _PyErr_SetString(tstate, PyExc_RuntimeError, - "lost sys.displayhook"); return NULL; } - return PyObject_CallOneArg(hook, value); + PyObject *res = PyObject_CallOneArg(hook, value); + Py_DECREF(hook); + return res; } static int diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7b56034541756a..790a31200199e7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1200,8 +1200,12 @@ init_interp_main(PyThreadState *tstate) if (is_main_interp) { /* Initialize warnings. */ - PyObject *warnoptions = PySys_GetObject("warnoptions"); - if (warnoptions != NULL && PyList_Size(warnoptions) > 0) + PyObject *warnoptions; + if (PySys_GetOptionalAttrString("warnoptions", &warnoptions) < 0) { + return _PyStatus_ERR("can't initialize warnings"); + } + if (warnoptions != NULL && PyList_Check(warnoptions) && + PyList_Size(warnoptions) > 0) { PyObject *warnings_module = PyImport_ImportModule("warnings"); if (warnings_module == NULL) { @@ -1210,6 +1214,7 @@ init_interp_main(PyThreadState *tstate) } Py_XDECREF(warnings_module); } + Py_XDECREF(warnoptions); interp->runtime->initialized = 1; } @@ -1694,24 +1699,32 @@ file_is_closed(PyObject *fobj) static int flush_std_files(void) { - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *fout = _PySys_GetAttr(tstate, &_Py_ID(stdout)); - PyObject *ferr = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + PyObject *file; int status = 0; - if (fout != NULL && fout != Py_None && !file_is_closed(fout)) { - if (_PyFile_Flush(fout) < 0) { - PyErr_WriteUnraisable(fout); + if (PySys_GetOptionalAttr(&_Py_ID(stdout), &file) < 0) { + PyErr_WriteUnraisable(NULL); + status = -1; + } + else if (file != NULL && file != Py_None && !file_is_closed(file)) { + if (_PyFile_Flush(file) < 0) { + PyErr_WriteUnraisable(file); status = -1; } } + Py_XDECREF(file); - if (ferr != NULL && ferr != Py_None && !file_is_closed(ferr)) { - if (_PyFile_Flush(ferr) < 0) { + if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) { + PyErr_Clear(); + status = -1; + } + else if (file != NULL && file != Py_None && !file_is_closed(file)) { + if (_PyFile_Flush(file) < 0) { PyErr_Clear(); status = -1; } } + Py_XDECREF(file); return status; } @@ -2666,10 +2679,14 @@ _Py_FatalError_PrintExc(PyThreadState *tstate) return 0; } - PyObject *ferr = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + PyObject *ferr; + if (PySys_GetOptionalAttr(&_Py_ID(stderr), &ferr) < 0) { + _PyErr_Clear(tstate); + } if (ferr == NULL || ferr == Py_None) { /* sys.stderr is not set yet or set to None, no need to try to display the exception */ + Py_XDECREF(ferr); Py_DECREF(exc); return 0; } @@ -2685,6 +2702,7 @@ _Py_FatalError_PrintExc(PyThreadState *tstate) if (_PyFile_Flush(ferr) < 0) { _PyErr_Clear(tstate); } + Py_DECREF(ferr); return has_tb; } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index b915c063d0b456..79c23ba2c0e84c 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -111,17 +111,35 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag flags = &local_flags; } - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *v = _PySys_GetAttr(tstate, &_Py_ID(ps1)); + PyObject *v; + if (PySys_GetOptionalAttr(&_Py_ID(ps1), &v) < 0) { + PyErr_Print(); + return -1; + } if (v == NULL) { - _PySys_SetAttr(&_Py_ID(ps1), v = PyUnicode_FromString(">>> ")); - Py_XDECREF(v); + v = PyUnicode_FromString(">>> "); + if (v == NULL) { + PyErr_Clear(); + } + if (_PySys_SetAttr(&_Py_ID(ps1), v) < 0) { + PyErr_Clear(); + } + } + Py_XDECREF(v); + if (PySys_GetOptionalAttr(&_Py_ID(ps2), &v) < 0) { + PyErr_Print(); + return -1; } - v = _PySys_GetAttr(tstate, &_Py_ID(ps2)); if (v == NULL) { - _PySys_SetAttr(&_Py_ID(ps2), v = PyUnicode_FromString("... ")); - Py_XDECREF(v); + v = PyUnicode_FromString("... "); + if (v == NULL) { + PyErr_Clear(); + } + if (_PySys_SetAttr(&_Py_ID(ps2), v) < 0) { + PyErr_Clear(); + } } + Py_XDECREF(v); #ifdef Py_REF_DEBUG int show_ref_count = _Py_GetConfig()->show_ref_count; @@ -181,31 +199,37 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename, PyCompilerFlags *flags, PyArena *arena, mod_ty *pmod, PyObject** interactive_src) { - PyThreadState *tstate = _PyThreadState_GET(); - // Get sys.stdin.encoding (as UTF-8) - PyObject *attr; // borrowed ref + PyObject *attr; PyObject *encoding_obj = NULL; const char *encoding = NULL; if (fp == stdin) { - attr = _PySys_GetAttr(tstate, &_Py_ID(stdin)); - if (attr && attr != Py_None) { - encoding_obj = PyObject_GetAttr(attr, &_Py_ID(encoding)); - if (encoding_obj) { + if (PySys_GetOptionalAttr(&_Py_ID(stdin), &attr) < 0) { + PyErr_Clear(); + } + else if (attr != NULL && attr != Py_None) { + if (PyObject_GetOptionalAttr(attr, &_Py_ID(encoding), &encoding_obj) < 0) { + PyErr_Clear(); + } + else if (encoding_obj && PyUnicode_Check(encoding_obj)) { encoding = PyUnicode_AsUTF8(encoding_obj); if (!encoding) { PyErr_Clear(); } } } + Py_XDECREF(attr); } // Get sys.ps1 (as UTF-8) - attr = _PySys_GetAttr(tstate, &_Py_ID(ps1)); PyObject *ps1_obj = NULL; const char *ps1 = ""; - if (attr != NULL) { + if (PySys_GetOptionalAttr(&_Py_ID(ps1), &attr) < 0) { + PyErr_Clear(); + } + else if (attr != NULL) { ps1_obj = PyObject_Str(attr); + Py_DECREF(attr); if (ps1_obj == NULL) { PyErr_Clear(); } @@ -219,11 +243,14 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename, } // Get sys.ps2 (as UTF-8) - attr = _PySys_GetAttr(tstate, &_Py_ID(ps2)); PyObject *ps2_obj = NULL; const char *ps2 = ""; - if (attr != NULL) { + if (PySys_GetOptionalAttr(&_Py_ID(ps2), &attr) < 0) { + PyErr_Clear(); + } + else if (attr != NULL) { ps2_obj = PyObject_Str(attr); + Py_DECREF(attr); if (ps2_obj == NULL) { PyErr_Clear(); } @@ -559,13 +586,14 @@ _Py_HandleSystemExit(int *exitcode_p) exitcode = (int)PyLong_AsLong(exc); } else { - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr)); - /* We clear the exception here to avoid triggering the assertion - * in PyObject_Str that ensures it won't silently lose exception - * details. - */ - PyErr_Clear(); + PyObject *sys_stderr; + if (PySys_GetOptionalAttr(&_Py_ID(stderr), &sys_stderr) < 0) { + /* We clear the exception here to avoid triggering the assertion + * in PyObject_Str that ensures it won't silently lose exception + * details. + */ + PyErr_Clear(); + } if (sys_stderr != NULL && sys_stderr != Py_None) { PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW); } else { @@ -573,6 +601,7 @@ _Py_HandleSystemExit(int *exitcode_p) fflush(stderr); } PySys_WriteStderr("\n"); + Py_XDECREF(sys_stderr); exitcode = 1; } @@ -596,7 +625,7 @@ handle_system_exit(void) static void _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) { - PyObject *typ = NULL, *tb = NULL; + PyObject *typ = NULL, *tb = NULL, *hook = NULL; handle_system_exit(); PyObject *exc = _PyErr_GetRaisedException(tstate); @@ -625,7 +654,9 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) _PyErr_Clear(tstate); } } - PyObject *hook = _PySys_GetAttr(tstate, &_Py_ID(excepthook)); + if (PySys_GetOptionalAttr(&_Py_ID(excepthook), &hook) < 0) { + PyErr_Clear(); + } if (_PySys_Audit(tstate, "sys.excepthook", "OOOO", hook ? hook : Py_None, typ, exc, tb) < 0) { if (PyErr_ExceptionMatches(PyExc_RuntimeError)) { @@ -659,6 +690,7 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) } done: + Py_XDECREF(hook); Py_XDECREF(typ); Py_XDECREF(exc); Py_XDECREF(tb); @@ -1106,17 +1138,24 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb) void PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb) { - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + PyObject *file; + if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) { + PyObject *exc = PyErr_GetRaisedException(); + _PyObject_Dump(value); + fprintf(stderr, "lost sys.stderr\n"); + _PyObject_Dump(exc); + Py_DECREF(exc); + return; + } if (file == NULL) { _PyObject_Dump(value); fprintf(stderr, "lost sys.stderr\n"); return; } if (file == Py_None) { + Py_DECREF(file); return; } - Py_INCREF(file); _PyErr_Display(file, NULL, value, tb); Py_DECREF(file); } @@ -1203,11 +1242,15 @@ PyRun_FileExFlags(FILE *fp, const char *filename, int start, PyObject *globals, static void flush_io_stream(PyThreadState *tstate, PyObject *name) { - PyObject *f = _PySys_GetAttr(tstate, name); + PyObject *f; + if (PySys_GetOptionalAttr(name, &f) < 0) { + PyErr_Clear(); + } if (f != NULL) { if (_PyFile_Flush(f) < 0) { PyErr_Clear(); } + Py_DECREF(f); } } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 3debe7f7c139c6..284dcca26e9f67 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -72,45 +72,76 @@ module sys PyObject * -_PySys_GetAttr(PyThreadState *tstate, PyObject *name) +PySys_GetAttr(PyObject *name) { - PyObject *sd = tstate->interp->sysdict; - if (sd == NULL) { + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *sysdict = tstate->interp->sysdict; + if (sysdict == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no sys module"); return NULL; } - PyObject *exc = _PyErr_GetRaisedException(tstate); - /* XXX Suppress a new exception if it was raised and restore - * the old one. */ - PyObject *value = _PyDict_GetItemWithError(sd, name); - _PyErr_SetRaisedException(tstate, exc); + PyObject *value; + if (PyDict_GetItemRef(sysdict, name, &value) == 0) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%S", name); + } return value; } -static PyObject * -_PySys_GetObject(PyInterpreterState *interp, const char *name) +PyObject * +PySys_GetAttrString(const char *name) { - PyObject *sysdict = interp->sysdict; + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *sysdict = tstate->interp->sysdict; if (sysdict == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no sys module"); return NULL; } PyObject *value; - if (PyDict_GetItemStringRef(sysdict, name, &value) != 1) { - return NULL; + if (PyDict_GetItemStringRef(sysdict, name, &value) == 0) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); } - Py_DECREF(value); // return a borrowed reference return value; } +int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *sysdict = tstate->interp->sysdict; + if (sysdict == NULL) { + *value = NULL; + return 0; + } + return PyDict_GetItemRef(sysdict, name, value); +} + +int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *sysdict = tstate->interp->sysdict; + if (sysdict == NULL) { + *value = NULL; + return 0; + } + return PyDict_GetItemStringRef(sysdict, name, value); +} + PyObject * PySys_GetObject(const char *name) { PyThreadState *tstate = _PyThreadState_GET(); - + PyObject *sysdict = tstate->interp->sysdict; + if (sysdict == NULL) { + return NULL; + } PyObject *exc = _PyErr_GetRaisedException(tstate); - PyObject *value = _PySys_GetObject(tstate->interp, name); + PyObject *value; + (void) PyDict_GetItemStringRef(sysdict, name, &value); /* XXX Suppress a new exception if it was raised and restore * the old one. */ _PyErr_SetRaisedException(tstate, exc); + Py_XDECREF(value); // return a borrowed reference return value; } @@ -121,6 +152,10 @@ sys_set_object(PyInterpreterState *interp, PyObject *key, PyObject *v) return -1; } PyObject *sd = interp->sysdict; + if (sd == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no sys module"); + return -1; + } if (v == NULL) { v = _PyDict_Pop(sd, key, Py_None); if (v == NULL) { @@ -753,9 +788,13 @@ sys_displayhook(PyObject *module, PyObject *o) } if (PyObject_SetAttr(builtins, &_Py_ID(_), Py_None) != 0) return NULL; - outf = _PySys_GetAttr(tstate, &_Py_ID(stdout)); - if (outf == NULL || outf == Py_None) { + outf = PySys_GetAttr(&_Py_ID(stdout)); + if (outf == NULL) { + return NULL; + } + if (outf == Py_None) { _PyErr_SetString(tstate, PyExc_RuntimeError, "lost sys.stdout"); + Py_DECREF(outf); return NULL; } if (PyFile_WriteObject(o, outf, 0) != 0) { @@ -766,18 +805,24 @@ sys_displayhook(PyObject *module, PyObject *o) _PyErr_Clear(tstate); err = sys_displayhook_unencodable(outf, o); if (err) { + Py_DECREF(outf); return NULL; } } else { + Py_DECREF(outf); return NULL; } } _Py_DECLARE_STR(newline, "\n"); - if (PyFile_WriteObject(&_Py_STR(newline), outf, Py_PRINT_RAW) != 0) + if (PyFile_WriteObject(&_Py_STR(newline), outf, Py_PRINT_RAW) != 0) { + Py_DECREF(outf); return NULL; - if (PyObject_SetAttr(builtins, &_Py_ID(_), o) != 0) + } + Py_DECREF(outf); + if (PyObject_SetAttr(builtins, &_Py_ID(_), o) != 0) { return NULL; + } Py_RETURN_NONE; } @@ -2633,7 +2678,10 @@ _PySys_ReadPreinitXOptions(PyConfig *config) static PyObject * get_warnoptions(PyThreadState *tstate) { - PyObject *warnoptions = _PySys_GetAttr(tstate, &_Py_ID(warnoptions)); + PyObject *warnoptions; + if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) { + return NULL; + } if (warnoptions == NULL || !PyList_Check(warnoptions)) { /* PEP432 TODO: we can reach this if warnoptions is NULL in the main * interpreter config. When that happens, we need to properly set @@ -2645,6 +2693,7 @@ get_warnoptions(PyThreadState *tstate) * call optional for embedding applications, thus making this * reachable again. */ + Py_XDECREF(warnoptions); warnoptions = PyList_New(0); if (warnoptions == NULL) { return NULL; @@ -2653,7 +2702,6 @@ get_warnoptions(PyThreadState *tstate) Py_DECREF(warnoptions); return NULL; } - Py_DECREF(warnoptions); } return warnoptions; } @@ -2667,10 +2715,15 @@ PySys_ResetWarnOptions(void) return; } - PyObject *warnoptions = _PySys_GetAttr(tstate, &_Py_ID(warnoptions)); - if (warnoptions == NULL || !PyList_Check(warnoptions)) + PyObject *warnoptions; + if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) { + PyErr_Clear(); return; - PyList_SetSlice(warnoptions, 0, PyList_GET_SIZE(warnoptions), NULL); + } + if (warnoptions != NULL && PyList_Check(warnoptions)) { + PyList_SetSlice(warnoptions, 0, PyList_GET_SIZE(warnoptions), NULL); + } + Py_XDECREF(warnoptions); } static int @@ -2681,8 +2734,10 @@ _PySys_AddWarnOptionWithError(PyThreadState *tstate, PyObject *option) return -1; } if (PyList_Append(warnoptions, option)) { + Py_DECREF(warnoptions); return -1; } + Py_DECREF(warnoptions); return 0; } @@ -2723,16 +2778,24 @@ _Py_COMP_DIAG_POP PyAPI_FUNC(int) PySys_HasWarnOptions(void) { - PyThreadState *tstate = _PyThreadState_GET(); - PyObject *warnoptions = _PySys_GetAttr(tstate, &_Py_ID(warnoptions)); - return (warnoptions != NULL && PyList_Check(warnoptions) - && PyList_GET_SIZE(warnoptions) > 0); + PyObject *warnoptions; + if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) { + PyErr_Clear(); + return 0; + } + int r = (warnoptions != NULL && PyList_Check(warnoptions) && + PyList_GET_SIZE(warnoptions) > 0); + Py_XDECREF(warnoptions); + return r; } static PyObject * get_xoptions(PyThreadState *tstate) { - PyObject *xoptions = _PySys_GetAttr(tstate, &_Py_ID(_xoptions)); + PyObject *xoptions; + if (PySys_GetOptionalAttr(&_Py_ID(_xoptions), &xoptions) < 0) { + return NULL; + } if (xoptions == NULL || !PyDict_Check(xoptions)) { /* PEP432 TODO: we can reach this if xoptions is NULL in the main * interpreter config. When that happens, we need to properly set @@ -2744,6 +2807,7 @@ get_xoptions(PyThreadState *tstate) * call optional for embedding applications, thus making this * reachable again. */ + Py_XDECREF(xoptions); xoptions = PyDict_New(); if (xoptions == NULL) { return NULL; @@ -2752,7 +2816,6 @@ get_xoptions(PyThreadState *tstate) Py_DECREF(xoptions); return NULL; } - Py_DECREF(xoptions); } return xoptions; } @@ -2791,11 +2854,13 @@ _PySys_AddXOptionWithError(const wchar_t *s) } Py_DECREF(name); Py_DECREF(value); + Py_DECREF(opts); return 0; error: Py_XDECREF(name); Py_XDECREF(value); + Py_XDECREF(opts); return -1; } @@ -2818,7 +2883,9 @@ PyObject * PySys_GetXOptions(void) { PyThreadState *tstate = _PyThreadState_GET(); - return get_xoptions(tstate); + PyObject *opts = get_xoptions(tstate); + Py_XDECREF(opts); + return opts; } /* XXX This doc string is too long to be a single string literal in VC++ 5.0. @@ -3543,16 +3610,15 @@ _PySys_UpdateConfig(PyThreadState *tstate) #undef COPY_WSTR // sys.flags - PyObject *flags = _PySys_GetObject(interp, "flags"); // borrowed ref + PyObject *flags = PySys_GetAttrString("flags"); if (flags == NULL) { - if (!_PyErr_Occurred(tstate)) { - _PyErr_SetString(tstate, PyExc_RuntimeError, "lost sys.flags"); - } return -1; } if (set_flags_from_config(interp, flags) < 0) { + Py_DECREF(flags); return -1; } + Py_DECREF(flags); SET_SYS("dont_write_bytecode", PyBool_FromLong(!config->write_bytecode)); @@ -3782,12 +3848,15 @@ PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath) Py_FatalError("can't compute path0 from argv"); } - PyObject *sys_path = _PySys_GetAttr(tstate, &_Py_ID(path)); - if (sys_path != NULL) { + PyObject *sys_path; + if (PySys_GetOptionalAttr(&_Py_ID(path), &sys_path) < 0) { + Py_FatalError("can't get sys.path"); + } + else if (sys_path != NULL) { if (PyList_Insert(sys_path, 0, path0) < 0) { - Py_DECREF(path0); Py_FatalError("can't prepend path0 to sys.path"); } + Py_DECREF(sys_path); } Py_DECREF(path0); } @@ -3876,8 +3945,8 @@ sys_write(PyObject *key, FILE *fp, const char *format, va_list va) PyThreadState *tstate = _PyThreadState_GET(); PyObject *exc = _PyErr_GetRaisedException(tstate); - file = _PySys_GetAttr(tstate, key); written = PyOS_vsnprintf(buffer, sizeof(buffer), format, va); + file = PySys_GetAttr(key); if (sys_pyfile_write(buffer, file) != 0) { _PyErr_Clear(tstate); fputs(buffer, fp); @@ -3887,6 +3956,7 @@ sys_write(PyObject *key, FILE *fp, const char *format, va_list va) if (sys_pyfile_write(truncated, file) != 0) fputs(truncated, fp); } + Py_XDECREF(file); _PyErr_SetRaisedException(tstate, exc); } @@ -3918,15 +3988,16 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va) PyThreadState *tstate = _PyThreadState_GET(); PyObject *exc = _PyErr_GetRaisedException(tstate); - file = _PySys_GetAttr(tstate, key); message = PyUnicode_FromFormatV(format, va); if (message != NULL) { + file = PySys_GetAttr(key); if (sys_pyfile_write_unicode(message, file) != 0) { _PyErr_Clear(tstate); utf8 = PyUnicode_AsUTF8(message); if (utf8 != NULL) fputs(utf8, fp); } + Py_XDECREF(file); Py_DECREF(message); } _PyErr_SetRaisedException(tstate, exc); diff --git a/Python/traceback.c b/Python/traceback.c index 05d841e56ad7bd..f7cc9596792284 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -13,7 +13,6 @@ #include "pycore_pyarena.h" // _PyArena_Free() #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_traceback.h" // EXCEPTION_TB_HEADER #include "../Parser/pegen.h" // _PyPegen_byte_offset_to_character_offset() @@ -329,9 +328,13 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject * taillen = strlen(tail); PyThreadState *tstate = _PyThreadState_GET(); - syspath = _PySys_GetAttr(tstate, &_Py_ID(path)); - if (syspath == NULL || !PyList_Check(syspath)) + if (PySys_GetOptionalAttr(&_Py_ID(path), &syspath) < 0) { + PyErr_Clear(); + goto error; + } + if (syspath == NULL || !PyList_Check(syspath)) { goto error; + } npath = PyList_Size(syspath); open = PyObject_GetAttr(io, &_Py_ID(open)); @@ -374,6 +377,7 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject * result = NULL; finally: Py_XDECREF(open); + Py_XDECREF(syspath); Py_DECREF(filebytes); return result; } @@ -735,17 +739,21 @@ _PyTraceBack_Print(PyObject *v, const char *header, PyObject *f) PyErr_BadInternalCall(); return -1; } - limitv = PySys_GetObject("tracebacklimit"); - if (limitv && PyLong_Check(limitv)) { + if (PySys_GetOptionalAttrString("tracebacklimit", &limitv) < 0) { + return -1; + } + else if (limitv != NULL && PyLong_Check(limitv)) { int overflow; limit = PyLong_AsLongAndOverflow(limitv, &overflow); if (overflow > 0) { limit = LONG_MAX; } else if (limit <= 0) { + Py_DECREF(limitv); return 0; } } + Py_XDECREF(limitv); if (PyFile_WriteString(header, f) < 0) { return -1; From eb42b39b0658ae0e901c6b2af7bbdafeccaf115c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 18 Oct 2023 17:09:26 +0300 Subject: [PATCH 02/13] Update Misc/stable_abi.toml --- Doc/data/stable_abi.dat | 4 ++++ Lib/test/test_stable_abi_ctypes.py | 4 ++++ Misc/stable_abi.toml | 8 ++++++++ PC/python3dll.c | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 811b1bd84d2417..e530ee9008898f 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -610,7 +610,11 @@ function,PySys_Audit,3.13,, function,PySys_AuditTuple,3.13,, function,PySys_FormatStderr,3.2,, function,PySys_FormatStdout,3.2,, +function,PySys_GetAttr,3.13,, +function,PySys_GetAttrString,3.13,, function,PySys_GetObject,3.2,, +function,PySys_GetOptionalAttr,3.13,, +function,PySys_GetOptionalAttrString,3.13,, function,PySys_GetXOptions,3.7,, function,PySys_ResetWarnOptions,3.2,, function,PySys_SetObject,3.2,, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 6d5353c22764df..16e78fd298a6c4 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -630,7 +630,11 @@ def test_windows_feature_macros(self): "PySys_AuditTuple", "PySys_FormatStderr", "PySys_FormatStdout", + "PySys_GetAttr", + "PySys_GetAttrString", "PySys_GetObject", + "PySys_GetOptionalAttr", + "PySys_GetOptionalAttrString", "PySys_GetXOptions", "PySys_HasWarnOptions", "PySys_ResetWarnOptions", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 75c260c8f1b2be..e0c5922a936f7f 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2478,3 +2478,11 @@ added = '3.13' [function.PySys_AuditTuple] added = '3.13' +[function.PySys_GetAttr] + added = '3.13' +[function.PySys_GetAttrString] + added = '3.13' +[function.PySys_GetOptionalAttr] + added = '3.13' +[function.PySys_GetOptionalAttrString] + added = '3.13' diff --git a/PC/python3dll.c b/PC/python3dll.c index d12889f44d65b6..8161a4b922582a 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -572,7 +572,11 @@ EXPORT_FUNC(PySys_Audit) EXPORT_FUNC(PySys_AuditTuple) EXPORT_FUNC(PySys_FormatStderr) EXPORT_FUNC(PySys_FormatStdout) +EXPORT_FUNC(PySys_GetAttr) +EXPORT_FUNC(PySys_GetAttrString) EXPORT_FUNC(PySys_GetObject) +EXPORT_FUNC(PySys_GetOptionalAttr) +EXPORT_FUNC(PySys_GetOptionalAttrString) EXPORT_FUNC(PySys_GetXOptions) EXPORT_FUNC(PySys_HasWarnOptions) EXPORT_FUNC(PySys_ResetWarnOptions) From 2eb95334bd2897379404d6f531b3b14e95786a30 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 10:25:26 +0300 Subject: [PATCH 03/13] Add tests. --- Lib/test/test_capi/test_misc.py | 49 +++++++++++++++++++ Modules/_testcapimodule.c | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index b3f32d860eee03..61ded53828dddc 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1098,6 +1098,55 @@ class Data(_testcapi.ObjExtraData): del d.extra self.assertIsNone(d.extra) + def test_sys_getattr(self): + sys_getattr = _testcapi.sys_getattr + + self.assertIs(sys_getattr('stdout'), sys.stdout) + with support.swap_attr(sys, '\U0001f40d', 42): + self.assertEqual(sys_getattr('\U0001f40d'), 42) + + with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexisting'): + sys_getattr('nonexisting') + with self.assertRaisesRegex(RuntimeError, r'lost sys\.1'): + sys_getattr(1) + self.assertRaises(TypeError, sys_getattr, []) + # CRASHES sys_getattr(NULL) + + def test_sys_getattrstring(self): + sys_getattr = _testcapi.sys_getattrstring + + self.assertIs(sys_getattr(b'stdout'), sys.stdout) + with support.swap_attr(sys, '\U0001f40d', 42): + self.assertEqual(sys_getattr('\U0001f40d'.encode()), 42) + + with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexisting'): + sys_getattr(b'nonexisting') + self.assertRaises(UnicodeDecodeError, sys_getattr, b'\xff') + # CRASHES sys_getattr(NULL) + + def test_sys_getoptionalattr(self): + sys_getattr = _testcapi.sys_getoptionalattr + + self.assertIs(sys_getattr('stdout'), sys.stdout) + with support.swap_attr(sys, '\U0001f40d', 42): + self.assertEqual(sys_getattr('\U0001f40d'), 42) + + self.assertIs(sys_getattr('nonexisting'), AttributeError) + self.assertIs(sys_getattr(1), AttributeError) + self.assertRaises(TypeError, sys_getattr, []) + # CRASHES sys_getattr(NULL) + + def test_sys_getoptionalattrstring(self): + sys_getattr = _testcapi.sys_getoptionalattrstring + + self.assertIs(sys_getattr(b'stdout'), sys.stdout) + with support.swap_attr(sys, '\U0001f40d', 42): + self.assertEqual(sys_getattr('\U0001f40d'.encode()), 42) + + self.assertIs(sys_getattr(b'nonexisting'), AttributeError) + self.assertRaises(UnicodeDecodeError, sys_getattr, b'\xff') + # CRASHES sys_getattr(NULL) + def test_sys_getobject(self): getobject = _testcapi.sys_getobject diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 577fea35d97b7e..d874558ea01160 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3231,6 +3231,86 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +sys_getattr(PyObject *Py_UNUSED(module), PyObject *name) +{ + NULLABLE(name); + PyObject *result = PySys_GetAttr(name); + if (result == NULL && PyErr_Occurred()) { + return NULL; + } + if (result == NULL) { + result = PyExc_AttributeError; + Py_INCREF(PyExc_AttributeError); + } + return result; +} + +static PyObject * +sys_getattrstring(PyObject *Py_UNUSED(module), PyObject *arg) +{ + const char *name; + Py_ssize_t size; + if (!PyArg_Parse(arg, "z#", &name, &size)) { + return NULL; + } + PyObject *result = PySys_GetAttrString(name); + if (result == NULL && PyErr_Occurred()) { + return NULL; + } + if (result == NULL) { + result = PyExc_AttributeError; + Py_INCREF(PyExc_AttributeError); + } + return result; +} + +static PyObject * +sys_getoptionalattr(PyObject *Py_UNUSED(module), PyObject *name) +{ + PyObject *value = UNINITIALIZED_PTR; + NULLABLE(name); + + switch (PySys_GetOptionalAttr(name, &value)) { + case -1: + assert(value == NULL); + return NULL; + case 0: + assert(value == NULL); + return Py_NewRef(PyExc_AttributeError); + case 1: + return value; + default: + Py_FatalError("PySys_GetOptionalAttr() returned invalid code"); + Py_UNREACHABLE(); + } +} + +static PyObject * +sys_getoptionalattrstring(PyObject *Py_UNUSED(module), PyObject *arg) +{ + PyObject *value = UNINITIALIZED_PTR; + const char *name; + Py_ssize_t size; + if (!PyArg_Parse(arg, "z#", &name, &size)) { + return NULL; + } + + switch (PySys_GetOptionalAttrString(name, &value)) { + case -1: + assert(value == NULL); + return NULL; + case 0: + assert(value == NULL); + return Py_NewRef(PyExc_AttributeError); + case 1: + return value; + default: + Py_FatalError("PySys_GetOptionalAttrString() returned invalid code"); + Py_UNREACHABLE(); + } +} + static PyObject * sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg) { @@ -3392,6 +3472,10 @@ static PyMethodDef TestMethods[] = { {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS}, {"test_weakref_capi", test_weakref_capi, METH_NOARGS}, + {"sys_getattr", sys_getattr, METH_O}, + {"sys_getattrstring", sys_getattrstring, METH_O}, + {"sys_getoptionalattr", sys_getoptionalattr, METH_O}, + {"sys_getoptionalattrstring", sys_getoptionalattrstring, METH_O}, {"sys_getobject", sys_getobject, METH_O}, {"sys_setobject", sys_setobject, METH_VARARGS}, {NULL, NULL} /* sentinel */ From 9fc2f3d5fbbe022fcb833fdcaeb2ae25e8561bad Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 11:11:42 +0300 Subject: [PATCH 04/13] Apply suggestions from code review Co-authored-by: Victor Stinner --- Doc/c-api/sys.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index a55b0dd8345297..ae14af9b2244b7 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -224,8 +224,8 @@ accessible to C code. They all work with the current interpreter thread's .. c:function:: PyObject *PySys_GetAttr(PyObject *name) - Return the object *name* from the :mod:`sys` module or ``NULL`` on failure. - Set :exc:`RuntimeError` and return ``NULL`` if it does not exist. + Get the attribute *name* of the :mod:`sys` module. Return a :term:`strong reference`. + Raise :exc:`RuntimeError` and return ``NULL`` if it does not exist. If the non-existing object should not be treated as a failure, you can use :c:func:`PySys_GetOptionalAttr` instead. From 65713ce3c355f0ade2ba31869473413045bb12aa Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 12:00:07 +0300 Subject: [PATCH 05/13] Address review comments. --- Lib/test/test_capi/test_misc.py | 38 ++++++++++++++++----------------- Modules/_testcapimodule.c | 24 ++++----------------- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 61ded53828dddc..d5ef45328987b8 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1113,39 +1113,39 @@ def test_sys_getattr(self): # CRASHES sys_getattr(NULL) def test_sys_getattrstring(self): - sys_getattr = _testcapi.sys_getattrstring + getattrstring = _testcapi.sys_getattrstring - self.assertIs(sys_getattr(b'stdout'), sys.stdout) + self.assertIs(getattrstring(b'stdout'), sys.stdout) with support.swap_attr(sys, '\U0001f40d', 42): - self.assertEqual(sys_getattr('\U0001f40d'.encode()), 42) + self.assertEqual(getattrstring('\U0001f40d'.encode()), 42) with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexisting'): - sys_getattr(b'nonexisting') - self.assertRaises(UnicodeDecodeError, sys_getattr, b'\xff') - # CRASHES sys_getattr(NULL) + getattrstring(b'nonexisting') + self.assertRaises(UnicodeDecodeError, getattrstring, b'\xff') + # CRASHES getattrstring(NULL) def test_sys_getoptionalattr(self): - sys_getattr = _testcapi.sys_getoptionalattr + getoptionalattr = _testcapi.sys_getoptionalattr - self.assertIs(sys_getattr('stdout'), sys.stdout) + self.assertIs(getoptionalattr('stdout'), sys.stdout) with support.swap_attr(sys, '\U0001f40d', 42): - self.assertEqual(sys_getattr('\U0001f40d'), 42) + self.assertEqual(getoptionalattr('\U0001f40d'), 42) - self.assertIs(sys_getattr('nonexisting'), AttributeError) - self.assertIs(sys_getattr(1), AttributeError) - self.assertRaises(TypeError, sys_getattr, []) - # CRASHES sys_getattr(NULL) + self.assertIs(getoptionalattr('nonexisting'), AttributeError) + self.assertIs(getoptionalattr(1), AttributeError) + self.assertRaises(TypeError, getoptionalattr, []) + # CRASHES getoptionalattr(NULL) def test_sys_getoptionalattrstring(self): - sys_getattr = _testcapi.sys_getoptionalattrstring + getoptionalattrstring = _testcapi.sys_getoptionalattrstring - self.assertIs(sys_getattr(b'stdout'), sys.stdout) + self.assertIs(getoptionalattrstring(b'stdout'), sys.stdout) with support.swap_attr(sys, '\U0001f40d', 42): - self.assertEqual(sys_getattr('\U0001f40d'.encode()), 42) + self.assertEqual(getoptionalattrstring('\U0001f40d'.encode()), 42) - self.assertIs(sys_getattr(b'nonexisting'), AttributeError) - self.assertRaises(UnicodeDecodeError, sys_getattr, b'\xff') - # CRASHES sys_getattr(NULL) + self.assertIs(getoptionalattrstring(b'nonexisting'), AttributeError) + self.assertRaises(UnicodeDecodeError, getoptionalattrstring, b'\xff') + # CRASHES getoptionalattrstring(NULL) def test_sys_getobject(self): getobject = _testcapi.sys_getobject diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d874558ea01160..a0923ba1674a7c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3235,15 +3235,7 @@ static PyObject * sys_getattr(PyObject *Py_UNUSED(module), PyObject *name) { NULLABLE(name); - PyObject *result = PySys_GetAttr(name); - if (result == NULL && PyErr_Occurred()) { - return NULL; - } - if (result == NULL) { - result = PyExc_AttributeError; - Py_INCREF(PyExc_AttributeError); - } - return result; + return PySys_GetAttr(name); } static PyObject * @@ -3254,15 +3246,7 @@ sys_getattrstring(PyObject *Py_UNUSED(module), PyObject *arg) if (!PyArg_Parse(arg, "z#", &name, &size)) { return NULL; } - PyObject *result = PySys_GetAttrString(name); - if (result == NULL && PyErr_Occurred()) { - return NULL; - } - if (result == NULL) { - result = PyExc_AttributeError; - Py_INCREF(PyExc_AttributeError); - } - return result; + return PySys_GetAttrString(name); } static PyObject * @@ -3274,6 +3258,7 @@ sys_getoptionalattr(PyObject *Py_UNUSED(module), PyObject *name) switch (PySys_GetOptionalAttr(name, &value)) { case -1: assert(value == NULL); + assert(PyErr_Occurred()); return NULL; case 0: assert(value == NULL); @@ -3282,7 +3267,6 @@ sys_getoptionalattr(PyObject *Py_UNUSED(module), PyObject *name) return value; default: Py_FatalError("PySys_GetOptionalAttr() returned invalid code"); - Py_UNREACHABLE(); } } @@ -3299,6 +3283,7 @@ sys_getoptionalattrstring(PyObject *Py_UNUSED(module), PyObject *arg) switch (PySys_GetOptionalAttrString(name, &value)) { case -1: assert(value == NULL); + assert(PyErr_Occurred()); return NULL; case 0: assert(value == NULL); @@ -3307,7 +3292,6 @@ sys_getoptionalattrstring(PyObject *Py_UNUSED(module), PyObject *arg) return value; default: Py_FatalError("PySys_GetOptionalAttrString() returned invalid code"); - Py_UNREACHABLE(); } } From e6ecf1170a9286a7448562e5649d991e06c90193 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 12:05:50 +0300 Subject: [PATCH 06/13] Check that the name is a string. --- Lib/test/test_capi/test_misc.py | 5 ++--- Python/sysmodule.c | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index d5ef45328987b8..5016db94b3eea6 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1107,8 +1107,7 @@ def test_sys_getattr(self): with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexisting'): sys_getattr('nonexisting') - with self.assertRaisesRegex(RuntimeError, r'lost sys\.1'): - sys_getattr(1) + self.assertRaises(TypeError, sys_getattr, 1) self.assertRaises(TypeError, sys_getattr, []) # CRASHES sys_getattr(NULL) @@ -1132,7 +1131,7 @@ def test_sys_getoptionalattr(self): self.assertEqual(getoptionalattr('\U0001f40d'), 42) self.assertIs(getoptionalattr('nonexisting'), AttributeError) - self.assertIs(getoptionalattr(1), AttributeError) + self.assertRaises(TypeError, getoptionalattr, 1) self.assertRaises(TypeError, getoptionalattr, []) # CRASHES getoptionalattr(NULL) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 284dcca26e9f67..c6109843b31baf 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -74,6 +74,12 @@ module sys PyObject * PySys_GetAttr(PyObject *name) { + if (!PyUnicode_Check(name)) { + PyErr_Format(PyExc_TypeError, + "attribute name must be string, not '%.200s'", + Py_TYPE(name)->tp_name); + return NULL; + } PyThreadState *tstate = _PyThreadState_GET(); PyObject *sysdict = tstate->interp->sysdict; if (sysdict == NULL) { @@ -106,6 +112,13 @@ PySys_GetAttrString(const char *name) int PySys_GetOptionalAttr(PyObject *name, PyObject **value) { + if (!PyUnicode_Check(name)) { + PyErr_Format(PyExc_TypeError, + "attribute name must be string, not '%.200s'", + Py_TYPE(name)->tp_name); + *value = NULL; + return -1; + } PyThreadState *tstate = _PyThreadState_GET(); PyObject *sysdict = tstate->interp->sysdict; if (sysdict == NULL) { From 516829e8e5059bc2d6cb5dfecd6b5bc608910c75 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 12:13:13 +0300 Subject: [PATCH 07/13] Make the new C API not public. --- Doc/whatsnew/3.13.rst | 6 ------ Include/cpython/sysmodule.h | 5 +++++ Include/sysmodule.h | 6 ------ .../C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c610b4f81a80ec..358bd1747cd2c5 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -993,12 +993,6 @@ New Features (Contributed by Serhiy Storchaka in :gh:`108511`.) -* Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, - :c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`. - They all are variants of :c:func:`PySys_GetObject` which return a new - :term:`strong reference` and can set an exception on failure. - (Contributed by Serhiy Storchaka in :gh:`108512`.) - * If Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and :c:func:`PyList_SET_ITEM` now check the index argument with an assertion. diff --git a/Include/cpython/sysmodule.h b/Include/cpython/sysmodule.h index df12ae440f024b..eb4b907fbcf689 100644 --- a/Include/cpython/sysmodule.h +++ b/Include/cpython/sysmodule.h @@ -10,6 +10,11 @@ PyAPI_FUNC(int) PySys_Audit( ...); PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*); +PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *); +PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *); +PyAPI_FUNC(int) PySys_GetOptionalAttr(PyObject *, PyObject **); +PyAPI_FUNC(int) PySys_GetOptionalAttrString(const char *, PyObject **); + typedef struct { FILE* perf_map; PyThread_type_lock map_lock; diff --git a/Include/sysmodule.h b/Include/sysmodule.h index b4557596aadd40..7406513ec1439a 100644 --- a/Include/sysmodule.h +++ b/Include/sysmodule.h @@ -8,12 +8,6 @@ extern "C" { #endif PyAPI_FUNC(PyObject *) PySys_GetObject(const char *); -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000 -PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *); -PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *); -PyAPI_FUNC(int) PySys_GetOptionalAttr(PyObject *, PyObject **); -PyAPI_FUNC(int) PySys_GetOptionalAttrString(const char *, PyObject **); -#endif PyAPI_FUNC(int) PySys_SetObject(const char *, PyObject *); PyAPI_FUNC(void) PySys_WriteStdout(const char *format, ...) diff --git a/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst b/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst index 279e588f3adcb7..89dd8ff31d8cec 100644 --- a/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst +++ b/Misc/NEWS.d/next/C API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst @@ -1,2 +1,2 @@ -Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, +Add private functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, :c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`. From 9503aaf4b1e7ef38946d59af9d4cad874dabef33 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Oct 2023 15:57:08 +0300 Subject: [PATCH 08/13] Remove from Misc/stable_abi.toml. --- Doc/data/stable_abi.dat | 4 ---- Lib/test/test_stable_abi_ctypes.py | 4 ---- Misc/stable_abi.toml | 8 -------- PC/python3dll.c | 4 ---- 4 files changed, 20 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index e530ee9008898f..811b1bd84d2417 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -610,11 +610,7 @@ function,PySys_Audit,3.13,, function,PySys_AuditTuple,3.13,, function,PySys_FormatStderr,3.2,, function,PySys_FormatStdout,3.2,, -function,PySys_GetAttr,3.13,, -function,PySys_GetAttrString,3.13,, function,PySys_GetObject,3.2,, -function,PySys_GetOptionalAttr,3.13,, -function,PySys_GetOptionalAttrString,3.13,, function,PySys_GetXOptions,3.7,, function,PySys_ResetWarnOptions,3.2,, function,PySys_SetObject,3.2,, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 16e78fd298a6c4..6d5353c22764df 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -630,11 +630,7 @@ def test_windows_feature_macros(self): "PySys_AuditTuple", "PySys_FormatStderr", "PySys_FormatStdout", - "PySys_GetAttr", - "PySys_GetAttrString", "PySys_GetObject", - "PySys_GetOptionalAttr", - "PySys_GetOptionalAttrString", "PySys_GetXOptions", "PySys_HasWarnOptions", "PySys_ResetWarnOptions", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index e0c5922a936f7f..75c260c8f1b2be 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2478,11 +2478,3 @@ added = '3.13' [function.PySys_AuditTuple] added = '3.13' -[function.PySys_GetAttr] - added = '3.13' -[function.PySys_GetAttrString] - added = '3.13' -[function.PySys_GetOptionalAttr] - added = '3.13' -[function.PySys_GetOptionalAttrString] - added = '3.13' diff --git a/PC/python3dll.c b/PC/python3dll.c index 8161a4b922582a..d12889f44d65b6 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -572,11 +572,7 @@ EXPORT_FUNC(PySys_Audit) EXPORT_FUNC(PySys_AuditTuple) EXPORT_FUNC(PySys_FormatStderr) EXPORT_FUNC(PySys_FormatStdout) -EXPORT_FUNC(PySys_GetAttr) -EXPORT_FUNC(PySys_GetAttrString) EXPORT_FUNC(PySys_GetObject) -EXPORT_FUNC(PySys_GetOptionalAttr) -EXPORT_FUNC(PySys_GetOptionalAttrString) EXPORT_FUNC(PySys_GetXOptions) EXPORT_FUNC(PySys_HasWarnOptions) EXPORT_FUNC(PySys_ResetWarnOptions) From dc26ec28981ad14dbf6dd1cff95311690a45c0a4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jan 2025 12:04:06 +0200 Subject: [PATCH 09/13] Add to the limited C API. --- Doc/data/stable_abi.dat | 4 ++++ Lib/test/test_stable_abi_ctypes.py | 4 ++++ Misc/stable_abi.toml | 8 ++++++++ PC/python3dll.c | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index c15f82603aa944..0de0e9a2b67dfb 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -626,7 +626,11 @@ func,PySys_Audit,3.13,, func,PySys_AuditTuple,3.13,, func,PySys_FormatStderr,3.2,, func,PySys_FormatStdout,3.2,, +func,PySys_GetAttr,3.14,, +func,PySys_GetAttrString,3.14,, func,PySys_GetObject,3.2,, +func,PySys_GetOptionalAttr,3.14,, +func,PySys_GetOptionalAttrString,3.14,, func,PySys_GetXOptions,3.7,, func,PySys_ResetWarnOptions,3.2,, func,PySys_SetArgv,3.2,, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index f3724ce6d4d15a..cc1fdceaff1386 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -655,7 +655,11 @@ def test_windows_feature_macros(self): "PySys_AuditTuple", "PySys_FormatStderr", "PySys_FormatStdout", + "PySys_GetAttr", + "PySys_GetAttrString", "PySys_GetObject", + "PySys_GetOptionalAttr", + "PySys_GetOptionalAttrString", "PySys_GetXOptions", "PySys_HasWarnOptions", "PySys_ResetWarnOptions", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 276526a1b6908e..bfc02b2239b84d 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2544,3 +2544,11 @@ added = '3.14' [function.Py_PACK_VERSION] added = '3.14' +[function.PySys_GetAttr] + added = '3.14' +[function.PySys_GetAttrString] + added = '3.14' +[function.PySys_GetOptionalAttr] + added = '3.14' +[function.PySys_GetOptionalAttrString] + added = '3.14' diff --git a/PC/python3dll.c b/PC/python3dll.c index 84b3c735240b73..4744a0cefe752c 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -592,7 +592,11 @@ EXPORT_FUNC(PySys_Audit) EXPORT_FUNC(PySys_AuditTuple) EXPORT_FUNC(PySys_FormatStderr) EXPORT_FUNC(PySys_FormatStdout) +EXPORT_FUNC(PySys_GetAttr) +EXPORT_FUNC(PySys_GetAttrString) EXPORT_FUNC(PySys_GetObject) +EXPORT_FUNC(PySys_GetOptionalAttr) +EXPORT_FUNC(PySys_GetOptionalAttrString) EXPORT_FUNC(PySys_GetXOptions) EXPORT_FUNC(PySys_HasWarnOptions) EXPORT_FUNC(PySys_ResetWarnOptions) From 104dcc215631763331ff4c005d8469afebe08eb0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jan 2025 12:19:39 +0200 Subject: [PATCH 10/13] Replace few new occurrences of PySys_GetObject(). --- Objects/moduleobject.c | 7 ++++++- Python/ceval.c | 7 ++++++- Python/initconfig.c | 16 ++-------------- Python/sysmodule.c | 5 +---- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index a8d64c9aefae6b..c06f18d2c2ad32 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1001,13 +1001,18 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) } int is_possibly_shadowing_stdlib = 0; if (is_possibly_shadowing) { - PyObject *stdlib_modules = PySys_GetObject("stdlib_module_names"); + PyObject *stdlib_modules; + if (PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) { + goto done; + } if (stdlib_modules && PyAnySet_Check(stdlib_modules)) { is_possibly_shadowing_stdlib = PySet_Contains(stdlib_modules, mod_name); if (is_possibly_shadowing_stdlib < 0) { + Py_DECREF(stdlib_modules); goto done; } } + Py_XDECREF(stdlib_modules); } if (is_possibly_shadowing_stdlib) { diff --git a/Python/ceval.c b/Python/ceval.c index 10c20faf852479..90fe994535dfb7 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2765,13 +2765,18 @@ _PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name) } int is_possibly_shadowing_stdlib = 0; if (is_possibly_shadowing) { - PyObject *stdlib_modules = PySys_GetObject("stdlib_module_names"); + PyObject *stdlib_modules; + if (PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) { + goto done; + } if (stdlib_modules && PyAnySet_Check(stdlib_modules)) { is_possibly_shadowing_stdlib = PySet_Contains(stdlib_modules, mod_name_or_unknown); if (is_possibly_shadowing_stdlib < 0) { + Py_DECREF(stdlib_modules); goto done; } } + Py_XDECREF(stdlib_modules); } if (origin == NULL && PyModule_Check(v)) { diff --git a/Python/initconfig.c b/Python/initconfig.c index 3a5b112d334c3f..12057808873c9d 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -4091,22 +4091,10 @@ _PyConfig_CreateXOptionsDict(const PyConfig *config) } -static PyObject* -config_get_sys(const char *name) -{ - PyObject *value = PySys_GetObject(name); - if (value == NULL) { - PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); - return NULL; - } - return Py_NewRef(value); -} - - static int config_get_sys_write_bytecode(const PyConfig *config, int *value) { - PyObject *attr = config_get_sys("dont_write_bytecode"); + PyObject *attr = PySys_GetAttrString("dont_write_bytecode"); if (attr == NULL) { return -1; } @@ -4127,7 +4115,7 @@ config_get(const PyConfig *config, const PyConfigSpec *spec, { if (use_sys) { if (spec->sys.attr != NULL) { - return config_get_sys(spec->sys.attr); + return PySys_GetAttrString(spec->sys.attr); } if (strcmp(spec->name, "write_bytecode") == 0) { diff --git a/Python/sysmodule.c b/Python/sysmodule.c index c2f6dcba1a3278..e6f49e643af77d 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3230,11 +3230,8 @@ sys_set_flag(PyObject *flags, Py_ssize_t pos, PyObject *value) int _PySys_SetFlagObj(Py_ssize_t pos, PyObject *value) { - PyObject *flags = Py_XNewRef(PySys_GetObject("flags")); + PyObject *flags = PySys_GetAttrString("flags"); if (flags == NULL) { - if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.flags"); - } return -1; } From cf75fc306577e64196001a80a73f5613889d4bb6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jan 2025 12:31:56 +0200 Subject: [PATCH 11/13] Update the documentation. --- Doc/c-api/init_config.rst | 2 +- Doc/c-api/sys.rst | 8 ++++---- Doc/whatsnew/3.14.rst | 5 +++++ .../C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 0d8c6c5a815d6e..f792007fd46d13 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -2098,7 +2098,7 @@ initialization:: /* Specify sys.path explicitly */ /* If you want to modify the default set of paths, finish - initialization first and then use PySys_GetAttr("path") */ + initialization first and then use PySys_GetAttrString("path") */ config.module_search_paths_set = 1; status = PyWideStringList_Append(&config.module_search_paths, L"/path/to/stdlib"); diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index 1f03e0501b46c2..12b77c90cd571d 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -266,7 +266,7 @@ accessible to C code. They all work with the current interpreter thread's If the non-existing object should not be treated as a failure, you can use :c:func:`PySys_GetOptionalAttr` instead. - .. versionadded:: 3.13 + .. versionadded:: next .. c:function:: PyObject *PySys_GetAttrString(const char *name) @@ -277,7 +277,7 @@ accessible to C code. They all work with the current interpreter thread's If the non-existing object should not be treated as a failure, you can use :c:func:`PySys_GetOptionalAttrString` instead. - .. versionadded:: 3.13 + .. versionadded:: next .. c:function:: int PySys_GetOptionalAttr(PyObject *name, PyObject **result); @@ -291,7 +291,7 @@ accessible to C code. They all work with the current interpreter thread's If other error occurred, set an exception, set *\*result* to ``NULL`` and return ``-1``. - .. versionadded:: 3.13 + .. versionadded:: next .. c:function:: int PySys_GetOptionalAttrString(const char *name, PyObject **result); @@ -299,7 +299,7 @@ accessible to C code. They all work with the current interpreter thread's specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. - .. versionadded:: 3.13 + .. versionadded:: next .. c:function:: PyObject *PySys_GetObject(const char *name) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2c10d7fefd44ab..7f1fe579754a34 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1289,6 +1289,11 @@ New features (Contributed by Sergey B Kirpichev and Victor Stinner in :gh:`102471`.) +* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, + :c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString` + functions as replacements for :c:func:`Pysys_GetObject`. + (Contributed by Serhiy Storchaka in :gh:`108512`.) + * Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier superclass identification, which attempts to resolve the `type checking issue `__ mentioned in :pep:`630` diff --git a/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst b/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst index 89dd8ff31d8cec..279e588f3adcb7 100644 --- a/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst +++ b/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst @@ -1,2 +1,2 @@ -Add private functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, +Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, :c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`. From 943465930663b90a2efbe37f96de4d7355d594c1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jan 2025 12:47:16 +0200 Subject: [PATCH 12/13] Clean up. --- Python/errors.c | 1 - Python/pylifecycle.c | 2 +- Python/pythonrun.c | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Python/errors.c b/Python/errors.c index 176353ff5dd426..11a74f6f25cfcf 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -8,7 +8,6 @@ #include "pycore_pyerrors.h" // _PyErr_Format() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_structseq.h" // _PyStructSequence_FiniBuiltin() -#include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_traceback.h" // _PyTraceBack_FromFrame() #ifdef MS_WINDOWS diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 9f8848331c7016..4cfc52acefefe0 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -27,7 +27,7 @@ #include "pycore_runtime_init.h" // _PyRuntimeState_INIT #include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_sliceobject.h" // _PySlice_Fini() -#include "pycore_sysmodule.h" // _PySys_GetAttr() +#include "pycore_sysmodule.h" // _PySys_ClearAttrString() #include "pycore_traceback.h" // _Py_DumpTracebackThreads() #include "pycore_uniqueid.h" // _PyObject_FinalizeUniqueIdPool() #include "pycore_typeobject.h" // _PyTypes_InitTypes() diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 0606db2935abc8..a08edbd8ab1751 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -23,7 +23,7 @@ #include "pycore_pylifecycle.h" // _Py_FdIsInteractive() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_pythonrun.h" // export _PyRun_InteractiveLoopObject() -#include "pycore_sysmodule.h" // _PySys_GetAttr() +#include "pycore_sysmodule.h" // _PySys_SetAttr() #include "pycore_traceback.h" // _PyTraceBack_Print() #include "errcode.h" // E_EOF From 56639d87e9bcf5b2c981b56ea0852584e34d830f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jan 2025 13:29:12 +0200 Subject: [PATCH 13/13] Fix a typo. --- Doc/whatsnew/3.14.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 7f1fe579754a34..d1df869a5c813b 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1291,7 +1291,7 @@ New features * Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, :c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString` - functions as replacements for :c:func:`Pysys_GetObject`. + functions as replacements for :c:func:`PySys_GetObject`. (Contributed by Serhiy Storchaka in :gh:`108512`.) * Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier