diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -1636,6 +1636,27 @@ class AbstractPickleTests(unittest.TestC unpickled = self.loads(self.dumps(method, proto)) self.assertEqual(method(*args), unpickled(*args)) + def test_local_lookup_error(self): + # Test that whichmodule() errors out cleanly when looking up + # an assumed globally-reachable object fails. + def f(): + pass + # Since the function is local, lookup will fail + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + with self.assertRaises((AttributeError, pickle.PicklingError)): + pickletools.dis(self.dumps(f, proto)) + # Same without a __module__ attribute (exercises a different path + # in _pickle.c). + del f.__module__ + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + with self.assertRaises((AttributeError, pickle.PicklingError)): + pickletools.dis(self.dumps(f, proto)) + # Yet a different path. + f.__name__ = f.__qualname__ + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + with self.assertRaises((AttributeError, pickle.PicklingError)): + pickletools.dis(self.dumps(f, proto)) + class BigmemPickleTests(unittest.TestCase): diff --git a/Modules/_pickle.c b/Modules/_pickle.c --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -1547,10 +1547,16 @@ get_dotted_path(PyObject *obj, PyObject n = PyList_GET_SIZE(dotted_path); assert(n >= 1); if (!allow_qualname && n > 1) { - PyErr_Format(PyExc_AttributeError, - "Can't get qualified attribute %R on %R;" - "use protocols >= 4 to enable support", - name, obj); + if (obj == NULL) + PyErr_Format(PyExc_AttributeError, + "Can't pickle qualified object %R; " + "use protocols >= 4 to enable support", + name); + else + PyErr_Format(PyExc_AttributeError, + "Can't pickle qualified attribute %R on %R; " + "use protocols >= 4 to enable support", + name, obj); Py_DECREF(dotted_path); return NULL; } @@ -1562,8 +1568,12 @@ get_dotted_path(PyObject *obj, PyObject assert(PyBool_Check(result)); Py_DECREF(result); if (is_equal) { - PyErr_Format(PyExc_AttributeError, - "Can't get local attribute %R on %R", name, obj); + if (obj == NULL) + PyErr_Format(PyExc_AttributeError, + "Can't pickle local object %R", name); + else + PyErr_Format(PyExc_AttributeError, + "Can't pickle local attribute %R on %R", name, obj); Py_DECREF(dotted_path); return NULL; } @@ -1653,7 +1663,7 @@ whichmodule(PyObject *global, PyObject * return NULL; } - dotted_path = get_dotted_path(module, global_name, allow_qualname); + dotted_path = get_dotted_path(NULL, global_name, allow_qualname); if (dotted_path == NULL) return NULL;