Issue #9757: memoryview objects get a release() method to release the
underlying buffer (previously this was only done when deallocating the
memoryview), and gain support for the context management protocol.
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index 802a922..36626b2 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -3,6 +3,23 @@
 
 #include "Python.h"
 
+#define IS_RELEASED(memobj) \
+    (((PyMemoryViewObject *) memobj)->view.buf == NULL)
+
+#define CHECK_RELEASED(memobj) \
+    if (IS_RELEASED(memobj)) { \
+        PyErr_SetString(PyExc_ValueError, \
+                        "operation forbidden on released memoryview object"); \
+        return NULL; \
+    }
+
+#define CHECK_RELEASED_INT(memobj) \
+    if (IS_RELEASED(memobj)) { \
+        PyErr_SetString(PyExc_ValueError, \
+                        "operation forbidden on released memoryview object"); \
+        return -1; \
+    }
+
 static Py_ssize_t
 get_shape0(Py_buffer *buf)
 {
@@ -34,6 +51,7 @@
 memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags)
 {
     int res = 0;
+    CHECK_RELEASED_INT(self);
     /* XXX for whatever reason fixing the flags seems necessary */
     if (self->view.readonly)
         flags &= ~PyBUF_WRITABLE;
@@ -330,12 +348,14 @@
 static PyObject *
 memory_format_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return PyUnicode_FromString(self->view.format);
 }
 
 static PyObject *
 memory_itemsize_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return PyLong_FromSsize_t(self->view.itemsize);
 }
 
@@ -366,30 +386,35 @@
 static PyObject *
 memory_shape_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return _IntTupleFromSsizet(self->view.ndim, self->view.shape);
 }
 
 static PyObject *
 memory_strides_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return _IntTupleFromSsizet(self->view.ndim, self->view.strides);
 }
 
 static PyObject *
 memory_suboffsets_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return _IntTupleFromSsizet(self->view.ndim, self->view.suboffsets);
 }
 
 static PyObject *
 memory_readonly_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return PyBool_FromLong(self->view.readonly);
 }
 
 static PyObject *
 memory_ndim_get(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED(self);
     return PyLong_FromLong(self->view.ndim);
 }
 
@@ -408,6 +433,7 @@
 static PyObject *
 memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs)
 {
+    CHECK_RELEASED(mem);
     return PyObject_CallFunctionObjArgs(
             (PyObject *) &PyBytes_Type, mem, NULL);
 }
@@ -423,6 +449,7 @@
     PyObject *res, *item;
     char *buf;
 
+    CHECK_RELEASED(mem);
     if (strcmp(view->format, "B") || view->itemsize != 1) {
         PyErr_SetString(PyExc_NotImplementedError, 
                 "tolist() only supports byte views");
@@ -449,17 +476,9 @@
     return res;
 }
 
-static PyMethodDef memory_methods[] = {
-    {"tobytes", (PyCFunction)memory_tobytes, METH_NOARGS, NULL},
-    {"tolist", (PyCFunction)memory_tolist, METH_NOARGS, NULL},
-    {NULL,          NULL}           /* sentinel */
-};
-
-
 static void
-memory_dealloc(PyMemoryViewObject *self)
+do_release(PyMemoryViewObject *self)
 {
-    _PyObject_GC_UNTRACK(self);
     if (self->view.obj != NULL) {
         if (self->base && PyTuple_Check(self->base)) {
             /* Special case when first element is generic object
@@ -484,19 +503,57 @@
         }
         Py_CLEAR(self->base);
     }
+    self->view.obj = NULL;
+    self->view.buf = NULL;
+}
+
+static PyObject *
+memory_enter(PyObject *self, PyObject *args)
+{
+    CHECK_RELEASED(self);
+    Py_INCREF(self);
+    return self;
+}
+
+static PyObject *
+memory_exit(PyObject *self, PyObject *args)
+{
+    do_release((PyMemoryViewObject *) self);
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef memory_methods[] = {
+    {"release", memory_exit, METH_NOARGS},
+    {"tobytes", (PyCFunction)memory_tobytes, METH_NOARGS, NULL},
+    {"tolist", (PyCFunction)memory_tolist, METH_NOARGS, NULL},
+    {"__enter__", memory_enter, METH_NOARGS},
+    {"__exit__", memory_exit, METH_VARARGS},
+    {NULL,          NULL}           /* sentinel */
+};
+
+
+static void
+memory_dealloc(PyMemoryViewObject *self)
+{
+    _PyObject_GC_UNTRACK(self);
+    do_release(self);
     PyObject_GC_Del(self);
 }
 
 static PyObject *
 memory_repr(PyMemoryViewObject *self)
 {
-    return PyUnicode_FromFormat("<memory at %p>", self);
+    if (IS_RELEASED(self))
+        return PyUnicode_FromFormat("<released memory at %p>", self);
+    else
+        return PyUnicode_FromFormat("<memory at %p>", self);
 }
 
 /* Sequence methods */
 static Py_ssize_t
 memory_length(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED_INT(self);
     return get_shape0(&self->view);
 }
 
@@ -508,6 +565,7 @@
 {
     Py_buffer *view = &(self->view);
 
+    CHECK_RELEASED(self);
     if (view->ndim == 0) {
         PyErr_SetString(PyExc_IndexError,
                         "invalid indexing of 0-dim memory");
@@ -557,6 +615,7 @@
     Py_buffer *view;
     view = &(self->view);
     
+    CHECK_RELEASED(self);
     if (view->ndim == 0) {
         if (key == Py_Ellipsis ||
             (PyTuple_Check(key) && PyTuple_GET_SIZE(key)==0)) {
@@ -626,6 +685,7 @@
     Py_buffer *view = &(self->view);
     char *srcbuf, *destbuf;
 
+    CHECK_RELEASED_INT(self);
     if (view->readonly) {
         PyErr_SetString(PyExc_TypeError,
             "cannot modify read-only memory");
@@ -718,6 +778,11 @@
     ww.obj = NULL;
     if (op != Py_EQ && op != Py_NE)
         goto _notimpl;
+    if ((PyMemoryView_Check(v) && IS_RELEASED(v)) ||
+        (PyMemoryView_Check(w) && IS_RELEASED(w))) {
+        equal = (v == w);
+        goto _end;
+    }
     if (PyObject_GetBuffer(v, &vv, PyBUF_CONTIG_RO) == -1) {
         PyErr_Clear();
         goto _notimpl;