From c72654556e337e64ed967ed118df8e7f510e3382 Mon Sep 17 00:00:00 2001
From: Stefan Behnel <stefan_ml@behnel.de>
Date: Thu, 15 Feb 2018 21:22:02 +0100
Subject: [PATCH] Disable auto-detection of read-only memory views (too unsafe)
 and acquire them only as read-only if the memory view dtype is a "const"
 type.

---
 CHANGES.rst                                   |  6 ++--
 Cython/Compiler/PyrexTypes.py                 | 33 +++++++++++--------
 docs/src/userguide/memoryviews.rst            | 16 ++++-----
 .../memoryview/numpy_memoryview_readonly.pyx  |  7 ++--
 4 files changed, 33 insertions(+), 29 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 3bfd60e46..19fbba126 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -15,10 +15,8 @@ Features added
 * Type inference is now supported for Pythran compiled NumPy expressions.
   Patch by Nils Braun.  (Github issue #1954)
 
-* Read-only buffers are automatically supported for memoryviews if it can be
-  determined at compile time that the code does not need write access.
-  They can also be allowed explicitly by adding the ``const`` modifier to their
-  declaration.  (Github issues #1605, #1869)
+* The ``const`` modifier can be applied to memoryview declarations to allow
+  read-only buffers as input.  (Github issues #1605, #1869)
 
 * When compiling with gcc, the module init function is now tuned for small
   code size instead of whatever compile flags were provided externally.
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
index 8f61107e8..c4dc1176d 100644
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -907,9 +907,12 @@ class MemoryViewSliceType(PyrexType):
 
     def from_py_call_code(self, source_code, result_code, error_pos, code,
                           from_py_function=None, error_condition=None):
+        # NOTE: auto-detection of readonly buffers is disabled:
+        # writable = self.writable_needed or not self.dtype.is_const
+        writable = not self.dtype.is_const
         return self._assign_from_py_code(
             source_code, result_code, error_pos, code, from_py_function, error_condition,
-            extra_args=['PyBUF_WRITABLE' if self.writable_needed else '0'])
+            extra_args=['PyBUF_WRITABLE' if writable else '0'])
 
     def create_to_py_utility_code(self, env):
         self._dtype_to_py_func, self._dtype_from_py_func = self.dtype_object_conversion_funcs(env)
@@ -937,25 +940,29 @@ class MemoryViewSliceType(PyrexType):
         if self.dtype.is_pyobject:
             utility_name = "MemviewObjectToObject"
         else:
-            to_py = self.dtype.create_to_py_utility_code(env)
-            from_py = self.dtype.create_from_py_utility_code(env)
-            if not (to_py or from_py):
-                return "NULL", "NULL"
+            self.dtype.create_to_py_utility_code(env)
+            to_py_function = self.dtype.to_py_function
 
-            if not self.dtype.to_py_function:
-                get_function = "NULL"
+            from_py_function = None
+            if not self.dtype.is_const:
+                self.dtype.create_from_py_utility_code(env)
+                from_py_function = self.dtype.from_py_function
 
-            if not self.dtype.from_py_function:
+            if not (to_py_function or from_py_function):
+                return "NULL", "NULL"
+            if not to_py_function:
+                get_function = "NULL"
+            if not from_py_function:
                 set_function = "NULL"
 
             utility_name = "MemviewDtypeToObject"
             error_condition = (self.dtype.error_condition('value') or
                                'PyErr_Occurred()')
             context.update(
-                to_py_function = self.dtype.to_py_function,
-                from_py_function = self.dtype.from_py_function,
-                dtype = self.dtype.empty_declaration_code(),
-                error_condition = error_condition,
+                to_py_function=to_py_function,
+                from_py_function=from_py_function,
+                dtype=self.dtype.empty_declaration_code(),
+                error_condition=error_condition,
             )
 
         utility = TempitaUtilityCode.load_cached(
@@ -2456,7 +2463,7 @@ class CArrayType(CPointerBaseType):
 
     def from_py_call_code(self, source_code, result_code, error_pos, code,
                           from_py_function=None, error_condition=None):
-        assert not error_condition
+        assert not error_condition, '%s: %s' % (error_pos, error_condition)
         call_code = "%s(%s, %s, %s)" % (
             from_py_function or self.from_py_function,
             source_code, result_code, self.size)
diff --git a/docs/src/userguide/memoryviews.rst b/docs/src/userguide/memoryviews.rst
index 8ccb9e144..6bb2b4da1 100644
--- a/docs/src/userguide/memoryviews.rst
+++ b/docs/src/userguide/memoryviews.rst
@@ -244,14 +244,14 @@ Note that this does not *require* the input to be read-only::
     a = np.linspace(0, 10, num=50)
     myslice = a
 
-Writable buffers will still be accepted by ``const`` views, but read-only
-buffers are not accepted for non-const, writable views.
-
-Cython will also request a read-only view automatically if it can determine
-at compile time that a writable buffer is not required.  The support for
-automatically distinguishing between buffer usage types, and the compile
-time correctness checks for read-only views are expected to further improve
-over time.
+Writable buffers are still accepted by ``const`` views, but read-only
+buffers are not accepted for non-const, writable views::
+
+    cdef double[:] myslice   # a normal read-write memory view
+
+    a = np.linspace(0, 10, num=50)
+    a.setflags(write=False)
+    myslice = a   # ERROR: requesting writable memory view from read-only buffer!
 
 
 Comparison to the old buffer support
diff --git a/tests/memoryview/numpy_memoryview_readonly.pyx b/tests/memoryview/numpy_memoryview_readonly.pyx
index 195b66809..f59339f54 100644
--- a/tests/memoryview/numpy_memoryview_readonly.pyx
+++ b/tests/memoryview/numpy_memoryview_readonly.pyx
@@ -9,9 +9,8 @@ def new_array():
 ARRAY = new_array()
 
 
-cdef getmax(double[:] x):
-    """Example code, should work with both
-    ro and rw memoryviews"""
+cdef getmax(const double[:] x):
+    """Example code, should work with both ro and rw memoryviews"""
     cdef double max_val = -float('inf')
     for val in x:
         if val > max_val:
@@ -25,7 +24,7 @@ cdef update_array(double [:] x):
 
 
 cdef getconst(const double [:] x):
-    """Should only accept ro memoryviews"""
+    """Should accept ro memoryviews"""
     return x[0]
 
 
-- 
2.30.9