diff options
author | nkozlovskiy <nmk@ydb.tech> | 2023-09-29 12:24:06 +0300 |
---|---|---|
committer | nkozlovskiy <nmk@ydb.tech> | 2023-09-29 12:41:34 +0300 |
commit | e0e3e1717e3d33762ce61950504f9637a6e669ed (patch) | |
tree | bca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/tools/python3/src/Python/_warnings.c | |
parent | 38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff) | |
download | ydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz |
add ydb deps
Diffstat (limited to 'contrib/tools/python3/src/Python/_warnings.c')
-rw-r--r-- | contrib/tools/python3/src/Python/_warnings.c | 1419 |
1 files changed, 1419 insertions, 0 deletions
diff --git a/contrib/tools/python3/src/Python/_warnings.c b/contrib/tools/python3/src/Python/_warnings.c new file mode 100644 index 0000000000..2815f7987a --- /dev/null +++ b/contrib/tools/python3/src/Python/_warnings.c @@ -0,0 +1,1419 @@ +#include "Python.h" +#include "pycore_initconfig.h" +#include "pycore_interp.h" // PyInterpreterState.warnings +#include "pycore_long.h" // _PyLong_GetZero() +#include "pycore_pyerrors.h" +#include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_frame.h" +#include "clinic/_warnings.c.h" + +#define MODULE_NAME "_warnings" + +PyDoc_STRVAR(warnings__doc__, +MODULE_NAME " provides basic warning filtering support.\n" +"It is a helper module to speed up interpreter start-up."); + + +/*************************************************************************/ + +typedef struct _warnings_runtime_state WarningsState; + +static inline int +check_interp(PyInterpreterState *interp) +{ + if (interp == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "warnings_get_state: could not identify " + "current interpreter"); + return 0; + } + return 1; +} + +static inline PyInterpreterState * +get_current_interp(void) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + return check_interp(interp) ? interp : NULL; +} + +static inline PyThreadState * +get_current_tstate(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + if (tstate == NULL) { + (void)check_interp(NULL); + return NULL; + } + return check_interp(tstate->interp) ? tstate : NULL; +} + +/* Given a module object, get its per-module state. */ +static WarningsState * +warnings_get_state(PyInterpreterState *interp) +{ + return &interp->warnings; +} + +/* Clear the given warnings module state. */ +static void +warnings_clear_state(WarningsState *st) +{ + Py_CLEAR(st->filters); + Py_CLEAR(st->once_registry); + Py_CLEAR(st->default_action); +} + +#ifndef Py_DEBUG +static PyObject * +create_filter(PyObject *category, PyObject *action_str, const char *modname) +{ + PyObject *modname_obj = NULL; + + /* Default to "no module name" for initial filter set */ + if (modname != NULL) { + modname_obj = PyUnicode_InternFromString(modname); + if (modname_obj == NULL) { + return NULL; + } + } else { + modname_obj = Py_NewRef(Py_None); + } + + /* This assumes the line number is zero for now. */ + PyObject *filter = PyTuple_Pack(5, action_str, Py_None, + category, modname_obj, _PyLong_GetZero()); + Py_DECREF(modname_obj); + return filter; +} +#endif + +static PyObject * +init_filters(PyInterpreterState *interp) +{ +#ifdef Py_DEBUG + /* Py_DEBUG builds show all warnings by default */ + return PyList_New(0); +#else + /* Other builds ignore a number of warning categories by default */ + PyObject *filters = PyList_New(5); + if (filters == NULL) { + return NULL; + } + + size_t pos = 0; /* Post-incremented in each use. */ +#define ADD(TYPE, ACTION, MODNAME) \ + PyList_SET_ITEM(filters, pos++, \ + create_filter(TYPE, &_Py_ID(ACTION), MODNAME)); + ADD(PyExc_DeprecationWarning, default, "__main__"); + ADD(PyExc_DeprecationWarning, ignore, NULL); + ADD(PyExc_PendingDeprecationWarning, ignore, NULL); + ADD(PyExc_ImportWarning, ignore, NULL); + ADD(PyExc_ResourceWarning, ignore, NULL); +#undef ADD + + for (size_t x = 0; x < pos; x++) { + if (PyList_GET_ITEM(filters, x) == NULL) { + Py_DECREF(filters); + return NULL; + } + } + return filters; +#endif +} + +/* Initialize the given warnings module state. */ +int +_PyWarnings_InitState(PyInterpreterState *interp) +{ + WarningsState *st = &interp->warnings; + + if (st->filters == NULL) { + st->filters = init_filters(interp); + if (st->filters == NULL) { + return -1; + } + } + + if (st->once_registry == NULL) { + st->once_registry = PyDict_New(); + if (st->once_registry == NULL) { + return -1; + } + } + + if (st->default_action == NULL) { + st->default_action = PyUnicode_FromString("default"); + if (st->default_action == NULL) { + return -1; + } + } + + st->filters_version = 0; + return 0; +} + + +/*************************************************************************/ + +static int +check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg) +{ + PyObject *result; + int rc; + + /* A 'None' filter always matches */ + if (obj == Py_None) + return 1; + + /* An internal plain text default filter must match exactly */ + if (PyUnicode_CheckExact(obj)) { + int cmp_result = PyUnicode_Compare(obj, arg); + if (cmp_result == -1 && PyErr_Occurred()) { + return -1; + } + return !cmp_result; + } + + /* Otherwise assume a regex filter and call its match() method */ + result = PyObject_CallMethodOneArg(obj, &_Py_ID(match), arg); + if (result == NULL) + return -1; + + rc = PyObject_IsTrue(result); + Py_DECREF(result); + return rc; +} + +#define GET_WARNINGS_ATTR(interp, ATTR, try_import) \ + get_warnings_attr(interp, &_Py_ID(ATTR), try_import) + +/* + Returns a new reference. + A NULL return value can mean false or an error. +*/ +static PyObject * +get_warnings_attr(PyInterpreterState *interp, PyObject *attr, int try_import) +{ + PyObject *warnings_module, *obj; + + /* don't try to import after the start of the Python finallization */ + if (try_import && !_Py_IsFinalizing()) { + warnings_module = PyImport_Import(&_Py_ID(warnings)); + if (warnings_module == NULL) { + /* Fallback to the C implementation if we cannot get + the Python implementation */ + if (PyErr_ExceptionMatches(PyExc_ImportError)) { + PyErr_Clear(); + } + return NULL; + } + } + else { + /* if we're so late into Python finalization that the module dict is + gone, then we can't even use PyImport_GetModule without triggering + an interpreter abort. + */ + if (!interp->modules) { + return NULL; + } + warnings_module = PyImport_GetModule(&_Py_ID(warnings)); + if (warnings_module == NULL) + return NULL; + } + + (void)_PyObject_LookupAttr(warnings_module, attr, &obj); + Py_DECREF(warnings_module); + return obj; +} + + +static PyObject * +get_once_registry(PyInterpreterState *interp) +{ + PyObject *registry; + + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return NULL; + } + + registry = GET_WARNINGS_ATTR(interp, onceregistry, 0); + if (registry == NULL) { + if (PyErr_Occurred()) + return NULL; + assert(st->once_registry); + return st->once_registry; + } + if (!PyDict_Check(registry)) { + PyErr_Format(PyExc_TypeError, + MODULE_NAME ".onceregistry must be a dict, " + "not '%.200s'", + Py_TYPE(registry)->tp_name); + Py_DECREF(registry); + return NULL; + } + Py_SETREF(st->once_registry, registry); + return registry; +} + + +static PyObject * +get_default_action(PyInterpreterState *interp) +{ + PyObject *default_action; + + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return NULL; + } + + default_action = GET_WARNINGS_ATTR(interp, defaultaction, 0); + if (default_action == NULL) { + if (PyErr_Occurred()) { + return NULL; + } + assert(st->default_action); + return st->default_action; + } + if (!PyUnicode_Check(default_action)) { + PyErr_Format(PyExc_TypeError, + MODULE_NAME ".defaultaction must be a string, " + "not '%.200s'", + Py_TYPE(default_action)->tp_name); + Py_DECREF(default_action); + return NULL; + } + Py_SETREF(st->default_action, default_action); + return default_action; +} + + +/* The item is a new reference. */ +static PyObject* +get_filter(PyInterpreterState *interp, PyObject *category, + PyObject *text, Py_ssize_t lineno, + PyObject *module, PyObject **item) +{ + PyObject *action; + Py_ssize_t i; + PyObject *warnings_filters; + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return NULL; + } + + warnings_filters = GET_WARNINGS_ATTR(interp, filters, 0); + if (warnings_filters == NULL) { + if (PyErr_Occurred()) + return NULL; + } + else { + Py_SETREF(st->filters, warnings_filters); + } + + PyObject *filters = st->filters; + if (filters == NULL || !PyList_Check(filters)) { + PyErr_SetString(PyExc_ValueError, + MODULE_NAME ".filters must be a list"); + return NULL; + } + + /* WarningsState.filters could change while we are iterating over it. */ + for (i = 0; i < PyList_GET_SIZE(filters); i++) { + PyObject *tmp_item, *action, *msg, *cat, *mod, *ln_obj; + Py_ssize_t ln; + int is_subclass, good_msg, good_mod; + + tmp_item = PyList_GET_ITEM(filters, i); + if (!PyTuple_Check(tmp_item) || PyTuple_GET_SIZE(tmp_item) != 5) { + PyErr_Format(PyExc_ValueError, + MODULE_NAME ".filters item %zd isn't a 5-tuple", i); + return NULL; + } + + /* Python code: action, msg, cat, mod, ln = item */ + Py_INCREF(tmp_item); + action = PyTuple_GET_ITEM(tmp_item, 0); + msg = PyTuple_GET_ITEM(tmp_item, 1); + cat = PyTuple_GET_ITEM(tmp_item, 2); + mod = PyTuple_GET_ITEM(tmp_item, 3); + ln_obj = PyTuple_GET_ITEM(tmp_item, 4); + + if (!PyUnicode_Check(action)) { + PyErr_Format(PyExc_TypeError, + "action must be a string, not '%.200s'", + Py_TYPE(action)->tp_name); + Py_DECREF(tmp_item); + return NULL; + } + + good_msg = check_matched(interp, msg, text); + if (good_msg == -1) { + Py_DECREF(tmp_item); + return NULL; + } + + good_mod = check_matched(interp, mod, module); + if (good_mod == -1) { + Py_DECREF(tmp_item); + return NULL; + } + + is_subclass = PyObject_IsSubclass(category, cat); + if (is_subclass == -1) { + Py_DECREF(tmp_item); + return NULL; + } + + ln = PyLong_AsSsize_t(ln_obj); + if (ln == -1 && PyErr_Occurred()) { + Py_DECREF(tmp_item); + return NULL; + } + + if (good_msg && is_subclass && good_mod && (ln == 0 || lineno == ln)) { + *item = tmp_item; + return action; + } + + Py_DECREF(tmp_item); + } + + action = get_default_action(interp); + if (action != NULL) { + Py_INCREF(Py_None); + *item = Py_None; + return action; + } + + return NULL; +} + + +static int +already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key, + int should_set) +{ + PyObject *version_obj, *already_warned; + + if (key == NULL) + return -1; + + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return -1; + } + version_obj = _PyDict_GetItemWithError(registry, &_Py_ID(version)); + if (version_obj == NULL + || !PyLong_CheckExact(version_obj) + || PyLong_AsLong(version_obj) != st->filters_version) + { + if (PyErr_Occurred()) { + return -1; + } + PyDict_Clear(registry); + version_obj = PyLong_FromLong(st->filters_version); + if (version_obj == NULL) + return -1; + if (PyDict_SetItem(registry, &_Py_ID(version), version_obj) < 0) { + Py_DECREF(version_obj); + return -1; + } + Py_DECREF(version_obj); + } + else { + already_warned = PyDict_GetItemWithError(registry, key); + if (already_warned != NULL) { + int rc = PyObject_IsTrue(already_warned); + if (rc != 0) + return rc; + } + else if (PyErr_Occurred()) { + return -1; + } + } + + /* This warning wasn't found in the registry, set it. */ + if (should_set) + return PyDict_SetItem(registry, key, Py_True); + return 0; +} + +/* New reference. */ +static PyObject * +normalize_module(PyObject *filename) +{ + PyObject *module; + int kind; + const void *data; + Py_ssize_t len; + + len = PyUnicode_GetLength(filename); + if (len < 0) + return NULL; + + if (len == 0) + return PyUnicode_FromString("<unknown>"); + + kind = PyUnicode_KIND(filename); + data = PyUnicode_DATA(filename); + + /* if filename.endswith(".py"): */ + if (len >= 3 && + PyUnicode_READ(kind, data, len-3) == '.' && + PyUnicode_READ(kind, data, len-2) == 'p' && + PyUnicode_READ(kind, data, len-1) == 'y') + { + module = PyUnicode_Substring(filename, 0, len-3); + } + else { + module = filename; + Py_INCREF(module); + } + return module; +} + +static int +update_registry(PyInterpreterState *interp, PyObject *registry, PyObject *text, + PyObject *category, int add_zero) +{ + PyObject *altkey; + int rc; + + if (add_zero) + altkey = PyTuple_Pack(3, text, category, _PyLong_GetZero()); + else + altkey = PyTuple_Pack(2, text, category); + + rc = already_warned(interp, registry, altkey, 1); + Py_XDECREF(altkey); + return rc; +} + +static void +show_warning(PyThreadState *tstate, PyObject *filename, int lineno, + PyObject *text, PyObject *category, PyObject *sourceline) +{ + PyObject *f_stderr; + PyObject *name; + char lineno_str[128]; + + PyOS_snprintf(lineno_str, sizeof(lineno_str), ":%d: ", lineno); + + name = PyObject_GetAttr(category, &_Py_ID(__name__)); + if (name == NULL) { + goto error; + } + + f_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr)); + if (f_stderr == NULL) { + fprintf(stderr, "lost sys.stderr\n"); + goto error; + } + + /* Print "filename:lineno: category: text\n" */ + if (PyFile_WriteObject(filename, f_stderr, Py_PRINT_RAW) < 0) + goto error; + if (PyFile_WriteString(lineno_str, f_stderr) < 0) + goto error; + if (PyFile_WriteObject(name, f_stderr, Py_PRINT_RAW) < 0) + goto error; + if (PyFile_WriteString(": ", f_stderr) < 0) + goto error; + if (PyFile_WriteObject(text, f_stderr, Py_PRINT_RAW) < 0) + goto error; + if (PyFile_WriteString("\n", f_stderr) < 0) + goto error; + Py_CLEAR(name); + + /* Print " source_line\n" */ + if (sourceline) { + int kind; + const void *data; + Py_ssize_t i, len; + Py_UCS4 ch; + PyObject *truncated; + + if (PyUnicode_READY(sourceline) < 1) + goto error; + + kind = PyUnicode_KIND(sourceline); + data = PyUnicode_DATA(sourceline); + len = PyUnicode_GET_LENGTH(sourceline); + for (i=0; i<len; i++) { + ch = PyUnicode_READ(kind, data, i); + if (ch != ' ' && ch != '\t' && ch != '\014') + break; + } + + truncated = PyUnicode_Substring(sourceline, i, len); + if (truncated == NULL) + goto error; + + PyFile_WriteObject(sourceline, f_stderr, Py_PRINT_RAW); + Py_DECREF(truncated); + PyFile_WriteString("\n", f_stderr); + } + else { + _Py_DisplaySourceLine(f_stderr, filename, lineno, 2, NULL, NULL); + } + +error: + Py_XDECREF(name); + PyErr_Clear(); +} + +static int +call_show_warning(PyThreadState *tstate, PyObject *category, + PyObject *text, PyObject *message, + PyObject *filename, int lineno, PyObject *lineno_obj, + PyObject *sourceline, PyObject *source) +{ + PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL; + PyInterpreterState *interp = tstate->interp; + + /* If the source parameter is set, try to get the Python implementation. + The Python implementation is able to log the traceback where the source + was allocated, whereas the C implementation doesn't. */ + show_fn = GET_WARNINGS_ATTR(interp, _showwarnmsg, source != NULL); + if (show_fn == NULL) { + if (PyErr_Occurred()) + return -1; + show_warning(tstate, filename, lineno, text, category, sourceline); + return 0; + } + + if (!PyCallable_Check(show_fn)) { + PyErr_SetString(PyExc_TypeError, + "warnings._showwarnmsg() must be set to a callable"); + goto error; + } + + warnmsg_cls = GET_WARNINGS_ATTR(interp, WarningMessage, 0); + if (warnmsg_cls == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, + "unable to get warnings.WarningMessage"); + } + goto error; + } + + msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, + filename, lineno_obj, Py_None, Py_None, source, + NULL); + Py_DECREF(warnmsg_cls); + if (msg == NULL) + goto error; + + res = PyObject_CallOneArg(show_fn, msg); + Py_DECREF(show_fn); + Py_DECREF(msg); + + if (res == NULL) + return -1; + + Py_DECREF(res); + return 0; + +error: + Py_XDECREF(show_fn); + return -1; +} + +static PyObject * +warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message, + PyObject *filename, int lineno, + PyObject *module, PyObject *registry, PyObject *sourceline, + PyObject *source) +{ + PyObject *key = NULL, *text = NULL, *result = NULL, *lineno_obj = NULL; + PyObject *item = NULL; + PyObject *action; + int rc; + PyInterpreterState *interp = tstate->interp; + + /* module can be None if a warning is emitted late during Python shutdown. + In this case, the Python warnings module was probably unloaded, filters + are no more available to choose as action. It is safer to ignore the + warning and do nothing. */ + if (module == Py_None) + Py_RETURN_NONE; + + if (registry && !PyDict_Check(registry) && (registry != Py_None)) { + PyErr_SetString(PyExc_TypeError, "'registry' must be a dict or None"); + return NULL; + } + + /* Normalize module. */ + if (module == NULL) { + module = normalize_module(filename); + if (module == NULL) + return NULL; + } + else + Py_INCREF(module); + + /* Normalize message. */ + Py_INCREF(message); /* DECREF'ed in cleanup. */ + rc = PyObject_IsInstance(message, PyExc_Warning); + if (rc == -1) { + goto cleanup; + } + if (rc == 1) { + text = PyObject_Str(message); + if (text == NULL) + goto cleanup; + category = (PyObject*)Py_TYPE(message); + } + else { + text = message; + message = PyObject_CallOneArg(category, message); + if (message == NULL) + goto cleanup; + } + + lineno_obj = PyLong_FromLong(lineno); + if (lineno_obj == NULL) + goto cleanup; + + if (source == Py_None) { + source = NULL; + } + + /* Create key. */ + key = PyTuple_Pack(3, text, category, lineno_obj); + if (key == NULL) + goto cleanup; + + if ((registry != NULL) && (registry != Py_None)) { + rc = already_warned(interp, registry, key, 0); + if (rc == -1) + goto cleanup; + else if (rc == 1) + goto return_none; + /* Else this warning hasn't been generated before. */ + } + + action = get_filter(interp, category, text, lineno, module, &item); + if (action == NULL) + goto cleanup; + + if (_PyUnicode_EqualToASCIIString(action, "error")) { + PyErr_SetObject(category, message); + goto cleanup; + } + + if (_PyUnicode_EqualToASCIIString(action, "ignore")) { + goto return_none; + } + + /* Store in the registry that we've been here, *except* when the action + is "always". */ + rc = 0; + if (!_PyUnicode_EqualToASCIIString(action, "always")) { + if (registry != NULL && registry != Py_None && + PyDict_SetItem(registry, key, Py_True) < 0) + { + goto cleanup; + } + + if (_PyUnicode_EqualToASCIIString(action, "once")) { + if (registry == NULL || registry == Py_None) { + registry = get_once_registry(interp); + if (registry == NULL) + goto cleanup; + } + /* WarningsState.once_registry[(text, category)] = 1 */ + rc = update_registry(interp, registry, text, category, 0); + } + else if (_PyUnicode_EqualToASCIIString(action, "module")) { + /* registry[(text, category, 0)] = 1 */ + if (registry != NULL && registry != Py_None) + rc = update_registry(interp, registry, text, category, 0); + } + else if (!_PyUnicode_EqualToASCIIString(action, "default")) { + PyErr_Format(PyExc_RuntimeError, + "Unrecognized action (%R) in warnings.filters:\n %R", + action, item); + goto cleanup; + } + } + + if (rc == 1) /* Already warned for this module. */ + goto return_none; + if (rc == 0) { + if (call_show_warning(tstate, category, text, message, filename, + lineno, lineno_obj, sourceline, source) < 0) + goto cleanup; + } + else /* if (rc == -1) */ + goto cleanup; + + return_none: + result = Py_None; + Py_INCREF(result); + + cleanup: + Py_XDECREF(item); + Py_XDECREF(key); + Py_XDECREF(text); + Py_XDECREF(lineno_obj); + Py_DECREF(module); + Py_XDECREF(message); + return result; /* Py_None or NULL. */ +} + +static int +is_internal_frame(PyFrameObject *frame) +{ + if (frame == NULL) { + return 0; + } + + PyCodeObject *code = PyFrame_GetCode(frame); + PyObject *filename = code->co_filename; + Py_DECREF(code); + + if (filename == NULL) { + return 0; + } + if (!PyUnicode_Check(filename)) { + return 0; + } + + int contains = PyUnicode_Contains(filename, &_Py_ID(importlib)); + if (contains < 0) { + return 0; + } + else if (contains > 0) { + contains = PyUnicode_Contains(filename, &_Py_ID(_bootstrap)); + if (contains < 0) { + return 0; + } + else if (contains > 0) { + return 1; + } + } + + return 0; +} + +static PyFrameObject * +next_external_frame(PyFrameObject *frame) +{ + do { + PyFrameObject *back = PyFrame_GetBack(frame); + Py_DECREF(frame); + frame = back; + } while (frame != NULL && is_internal_frame(frame)); + + return frame; +} + +/* filename, module, and registry are new refs, globals is borrowed */ +/* Returns 0 on error (no new refs), 1 on success */ +static int +setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, + PyObject **module, PyObject **registry) +{ + PyObject *globals; + + /* Setup globals, filename and lineno. */ + PyThreadState *tstate = get_current_tstate(); + if (tstate == NULL) { + return 0; + } + PyInterpreterState *interp = tstate->interp; + PyFrameObject *f = PyThreadState_GetFrame(tstate); + // Stack level comparisons to Python code is off by one as there is no + // warnings-related stack level to avoid. + if (stack_level <= 0 || is_internal_frame(f)) { + while (--stack_level > 0 && f != NULL) { + PyFrameObject *back = PyFrame_GetBack(f); + Py_DECREF(f); + f = back; + } + } + else { + while (--stack_level > 0 && f != NULL) { + f = next_external_frame(f); + } + } + + if (f == NULL) { + globals = interp->sysdict; + *filename = PyUnicode_FromString("sys"); + *lineno = 1; + } + else { + globals = f->f_frame->f_globals; + *filename = f->f_frame->f_code->co_filename; + Py_INCREF(*filename); + *lineno = PyFrame_GetLineNumber(f); + Py_DECREF(f); + } + + *module = NULL; + + /* Setup registry. */ + assert(globals != NULL); + assert(PyDict_Check(globals)); + *registry = _PyDict_GetItemWithError(globals, &_Py_ID(__warningregistry__)); + if (*registry == NULL) { + int rc; + + if (_PyErr_Occurred(tstate)) { + goto handle_error; + } + *registry = PyDict_New(); + if (*registry == NULL) + goto handle_error; + + rc = PyDict_SetItem(globals, &_Py_ID(__warningregistry__), *registry); + if (rc < 0) + goto handle_error; + } + else + Py_INCREF(*registry); + + /* Setup module. */ + *module = _PyDict_GetItemWithError(globals, &_Py_ID(__name__)); + if (*module == Py_None || (*module != NULL && PyUnicode_Check(*module))) { + Py_INCREF(*module); + } + else if (_PyErr_Occurred(tstate)) { + goto handle_error; + } + else { + *module = PyUnicode_FromString("<string>"); + if (*module == NULL) + goto handle_error; + } + + return 1; + + handle_error: + Py_XDECREF(*registry); + Py_XDECREF(*module); + Py_DECREF(*filename); + return 0; +} + +static PyObject * +get_category(PyObject *message, PyObject *category) +{ + int rc; + + /* Get category. */ + rc = PyObject_IsInstance(message, PyExc_Warning); + if (rc == -1) + return NULL; + + if (rc == 1) + category = (PyObject*)Py_TYPE(message); + else if (category == NULL || category == Py_None) + category = PyExc_UserWarning; + + /* Validate category. */ + rc = PyObject_IsSubclass(category, PyExc_Warning); + /* category is not a subclass of PyExc_Warning or + PyObject_IsSubclass raised an error */ + if (rc == -1 || rc == 0) { + PyErr_Format(PyExc_TypeError, + "category must be a Warning subclass, not '%s'", + Py_TYPE(category)->tp_name); + return NULL; + } + + return category; +} + +static PyObject * +do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level, + PyObject *source) +{ + PyObject *filename, *module, *registry, *res; + int lineno; + + PyThreadState *tstate = get_current_tstate(); + if (tstate == NULL) { + return NULL; + } + + if (!setup_context(stack_level, &filename, &lineno, &module, ®istry)) + return NULL; + + res = warn_explicit(tstate, category, message, filename, lineno, module, registry, + NULL, source); + Py_DECREF(filename); + Py_DECREF(registry); + Py_DECREF(module); + return res; +} + +/*[clinic input] +warn as warnings_warn + + message: object + category: object = None + stacklevel: Py_ssize_t = 1 + source: object = None + +Issue a warning, or maybe ignore it or raise an exception. +[clinic start generated code]*/ + +static PyObject * +warnings_warn_impl(PyObject *module, PyObject *message, PyObject *category, + Py_ssize_t stacklevel, PyObject *source) +/*[clinic end generated code: output=31ed5ab7d8d760b2 input=bfdf5cf99f6c4edd]*/ +{ + category = get_category(message, category); + if (category == NULL) + return NULL; + return do_warn(message, category, stacklevel, source); +} + +static PyObject * +get_source_line(PyInterpreterState *interp, PyObject *module_globals, int lineno) +{ + PyObject *loader; + PyObject *module_name; + PyObject *get_source; + PyObject *source; + PyObject *source_list; + PyObject *source_line; + + /* Check/get the requisite pieces needed for the loader. */ + loader = _PyDict_GetItemWithError(module_globals, &_Py_ID(__loader__)); + if (loader == NULL) { + return NULL; + } + Py_INCREF(loader); + module_name = _PyDict_GetItemWithError(module_globals, &_Py_ID(__name__)); + if (!module_name) { + Py_DECREF(loader); + return NULL; + } + Py_INCREF(module_name); + + /* Make sure the loader implements the optional get_source() method. */ + (void)_PyObject_LookupAttr(loader, &_Py_ID(get_source), &get_source); + Py_DECREF(loader); + if (!get_source) { + Py_DECREF(module_name); + return NULL; + } + /* Call get_source() to get the source code. */ + source = PyObject_CallOneArg(get_source, module_name); + Py_DECREF(get_source); + Py_DECREF(module_name); + if (!source) { + return NULL; + } + if (source == Py_None) { + Py_DECREF(source); + return NULL; + } + + /* Split the source into lines. */ + source_list = PyUnicode_Splitlines(source, 0); + Py_DECREF(source); + if (!source_list) { + return NULL; + } + + /* Get the source line. */ + source_line = PyList_GetItem(source_list, lineno-1); + Py_XINCREF(source_line); + Py_DECREF(source_list); + return source_line; +} + +static PyObject * +warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_list[] = {"message", "category", "filename", "lineno", + "module", "registry", "module_globals", + "source", 0}; + PyObject *message; + PyObject *category; + PyObject *filename; + int lineno; + PyObject *module = NULL; + PyObject *registry = NULL; + PyObject *module_globals = NULL; + PyObject *sourceobj = NULL; + PyObject *source_line = NULL; + PyObject *returned; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOUi|OOOO:warn_explicit", + kwd_list, &message, &category, &filename, &lineno, &module, + ®istry, &module_globals, &sourceobj)) + return NULL; + + PyThreadState *tstate = get_current_tstate(); + if (tstate == NULL) { + return NULL; + } + + if (module_globals && module_globals != Py_None) { + if (!PyDict_Check(module_globals)) { + PyErr_Format(PyExc_TypeError, + "module_globals must be a dict, not '%.200s'", + Py_TYPE(module_globals)->tp_name); + return NULL; + } + + source_line = get_source_line(tstate->interp, module_globals, lineno); + if (source_line == NULL && PyErr_Occurred()) { + return NULL; + } + } + returned = warn_explicit(tstate, category, message, filename, lineno, module, + registry, source_line, sourceobj); + Py_XDECREF(source_line); + return returned; +} + +static PyObject * +warnings_filters_mutated(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyInterpreterState *interp = get_current_interp(); + if (interp == NULL) { + return NULL; + } + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return NULL; + } + st->filters_version++; + Py_RETURN_NONE; +} + + +/* Function to issue a warning message; may raise an exception. */ + +static int +warn_unicode(PyObject *category, PyObject *message, + Py_ssize_t stack_level, PyObject *source) +{ + PyObject *res; + + if (category == NULL) + category = PyExc_RuntimeWarning; + + res = do_warn(message, category, stack_level, source); + if (res == NULL) + return -1; + Py_DECREF(res); + + return 0; +} + +static int +_PyErr_WarnFormatV(PyObject *source, + PyObject *category, Py_ssize_t stack_level, + const char *format, va_list vargs) +{ + PyObject *message; + int res; + + message = PyUnicode_FromFormatV(format, vargs); + if (message == NULL) + return -1; + + res = warn_unicode(category, message, stack_level, source); + Py_DECREF(message); + return res; +} + +int +PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level, + const char *format, ...) +{ + int res; + va_list vargs; + +#ifdef HAVE_STDARG_PROTOTYPES + va_start(vargs, format); +#else + va_start(vargs); +#endif + res = _PyErr_WarnFormatV(NULL, category, stack_level, format, vargs); + va_end(vargs); + return res; +} + +static int +_PyErr_WarnFormat(PyObject *source, PyObject *category, Py_ssize_t stack_level, + const char *format, ...) +{ + int res; + va_list vargs; + +#ifdef HAVE_STDARG_PROTOTYPES + va_start(vargs, format); +#else + va_start(vargs); +#endif + res = _PyErr_WarnFormatV(source, category, stack_level, format, vargs); + va_end(vargs); + return res; +} + +int +PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level, + const char *format, ...) +{ + int res; + va_list vargs; + +#ifdef HAVE_STDARG_PROTOTYPES + va_start(vargs, format); +#else + va_start(vargs); +#endif + res = _PyErr_WarnFormatV(source, PyExc_ResourceWarning, + stack_level, format, vargs); + va_end(vargs); + return res; +} + + +int +PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level) +{ + int ret; + PyObject *message = PyUnicode_FromString(text); + if (message == NULL) + return -1; + ret = warn_unicode(category, message, stack_level, NULL); + Py_DECREF(message); + return ret; +} + +/* PyErr_Warn is only for backwards compatibility and will be removed. + Use PyErr_WarnEx instead. */ + +#undef PyErr_Warn + +int +PyErr_Warn(PyObject *category, const char *text) +{ + return PyErr_WarnEx(category, text, 1); +} + +/* Warning with explicit origin */ +int +PyErr_WarnExplicitObject(PyObject *category, PyObject *message, + PyObject *filename, int lineno, + PyObject *module, PyObject *registry) +{ + PyObject *res; + if (category == NULL) + category = PyExc_RuntimeWarning; + PyThreadState *tstate = get_current_tstate(); + if (tstate == NULL) { + return -1; + } + res = warn_explicit(tstate, category, message, filename, lineno, + module, registry, NULL, NULL); + if (res == NULL) + return -1; + Py_DECREF(res); + return 0; +} + +int +PyErr_WarnExplicit(PyObject *category, const char *text, + const char *filename_str, int lineno, + const char *module_str, PyObject *registry) +{ + PyObject *message = PyUnicode_FromString(text); + if (message == NULL) { + return -1; + } + PyObject *filename = PyUnicode_DecodeFSDefault(filename_str); + if (filename == NULL) { + Py_DECREF(message); + return -1; + } + PyObject *module = NULL; + if (module_str != NULL) { + module = PyUnicode_FromString(module_str); + if (module == NULL) { + Py_DECREF(filename); + Py_DECREF(message); + return -1; + } + } + + int ret = PyErr_WarnExplicitObject(category, message, filename, lineno, + module, registry); + Py_XDECREF(module); + Py_DECREF(filename); + Py_DECREF(message); + return ret; +} + +int +PyErr_WarnExplicitFormat(PyObject *category, + const char *filename_str, int lineno, + const char *module_str, PyObject *registry, + const char *format, ...) +{ + PyObject *message; + PyObject *module = NULL; + PyObject *filename = PyUnicode_DecodeFSDefault(filename_str); + int ret = -1; + va_list vargs; + + if (filename == NULL) + goto exit; + if (module_str != NULL) { + module = PyUnicode_FromString(module_str); + if (module == NULL) + goto exit; + } + +#ifdef HAVE_STDARG_PROTOTYPES + va_start(vargs, format); +#else + va_start(vargs); +#endif + message = PyUnicode_FromFormatV(format, vargs); + if (message != NULL) { + PyObject *res; + PyThreadState *tstate = get_current_tstate(); + if (tstate != NULL) { + res = warn_explicit(tstate, category, message, filename, lineno, + module, registry, NULL, NULL); + Py_DECREF(message); + if (res != NULL) { + Py_DECREF(res); + ret = 0; + } + } + } + va_end(vargs); +exit: + Py_XDECREF(module); + Py_XDECREF(filename); + return ret; +} + +void +_PyErr_WarnUnawaitedCoroutine(PyObject *coro) +{ + /* First, we attempt to funnel the warning through + warnings._warn_unawaited_coroutine. + + This could raise an exception, due to: + - a bug + - some kind of shutdown-related brokenness + - succeeding, but with an "error" warning filter installed, so the + warning is converted into a RuntimeWarning exception + + In the first two cases, we want to print the error (so we know what it + is!), and then print a warning directly as a fallback. In the last + case, we want to print the error (since it's the warning!), but *not* + do a fallback. And after we print the error we can't check for what + type of error it was (because PyErr_WriteUnraisable clears it), so we + need a flag to keep track. + + Since this is called from __del__ context, it's careful to never raise + an exception. + */ + int warned = 0; + PyInterpreterState *interp = _PyInterpreterState_GET(); + assert(interp != NULL); + PyObject *fn = GET_WARNINGS_ATTR(interp, _warn_unawaited_coroutine, 1); + if (fn) { + PyObject *res = PyObject_CallOneArg(fn, coro); + Py_DECREF(fn); + if (res || PyErr_ExceptionMatches(PyExc_RuntimeWarning)) { + warned = 1; + } + Py_XDECREF(res); + } + + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(coro); + } + if (!warned) { + if (_PyErr_WarnFormat(coro, PyExc_RuntimeWarning, 1, + "coroutine '%S' was never awaited", + ((PyCoroObject *)coro)->cr_qualname) < 0) + { + PyErr_WriteUnraisable(coro); + } + } +} + +PyDoc_STRVAR(warn_explicit_doc, +"Low-level interface to warnings functionality."); + +static PyMethodDef warnings_functions[] = { + WARNINGS_WARN_METHODDEF + {"warn_explicit", _PyCFunction_CAST(warnings_warn_explicit), + METH_VARARGS | METH_KEYWORDS, warn_explicit_doc}, + {"_filters_mutated", _PyCFunction_CAST(warnings_filters_mutated), METH_NOARGS, + NULL}, + /* XXX(brett.cannon): add showwarning? */ + /* XXX(brett.cannon): Reasonable to add formatwarning? */ + {NULL, NULL} /* sentinel */ +}; + + +static int +warnings_module_exec(PyObject *module) +{ + PyInterpreterState *interp = get_current_interp(); + if (interp == NULL) { + return -1; + } + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return -1; + } + if (PyModule_AddObjectRef(module, "filters", st->filters) < 0) { + return -1; + } + if (PyModule_AddObjectRef(module, "_onceregistry", st->once_registry) < 0) { + return -1; + } + if (PyModule_AddObjectRef(module, "_defaultaction", st->default_action) < 0) { + return -1; + } + return 0; +} + + +static PyModuleDef_Slot warnings_slots[] = { + {Py_mod_exec, warnings_module_exec}, + {0, NULL} +}; + +static struct PyModuleDef warnings_module = { + PyModuleDef_HEAD_INIT, + .m_name = MODULE_NAME, + .m_doc = warnings__doc__, + .m_size = 0, + .m_methods = warnings_functions, + .m_slots = warnings_slots, +}; + + +PyMODINIT_FUNC +_PyWarnings_Init(void) +{ + return PyModuleDef_Init(&warnings_module); +} + +// We need this to ensure that warnings still work until late in finalization. +void +_PyWarnings_Fini(PyInterpreterState *interp) +{ + warnings_clear_state(&interp->warnings); +} |