diff -r 3537994fa43b Lib/test/test_format.py --- a/Lib/test/test_format.py Sat Nov 15 13:21:01 2014 +0200 +++ b/Lib/test/test_format.py Mon Nov 17 14:21:42 2014 +0200 @@ -299,6 +299,44 @@ class FormatTest(unittest.TestCase): else: raise TestFailed, '"%*d"%(maxsize, -127) should fail' + def test_invalid_special_methods(self): + tests = [] + for f in 'sriduoxXfge': + tests.append(('%' + f, 1, TypeError)) + tests.append(('%#' + f, 1, TypeError)) + for r in ['', '-', 'L', '-L']: + for f in 'iduoxX': + tests.append(('%' + f, r, ValueError)) + tests.append(('%#' + f, r, ValueError)) + tests.append(('%o', 'abc', ValueError)) + for r in ('abc', '0abc', '0x', '0xL'): + for f in 'xX': + tests.append(('%' + f, r, ValueError)) + for r in ('0x', '0xL'): + for f in 'xX': + tests.append(('%#' + f, r, ValueError)) + + class X(long): + def __repr__(self): + return result + def __str__(self): + return result + def __oct__(self): + return result + def __hex__(self): + return result + def __float__(self): + return result + for fmt, result, exc in tests: + try: + fmt % X() + except exc: + pass + else: + self.fail('%s not raised for %r format of %r' % + (exc.__name__, fmt, result)) + + def test_main(): test_support.run_unittest(FormatTest) diff -r 3537994fa43b Objects/stringobject.c --- a/Objects/stringobject.c Sat Nov 15 13:21:01 2014 +0200 +++ b/Objects/stringobject.c Mon Nov 17 14:21:42 2014 +0200 @@ -4015,17 +4015,21 @@ PyObject* Py_ssize_t llen; int numdigits; /* len == numnondigits + numdigits */ int numnondigits = 0; + const char *method; switch (type) { case 'd': case 'u': + method = "str"; result = Py_TYPE(val)->tp_str(val); break; case 'o': + method = "oct"; result = Py_TYPE(val)->tp_as_number->nb_oct(val); break; case 'x': case 'X': + method = "hex"; numnondigits = 2; result = Py_TYPE(val)->tp_as_number->nb_hex(val); break; @@ -4041,25 +4045,40 @@ PyObject* return NULL; } - /* To modify the string in-place, there can only be one reference. */ - if (Py_REFCNT(result) != 1) { - PyErr_BadInternalCall(); - return NULL; - } - llen = PyString_Size(result); + llen = PyString_GET_SIZE(result); if (llen > INT_MAX) { PyErr_SetString(PyExc_ValueError, "string too large in _PyString_FormatLong"); + Py_DECREF(result); return NULL; } len = (int)llen; - if (buf[len-1] == 'L') { + if (len > 0 && buf[len-1] == 'L') { --len; - buf[len] = '\0'; + if (len == 0) + goto error; + if (Py_REFCNT(result) == 1) + buf[len] = '\0'; + } + /* To modify the string in-place, there can only be one reference. */ + if ((type == 'X' || len < (int)llen) && Py_REFCNT(result) != 1) { + /* Do not use PyString_FromStringAndSize(buf, len) because it can + * return existing object if len <= 1. */ + PyObject *r1 = PyString_FromStringAndSize(NULL, len); + if (!r1) { + Py_DECREF(result); + return NULL; + } + assert(len == 0 || Py_REFCNT(r1) == 1); + memcpy(PyString_AS_STRING(r1), buf, len); + Py_DECREF(result); + result = r1; + buf = PyString_AS_STRING(result); } sign = buf[0] == '-'; numnondigits += sign; numdigits = len - numnondigits; - assert(numdigits > 0); + if (numdigits <= 0) + goto error; /* Get rid of base marker unless F_ALT */ if ((flags & F_ALT) == 0) { @@ -4067,7 +4086,8 @@ PyObject* int skipped = 0; switch (type) { case 'o': - assert(buf[sign] == '0'); + if (buf[sign] != '0') + goto error; /* If 0 is only digit, leave it alone. */ if (numdigits > 1) { skipped = 1; @@ -4076,8 +4096,9 @@ PyObject* break; case 'x': case 'X': - assert(buf[sign] == '0'); - assert(buf[sign + 1] == 'x'); + if (buf[sign] != '0' || + buf[sign + 1] != 'x') + goto error; skipped = 2; numnondigits -= 2; break; @@ -4126,6 +4147,13 @@ PyObject* *pbuf = buf; *plen = len; return result; + +error: + PyErr_Format(PyExc_ValueError, + "%%%c format: invalid result of __%s__ (type=%.200s)", + type, method, Py_TYPE(val)->tp_name); + Py_DECREF(result); + return NULL; } Py_LOCAL_INLINE(int)