Skip to content

Commit c30f27d

Browse files
Issue #11145: Fixed miscellaneous issues with C-style formatting of types
with custom __oct__ and __hex__.
1 parent b398d2c commit c30f27d

File tree

3 files changed

+126
-69
lines changed

3 files changed

+126
-69
lines changed

Lib/test/test_format.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,44 @@ def __oct__(self):
300300
else:
301301
raise TestFailed, '"%*d"%(maxsize, -127) should fail'
302302

303+
def test_invalid_special_methods(self):
304+
tests = []
305+
for f in 'sriduoxXfge':
306+
tests.append(('%' + f, 1, TypeError))
307+
tests.append(('%#' + f, 1, TypeError))
308+
for r in ['', '-', 'L', '-L']:
309+
for f in 'iduoxX':
310+
tests.append(('%' + f, r, ValueError))
311+
tests.append(('%#' + f, r, ValueError))
312+
tests.append(('%o', 'abc', ValueError))
313+
for r in ('abc', '0abc', '0x', '0xL'):
314+
for f in 'xX':
315+
tests.append(('%' + f, r, ValueError))
316+
for r in ('0x', '0xL'):
317+
for f in 'xX':
318+
tests.append(('%#' + f, r, ValueError))
319+
320+
class X(long):
321+
def __repr__(self):
322+
return result
323+
def __str__(self):
324+
return result
325+
def __oct__(self):
326+
return result
327+
def __hex__(self):
328+
return result
329+
def __float__(self):
330+
return result
331+
for fmt, result, exc in tests:
332+
try:
333+
fmt % X()
334+
except exc:
335+
pass
336+
else:
337+
self.fail('%s not raised for %r format of %r' %
338+
(exc.__name__, fmt, result))
339+
340+
303341
def test_main():
304342
test_support.run_unittest(FormatTest)
305343

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 2.7.13?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #11145: Fixed miscellaneous issues with C-style formatting of types
14+
with custom __oct__ and __hex__.
15+
1316
- Issue #24469: Fixed memory leak caused by int subclasses without overridden
1417
tp_free (e.g. C-inherited Cython classes).
1518

Objects/stringobject.c

Lines changed: 85 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4006,26 +4006,30 @@ PyObject*
40064006
_PyString_FormatLong(PyObject *val, int flags, int prec, int type,
40074007
char **pbuf, int *plen)
40084008
{
4009-
PyObject *result = NULL;
4009+
PyObject *result = NULL, *r1;
4010+
const char *s;
40104011
char *buf;
40114012
Py_ssize_t i;
40124013
int sign; /* 1 if '-', else 0 */
40134014
int len; /* number of characters */
40144015
Py_ssize_t llen;
4015-
int numdigits; /* len == numnondigits + numdigits */
4016-
int numnondigits = 0;
4016+
int numdigits; /* len == numnondigits + skipped + numdigits */
4017+
int numnondigits, skipped, filled;
4018+
const char *method;
40174019

40184020
switch (type) {
40194021
case 'd':
40204022
case 'u':
4023+
method = "str";
40214024
result = Py_TYPE(val)->tp_str(val);
40224025
break;
40234026
case 'o':
4027+
method = "oct";
40244028
result = Py_TYPE(val)->tp_as_number->nb_oct(val);
40254029
break;
40264030
case 'x':
40274031
case 'X':
4028-
numnondigits = 2;
4032+
method = "hex";
40294033
result = Py_TYPE(val)->tp_as_number->nb_hex(val);
40304034
break;
40314035
default:
@@ -4034,97 +4038,109 @@ _PyString_FormatLong(PyObject *val, int flags, int prec, int type,
40344038
if (!result)
40354039
return NULL;
40364040

4037-
buf = PyString_AsString(result);
4038-
if (!buf) {
4041+
if (PyString_AsStringAndSize(result, (char **)&s, &llen) < 0) {
40394042
Py_DECREF(result);
40404043
return NULL;
40414044
}
4042-
4043-
/* To modify the string in-place, there can only be one reference. */
4044-
if (Py_REFCNT(result) != 1) {
4045-
PyErr_BadInternalCall();
4046-
return NULL;
4047-
}
4048-
llen = PyString_Size(result);
40494045
if (llen > INT_MAX) {
40504046
PyErr_SetString(PyExc_ValueError, "string too large in _PyString_FormatLong");
4047+
Py_DECREF(result);
40514048
return NULL;
40524049
}
40534050
len = (int)llen;
4054-
if (buf[len-1] == 'L') {
4051+
if (len > 0 && s[len-1] == 'L') {
40554052
--len;
4056-
buf[len] = '\0';
4057-
}
4058-
sign = buf[0] == '-';
4059-
numnondigits += sign;
4060-
numdigits = len - numnondigits;
4061-
assert(numdigits > 0);
4062-
4063-
/* Get rid of base marker unless F_ALT */
4064-
if ((flags & F_ALT) == 0) {
4065-
/* Need to skip 0x, 0X or 0. */
4066-
int skipped = 0;
4067-
switch (type) {
4068-
case 'o':
4069-
assert(buf[sign] == '0');
4070-
/* If 0 is only digit, leave it alone. */
4071-
if (numdigits > 1) {
4072-
skipped = 1;
4073-
--numdigits;
4074-
}
4075-
break;
4076-
case 'x':
4077-
case 'X':
4078-
assert(buf[sign] == '0');
4079-
assert(buf[sign + 1] == 'x');
4053+
if (len == 0)
4054+
goto error;
4055+
}
4056+
sign = s[0] == '-';
4057+
numnondigits = sign;
4058+
4059+
/* Need to skip 0x, 0X or 0. */
4060+
skipped = 0;
4061+
switch (type) {
4062+
case 'o':
4063+
if (s[sign] != '0')
4064+
goto error;
4065+
/* If 0 is only digit, leave it alone. */
4066+
if ((flags & F_ALT) == 0 && len - sign > 1)
4067+
skipped = 1;
4068+
break;
4069+
case 'x':
4070+
case 'X':
4071+
if (s[sign] != '0' || (s[sign + 1] != 'x' && s[sign + 1] != 'X'))
4072+
goto error;
4073+
if ((flags & F_ALT) == 0)
40804074
skipped = 2;
4081-
numnondigits -= 2;
4082-
break;
4083-
}
4084-
if (skipped) {
4085-
buf += skipped;
4086-
len -= skipped;
4087-
if (sign)
4088-
buf[0] = '-';
4089-
}
4090-
assert(len == numnondigits + numdigits);
4091-
assert(numdigits > 0);
4075+
else
4076+
numnondigits += 2;
4077+
break;
40924078
}
4079+
numdigits = len - numnondigits - skipped;
4080+
if (numdigits <= 0)
4081+
goto error;
4082+
4083+
filled = prec - numdigits;
4084+
if (filled < 0)
4085+
filled = 0;
4086+
len = numnondigits + filled + numdigits;
40934087

4094-
/* Fill with leading zeroes to meet minimum width. */
4095-
if (prec > numdigits) {
4096-
PyObject *r1 = PyString_FromStringAndSize(NULL,
4097-
numnondigits + prec);
4098-
char *b1;
4099-
if (!r1) {
4100-
Py_DECREF(result);
4088+
/* To modify the string in-place, there can only be one reference. */
4089+
if (skipped >= filled &&
4090+
PyString_CheckExact(result) &&
4091+
Py_REFCNT(result) == 1 &&
4092+
!PyString_CHECK_INTERNED(result))
4093+
{
4094+
r1 = NULL;
4095+
buf = (char *)s + skipped - filled;
4096+
}
4097+
else {
4098+
r1 = result;
4099+
result = PyString_FromStringAndSize(NULL, len);
4100+
if (!result) {
4101+
Py_DECREF(r1);
41014102
return NULL;
41024103
}
4103-
b1 = PyString_AS_STRING(r1);
4104-
for (i = 0; i < numnondigits; ++i)
4105-
*b1++ = *buf++;
4106-
for (i = 0; i < prec - numdigits; i++)
4107-
*b1++ = '0';
4108-
for (i = 0; i < numdigits; i++)
4109-
*b1++ = *buf++;
4110-
*b1 = '\0';
4111-
Py_DECREF(result);
4112-
result = r1;
41134104
buf = PyString_AS_STRING(result);
4114-
len = numnondigits + prec;
41154105
}
41164106

4107+
for (i = numnondigits; --i >= 0;)
4108+
buf[i] = s[i];
4109+
buf += numnondigits;
4110+
s += numnondigits + skipped;
4111+
for (i = 0; i < filled; i++)
4112+
*buf++ = '0';
4113+
if (r1 == NULL) {
4114+
assert(buf == s);
4115+
buf += numdigits;
4116+
}
4117+
else {
4118+
for (i = 0; i < numdigits; i++)
4119+
*buf++ = *s++;
4120+
}
4121+
*buf = '\0';
4122+
buf -= len;
4123+
Py_XDECREF(r1);
4124+
41174125
/* Fix up case for hex conversions. */
41184126
if (type == 'X') {
41194127
/* Need to convert all lower case letters to upper case.
41204128
and need to convert 0x to 0X (and -0x to -0X). */
4121-
for (i = 0; i < len; i++)
4122-
if (buf[i] >= 'a' && buf[i] <= 'x')
4129+
for (i = 0; i < len; i++) {
4130+
if (buf[i] >= 'a' && buf[i] <= 'z')
41234131
buf[i] -= 'a'-'A';
4132+
}
41244133
}
41254134
*pbuf = buf;
41264135
*plen = len;
41274136
return result;
4137+
4138+
error:
4139+
PyErr_Format(PyExc_ValueError,
4140+
"%%%c format: invalid result of __%s__ (type=%.200s)",
4141+
type, method, Py_TYPE(val)->tp_name);
4142+
Py_DECREF(result);
4143+
return NULL;
41284144
}
41294145

41304146
Py_LOCAL_INLINE(int)

0 commit comments

Comments
 (0)