Skip to content

Commit 9f8ad3f

Browse files
serhiy-storchakazhangyangyu
authored andcommitted
bpo-29568: Disable any characters between two percents for escaped percent "%%" in the format string for classic string formatting. (GH-513)
1 parent c393ee8 commit 9f8ad3f

File tree

4 files changed

+57
-58
lines changed

4 files changed

+57
-58
lines changed

Lib/test/test_format.py

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,34 @@ def testcommon(formatstr, args, output=None, limit=None, overflowok=False):
7070
testformat(b_format, b_args, b_output, limit, overflowok)
7171
testformat(ba_format, b_args, ba_output, limit, overflowok)
7272

73+
def test_exc(formatstr, args, exception, excmsg):
74+
try:
75+
testformat(formatstr, args)
76+
except exception as exc:
77+
if str(exc) == excmsg:
78+
if verbose:
79+
print("yes")
80+
else:
81+
if verbose: print('no')
82+
print('Unexpected ', exception, ':', repr(str(exc)))
83+
except:
84+
if verbose: print('no')
85+
print('Unexpected exception')
86+
raise
87+
else:
88+
raise TestFailed('did not get expected exception: %s' % excmsg)
89+
90+
def test_exc_common(formatstr, args, exception, excmsg):
91+
# test str and bytes
92+
test_exc(formatstr, args, exception, excmsg)
93+
test_exc(formatstr.encode('ascii'), args, exception, excmsg)
7394

7495
class FormatTest(unittest.TestCase):
7596

7697
def test_common_format(self):
7798
# test the format identifiers that work the same across
7899
# str, bytes, and bytearrays (integer, float, oct, hex)
100+
testcommon("%%", (), "%")
79101
testcommon("%.1d", (1,), "1")
80102
testcommon("%.*d", (sys.maxsize,1), overflowok=True) # expect overflow
81103
testcommon("%.100d", (1,), '00000000000000000000000000000000000000'
@@ -246,6 +268,20 @@ def test_common_format(self):
246268
testcommon('%g', 1.1, '1.1')
247269
testcommon('%#g', 1.1, '1.10000')
248270

271+
if verbose:
272+
print('Testing exceptions')
273+
test_exc_common('%', (), ValueError, "incomplete format")
274+
test_exc_common('% %s', 1, ValueError,
275+
"unsupported format character '%' (0x25) at index 2")
276+
test_exc_common('%d', '1', TypeError,
277+
"%d format: a number is required, not str")
278+
test_exc_common('%d', b'1', TypeError,
279+
"%d format: a number is required, not bytes")
280+
test_exc_common('%x', '1', TypeError,
281+
"%x format: an integer is required, not str")
282+
test_exc_common('%x', 3.14, TypeError,
283+
"%x format: an integer is required, not float")
284+
249285
def test_str_format(self):
250286
testformat("%r", "\u0378", "'\\u0378'") # non printable
251287
testformat("%a", "\u0378", "'\\u0378'") # non printable
@@ -255,29 +291,10 @@ def test_str_format(self):
255291
# Test exception for unknown format characters, etc.
256292
if verbose:
257293
print('Testing exceptions')
258-
def test_exc(formatstr, args, exception, excmsg):
259-
try:
260-
testformat(formatstr, args)
261-
except exception as exc:
262-
if str(exc) == excmsg:
263-
if verbose:
264-
print("yes")
265-
else:
266-
if verbose: print('no')
267-
print('Unexpected ', exception, ':', repr(str(exc)))
268-
except:
269-
if verbose: print('no')
270-
print('Unexpected exception')
271-
raise
272-
else:
273-
raise TestFailed('did not get expected exception: %s' % excmsg)
274294
test_exc('abc %b', 1, ValueError,
275295
"unsupported format character 'b' (0x62) at index 5")
276296
#test_exc(unicode('abc %\u3000','raw-unicode-escape'), 1, ValueError,
277297
# "unsupported format character '?' (0x3000) at index 5")
278-
test_exc('%d', '1', TypeError, "%d format: a number is required, not str")
279-
test_exc('%x', '1', TypeError, "%x format: an integer is required, not str")
280-
test_exc('%x', 3.14, TypeError, "%x format: an integer is required, not float")
281298
test_exc('%g', '1', TypeError, "must be real number, not str")
282299
test_exc('no format', '1', TypeError,
283300
"not all arguments converted during string formatting")
@@ -334,28 +351,6 @@ def __bytes__(self):
334351
# Test exception for unknown format characters, etc.
335352
if verbose:
336353
print('Testing exceptions')
337-
def test_exc(formatstr, args, exception, excmsg):
338-
try:
339-
testformat(formatstr, args)
340-
except exception as exc:
341-
if str(exc) == excmsg:
342-
if verbose:
343-
print("yes")
344-
else:
345-
if verbose: print('no')
346-
print('Unexpected ', exception, ':', repr(str(exc)))
347-
except:
348-
if verbose: print('no')
349-
print('Unexpected exception')
350-
raise
351-
else:
352-
raise TestFailed('did not get expected exception: %s' % excmsg)
353-
test_exc(b'%d', '1', TypeError,
354-
"%d format: a number is required, not str")
355-
test_exc(b'%d', b'1', TypeError,
356-
"%d format: a number is required, not bytes")
357-
test_exc(b'%x', 3.14, TypeError,
358-
"%x format: an integer is required, not float")
359354
test_exc(b'%g', '1', TypeError, "float argument required, not str")
360355
test_exc(b'%g', b'1', TypeError, "float argument required, not bytes")
361356
test_exc(b'no format', 7, TypeError,

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 3.7.0 alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-29568: Escaped percent "%%" in the format string for classic string
14+
formatting no longer allows any characters between two percents.
15+
1316
- bpo-29714: Fix a regression that bytes format may fail when containing zero
1417
bytes inside.
1518

Objects/bytesobject.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,12 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
650650
#endif
651651

652652
fmt++;
653+
if (*fmt == '%') {
654+
*res++ = '%';
655+
fmt++;
656+
fmtcnt--;
657+
continue;
658+
}
653659
if (*fmt == '(') {
654660
const char *keystart;
655661
Py_ssize_t keylen;
@@ -794,11 +800,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
794800
"incomplete format");
795801
goto error;
796802
}
797-
if (c != '%') {
798-
v = getnextarg(args, arglen, &argidx);
799-
if (v == NULL)
800-
goto error;
801-
}
803+
v = getnextarg(args, arglen, &argidx);
804+
if (v == NULL)
805+
goto error;
802806

803807
if (fmtcnt < 0) {
804808
/* last writer: disable writer overallocation */
@@ -808,10 +812,6 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
808812
sign = 0;
809813
fill = ' ';
810814
switch (c) {
811-
case '%':
812-
*res++ = '%';
813-
continue;
814-
815815
case 'r':
816816
// %r is only for 2/3 code; 3 only code should use %a
817817
case 'a':
@@ -1017,7 +1017,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
10171017
res += (width - len);
10181018
}
10191019

1020-
if (dict && (argidx < arglen) && c != '%') {
1020+
if (dict && (argidx < arglen)) {
10211021
PyErr_SetString(PyExc_TypeError,
10221022
"not all arguments converted during bytes formatting");
10231023
Py_XDECREF(temp);

Objects/unicodeobject.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14617,12 +14617,6 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
1461714617
if (ctx->fmtcnt == 0)
1461814618
ctx->writer.overallocate = 0;
1461914619

14620-
if (arg->ch == '%') {
14621-
if (_PyUnicodeWriter_WriteCharInline(writer, '%') < 0)
14622-
return -1;
14623-
return 1;
14624-
}
14625-
1462614620
v = unicode_format_getnextarg(ctx);
1462714621
if (v == NULL)
1462814622
return -1;
@@ -14882,6 +14876,13 @@ unicode_format_arg(struct unicode_formatter_t *ctx)
1488214876
int ret;
1488314877

1488414878
arg.ch = PyUnicode_READ(ctx->fmtkind, ctx->fmtdata, ctx->fmtpos);
14879+
if (arg.ch == '%') {
14880+
ctx->fmtpos++;
14881+
ctx->fmtcnt--;
14882+
if (_PyUnicodeWriter_WriteCharInline(&ctx->writer, '%') < 0)
14883+
return -1;
14884+
return 0;
14885+
}
1488514886
arg.flags = 0;
1488614887
arg.width = -1;
1488714888
arg.prec = -1;
@@ -14903,7 +14904,7 @@ unicode_format_arg(struct unicode_formatter_t *ctx)
1490314904
return -1;
1490414905
}
1490514906

14906-
if (ctx->dict && (ctx->argidx < ctx->arglen) && arg.ch != '%') {
14907+
if (ctx->dict && (ctx->argidx < ctx->arglen)) {
1490714908
PyErr_SetString(PyExc_TypeError,
1490814909
"not all arguments converted during string formatting");
1490914910
return -1;

0 commit comments

Comments
 (0)