add a replacement API for PyCObject, PyCapsule #5630

All stdlib modules with C-APIs now use this.

Patch by Larry Hastings
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index 3300cde..cbc5cf8 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -78,6 +78,16 @@
 #define DONT_USE_SEH
 #endif
 
+#define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem"
+
+static void pymem_destructor(PyObject *ptr)
+{
+	void *p = PyCapsule_GetPointer(ptr, CTYPES_CAPSULE_NAME_PYMEM);
+	if (p) {
+		PyMem_Free(p);
+	}
+}
+
 /*
   ctypes maintains thread-local storage that has space for two error numbers:
   private copies of the system 'errno' value and, on Windows, the system error code
@@ -136,7 +146,7 @@
 		if (space == NULL)
 			return NULL;
 		memset(space, 0, sizeof(int) * 2);
-		errobj = PyCObject_FromVoidPtr(space, PyMem_Free);
+		errobj = PyCapsule_New(space, CTYPES_CAPSULE_NAME_PYMEM, pymem_destructor);
 		if (errobj == NULL)
 			return NULL;
 		if (-1 == PyDict_SetItem(dict, error_object_name,
@@ -145,7 +155,7 @@
 			return NULL;
 		}
 	}
-	*pspace = (int *)PyCObject_AsVoidPtr(errobj);
+	*pspace = (int *)PyCapsule_GetPointer(errobj, CTYPES_CAPSULE_NAME_PYMEM);
 	return errobj;
 }
 
@@ -658,7 +668,7 @@
 			return -1;
 		}
 		memset(pa->value.p, 0, size);
-		pa->keep = PyCObject_FromVoidPtr(pa->value.p, PyMem_Free);
+		pa->keep = PyCapsule_New(pa->value.p, CTYPES_CAPSULE_NAME_PYMEM, pymem_destructor);
 		if (!pa->keep) {
 			PyMem_Free(pa->value.p);
 			return -1;
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index ff24405..d7966a5 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -6,6 +6,18 @@
 #endif
 #include "ctypes.h"
 
+
+#define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem"
+
+static void pymem_destructor(PyObject *ptr)
+{
+	void *p = PyCapsule_GetPointer(ptr, CTYPES_CFIELD_CAPSULE_NAME_PYMEM);
+	if (p) {
+		PyMem_Free(p);
+	}
+}
+
+
 /******************************************************************/
 /*
   PyCField_Type
@@ -1477,7 +1489,7 @@
 			return PyErr_NoMemory();
 		}
 		memset(buffer, 0, size);
-		keep = PyCObject_FromVoidPtr(buffer, PyMem_Free);
+		keep = PyCapsule_New(buffer, CTYPES_CFIELD_CAPSULE_NAME_PYMEM, pymem_destructor);
 		if (!keep) {
 			Py_DECREF(value);
 			PyMem_Free(buffer);
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 5cb198e..a92cc8e 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -104,6 +104,7 @@
 
 #include "Python.h"
 
+
 #ifdef __osf__
 #define STRICT_SYSV_CURSES      /* Don't use ncurses extensions */
 #endif
@@ -174,7 +175,7 @@
 /*
  * Check the return code from a curses function and return None 
  * or raise an exception as appropriate.  These are exported using the
- * CObject API. 
+ * capsule API. 
  */
 
 static PyObject *
@@ -2827,8 +2828,8 @@
 		return NULL;
 	ModDict = d; /* For PyCurses_InitScr to use later */
 
-	/* Add a CObject for the C API */
-	c_api_object = PyCObject_FromVoidPtr((void *)PyCurses_API, NULL);
+	/* Add a capsule for the C API */
+	c_api_object = PyCapsule_New(PyCurses_API, PyCurses_CAPSULE_NAME, NULL);
 	PyDict_SetItemString(d, "_C_API", c_api_object);
 	Py_DECREF(c_api_object);
 
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index ac118b4..ae09893 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -2809,7 +2809,7 @@
 
 #if defined(USE_PYEXPAT_CAPI)
     /* link against pyexpat, if possible */
-    capi = PyCObject_Import("pyexpat", "expat_CAPI");
+    capi = PyCapsule_Import(PyExpat_CAPSULE_NAME, 0);
     if (capi &&
         strcmp(capi->magic, PyExpat_CAPI_MAGIC) == 0 &&
         capi->size <= sizeof(*expat_capi) &&
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 3c9dd61..b400057 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -71,6 +71,8 @@
 /* Include symbols from _socket module */
 #include "socketmodule.h"
 
+static PySocketModule_APIObject PySocketModule;
+
 #if defined(HAVE_POLL_H)
 #include <poll.h>
 #elif defined(HAVE_SYS_POLL_H)
@@ -1626,6 +1628,7 @@
 PyInit__ssl(void)
 {
 	PyObject *m, *d;
+        PySocketModule_APIObject *socket_api;
 
 	if (PyType_Ready(&PySSL_Type) < 0)
 		return NULL;
@@ -1636,8 +1639,10 @@
 	d = PyModule_GetDict(m);
 
 	/* Load _socket module and its C API */
-	if (PySocketModule_ImportModuleAndAPI())
+        socket_api = PySocketModule_ImportModuleAndAPI();
+	if (!socket_api)
 		return NULL;
+        PySocketModule = *socket_api;
 
 	/* Init OpenSSL */
 	SSL_load_error_strings();
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 3f7190c..f3899f5 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -1102,6 +1102,155 @@
 }
 
 
+/* Coverage testing of capsule objects. */
+
+static const char *capsule_name = "capsule name";
+static       char *capsule_pointer = "capsule pointer";
+static       char *capsule_context = "capsule context";
+static const char *capsule_error = NULL;
+static int
+capsule_destructor_call_count = 0;
+
+static void
+capsule_destructor(PyObject *o) {
+	capsule_destructor_call_count++;
+	if (PyCapsule_GetContext(o) != capsule_context) {
+		capsule_error = "context did not match in destructor!";
+	} else if (PyCapsule_GetDestructor(o) != capsule_destructor) {
+		capsule_error = "destructor did not match in destructor!  (woah!)";
+	} else if (PyCapsule_GetName(o) != capsule_name) {
+		capsule_error = "name did not match in destructor!";
+	} else if (PyCapsule_GetPointer(o, capsule_name) != capsule_pointer) {
+		capsule_error = "pointer did not match in destructor!";
+	}
+}
+
+typedef struct {
+	char *name;
+	char *module;
+	char *attribute;
+} known_capsule;
+
+static PyObject *
+test_capsule(PyObject *self, PyObject *args)
+{
+	PyObject *object;
+	const char *error = NULL;
+	void *pointer;
+	void *pointer2;
+	known_capsule known_capsules[] = {
+		#define KNOWN_CAPSULE(module, name)	{ module "." name, module, name }
+		KNOWN_CAPSULE("_socket", "CAPI"),
+		KNOWN_CAPSULE("_curses", "_C_API"),
+		KNOWN_CAPSULE("datetime", "datetime_CAPI"),
+		{ NULL, NULL },
+	};
+	known_capsule *known = &known_capsules[0];
+
+#define FAIL(x) { error = (x); goto exit; }
+
+#define CHECK_DESTRUCTOR \
+	if (capsule_error) { \
+		FAIL(capsule_error); \
+	} \
+	else if (!capsule_destructor_call_count) {	\
+		FAIL("destructor not called!"); \
+	} \
+	capsule_destructor_call_count = 0; \
+
+	object = PyCapsule_New(capsule_pointer, capsule_name, capsule_destructor);
+	PyCapsule_SetContext(object, capsule_context);
+	capsule_destructor(object);
+	CHECK_DESTRUCTOR;
+	Py_DECREF(object);
+	CHECK_DESTRUCTOR;
+
+	object = PyCapsule_New(known, "ignored", NULL);
+	PyCapsule_SetPointer(object, capsule_pointer);
+	PyCapsule_SetName(object, capsule_name);
+	PyCapsule_SetDestructor(object, capsule_destructor);
+	PyCapsule_SetContext(object, capsule_context);
+	capsule_destructor(object);
+	CHECK_DESTRUCTOR;
+	/* intentionally access using the wrong name */
+	pointer2 = PyCapsule_GetPointer(object, "the wrong name");
+	if (!PyErr_Occurred()) {
+		FAIL("PyCapsule_GetPointer should have failed but did not!");
+	}
+	PyErr_Clear();
+	if (pointer2) {
+		if (pointer2 == capsule_pointer) {
+			FAIL("PyCapsule_GetPointer should not have"
+				 " returned the internal pointer!");
+		} else {
+			FAIL("PyCapsule_GetPointer should have "
+				 "returned NULL pointer but did not!");
+		}
+	}
+	PyCapsule_SetDestructor(object, NULL);
+	Py_DECREF(object);
+	if (capsule_destructor_call_count) {
+		FAIL("destructor called when it should not have been!");
+	}
+
+	for (known = &known_capsules[0]; known->module != NULL; known++) {
+		/* yeah, ordinarily I wouldn't do this either,
+		   but it's fine for this test harness.
+		*/
+		static char buffer[256];
+#undef FAIL
+#define FAIL(x) \
+		{ \
+		sprintf(buffer, "%s module: \"%s\" attribute: \"%s\"", \
+			x, known->module, known->attribute); \
+		error = buffer; \
+		goto exit; \
+		} \
+
+		PyObject *module = PyImport_ImportModule(known->module);
+		if (module) {
+			pointer = PyCapsule_Import(known->name, 0);
+			if (!pointer) {
+				Py_DECREF(module);
+				FAIL("PyCapsule_GetPointer returned NULL unexpectedly!");
+			}
+			object = PyObject_GetAttrString(module, known->attribute);
+			if (!object) {
+				Py_DECREF(module);
+				return NULL;
+			}
+			pointer2 = PyCapsule_GetPointer(object,
+						"weebles wobble but they don't fall down");
+			if (!PyErr_Occurred()) {
+				Py_DECREF(object);
+				Py_DECREF(module);
+				FAIL("PyCapsule_GetPointer should have failed but did not!");
+			}
+			PyErr_Clear();
+			if (pointer2) {
+				Py_DECREF(module);
+				Py_DECREF(object);
+				if (pointer2 == pointer) {
+					FAIL("PyCapsule_GetPointer should not have" 
+						 " returned its internal pointer!");
+				} else {
+					FAIL("PyCapsule_GetPointer should have" 
+						 " returned NULL pointer but did not!");
+				}
+			}
+			Py_DECREF(object);
+			Py_DECREF(module);
+		}
+	}
+
+  exit:
+	if (error) {
+		return raiseTestError("test_capsule", error);
+	}
+	Py_RETURN_NONE;
+#undef FAIL
+}
+
 #ifdef HAVE_GETTIMEOFDAY
 /* Profiling of integer performance */
 static void print_delta(int test, struct timeval *s, struct timeval *e)
@@ -1280,9 +1429,10 @@
 	{"test_empty_argparse", (PyCFunction)test_empty_argparse,METH_NOARGS},
 	{"test_null_strings",	(PyCFunction)test_null_strings,	 METH_NOARGS},
 	{"test_string_from_format", (PyCFunction)test_string_from_format, METH_NOARGS},
-	{"test_string_to_double", (PyCFunction)test_string_to_double, METH_NOARGS},
 	{"test_with_docstring", (PyCFunction)test_with_docstring, METH_NOARGS,
 	 PyDoc_STR("This is a pretty normal docstring.")},
+	{"test_string_to_double", (PyCFunction)test_string_to_double, METH_NOARGS},
+	{"test_capsule", (PyCFunction)test_capsule, METH_NOARGS},
 
 	{"getargs_tuple",	getargs_tuple,			 METH_VARARGS},
 	{"getargs_keywords", (PyCFunction)getargs_keywords, 
diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h
index 0530a33..e630671 100644
--- a/Modules/cjkcodecs/cjkcodecs.h
+++ b/Modules/cjkcodecs/cjkcodecs.h
@@ -239,6 +239,8 @@
 	static const MultibyteCodec *codec_list =	\
 		(const MultibyteCodec *)_codec_list;
 
+
+
 static PyObject *
 getmultibytecodec(void)
 {
@@ -284,7 +286,7 @@
 		return NULL;
 	}
 
-	codecobj = PyCObject_FromVoidPtr((void *)codec, NULL);
+	codecobj = PyCapsule_New((void *)codec, PyMultibyteCodec_CAPSULE_NAME, NULL);
 	if (codecobj == NULL)
 		return NULL;
 
@@ -309,7 +311,7 @@
 		int r;
 		strcpy(mhname + sizeof("__map_") - 1, h->charset);
 		r = PyModule_AddObject(module, mhname,
-				PyCObject_FromVoidPtr((void *)h, NULL));
+				PyCapsule_New((void *)h, PyMultibyteCodec_CAPSULE_NAME, NULL));
 		if (r == -1)
 			return -1;
 	}
@@ -364,14 +366,14 @@
 	o = PyObject_GetAttrString(mod, (char*)symbol);
 	if (o == NULL)
 		goto errorexit;
-	else if (!PyCObject_Check(o)) {
+	else if (!PyCapsule_IsValid(o, PyMultibyteCodec_CAPSULE_NAME)) {
 		PyErr_SetString(PyExc_ValueError,
-				"map data must be a CObject.");
+				"map data must be a Capsule.");
 		goto errorexit;
 	}
 	else {
 		struct dbcs_map *map;
-		map = PyCObject_AsVoidPtr(o);
+		map = PyCapsule_GetPointer(o, PyMultibyteCodec_CAPSULE_NAME);
 		if (encmap != NULL)
 			*encmap = map->encmap;
 		if (decmap != NULL)
diff --git a/Modules/cjkcodecs/multibytecodec.c b/Modules/cjkcodecs/multibytecodec.c
index c6b3492..1735dfd 100644
--- a/Modules/cjkcodecs/multibytecodec.c
+++ b/Modules/cjkcodecs/multibytecodec.c
@@ -1793,12 +1793,12 @@
 	MultibyteCodecObject *self;
 	MultibyteCodec *codec;
 
-	if (!PyCObject_Check(arg)) {
+	if (!PyCapsule_IsValid(arg, PyMultibyteCodec_CAPSULE_NAME)) {
 		PyErr_SetString(PyExc_ValueError, "argument type invalid");
 		return NULL;
 	}
 
-	codec = PyCObject_AsVoidPtr(arg);
+	codec = PyCapsule_GetPointer(arg, PyMultibyteCodec_CAPSULE_NAME);
 	if (codec->codecinit != NULL && codec->codecinit(codec->config) != 0)
 		return NULL;
 
diff --git a/Modules/cjkcodecs/multibytecodec.h b/Modules/cjkcodecs/multibytecodec.h
index 22ea5d4..71c02cc 100644
--- a/Modules/cjkcodecs/multibytecodec.h
+++ b/Modules/cjkcodecs/multibytecodec.h
@@ -132,6 +132,9 @@
 #define MBENC_FLUSH		0x0001 /* encode all characters encodable */
 #define MBENC_MAX		MBENC_FLUSH
 
+#define PyMultibyteCodec_CAPSULE_NAME "multibytecodec.__map_*"
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 0c6e664..8ba3474 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -4792,11 +4792,10 @@
 	Py_INCREF(&PyDateTime_TZInfoType);
 	PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType);
 
-        x = PyCObject_FromVoidPtrAndDesc(&CAPI, (void*) DATETIME_API_MAGIC,
-                NULL);
-        if (x == NULL)
-            return NULL;
-        PyModule_AddObject(m, "datetime_CAPI", x);
+    x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL);
+    if (x == NULL)
+        return NULL;
+    PyModule_AddObject(m, "datetime_CAPI", x);
 
 	/* A 4-year cycle has an extra leap day over what we'd get from
 	 * pasting together 4 single years.
diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
index a968e52..7a862c0 100644
--- a/Modules/pyexpat.c
+++ b/Modules/pyexpat.c
@@ -1987,8 +1987,8 @@
     capi.SetUnknownEncodingHandler = XML_SetUnknownEncodingHandler;
     capi.SetUserData = XML_SetUserData;
     
-    /* export as cobject */
-    capi_object = PyCObject_FromVoidPtr(&capi, NULL);
+    /* export using capsule */
+    capi_object = PyCapsule_New(&capi, PyExpat_CAPSULE_NAME, NULL);
     if (capi_object)
         PyModule_AddObject(m, "expat_CAPI", capi_object);
     return m;
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index c229c07..6ec5220 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -4146,6 +4146,14 @@
         NULL
 };
 
+PySocketModule_APIObject *
+PySocketModule_ImportModuleAndAPI(void)
+{
+	void *api;
+	api = PyCapsule_Import(PySocket_CAPSULE_NAME, 1);;
+	return (PySocketModule_APIObject *)api;
+}
+
 
 /* Initialize the _socket module.
 
@@ -4231,7 +4239,7 @@
 
 	/* Export C API */
 	if (PyModule_AddObject(m, PySocket_CAPI_NAME,
-	       PyCObject_FromVoidPtr((void *)&PySocketModuleAPI, NULL)
+	       PyCapsule_New(&PySocketModuleAPI, PySocket_CAPSULE_NAME, NULL)
 				 ) != 0)
 		return NULL;
 
diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h
index 8d19cbf..a323409 100644
--- a/Modules/socketmodule.h
+++ b/Modules/socketmodule.h
@@ -78,6 +78,7 @@
 /* Python module and C API name */
 #define PySocket_MODULE_NAME	"_socket"
 #define PySocket_CAPI_NAME	"CAPI"
+#define PySocket_CAPSULE_NAME	PySocket_MODULE_NAME "." PySocket_CAPI_NAME
 
 /* Abstract the socket file descriptor type */
 #ifdef MS_WINDOWS
@@ -142,12 +143,12 @@
     the _socket module. Since cross-DLL linking introduces a lot of
     problems on many platforms, the "trick" is to wrap the
     C API of a module in a struct which then gets exported to
-    other modules via a PyCObject.
+    other modules via a PyCapsule.
 
     The code in socketmodule.c defines this struct (which currently
     only contains the type object reference, but could very
     well also include other C APIs needed by other modules)
-    and exports it as PyCObject via the module dictionary
+    and exports it as PyCapsule via the module dictionary
     under the name "CAPI".
 
     Other modules can now include the socketmodule.h file
@@ -212,49 +213,11 @@
    ...
 */
 
-static
-PySocketModule_APIObject PySocketModule;
-
 /* You *must* call this before using any of the functions in
    PySocketModule and check its outcome; otherwise all accesses will
    result in a segfault. Returns 0 on success. */
 
-#ifndef DPRINTF
-# define DPRINTF if (0) printf
-#endif
-
-static
-int PySocketModule_ImportModuleAndAPI(void)
-{
-	PyObject *mod = 0, *v = 0;
-	char *apimodule = PySocket_MODULE_NAME;
-	char *apiname = PySocket_CAPI_NAME;
-	void *api;
-
-	DPRINTF("Importing the %s C API...\n", apimodule);
-	mod = PyImport_ImportModuleNoBlock(apimodule);
-	if (mod == NULL)
-		goto onError;
-	DPRINTF(" %s package found\n", apimodule);
-	v = PyObject_GetAttrString(mod, apiname);
-	if (v == NULL)
-		goto onError;
-	Py_DECREF(mod);
-	DPRINTF(" API object %s found\n", apiname);
-	api = PyCObject_AsVoidPtr(v);
-	if (api == NULL)
-		goto onError;
-	Py_DECREF(v);
-	memcpy(&PySocketModule, api, sizeof(PySocketModule));
-	DPRINTF(" API object loaded and initialized.\n");
-	return 0;
-
- onError:
-	DPRINTF(" not found.\n");
-	Py_XDECREF(mod);
-	Py_XDECREF(v);
-	return -1;
-}
+PyAPI_FUNC(PySocketModule_APIObject *) PySocketModule_ImportModuleAndAPI(void);
 
 #endif /* !PySocket_BUILDING_SOCKET */
 
diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c
index 301cee7..2dddc48 100644
--- a/Modules/unicodedata.c
+++ b/Modules/unicodedata.c
@@ -1275,7 +1275,7 @@
         PyModule_AddObject(m, "ucd_3_2_0", v);
 
     /* Export C API */
-    v = PyCObject_FromVoidPtr((void *) &hashAPI, NULL);
+    v = PyCapsule_New((void *)&hashAPI, PyUnicodeData_CAPSULE_NAME, NULL);
     if (v != NULL)
         PyModule_AddObject(m, "ucnhash_CAPI", v);
     return m;