Repository: cupy/cupy

Issue:
`TestNpz.test_dump` test failure
https://jenkins.preferred.jp/job/chainer/job/cupy_pr/161/TEST=cupy-py3,label=mn1-p100/console



Discussion:

Resolving patch:
diff --git a/cupy/io/npz.py b/cupy/io/npz.py
--- a/cupy/io/npz.py
+++ b/cupy/io/npz.py
@@ -1,8 +1,13 @@
+import warnings
+
 import numpy
 
 import cupy
 
 
+_support_allow_pickle = (numpy.lib.NumpyVersion(numpy.__version__) >= '1.10.0')
+
+
 class NpzFile(object):
 
     def __init__(self, npz_file):
@@ -23,7 +28,7 @@ def close(self):
         self.npz_file.close()
 
 
-def load(file, mmap_mode=None):
+def load(file, mmap_mode=None, allow_pickle=None):
     """Loads arrays or pickled objects from ``.npy``, ``.npz`` or pickled file.
 
     This function just calls ``numpy.load`` and then sends the arrays to the
@@ -35,6 +40,16 @@ def load(file, mmap_mode=None):
         mmap_mode (None, 'r+', 'r', 'w+', 'c'): If not ``None``, memory-map the
             file to construct an intermediate :class:`numpy.ndarray` object and
             transfer it to the current device.
+        allow_pickle (bool): Allow loading pickled object arrays stored in npy
+            files. Reasons for disallowing pickles include security, as
+            loading pickled data can execute arbitrary code. If pickles are
+            disallowed, loading object arrays will fail.
+            Please be aware that CuPy does not support arrays with dtype of
+            `object`.
+            The default is False.
+            This option is available only for NumPy 1.10 or later.
+            In NumPy 1.9, this option cannot be specified (loading pickled
+            objects is always allowed).
 
     Returns:
         CuPy array or NpzFile object depending on the type of the file. NpzFile
@@ -44,7 +59,14 @@ def load(file, mmap_mode=None):
     .. seealso:: :func:`numpy.load`
 
     """
-    obj = numpy.load(file, mmap_mode)
+    if _support_allow_pickle:
+        allow_pickle = False if allow_pickle is None else allow_pickle
+        obj = numpy.load(file, mmap_mode, allow_pickle)
+    else:
+        if allow_pickle is not None:
+            warnings.warn('allow_pickle option is not supported in NumPy 1.9')
+        obj = numpy.load(file, mmap_mode)
+
     if isinstance(obj, numpy.ndarray):
         return cupy.array(obj)
     elif isinstance(obj, numpy.lib.npyio.NpzFile):
@@ -53,18 +75,35 @@ def load(file, mmap_mode=None):
         return obj
 
 
-def save(file, arr):
+def save(file, arr, allow_pickle=None):
     """Saves an array to a binary file in ``.npy`` format.
 
     Args:
         file (file or str): File or filename to save.
         arr (array_like): Array to save. It should be able to feed to
             :func:`cupy.asnumpy`.
+        allow_pickle (bool): Allow saving object arrays using Python pickles.
+            Reasons for disallowing pickles include security (loading pickled
+            data can execute arbitrary code) and portability (pickled objects
+            may not be loadable on different Python installations, for example
+            if the stored objects require libraries that are not available,
+            and not all pickled data is compatible between Python 2 and Python
+            3).
+            The default is True.
+            This option is available only for NumPy 1.10 or later.
+            In NumPy 1.9, this option cannot be specified (saving objects
+            using pickles is always allowed).
 
     .. seealso:: :func:`numpy.save`
 
     """
-    numpy.save(file, cupy.asnumpy(arr))
+    if _support_allow_pickle:
+        allow_pickle = True if allow_pickle is None else allow_pickle
+        numpy.save(file, cupy.asnumpy(arr), allow_pickle)
+    else:
+        if allow_pickle is not None:
+            warnings.warn('allow_pickle option is not supported in NumPy 1.9')
+        numpy.save(file, cupy.asnumpy(arr))
 
 
 def savez(file, *args, **kwds):


Test patch:
diff --git a/tests/cupy_tests/io_tests/test_npz.py b/tests/cupy_tests/io_tests/test_npz.py
--- a/tests/cupy_tests/io_tests/test_npz.py
+++ b/tests/cupy_tests/io_tests/test_npz.py
@@ -23,6 +23,38 @@ def test_save_load(self, dtype):
 
         testing.assert_array_equal(a, b)
 
+    @testing.with_requires('numpy>=1.10')
+    def test_save_pickle(self):
+        data = object()
+
+        sio = six.BytesIO()
+        with self.assertRaises(ValueError):
+            cupy.save(sio, data, allow_pickle=False)
+        sio.close()
+
+        sio = six.BytesIO()
+        cupy.save(sio, data, allow_pickle=True)
+        sio.close()
+
+    @testing.with_requires('numpy>=1.10')
+    def test_load_pickle(self):
+        a = testing.shaped_arange((2, 3, 4), dtype=cupy.float32)
+
+        sio = six.BytesIO()
+        a.dump(sio)
+        s = sio.getvalue()
+        sio.close()
+
+        sio = six.BytesIO(s)
+        b = cupy.load(sio, allow_pickle=True)
+        testing.assert_array_equal(a, b)
+        sio.close()
+
+        sio = six.BytesIO(s)
+        with self.assertRaises(ValueError):
+            cupy.load(sio, allow_pickle=False)
+        sio.close()
+
     @testing.for_all_dtypes()
     def check_savez(self, savez, dtype):
         a1 = testing.shaped_arange((2, 3, 4), dtype=dtype)
@@ -56,6 +88,7 @@ def test_pickle(self, dtype):
         testing.assert_array_equal(a, b)
 
     @testing.for_all_dtypes()
+    @testing.with_requires('numpy>=1.10')
     def test_dump(self, dtype):
         a = testing.shaped_arange((2, 3, 4), dtype=dtype)
 
@@ -65,7 +98,7 @@ def test_dump(self, dtype):
         sio.close()
 
         sio = six.BytesIO(s)
-        b = cupy.load(sio)
+        b = cupy.load(sio, allow_pickle=True)
         sio.close()
 
         testing.assert_array_equal(a, b)