]> granicus.if.org Git - python/commitdiff
On ResourceWarning, log traceback where the object was allocated
authorVictor Stinner <victor.stinner@gmail.com>
Sat, 19 Mar 2016 00:03:51 +0000 (01:03 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Sat, 19 Mar 2016 00:03:51 +0000 (01:03 +0100)
Issue #26567:

* Add a new function PyErr_ResourceWarning() function to pass the destroyed
  object
* Add a source attribute to warnings.WarningMessage
* Add warnings._showwarnmsg() which uses tracemalloc to get the traceback where
  source object was allocated.

Doc/c-api/exceptions.rst
Doc/library/warnings.rst
Doc/whatsnew/3.6.rst
Include/warnings.h
Lib/test/test_warnings/__init__.py
Lib/warnings.py
Misc/NEWS
Modules/_io/fileio.c
Modules/posixmodule.c
Modules/socketmodule.c
Python/_warnings.c

index 1e708a81284f296d648ef7b469a5912e266a3cbf..57f36acc85dab2568aceb6c89d7c47c61b678751 100644 (file)
@@ -334,6 +334,14 @@ an error value).
    .. versionadded:: 3.2
 
 
+.. c:function:: int PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level, const char *format, ...)
+
+   Function similar to :c:func:`PyErr_WarnFormat`, but *category* is
+   :exc:`ResourceWarning` and pass *source* to :func:`warnings.WarningMessage`.
+
+   .. versionadded:: 3.6
+
+
 Querying the error indicator
 ============================
 
index 8a538adb9b0282641a4360c96336f572af29d318..4ce88ab3344a027d9ad57fa925dc34116bf14902 100644 (file)
@@ -319,7 +319,7 @@ Available Functions
    of the warning message).
 
 
-.. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None)
+.. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)
 
    This is a low-level interface to the functionality of :func:`warn`, passing in
    explicitly the message, category, filename and line number, and optionally the
@@ -335,6 +335,12 @@ Available Functions
    source for modules found in zipfiles or other non-filesystem import
    sources).
 
+   *source*, if supplied, is the destroyed object which emitted a
+   :exc:`ResourceWarning`.
+
+   .. versionchanged:: 3.6
+      Add the *source* parameter.
+
 
 .. function:: showwarning(message, category, filename, lineno, file=None, line=None)
 
index cc635892d41ec027b3d0ea59255a7f52984b9e32..b7099170f5e81e35146d888228ae69e37cd7aea3 100644 (file)
@@ -258,6 +258,40 @@ urllib.robotparser
 (Contributed by Nikolay Bogoychev in :issue:`16099`.)
 
 
+warnings
+--------
+
+A new optional *source* parameter has been added to the
+:func:`warnings.warn_explicit` function: the destroyed object which emitted a
+:exc:`ResourceWarning`. A *source* attribute has also been added to
+:class:`warnings.WarningMessage` (contributed by Victor Stinner in
+:issue:`26568` and :issue:`26567`).
+
+When a :exc:`ResourceWarning` warning is logged, the :mod:`tracemalloc` is now
+used to try to retrieve the traceback where the detroyed object was allocated.
+
+Example with the script ``example.py``::
+
+    def func():
+        f = open(__file__)
+        f = None
+
+    func()
+
+Output of the command ``python3.6 -Wd -X tracemalloc=5 example.py``::
+
+    example.py:3: ResourceWarning: unclosed file <...>
+      f = None
+    Object allocated at (most recent call first):
+      File "example.py", lineno 2
+        f = open(__file__)
+      File "example.py", lineno 5
+        func()
+
+The "Object allocated at" traceback is new and only displayed if
+:mod:`tracemalloc` is tracing Python memory allocations.
+
+
 zipfile
 -------
 
index effb9fad7196f4edb8dc4e12bcf9dd667d3ea4c4..c1c6992553ec0618b7b16ac78a7ee719dde5c5cd 100644 (file)
@@ -17,6 +17,13 @@ PyAPI_FUNC(int) PyErr_WarnFormat(
     Py_ssize_t stack_level,
     const char *format,         /* ASCII-encoded string  */
     ...);
+
+/* Emit a ResourceWarning warning */
+PyAPI_FUNC(int) PyErr_ResourceWarning(
+    PyObject *source,
+    Py_ssize_t stack_level,
+    const char *format,         /* ASCII-encoded string  */
+    ...);
 #ifndef Py_LIMITED_API
 PyAPI_FUNC(int) PyErr_WarnExplicitObject(
     PyObject *category,
index 70eae4c35e7df272a1140c3f2b0dea656d780a6e..a1b3dba7017e194f62399f3fe91b0d777b56f1c5 100644 (file)
@@ -2,7 +2,10 @@ from contextlib import contextmanager
 import linecache
 import os
 from io import StringIO
+import re
 import sys
+import tempfile
+import textwrap
 import unittest
 from test import support
 from test.support.script_helper import assert_python_ok, assert_python_failure
@@ -763,12 +766,39 @@ class WarningsDisplayTests(BaseTest):
                                 file_object, expected_file_line)
         self.assertEqual(expect, file_object.getvalue())
 
+
 class CWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase):
     module = c_warnings
 
 class PyWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase):
     module = py_warnings
 
+    def test_tracemalloc(self):
+        with tempfile.NamedTemporaryFile("w", suffix=".py") as tmpfile:
+            tmpfile.write(textwrap.dedent("""
+                def func():
+                    f = open(__file__)
+                    # Emit ResourceWarning
+                    f = None
+
+                func()
+            """))
+            tmpfile.flush()
+            fname = tmpfile.name
+            res = assert_python_ok('-Wd', '-X', 'tracemalloc=2', fname)
+        stderr = res.err.decode('ascii', 'replace')
+        stderr = re.sub('<.*>', '<...>', stderr)
+        expected = textwrap.dedent(f'''
+            {fname}:5: ResourceWarning: unclosed file <...>
+              f = None
+            Object allocated at (most recent call first):
+              File "{fname}", lineno 3
+                f = open(__file__)
+              File "{fname}", lineno 7
+                func()
+        ''').strip()
+        self.assertEqual(stderr, expected)
+
 
 class CatchWarningTests(BaseTest):
 
index f54726a45fc8b7025759dc8f91e48a0c5403b838..1566065093eaaffb7b612fe635936a5eb27b098c 100644 (file)
@@ -2,6 +2,7 @@
 
 import sys
 
+
 __all__ = ["warn", "warn_explicit", "showwarning",
            "formatwarning", "filterwarnings", "simplefilter",
            "resetwarnings", "catch_warnings"]
@@ -66,6 +67,18 @@ def _formatwarnmsg(msg):
     if line:
         line = line.strip()
         s += "  %s\n" % line
+    if msg.source is not None:
+        import tracemalloc
+        tb = tracemalloc.get_object_traceback(msg.source)
+        if tb is not None:
+            s += 'Object allocated at (most recent call first):\n'
+            for frame in tb:
+                s += ('  File "%s", lineno %s\n'
+                      % (frame.filename, frame.lineno))
+                line = linecache.getline(frame.filename, frame.lineno)
+                if line:
+                    line = line.strip()
+                    s += '    %s\n' % line
     return s
 
 def filterwarnings(action, message="", category=Warning, module="", lineno=0,
@@ -267,7 +280,8 @@ def warn(message, category=None, stacklevel=1):
                   globals)
 
 def warn_explicit(message, category, filename, lineno,
-                  module=None, registry=None, module_globals=None):
+                  module=None, registry=None, module_globals=None,
+                  source=None):
     lineno = int(lineno)
     if module is None:
         module = filename or "<unknown>"
@@ -333,17 +347,17 @@ def warn_explicit(message, category, filename, lineno,
               "Unrecognized action (%r) in warnings.filters:\n %s" %
               (action, item))
     # Print message and context
-    msg = WarningMessage(message, category, filename, lineno)
+    msg = WarningMessage(message, category, filename, lineno, source)
     _showwarnmsg(msg)
 
 
 class WarningMessage(object):
 
     _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
-                        "line")
+                        "line", "source")
 
     def __init__(self, message, category, filename, lineno, file=None,
-                    line=None):
+                 line=None, source=None):
         local_values = locals()
         for attr in self._WARNING_DETAILS:
             setattr(self, attr, local_values[attr])
index cef2b817b44248443ac95b65b6371fb92df2f8d6..832d1accd441c3289312c9965eaf6d4be96657cf 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -226,6 +226,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #26567: Add a new function :c:func:`PyErr_ResourceWarning` function to
+  pass the destroyed object. Add a *source* attribute to
+  :class:`warnings.WarningMessage`. Add warnings._showwarnmsg() which uses
+  tracemalloc to get the traceback where source object was allocated.
+
 - Issue #26313: ssl.py _load_windows_store_certs fails if windows cert store
   is empty. Patch by Baji.
 
index 8bf3922676a6f8cc1ff4f4a50cc443125834c0f0..a02a9c1be33351512d7626130a8d196898619f17 100644 (file)
@@ -92,8 +92,7 @@ fileio_dealloc_warn(fileio *self, PyObject *source)
     if (self->fd >= 0 && self->closefd) {
         PyObject *exc, *val, *tb;
         PyErr_Fetch(&exc, &val, &tb);
-        if (PyErr_WarnFormat(PyExc_ResourceWarning, 1,
-                             "unclosed file %R", source)) {
+        if (PyErr_ResourceWarning(source, 1, "unclosed file %R", source)) {
             /* Spurious errors can appear at shutdown */
             if (PyErr_ExceptionMatches(PyExc_Warning))
                 PyErr_WriteUnraisable((PyObject *) self);
index 65b20be4686e666794ec486e1d34bd758e43850f..3f22d14ffbb18ec335782d383b081d471440d6ac 100644 (file)
@@ -12111,8 +12111,8 @@ ScandirIterator_dealloc(ScandirIterator *iterator)
          */
         ++Py_REFCNT(iterator);
         PyErr_Fetch(&exc, &val, &tb);
-        if (PyErr_WarnFormat(PyExc_ResourceWarning, 1,
-                             "unclosed scandir iterator %R", iterator)) {
+        if (PyErr_ResourceWarning((PyObject *)iterator, 1,
+                                  "unclosed scandir iterator %R", iterator)) {
             /* Spurious errors can appear at shutdown */
             if (PyErr_ExceptionMatches(PyExc_Warning))
                 PyErr_WriteUnraisable((PyObject *) iterator);
index 77a6b313b0321986c96af89dfd05218e8b1aa0ff..657b04b7a6c63440d810420d9873e904b271c85f 100644 (file)
@@ -4170,8 +4170,7 @@ sock_dealloc(PySocketSockObject *s)
         Py_ssize_t old_refcount = Py_REFCNT(s);
         ++Py_REFCNT(s);
         PyErr_Fetch(&exc, &val, &tb);
-        if (PyErr_WarnFormat(PyExc_ResourceWarning, 1,
-                             "unclosed %R", s))
+        if (PyErr_ResourceWarning(s, 1, "unclosed %R", s))
             /* Spurious errors can appear at shutdown */
             if (PyErr_ExceptionMatches(PyExc_Warning))
                 PyErr_WriteUnraisable((PyObject *) s);
index a8c3703926471875b7033080d1585c00614ba90a..25299fb622fe29b689fe992b3934d0cd70c8b431 100644 (file)
@@ -287,8 +287,8 @@ update_registry(PyObject *registry, PyObject *text, PyObject *category,
 }
 
 static void
-show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
-                *category, PyObject *sourceline)
+show_warning(PyObject *filename, int lineno, PyObject *text,
+             PyObject *category, PyObject *sourceline)
 {
     PyObject *f_stderr;
     PyObject *name;
@@ -362,7 +362,7 @@ error:
 static int
 call_show_warning(PyObject *category, PyObject *text, PyObject *message,
                   PyObject *filename, int lineno, PyObject *lineno_obj,
-                  PyObject *sourceline)
+                  PyObject *sourceline, PyObject *source)
 {
     PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL;
 
@@ -388,7 +388,7 @@ call_show_warning(PyObject *category, PyObject *text, PyObject *message,
     }
 
     msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category,
-            filename, lineno_obj,
+            filename, lineno_obj, Py_None, Py_None, source,
             NULL);
     Py_DECREF(warnmsg_cls);
     if (msg == NULL)
@@ -412,7 +412,8 @@ error:
 static PyObject *
 warn_explicit(PyObject *category, PyObject *message,
               PyObject *filename, int lineno,
-              PyObject *module, PyObject *registry, PyObject *sourceline)
+              PyObject *module, PyObject *registry, PyObject *sourceline,
+              PyObject *source)
 {
     PyObject *key = NULL, *text = NULL, *result = NULL, *lineno_obj = NULL;
     PyObject *item = NULL;
@@ -521,7 +522,7 @@ warn_explicit(PyObject *category, PyObject *message,
         goto return_none;
     if (rc == 0) {
         if (call_show_warning(category, text, message, filename, lineno,
-                              lineno_obj, sourceline) < 0)
+                              lineno_obj, sourceline, source) < 0)
             goto cleanup;
     }
     else /* if (rc == -1) */
@@ -766,7 +767,8 @@ get_category(PyObject *message, PyObject *category)
 }
 
 static PyObject *
-do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level)
+do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level,
+        PyObject *source)
 {
     PyObject *filename, *module, *registry, *res;
     int lineno;
@@ -775,7 +777,7 @@ do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level)
         return NULL;
 
     res = warn_explicit(category, message, filename, lineno, module, registry,
-                        NULL);
+                        NULL, source);
     Py_DECREF(filename);
     Py_DECREF(registry);
     Py_DECREF(module);
@@ -796,14 +798,15 @@ warnings_warn(PyObject *self, PyObject *args, PyObject *kwds)
     category = get_category(message, category);
     if (category == NULL)
         return NULL;
-    return do_warn(message, category, stack_level);
+    return do_warn(message, category, stack_level, NULL);
 }
 
 static PyObject *
 warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
 {
     static char *kwd_list[] = {"message", "category", "filename", "lineno",
-                                "module", "registry", "module_globals", 0};
+                                "module", "registry", "module_globals",
+                                "source", 0};
     PyObject *message;
     PyObject *category;
     PyObject *filename;
@@ -811,10 +814,11 @@ warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
     PyObject *module = NULL;
     PyObject *registry = NULL;
     PyObject *module_globals = NULL;
+    PyObject *sourceobj = NULL;
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOUi|OOO:warn_explicit",
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOUi|OOOO:warn_explicit",
                 kwd_list, &message, &category, &filename, &lineno, &module,
-                &registry, &module_globals))
+                &registry, &module_globals, &sourceobj))
         return NULL;
 
     if (module_globals) {
@@ -870,14 +874,14 @@ warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
 
         /* Handle the warning. */
         returned = warn_explicit(category, message, filename, lineno, module,
-                                 registry, source_line);
+                                 registry, source_line, sourceobj);
         Py_DECREF(source_list);
         return returned;
     }
 
  standard_call:
     return warn_explicit(category, message, filename, lineno, module,
-                         registry, NULL);
+                         registry, NULL, sourceobj);
 }
 
 static PyObject *
@@ -892,14 +896,14 @@ warnings_filters_mutated(PyObject *self, PyObject *args)
 
 static int
 warn_unicode(PyObject *category, PyObject *message,
-             Py_ssize_t stack_level)
+             Py_ssize_t stack_level, PyObject *source)
 {
     PyObject *res;
 
     if (category == NULL)
         category = PyExc_RuntimeWarning;
 
-    res = do_warn(message, category, stack_level);
+    res = do_warn(message, category, stack_level, source);
     if (res == NULL)
         return -1;
     Py_DECREF(res);
@@ -907,12 +911,28 @@ warn_unicode(PyObject *category, PyObject *message,
     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 ret;
-    PyObject *message;
+    int res;
     va_list vargs;
 
 #ifdef HAVE_STDARG_PROTOTYPES
@@ -920,17 +940,30 @@ PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level,
 #else
     va_start(vargs);
 #endif
-    message = PyUnicode_FromFormatV(format, vargs);
-    if (message != NULL) {
-        ret = warn_unicode(category, message, stack_level);
-        Py_DECREF(message);
-    }
-    else
-        ret = -1;
+    res = _PyErr_WarnFormatV(NULL, category, stack_level, format, vargs);
     va_end(vargs);
-    return ret;
+    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)
 {
@@ -938,7 +971,7 @@ PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level)
     PyObject *message = PyUnicode_FromString(text);
     if (message == NULL)
         return -1;
-    ret = warn_unicode(category, message, stack_level);
+    ret = warn_unicode(category, message, stack_level, NULL);
     Py_DECREF(message);
     return ret;
 }
@@ -964,7 +997,7 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message,
     if (category == NULL)
         category = PyExc_RuntimeWarning;
     res = warn_explicit(category, message, filename, lineno,
-                        module, registry, NULL);
+                        module, registry, NULL, NULL);
     if (res == NULL)
         return -1;
     Py_DECREF(res);
@@ -1028,7 +1061,7 @@ PyErr_WarnExplicitFormat(PyObject *category,
     if (message != NULL) {
         PyObject *res;
         res = warn_explicit(category, message, filename, lineno,
-                            module, registry, NULL);
+                            module, registry, NULL, NULL);
         Py_DECREF(message);
         if (res != NULL) {
             Py_DECREF(res);