MERGE: startswith and endswith don't accept None as slice index. Patch by Torsten Becker. (closes #11828)

This commit is contained in:
Jesus Cea 2011-04-20 17:59:29 +02:00
commit c1ceb64e41
8 changed files with 197 additions and 64 deletions

View File

@ -1183,6 +1183,63 @@ class MixinStrUnicodeUserStringTest:
self.checkraises(ValueError, S, 'rpartition', '')
self.checkraises(TypeError, S, 'rpartition', None)
def test_none_arguments(self):
# issue 11828
s = 'hello'
self.checkequal(2, s, 'find', 'l', None)
self.checkequal(3, s, 'find', 'l', -2, None)
self.checkequal(2, s, 'find', 'l', None, -2)
self.checkequal(0, s, 'find', 'h', None, None)
self.checkequal(3, s, 'rfind', 'l', None)
self.checkequal(3, s, 'rfind', 'l', -2, None)
self.checkequal(2, s, 'rfind', 'l', None, -2)
self.checkequal(0, s, 'rfind', 'h', None, None)
self.checkequal(2, s, 'index', 'l', None)
self.checkequal(3, s, 'index', 'l', -2, None)
self.checkequal(2, s, 'index', 'l', None, -2)
self.checkequal(0, s, 'index', 'h', None, None)
self.checkequal(3, s, 'rindex', 'l', None)
self.checkequal(3, s, 'rindex', 'l', -2, None)
self.checkequal(2, s, 'rindex', 'l', None, -2)
self.checkequal(0, s, 'rindex', 'h', None, None)
self.checkequal(2, s, 'count', 'l', None)
self.checkequal(1, s, 'count', 'l', -2, None)
self.checkequal(1, s, 'count', 'l', None, -2)
self.checkequal(0, s, 'count', 'x', None, None)
self.checkequal(True, s, 'endswith', 'o', None)
self.checkequal(True, s, 'endswith', 'lo', -2, None)
self.checkequal(True, s, 'endswith', 'l', None, -2)
self.checkequal(False, s, 'endswith', 'x', None, None)
self.checkequal(True, s, 'startswith', 'h', None)
self.checkequal(True, s, 'startswith', 'l', -2, None)
self.checkequal(True, s, 'startswith', 'h', None, -2)
self.checkequal(False, s, 'startswith', 'x', None, None)
def test_find_etc_raise_correct_error_messages(self):
# issue 11828
s = 'hello'
x = 'x'
self.assertRaisesRegexp(TypeError, r'^find\(', s.find,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^rfind\(', s.rfind,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^index\(', s.index,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^rindex\(', s.rindex,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^count\(', s.count,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^startswith\(', s.startswith,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'^endswith\(', s.endswith,
x, None, None, None)
class MixinStrUnicodeTest:
# Additional tests that only work with str and unicode.

View File

@ -478,6 +478,68 @@ class BaseBytesTest(unittest.TestCase):
self.assertRaises(ValueError, self.type2test.maketrans, b'abc', b'xyzq')
self.assertRaises(TypeError, self.type2test.maketrans, 'abc', 'def')
def test_none_arguments(self):
# issue 11828
b = self.type2test(b'hello')
l = self.type2test(b'l')
h = self.type2test(b'h')
x = self.type2test(b'x')
o = self.type2test(b'o')
self.assertEqual(2, b.find(l, None))
self.assertEqual(3, b.find(l, -2, None))
self.assertEqual(2, b.find(l, None, -2))
self.assertEqual(0, b.find(h, None, None))
self.assertEqual(3, b.rfind(l, None))
self.assertEqual(3, b.rfind(l, -2, None))
self.assertEqual(2, b.rfind(l, None, -2))
self.assertEqual(0, b.rfind(h, None, None))
self.assertEqual(2, b.index(l, None))
self.assertEqual(3, b.index(l, -2, None))
self.assertEqual(2, b.index(l, None, -2))
self.assertEqual(0, b.index(h, None, None))
self.assertEqual(3, b.rindex(l, None))
self.assertEqual(3, b.rindex(l, -2, None))
self.assertEqual(2, b.rindex(l, None, -2))
self.assertEqual(0, b.rindex(h, None, None))
self.assertEqual(2, b.count(l, None))
self.assertEqual(1, b.count(l, -2, None))
self.assertEqual(1, b.count(l, None, -2))
self.assertEqual(0, b.count(x, None, None))
self.assertEqual(True, b.endswith(o, None))
self.assertEqual(True, b.endswith(o, -2, None))
self.assertEqual(True, b.endswith(l, None, -2))
self.assertEqual(False, b.endswith(x, None, None))
self.assertEqual(True, b.startswith(h, None))
self.assertEqual(True, b.startswith(l, -2, None))
self.assertEqual(True, b.startswith(h, None, -2))
self.assertEqual(False, b.startswith(x, None, None))
def test_find_etc_raise_correct_error_messages(self):
# issue 11828
b = self.type2test(b'hello')
x = self.type2test(b'x')
self.assertRaisesRegexp(TypeError, r'\bfind\b', b.find,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\brfind\b', b.rfind,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\bindex\b', b.index,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\brindex\b', b.rindex,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\bcount\b', b.count,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\bstartswith\b', b.startswith,
x, None, None, None)
self.assertRaisesRegexp(TypeError, r'\bendswith\b', b.endswith,
x, None, None, None)
class BytesTest(BaseBytesTest):
type2test = bytes

View File

@ -67,6 +67,7 @@ Donald Beaudry
David Beazley
Robin Becker
Neal Becker
Torsten Becker
Bill Bedford
Stefan Behnel
Reimer Behrends

View File

@ -88,6 +88,9 @@ Core and Builtins
- Issue #11272: On Windows, input() strips '\r' (and not only '\n'), and
sys.stdin uses universal newline (replace '\r\n' by '\n').
- issue #11828: startswith and endswith don't accept None as slice index.
Patch by Torsten Becker.
- Issue #10830: Fix PyUnicode_FromFormatV("%c") for non-BMP characters on
narrow build.

View File

@ -1081,8 +1081,8 @@ bytearray_find_internal(PyByteArrayObject *self, PyObject *args, int dir)
Py_ssize_t start=0, end=PY_SSIZE_T_MAX;
Py_ssize_t res;
if (!PyArg_ParseTuple(args, "O|O&O&:find/rfind/index/rindex", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("find/rfind/index/rindex",
args, &subobj, &start, &end))
return -2;
if (_getbuffer(subobj, &subbuf) < 0)
return -2;
@ -1132,8 +1132,7 @@ bytearray_count(PyByteArrayObject *self, PyObject *args)
Py_buffer vsub;
PyObject *count_obj;
if (!PyArg_ParseTuple(args, "O|O&O&:count", &sub_obj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("count", args, &sub_obj, &start, &end))
return NULL;
if (_getbuffer(sub_obj, &vsub) < 0)
@ -1315,8 +1314,7 @@ bytearray_startswith(PyByteArrayObject *self, PyObject *args)
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
@ -1355,8 +1353,7 @@ bytearray_endswith(PyByteArrayObject *self, PyObject *args)
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("endswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;

View File

@ -1240,19 +1240,9 @@ bytes_find_internal(PyBytesObject *self, PyObject *args, int dir)
const char *sub;
Py_ssize_t sub_len;
Py_ssize_t start=0, end=PY_SSIZE_T_MAX;
PyObject *obj_start=Py_None, *obj_end=Py_None;
if (!PyArg_ParseTuple(args, "O|OO:find/rfind/index/rindex", &subobj,
&obj_start, &obj_end))
return -2;
/* To support None in "start" and "end" arguments, meaning
the same as if they were not passed.
*/
if (obj_start != Py_None)
if (!_PyEval_SliceIndex(obj_start, &start))
return -2;
if (obj_end != Py_None)
if (!_PyEval_SliceIndex(obj_end, &end))
if (!stringlib_parse_args_finds("find/rfind/index/rindex",
args, &subobj, &start, &end))
return -2;
if (PyBytes_Check(subobj)) {
@ -1499,8 +1489,7 @@ bytes_count(PyBytesObject *self, PyObject *args)
Py_ssize_t sub_len;
Py_ssize_t start = 0, end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|O&O&:count", &sub_obj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("count", args, &sub_obj, &start, &end))
return NULL;
if (PyBytes_Check(sub_obj)) {
@ -2218,8 +2207,7 @@ bytes_startswith(PyBytesObject *self, PyObject *args)
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
@ -2259,8 +2247,7 @@ bytes_endswith(PyBytesObject *self, PyObject *args)
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("endswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;

View File

@ -93,32 +93,33 @@ stringlib_contains_obj(PyObject* str, PyObject* sub)
#endif /* STRINGLIB_WANT_CONTAINS_OBJ */
#if STRINGLIB_IS_UNICODE
/*
This function is a helper for the "find" family (find, rfind, index,
rindex) of unicodeobject.c file, because they all have the same
behaviour for the arguments.
rindex) and for count, startswith and endswith, because they all have
the same behaviour for the arguments.
It does not touch the variables received until it knows everything
is ok.
Note that we receive a pointer to the pointer of the substring object,
so when we create that object in this function we don't DECREF it,
because it continues living in the caller functions (those functions,
after finishing using the substring, must DECREF it).
*/
#define FORMAT_BUFFER_SIZE 50
Py_LOCAL_INLINE(int)
_ParseTupleFinds (PyObject *args, PyObject **substring,
Py_ssize_t *start, Py_ssize_t *end) {
PyObject *tmp_substring;
stringlib_parse_args_finds(const char * function_name, PyObject *args,
PyObject **subobj,
Py_ssize_t *start, Py_ssize_t *end)
{
PyObject *tmp_subobj;
Py_ssize_t tmp_start = 0;
Py_ssize_t tmp_end = PY_SSIZE_T_MAX;
PyObject *obj_start=Py_None, *obj_end=Py_None;
char format[FORMAT_BUFFER_SIZE] = "O|OO:";
size_t len = strlen(format);
if (!PyArg_ParseTuple(args, "O|OO:find", &tmp_substring,
&obj_start, &obj_end))
strncpy(format + len, function_name, FORMAT_BUFFER_SIZE - len - 1);
format[FORMAT_BUFFER_SIZE - 1] = '\0';
if (!PyArg_ParseTuple(args, format, &tmp_subobj, &obj_start, &obj_end))
return 0;
/* To support None in "start" and "end" arguments, meaning
@ -131,15 +132,43 @@ _ParseTupleFinds (PyObject *args, PyObject **substring,
if (!_PyEval_SliceIndex(obj_end, &tmp_end))
return 0;
*start = tmp_start;
*end = tmp_end;
*subobj = tmp_subobj;
return 1;
}
#undef FORMAT_BUFFER_SIZE
#if STRINGLIB_IS_UNICODE
/*
Wraps stringlib_parse_args_finds() and additionally ensures that the
first argument is a unicode object.
Note that we receive a pointer to the pointer of the substring object,
so when we create that object in this function we don't DECREF it,
because it continues living in the caller functions (those functions,
after finishing using the substring, must DECREF it).
*/
Py_LOCAL_INLINE(int)
stringlib_parse_args_finds_unicode(const char * function_name, PyObject *args,
PyUnicodeObject **substring,
Py_ssize_t *start, Py_ssize_t *end)
{
PyObject *tmp_substring;
if(stringlib_parse_args_finds(function_name, args, &tmp_substring,
start, end)) {
tmp_substring = PyUnicode_FromObject(tmp_substring);
if (!tmp_substring)
return 0;
*start = tmp_start;
*end = tmp_end;
*substring = tmp_substring;
*substring = (PyUnicodeObject *)tmp_substring;
return 1;
}
return 0;
}
#endif /* STRINGLIB_IS_UNICODE */

View File

@ -7515,13 +7515,8 @@ unicode_count(PyUnicodeObject *self, PyObject *args)
Py_ssize_t end = PY_SSIZE_T_MAX;
PyObject *result;
if (!PyArg_ParseTuple(args, "O|O&O&:count", &substring,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
return NULL;
substring = (PyUnicodeObject *)PyUnicode_FromObject(
(PyObject *)substring);
if (substring == NULL)
if (!stringlib_parse_args_finds_unicode("count", args, &substring,
&start, &end))
return NULL;
ADJUST_INDICES(start, end, self->length);
@ -7658,12 +7653,13 @@ Return -1 on failure.");
static PyObject *
unicode_find(PyUnicodeObject *self, PyObject *args)
{
PyObject *substring;
PyUnicodeObject *substring;
Py_ssize_t start;
Py_ssize_t end;
Py_ssize_t result;
if (!_ParseTupleFinds(args, &substring, &start, &end))
if (!stringlib_parse_args_finds_unicode("find", args, &substring,
&start, &end))
return NULL;
result = stringlib_find_slice(
@ -7720,11 +7716,12 @@ static PyObject *
unicode_index(PyUnicodeObject *self, PyObject *args)
{
Py_ssize_t result;
PyObject *substring;
PyUnicodeObject *substring;
Py_ssize_t start;
Py_ssize_t end;
if (!_ParseTupleFinds(args, &substring, &start, &end))
if (!stringlib_parse_args_finds_unicode("index", args, &substring,
&start, &end))
return NULL;
result = stringlib_find_slice(
@ -8583,12 +8580,13 @@ Return -1 on failure.");
static PyObject *
unicode_rfind(PyUnicodeObject *self, PyObject *args)
{
PyObject *substring;
PyUnicodeObject *substring;
Py_ssize_t start;
Py_ssize_t end;
Py_ssize_t result;
if (!_ParseTupleFinds(args, &substring, &start, &end))
if (!stringlib_parse_args_finds_unicode("rfind", args, &substring,
&start, &end))
return NULL;
result = stringlib_rfind_slice(
@ -8610,12 +8608,13 @@ Like S.rfind() but raise ValueError when the substring is not found.");
static PyObject *
unicode_rindex(PyUnicodeObject *self, PyObject *args)
{
PyObject *substring;
PyUnicodeObject *substring;
Py_ssize_t start;
Py_ssize_t end;
Py_ssize_t result;
if (!_ParseTupleFinds(args, &substring, &start, &end))
if (!stringlib_parse_args_finds_unicode("rindex", args, &substring,
&start, &end))
return NULL;
result = stringlib_rfind_slice(
@ -9083,8 +9082,7 @@ unicode_startswith(PyUnicodeObject *self,
Py_ssize_t end = PY_SSIZE_T_MAX;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
@ -9129,8 +9127,7 @@ unicode_endswith(PyUnicodeObject *self,
Py_ssize_t end = PY_SSIZE_T_MAX;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!stringlib_parse_args_finds("endswith", args, &subobj, &start, &end))
return NULL;
if (PyTuple_Check(subobj)) {
Py_ssize_t i;