gh-135801: Improve filtering by module in warn_explicit() without module argument (GH-140151)

* Try to match the module name pattern with module names constructed
  starting from different parent directories of the filename.
  E.g., for "/path/to/package/module" try to match with
  "path.to.package.module", "to.package.module", "package.module" and
  "module".
* Ignore trailing "/__init__.py".
* Ignore trailing ".pyw" on Windows.
* Keep matching with the full filename (without optional ".py" extension)
  for compatibility.
* Only ignore the case of the ".py" extension on Windows.
This commit is contained in:
Serhiy Storchaka 2025-10-30 15:55:39 +02:00 committed by GitHub
parent efc37ba49e
commit 6826166280
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 243 additions and 73 deletions

View file

@ -171,7 +171,7 @@ _PyWarnings_InitState(PyInterpreterState *interp)
/*************************************************************************/
static int
check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg)
check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg, PyObject *arg2)
{
PyObject *result;
int rc;
@ -182,6 +182,9 @@ check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg)
/* An internal plain text default filter must match exactly */
if (PyUnicode_CheckExact(obj)) {
if (arg == NULL) {
return 0;
}
int cmp_result = PyUnicode_Compare(obj, arg);
if (cmp_result == -1 && PyErr_Occurred()) {
return -1;
@ -190,10 +193,19 @@ check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg)
}
/* Otherwise assume a regex filter and call its match() method */
result = PyObject_CallMethodOneArg(obj, &_Py_ID(match), arg);
if (arg != NULL) {
result = PyObject_CallMethodOneArg(obj, &_Py_ID(match), arg);
}
else {
PyObject *match = PyImport_ImportModuleAttrString("_py_warnings", "_match_filename");
if (match == NULL) {
return -1;
}
result = PyObject_CallFunctionObjArgs(match, obj, arg2, NULL);
Py_DECREF(match);
}
if (result == NULL)
return -1;
rc = PyObject_IsTrue(result);
Py_DECREF(result);
return rc;
@ -423,7 +435,7 @@ get_default_action(PyInterpreterState *interp)
static bool
filter_search(PyInterpreterState *interp, PyObject *category,
PyObject *text, Py_ssize_t lineno,
PyObject *module, char *list_name, PyObject *filters,
PyObject *module, PyObject *filename, char *list_name, PyObject *filters,
PyObject **item, PyObject **matched_action) {
bool result = true;
*matched_action = NULL;
@ -459,14 +471,14 @@ filter_search(PyInterpreterState *interp, PyObject *category,
break;
}
good_msg = check_matched(interp, msg, text);
good_msg = check_matched(interp, msg, text, NULL);
if (good_msg == -1) {
Py_DECREF(tmp_item);
result = false;
break;
}
good_mod = check_matched(interp, mod, module);
good_mod = check_matched(interp, mod, module, filename);
if (good_mod == -1) {
Py_DECREF(tmp_item);
result = false;
@ -504,7 +516,7 @@ filter_search(PyInterpreterState *interp, PyObject *category,
static PyObject*
get_filter(PyInterpreterState *interp, PyObject *category,
PyObject *text, Py_ssize_t lineno,
PyObject *module, PyObject **item)
PyObject *module, PyObject *filename, PyObject **item)
{
#ifdef Py_DEBUG
WarningsState *st = warnings_get_state(interp);
@ -522,7 +534,7 @@ get_filter(PyInterpreterState *interp, PyObject *category,
use_global_filters = true;
} else {
PyObject *context_action = NULL;
if (!filter_search(interp, category, text, lineno, module, "_warnings_context _filters",
if (!filter_search(interp, category, text, lineno, module, filename, "_warnings_context _filters",
context_filters, item, &context_action)) {
Py_DECREF(context_filters);
return NULL;
@ -541,7 +553,7 @@ get_filter(PyInterpreterState *interp, PyObject *category,
if (filters == NULL) {
return NULL;
}
if (!filter_search(interp, category, text, lineno, module, "filters",
if (!filter_search(interp, category, text, lineno, module, filename, "filters",
filters, item, &action)) {
return NULL;
}
@ -612,39 +624,6 @@ already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key,
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 = Py_NewRef(filename);
}
return module;
}
static int
update_registry(PyInterpreterState *interp, PyObject *registry, PyObject *text,
PyObject *category, int add_zero)
@ -812,15 +791,6 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message,
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. */
if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
@ -858,7 +828,7 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message,
/* Else this warning hasn't been generated before. */
}
action = get_filter(interp, category, text, lineno, module, &item);
action = get_filter(interp, category, text, lineno, module, filename, &item);
if (action == NULL)
goto cleanup;
@ -921,7 +891,6 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message,
Py_XDECREF(key);
Py_XDECREF(text);
Py_XDECREF(lineno_obj);
Py_DECREF(module);
Py_XDECREF(message);
return result; /* Py_None or NULL. */
}