diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -291,6 +291,22 @@ self.assertEqual(self.type2test(b".").join([b"ab", b"cd"]), b"ab.cd") # XXX more... + + def test_join_with_memoryviews(self): + # see issue #15958 + if self.type2test is bytearray: + self.skipTest("join w/ memoryview not implemented yet") + sep1, sep2 = self.type2test(b''), self.type2test(b'-') + mfoo, mbar = memoryview(b'foo'), memoryview(b'bar') + bfoo, bbar = self.type2test(b'foo'), self.type2test(b'bar') + self.assertEqual(sep1.join([mfoo]), bfoo) + self.assertEqual(sep1.join([mfoo, bbar]), b'foobar') + self.assertEqual(sep1.join([bfoo, mbar]), b'foobar') + self.assertEqual(sep1.join([mfoo, mbar]), b'foobar') + self.assertEqual(sep2.join([mfoo, mbar]), b'foo-bar') + self.assertEqual(sep2.join([mfoo, bbar, mfoo]), b'foo-bar-foo') + + def test_count(self): b = self.type2test(b'mississippi') i = 105 diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -1117,6 +1117,7 @@ size_t sz = 0; Py_ssize_t i; PyObject *seq, *item; + Py_buffer view; seq = PySequence_Fast(orig, ""); if (seq == NULL) { @@ -1142,19 +1143,19 @@ * Do a pre-pass to figure out the total amount of space we'll * need (sz), and see whether all argument are bytes. */ - /* XXX Shouldn't we use _getbuffer() on these items instead? */ for (i = 0; i < seqlen; i++) { const size_t old_sz = sz; item = PySequence_Fast_GET_ITEM(seq, i); - if (!PyBytes_Check(item) && !PyByteArray_Check(item)) { + if (_getbuffer(item, &view) < 0) { PyErr_Format(PyExc_TypeError, - "sequence item %zd: expected bytes," - " %.80s found", + "sequence item %zd: expected bytes, bytearray, " + "or an object with a buffer interface, %.80s found", i, Py_TYPE(item)->tp_name); Py_DECREF(seq); return NULL; } - sz += Py_SIZE(item); + sz += view.len; + PyBuffer_Release(&view); if (i != 0) sz += seplen; if (sz < old_sz || sz > PY_SSIZE_T_MAX) { @@ -1177,20 +1178,19 @@ nowhere in this function where we release the GIL. */ p = PyBytes_AS_STRING(res); for (i = 0; i < seqlen; ++i) { - size_t n; - char *q; if (i) { Py_MEMCPY(p, sep, seplen); p += seplen; } item = PySequence_Fast_GET_ITEM(seq, i); - n = Py_SIZE(item); - if (PyBytes_Check(item)) - q = PyBytes_AS_STRING(item); - else - q = PyByteArray_AS_STRING(item); - Py_MEMCPY(p, q, n); - p += n; + if (_getbuffer(item, &view) < 0) { + Py_DECREF(seq); + Py_DECREF(res); + return NULL; + } + Py_MEMCPY(p, view.buf, view.len); + p += view.len; + PyBuffer_Release(&view); } Py_DECREF(seq);