diff --git a/Lib/test/picklecommon.py b/Lib/test/picklecommon.py index fece50f17fecca..573c324408af06 100644 --- a/Lib/test/picklecommon.py +++ b/Lib/test/picklecommon.py @@ -351,3 +351,34 @@ def pie(self): class Subclass(tuple): class Nested(str): pass + +# For test_private_methods +class PrivateMethods: + def __init__(self, value): + self.value = value + + def __private_method(self): + return self.value + + def get_method(self): + return self.__private_method + + @classmethod + def get_unbound_method(cls): + return cls.__private_method + + @classmethod + def __private_classmethod(cls): + return 43 + + @classmethod + def get_classmethod(cls): + return cls.__private_classmethod + + @staticmethod + def __private_staticmethod(): + return 44 + + @classmethod + def get_staticmethod(cls): + return cls.__private_staticmethod diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 09bfb374732e86..6c6a2176faf307 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -4117,6 +4117,21 @@ def test_c_methods(self): with self.subTest(proto=proto, descr=descr): self.assertRaises(TypeError, self.dumps, descr, proto) + def test_private_methods(self): + if self.py_version < (3, 15): + self.skipTest('not supported in Python < 3.15') + obj = PrivateMethods(42) + for proto in protocols: + with self.subTest(proto=proto): + unpickled = self.loads(self.dumps(obj.get_method(), proto)) + self.assertEqual(unpickled(), 42) + unpickled = self.loads(self.dumps(obj.get_unbound_method(), proto)) + self.assertEqual(unpickled(obj), 42) + unpickled = self.loads(self.dumps(obj.get_classmethod(), proto)) + self.assertEqual(unpickled(), 43) + unpickled = self.loads(self.dumps(obj.get_staticmethod(), proto)) + self.assertEqual(unpickled(), 44) + def test_compat_pickle(self): if self.py_version < (3, 4): self.skipTest("doesn't work in Python < 3.4'") diff --git a/Misc/NEWS.d/next/Library/2020-07-14-23-54-18.bpo-33007.TyI3_Q.rst b/Misc/NEWS.d/next/Library/2020-07-14-23-54-18.bpo-33007.TyI3_Q.rst new file mode 100644 index 00000000000000..3e956409d52a58 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-14-23-54-18.bpo-33007.TyI3_Q.rst @@ -0,0 +1 @@ +The :mod:`pickle` module now properly handles name-mangled private methods. diff --git a/Objects/classobject.c b/Objects/classobject.c index e71f301f2efd77..166b65fc18680f 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -7,6 +7,7 @@ #include "pycore_object.h" #include "pycore_pyerrors.h" #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_symtable.h" // _Py_Mangle() #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -140,9 +141,29 @@ method___reduce___impl(PyMethodObject *self) PyObject *funcself = PyMethod_GET_SELF(self); PyObject *func = PyMethod_GET_FUNCTION(self); PyObject *funcname = PyObject_GetAttr(func, &_Py_ID(__name__)); + Py_ssize_t len; if (funcname == NULL) { return NULL; } + if (PyUnicode_Check(funcname) && + (len = PyUnicode_GET_LENGTH(funcname)) > 2 && + PyUnicode_READ_CHAR(funcname, 0) == '_' && + PyUnicode_READ_CHAR(funcname, 1) == '_' && + !(PyUnicode_READ_CHAR(funcname, len-1) == '_' && + PyUnicode_READ_CHAR(funcname, len-2) == '_')) + { + PyObject *name = PyObject_GetAttr((PyObject *)Py_TYPE(funcself), + &_Py_ID(__name__)); + if (name == NULL) { + Py_DECREF(funcname); + return NULL; + } + Py_SETREF(funcname, _Py_Mangle(name, funcname)); + Py_DECREF(name); + if (funcname == NULL) { + return NULL; + } + } return Py_BuildValue( "N(ON)", _PyEval_GetBuiltin(&_Py_ID(getattr)), funcself, funcname); }