Issue #11145: Fixed miscellaneous issues with C-style formatting of types
with custom __oct__ and __hex__.
diff --git a/Objects/stringobject.c b/Objects/stringobject.c
index 60104d5..4e38735 100644
--- a/Objects/stringobject.c
+++ b/Objects/stringobject.c
@@ -4006,26 +4006,30 @@
_PyString_FormatLong(PyObject *val, int flags, int prec, int type,
char **pbuf, int *plen)
{
- PyObject *result = NULL;
+ PyObject *result = NULL, *r1;
+ const char *s;
char *buf;
Py_ssize_t i;
int sign; /* 1 if '-', else 0 */
int len; /* number of characters */
Py_ssize_t llen;
- int numdigits; /* len == numnondigits + numdigits */
- int numnondigits = 0;
+ int numdigits; /* len == numnondigits + skipped + numdigits */
+ int numnondigits, skipped, filled;
+ 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':
- numnondigits = 2;
+ method = "hex";
result = Py_TYPE(val)->tp_as_number->nb_hex(val);
break;
default:
@@ -4034,97 +4038,109 @@
if (!result)
return NULL;
- buf = PyString_AsString(result);
- if (!buf) {
+ if (PyString_AsStringAndSize(result, (char **)&s, &llen) < 0) {
Py_DECREF(result);
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);
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 && s[len-1] == 'L') {
--len;
- buf[len] = '\0';
+ if (len == 0)
+ goto error;
}
- sign = buf[0] == '-';
- numnondigits += sign;
- numdigits = len - numnondigits;
- assert(numdigits > 0);
+ sign = s[0] == '-';
+ numnondigits = sign;
- /* Get rid of base marker unless F_ALT */
- if ((flags & F_ALT) == 0) {
- /* Need to skip 0x, 0X or 0. */
- int skipped = 0;
- switch (type) {
- case 'o':
- assert(buf[sign] == '0');
- /* If 0 is only digit, leave it alone. */
- if (numdigits > 1) {
- skipped = 1;
- --numdigits;
- }
- break;
- case 'x':
- case 'X':
- assert(buf[sign] == '0');
- assert(buf[sign + 1] == 'x');
+ /* Need to skip 0x, 0X or 0. */
+ skipped = 0;
+ switch (type) {
+ case 'o':
+ if (s[sign] != '0')
+ goto error;
+ /* If 0 is only digit, leave it alone. */
+ if ((flags & F_ALT) == 0 && len - sign > 1)
+ skipped = 1;
+ break;
+ case 'x':
+ case 'X':
+ if (s[sign] != '0' || (s[sign + 1] != 'x' && s[sign + 1] != 'X'))
+ goto error;
+ if ((flags & F_ALT) == 0)
skipped = 2;
- numnondigits -= 2;
- break;
- }
- if (skipped) {
- buf += skipped;
- len -= skipped;
- if (sign)
- buf[0] = '-';
- }
- assert(len == numnondigits + numdigits);
- assert(numdigits > 0);
+ else
+ numnondigits += 2;
+ break;
}
+ numdigits = len - numnondigits - skipped;
+ if (numdigits <= 0)
+ goto error;
- /* Fill with leading zeroes to meet minimum width. */
- if (prec > numdigits) {
- PyObject *r1 = PyString_FromStringAndSize(NULL,
- numnondigits + prec);
- char *b1;
- if (!r1) {
- Py_DECREF(result);
+ filled = prec - numdigits;
+ if (filled < 0)
+ filled = 0;
+ len = numnondigits + filled + numdigits;
+
+ /* To modify the string in-place, there can only be one reference. */
+ if (skipped >= filled &&
+ PyString_CheckExact(result) &&
+ Py_REFCNT(result) == 1 &&
+ !PyString_CHECK_INTERNED(result))
+ {
+ r1 = NULL;
+ buf = (char *)s + skipped - filled;
+ }
+ else {
+ r1 = result;
+ result = PyString_FromStringAndSize(NULL, len);
+ if (!result) {
+ Py_DECREF(r1);
return NULL;
}
- b1 = PyString_AS_STRING(r1);
- for (i = 0; i < numnondigits; ++i)
- *b1++ = *buf++;
- for (i = 0; i < prec - numdigits; i++)
- *b1++ = '0';
- for (i = 0; i < numdigits; i++)
- *b1++ = *buf++;
- *b1 = '\0';
- Py_DECREF(result);
- result = r1;
buf = PyString_AS_STRING(result);
- len = numnondigits + prec;
}
+ for (i = numnondigits; --i >= 0;)
+ buf[i] = s[i];
+ buf += numnondigits;
+ s += numnondigits + skipped;
+ for (i = 0; i < filled; i++)
+ *buf++ = '0';
+ if (r1 == NULL) {
+ assert(buf == s);
+ buf += numdigits;
+ }
+ else {
+ for (i = 0; i < numdigits; i++)
+ *buf++ = *s++;
+ }
+ *buf = '\0';
+ buf -= len;
+ Py_XDECREF(r1);
+
/* Fix up case for hex conversions. */
if (type == 'X') {
/* Need to convert all lower case letters to upper case.
and need to convert 0x to 0X (and -0x to -0X). */
- for (i = 0; i < len; i++)
- if (buf[i] >= 'a' && buf[i] <= 'x')
+ for (i = 0; i < len; i++) {
+ if (buf[i] >= 'a' && buf[i] <= 'z')
buf[i] -= 'a'-'A';
+ }
}
*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)