From c5b1321a3d22c168525d76f89d01654b0d2c7591 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 29 Nov 2025 10:06:17 +0200 Subject: [PATCH 1/3] gh-142037: Improve error messages for printf-style formatting This affects string formatting as well as bytes and bytearray formatting. * For errors in the format string, always include the position of the start of the format unit. * For errors related to the formatted arguments, always include the number or the name of the argument. * Suggest more probable causes of errors in the format string (stray %, unsupported format, unexpected character). * Provide more information when the number of arguments does not match the number of format units. * Raise more specific errors when access of arguments by name is mixed with sequential access and if * is used with a mapping. * Add tests for some uncovered cases. --- Lib/test/test_bytes.py | 20 +- Lib/test/test_format.py | 222 +++++++++++++++--- Lib/test/test_peepholer.py | 19 +- Lib/test/test_str.py | 22 +- ...-11-29-10-06-06.gh-issue-142037.OpIGzK.rst | 7 + Objects/bytesobject.c | 196 +++++++++++----- Objects/unicode_format.c | 219 ++++++++++++----- 7 files changed, 519 insertions(+), 186 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index a6cf899fa51e75..5ee422d9f94ae4 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -781,16 +781,16 @@ def __int__(self): pi = PseudoFloat(3.1415) exceptions_params = [ - ('%x format: an integer is required, not float', b'%x', 3.14), - ('%X format: an integer is required, not float', b'%X', 2.11), - ('%o format: an integer is required, not float', b'%o', 1.79), - ('%x format: an integer is required, not PseudoFloat', b'%x', pi), - ('%x format: an integer is required, not complex', b'%x', 3j), - ('%X format: an integer is required, not complex', b'%X', 2j), - ('%o format: an integer is required, not complex', b'%o', 1j), - ('%u format: a real number is required, not complex', b'%u', 3j), - ('%i format: a real number is required, not complex', b'%i', 2j), - ('%d format: a real number is required, not complex', b'%d', 2j), + ('an integer is required for format %x, not float', b'%x', 3.14), + ('an integer is required for format %X, not float', b'%X', 2.11), + ('an integer is required for format %o, not float', b'%o', 1.79), + (r'an integer is required for format %x, not .*\.PseudoFloat', b'%x', pi), + ('an integer is required for format %x, not complex', b'%x', 3j), + ('an integer is required for format %X, not complex', b'%X', 2j), + ('an integer is required for format %o, not complex', b'%o', 1j), + ('a real number is required for format %u, not complex', b'%u', 3j), + ('a real number is required for format %i, not complex', b'%i', 2j), + ('a real number is required for format %d, not complex', b'%d', 2j), ( r'%c requires an integer in range\(256\)' r' or a single byte, not .*\.PseudoFloat', diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 1f626d87fa6c7a..b6d8f3227dced3 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -57,10 +57,6 @@ def testcommon(formatstr, args, output=None, limit=None, overflowok=False): else: b_format = formatstr ba_format = bytearray(b_format) - b_args = [] - if not isinstance(args, tuple): - args = (args, ) - b_args = tuple(args) if output is None: b_output = ba_output = None else: @@ -69,8 +65,8 @@ def testcommon(formatstr, args, output=None, limit=None, overflowok=False): else: b_output = output ba_output = bytearray(b_output) - testformat(b_format, b_args, b_output, limit, overflowok) - testformat(ba_format, b_args, ba_output, limit, overflowok) + testformat(b_format, args, b_output, limit, overflowok) + testformat(ba_format, args, ba_output, limit, overflowok) def test_exc(formatstr, args, exception, excmsg): try: @@ -82,6 +78,7 @@ def test_exc(formatstr, args, exception, excmsg): else: if verbose: print('no') print('Unexpected ', exception, ':', repr(str(exc))) + raise except: if verbose: print('no') print('Unexpected exception') @@ -92,6 +89,8 @@ def test_exc(formatstr, args, exception, excmsg): def test_exc_common(formatstr, args, exception, excmsg): # test str and bytes test_exc(formatstr, args, exception, excmsg) + if isinstance(args, dict): + args = {k.encode('ascii'): v for k, v in args.items()} test_exc(formatstr.encode('ascii'), args, exception, excmsg) class FormatTest(unittest.TestCase): @@ -272,45 +271,149 @@ def test_common_format(self): if verbose: print('Testing exceptions') - test_exc_common('%', (), ValueError, "incomplete format") - test_exc_common('% %s', 1, ValueError, - "unsupported format character '%' (0x25) at index 2") + test_exc_common('abc %', (), ValueError, "stray % at position 4") + test_exc_common('abc % %s', 1, ValueError, + "stray % at position 4 or unexpected format character '%' at position 6") + test_exc_common('abc %z', 1, ValueError, + "unsupported format %z at position 4") + test_exc_common("abc %Id", 1, ValueError, + "unsupported format %I at position 4") + test_exc_common("abc %'d", 1, ValueError, + "stray % at position 4 or unexpected format character ''' at position 5") + test_exc_common("abc %1 d", 1, ValueError, + "stray % at position 4 or unexpected format character ' ' at position 6") + test_exc_common('abc % (x)r', {}, ValueError, + "stray % at position 4 or unexpected format character '(' at position 6") + test_exc_common('abc %((x)r', {}, ValueError, + "stray % or incomplete format key at position 4") + test_exc_common('%r %r', 1, TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%r %r', (1,), TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%r', (), TypeError, + "not enough arguments for format string (got 0)") + test_exc_common('abc %' + '9'*50 + 'r', 1, ValueError, + "width too big at position 4") + test_exc_common('abc %.' + '9'*50 + 'r', 1, ValueError, + "precision too big at position 4") + test_exc_common('%r %*r', 1, TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%r %*r', (1,), TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%*r', (1,), TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%*r', (), TypeError, + "not enough arguments for format string (got 0)") + test_exc_common('%r %.*r', 1, TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%r %.*r', (1,), TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%.*r', (1,), TypeError, + "not enough arguments for format string (got 1)") + test_exc_common('%.*r', (), TypeError, + "not enough arguments for format string (got 0)") + test_exc_common('%(x)r', 1, TypeError, + "format requires a mapping, not int") + test_exc_common('%*r', 3.14, TypeError, + "* requires int, not float") + test_exc_common('%*r', (3.14, 1), TypeError, + "format argument 1: * requires int, not float") + test_exc_common('%.*r', 3.14, TypeError, + "* requires int, not float") + test_exc_common('%.*r', (3.14, 1), TypeError, + "format argument 1: * requires int, not float") + test_exc_common('%*r', (2**1000, 1), OverflowError, + "format argument 1: too big for width") + test_exc_common('%*r', (-2**1000, 1), OverflowError, + "format argument 1: too big for width") + test_exc_common('%.*r', (2**1000, 1), OverflowError, + "format argument 1: too big for precision") + test_exc_common('%.*r', (-2**1000, 1), OverflowError, + "format argument 1: too big for precision") test_exc_common('%d', '1', TypeError, - "%d format: a real number is required, not str") + "a real number is required for format %d, not str") test_exc_common('%d', b'1', TypeError, - "%d format: a real number is required, not bytes") + "a real number is required for format %d, not bytes") + test_exc_common('%d', ('1',), TypeError, + "format argument 1: a real number is required for format %d, not str") test_exc_common('%x', '1', TypeError, - "%x format: an integer is required, not str") + "an integer is required for format %x, not str") test_exc_common('%x', 3.14, TypeError, - "%x format: an integer is required, not float") + "an integer is required for format %x, not float") + test_exc_common('%x', ('1',), TypeError, + "format argument 1: an integer is required for format %x, not str") test_exc_common('%i', '1', TypeError, - "%i format: a real number is required, not str") + "a real number is required for format %i, not str") test_exc_common('%i', b'1', TypeError, - "%i format: a real number is required, not bytes") + "a real number is required for format %i, not bytes") + test_exc_common('%g', '1', TypeError, + "a real number is required for format %g, not str") + test_exc_common('%g', ('1',), TypeError, + "format argument 1: a real number is required for format %g, not str") def test_str_format(self): testformat("%r", "\u0378", "'\\u0378'") # non printable testformat("%a", "\u0378", "'\\u0378'") # non printable testformat("%r", "\u0374", "'\u0374'") # printable testformat("%a", "\u0374", "'\\u0374'") # printable + testformat('%(x)r', {'x': 1}, '1') # Test exception for unknown format characters, etc. if verbose: print('Testing exceptions') test_exc('abc %b', 1, ValueError, - "unsupported format character 'b' (0x62) at index 5") - #test_exc(unicode('abc %\u3000','raw-unicode-escape'), 1, ValueError, - # "unsupported format character '?' (0x3000) at index 5") - test_exc('%g', '1', TypeError, "must be real number, not str") + "unsupported format %b at position 4") + test_exc("abc %\nd", 1, ValueError, + "stray % at position 4 or unexpected format character U+000A at position 5") + test_exc("abc %\x1fd", 1, ValueError, + "stray % at position 4 or unexpected format character U+001F at position 5") + test_exc("abc %\x7fd", 1, ValueError, + "stray % at position 4 or unexpected format character U+007F at position 5") + test_exc("abc %\x80d", 1, ValueError, + "stray % at position 4 or unexpected format character U+0080 at position 5") + test_exc('abc %äd', 1, ValueError, + "stray % at position 4 or unexpected format character 'ä' (U+00E4) at position 5") + test_exc('abc %€d', 1, ValueError, + "stray % at position 4 or unexpected format character '€' (U+20AC) at position 5") test_exc('no format', '1', TypeError, - "not all arguments converted during string formatting") - test_exc('%c', -1, OverflowError, "%c arg not in range(0x110000)") + "not all arguments converted during string formatting (required 0, got 1)") + test_exc('%r', (1, 2), TypeError, + "not all arguments converted during string formatting (required 1, got 2)") + test_exc('%(x)r %r', {'x': 1}, ValueError, + "format requires a parenthesised mapping key at position 6") + test_exc('%(x)*r', {'x': 1}, ValueError, + "* cannot be used with a parenthesised mapping key at position 0") + test_exc('%(x).*r', {'x': 1}, ValueError, + "* cannot be used with a parenthesised mapping key at position 0") + test_exc('%(x)d', {'x': '1'}, TypeError, + "format argument 'x': a real number is required for format %d, not str") + test_exc('%(x)x', {'x': '1'}, TypeError, + "format argument 'x': an integer is required for format %x, not str") + test_exc('%(x)g', {'x': '1'}, TypeError, + "format argument 'x': a real number is required for format %g, not str") + test_exc('%c', -1, OverflowError, "%c argument not in range(0x110000)") + test_exc('%c', (-1,), OverflowError, + "format argument 1: %c argument not in range(0x110000)") + test_exc('%(x)c', {'x': -1}, OverflowError, + "format argument 'x': %c argument not in range(0x110000)") test_exc('%c', sys.maxunicode+1, OverflowError, - "%c arg not in range(0x110000)") - #test_exc('%c', 2**128, OverflowError, "%c arg not in range(0x110000)") - test_exc('%c', 3.14, TypeError, "%c requires an int or a unicode character, not float") - test_exc('%c', 'ab', TypeError, "%c requires an int or a unicode character, not a string of length 2") - test_exc('%c', b'x', TypeError, "%c requires an int or a unicode character, not bytes") + "%c argument not in range(0x110000)") + test_exc('%c', 2**128, OverflowError, + "%c argument not in range(0x110000)") + test_exc('%c', 3.14, TypeError, + "%c requires an integer or a unicode character, not float") + test_exc('%c', (3.14,), TypeError, + "format argument 1: %c requires an integer or a unicode character, not float") + test_exc('%(x)c', {'x': 3.14}, TypeError, + "format argument 'x': %c requires an integer or a unicode character, not float") + test_exc('%c', 'ab', TypeError, + "%c requires an integer or a unicode character, not a string of length 2") + test_exc('%c', ('ab',), TypeError, + "format argument 1: %c requires an integer or a unicode character, not a string of length 2") + test_exc('%(x)c', {'x': 'ab'}, TypeError, + "format argument 'x': %c requires an integer or a unicode character, not a string of length 2") + test_exc('%c', b'x', TypeError, + "%c requires an integer or a unicode character, not bytes") if maxsize == 2**31-1: # crashes 2.2.1 and earlier: @@ -355,36 +458,81 @@ def __bytes__(self): testcommon(b"%r", b"ghi", b"b'ghi'") testcommon(b"%r", "jkl", b"'jkl'") testcommon(b"%r", "\u0544", b"'\\u0544'") + testcommon(b'%(x)r', {b'x': 1}, b'1') # Test exception for unknown format characters, etc. if verbose: print('Testing exceptions') - test_exc(b'%g', '1', TypeError, "float argument required, not str") - test_exc(b'%g', b'1', TypeError, "float argument required, not bytes") + test_exc(b"abc %\nd", 1, ValueError, + "stray % at position 4 or unexpected format character with code 0x0a at position 5") + test_exc(b"abc %\x1fd", 1, ValueError, + "stray % at position 4 or unexpected format character with code 0x1f at position 5") + test_exc(b"abc %\x7fd", 1, ValueError, + "stray % at position 4 or unexpected format character with code 0x7f at position 5") + test_exc(b"abc %\x80d", 1, ValueError, + "stray % at position 4 or unexpected format character with code 0x80 at position 5") test_exc(b'no format', 7, TypeError, - "not all arguments converted during bytes formatting") + "not all arguments converted during bytes formatting (required 0, got 1)") test_exc(b'no format', b'1', TypeError, - "not all arguments converted during bytes formatting") + "not all arguments converted during bytes formatting (required 0, got 1)") test_exc(b'no format', bytearray(b'1'), TypeError, - "not all arguments converted during bytes formatting") + "not all arguments converted during bytes formatting (required 0, got 1)") + test_exc(b'%r', (1, 2), TypeError, + "not all arguments converted during bytes formatting (required 1, got 2)") + test_exc(b'%(x)r %r', {b'x': 1}, ValueError, + "format requires a parenthesised mapping key at position 6") + test_exc(b'%(x)*r', {b'x': 1}, ValueError, + "* cannot be used with a parenthesised mapping key at position 0") + test_exc(b'%(x).*r', {b'x': 1}, ValueError, + "* cannot be used with a parenthesised mapping key at position 0") + test_exc(b'%(x)d', {b'x': '1'}, TypeError, + "format argument b'x': a real number is required for format %d, not str") + test_exc(b'%(x)x', {b'x': '1'}, TypeError, + "format argument b'x': an integer is required for format %x, not str") + test_exc(b'%(x)g', {b'x': '1'}, TypeError, + "format argument b'x': a real number is required for format %g, not str") test_exc(b"%c", -1, OverflowError, - "%c arg not in range(256)") + "%c argument not in range(256)") + test_exc(b"%c", (-1,), OverflowError, + "format argument 1: %c argument not in range(256)") + test_exc(b"%(x)c", {b'x': -1}, OverflowError, + "format argument b'x': %c argument not in range(256)") test_exc(b"%c", 256, OverflowError, - "%c arg not in range(256)") + "%c argument not in range(256)") test_exc(b"%c", 2**128, OverflowError, - "%c arg not in range(256)") + "%c argument not in range(256)") test_exc(b"%c", b"Za", TypeError, "%c requires an integer in range(256) or a single byte, not a bytes object of length 2") + test_exc(b"%c", (b"Za",), TypeError, + "format argument 1: %c requires an integer in range(256) or a single byte, not a bytes object of length 2") + test_exc(b"%(x)c", {b'x': b"Za"}, TypeError, + "format argument b'x': %c requires an integer in range(256) or a single byte, not a bytes object of length 2") + test_exc(b"%c", bytearray(b"Za"), TypeError, + "%c requires an integer in range(256) or a single byte, not a bytearray object of length 2") + test_exc(b"%c", (bytearray(b"Za"),), TypeError, + "format argument 1: %c requires an integer in range(256) or a single byte, not a bytearray object of length 2") + test_exc(b"%(x)c", {b'x': bytearray(b"Za")}, TypeError, + "format argument b'x': %c requires an integer in range(256) or a single byte, not a bytearray object of length 2") test_exc(b"%c", "Y", TypeError, "%c requires an integer in range(256) or a single byte, not str") test_exc(b"%c", 3.14, TypeError, "%c requires an integer in range(256) or a single byte, not float") + test_exc(b"%c", (3.14,), TypeError, + "format argument 1: %c requires an integer in range(256) or a single byte, not float") + test_exc(b"%(x)c", {b'x': 3.14}, TypeError, + "format argument b'x': %c requires an integer in range(256) or a single byte, not float") test_exc(b"%b", "Xc", TypeError, "%b requires a bytes-like object, " - "or an object that implements __bytes__, not 'str'") + "or an object that implements __bytes__, not str") + test_exc(b"%b", ("Xc",), TypeError, + "format argument 1: %b requires a bytes-like object, " + "or an object that implements __bytes__, not str") + test_exc(b"%(x)b", {b'x': "Xc"}, TypeError, + "format argument b'x': %b requires a bytes-like object, " + "or an object that implements __bytes__, not str") test_exc(b"%s", "Wd", TypeError, "%b requires a bytes-like object, " - "or an object that implements __bytes__, not 'str'") + "or an object that implements __bytes__, not str") if maxsize == 2**31-1: # crashes 2.2.1 and earlier: @@ -626,7 +774,7 @@ def test_specifier_z_error(self): with self.assertRaisesRegex(ValueError, error_msg): f"{'x':zs}" # can't apply to string - error_msg = re.escape("unsupported format character 'z'") + error_msg = re.escape("unsupported format %z at position 0") with self.assertRaisesRegex(ValueError, error_msg): "%z.1f" % 0 # not allowed in old style string interpolation with self.assertRaisesRegex(ValueError, error_msg): diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 1caf6de4a14e39..b7e44381aa2a6f 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -733,22 +733,27 @@ def test_format_errors(self): with self.assertRaisesRegex(TypeError, 'not all arguments converted during string formatting'): eval("'%s' % (x, y)", {'x': 1, 'y': 2}) - with self.assertRaisesRegex(ValueError, 'incomplete format'): + with self.assertRaisesRegex(ValueError, 'stray % at position 2'): eval("'%s%' % (x,)", {'x': 1234}) - with self.assertRaisesRegex(ValueError, 'incomplete format'): + with self.assertRaisesRegex(ValueError, 'stray % at position 4'): eval("'%s%%%' % (x,)", {'x': 1234}) with self.assertRaisesRegex(TypeError, 'not enough arguments for format string'): eval("'%s%z' % (x,)", {'x': 1234}) - with self.assertRaisesRegex(ValueError, 'unsupported format character'): + with self.assertRaisesRegex(ValueError, + 'unsupported format %z at position 2'): eval("'%s%z' % (x, 5)", {'x': 1234}) - with self.assertRaisesRegex(TypeError, 'a real number is required, not str'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: a real number is required for format %d, not str'): eval("'%d' % (x,)", {'x': '1234'}) - with self.assertRaisesRegex(TypeError, 'an integer is required, not float'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: an integer is required for format %x, not float'): eval("'%x' % (x,)", {'x': 1234.56}) - with self.assertRaisesRegex(TypeError, 'an integer is required, not str'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: an integer is required for format %x, not str'): eval("'%x' % (x,)", {'x': '1234'}) - with self.assertRaisesRegex(TypeError, 'must be real number, not str'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: a real number is required for format %f, not str'): eval("'%f' % (x,)", {'x': '1234'}) with self.assertRaisesRegex(TypeError, 'not enough arguments for format string'): diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 2584fbf72d3fa6..538ecda905754e 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -1578,17 +1578,17 @@ def __int__(self): self.assertEqual('%X' % letter_m, '6D') self.assertEqual('%o' % letter_m, '155') self.assertEqual('%c' % letter_m, 'm') - self.assertRaisesRegex(TypeError, '%x format: an integer is required, not float', operator.mod, '%x', 3.14) - self.assertRaisesRegex(TypeError, '%X format: an integer is required, not float', operator.mod, '%X', 2.11) - self.assertRaisesRegex(TypeError, '%o format: an integer is required, not float', operator.mod, '%o', 1.79) - self.assertRaisesRegex(TypeError, '%x format: an integer is required, not PseudoFloat', operator.mod, '%x', pi) - self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 3j) - self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 2j) - self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1j) - self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j) - self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j) - self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j) - self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi) + self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not float', operator.mod, '%x', 3.14) + self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not float', operator.mod, '%X', 2.11) + self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not float', operator.mod, '%o', 1.79) + self.assertRaisesRegex(TypeError, r'an integer is required for format %x, not .*\.PseudoFloat', operator.mod, '%x', pi) + self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not complex', operator.mod, '%x', 3j) + self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not complex', operator.mod, '%X', 2j) + self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not complex', operator.mod, '%o', 1j) + self.assertRaisesRegex(TypeError, 'a real number is required for format %u, not complex', operator.mod, '%u', 3j) + self.assertRaisesRegex(TypeError, 'a real number is required for format %i, not complex', operator.mod, '%i', 2j) + self.assertRaisesRegex(TypeError, 'a real number is required for format %d, not complex', operator.mod, '%d', 1j) + self.assertRaisesRegex(TypeError, r'%c requires an integer or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi) class RaisingNumber: def __int__(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst new file mode 100644 index 00000000000000..6a59be7726f254 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst @@ -0,0 +1,7 @@ +Improve error messages for printf-style formatting. +For errors in the format string, always include the position of the +start of the format unit. +For errors related to the formatted arguments, always include the number +or the name of the argument. +Raise more specific errors and include more information (type and number +of arguments, most probable causes of error). diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 2b0925017f29e4..f47794a0003ad4 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -404,6 +404,20 @@ PyBytes_FromFormat(const char *format, ...) /* Helpers for formatstring */ +#define FORMAT_ERROR(EXC, FMT, ...) do { \ + if (key != NULL) { \ + PyErr_Format((EXC), "format argument %R: " FMT, \ + key, __VA_ARGS__); \ + } \ + else if (argidx >= 0) { \ + PyErr_Format((EXC), "format argument %zd: " FMT, \ + argidx, __VA_ARGS__); \ + } \ + else { \ + PyErr_Format((EXC), FMT, __VA_ARGS__); \ + } \ +} while (0) + Py_LOCAL_INLINE(PyObject *) getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) { @@ -415,15 +429,17 @@ getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) else return PyTuple_GetItem(args, argidx); } - PyErr_SetString(PyExc_TypeError, - "not enough arguments for format string"); + PyErr_Format(PyExc_TypeError, + "not enough arguments for format string (got %zd)", + arglen < 0 ? 1 : arglen); return NULL; } /* Returns a new reference to a PyBytes object, or NULL on failure. */ static char* -formatfloat(PyObject *v, int flags, int prec, int type, +formatfloat(PyObject *v, Py_ssize_t argidx, PyObject *key, + int flags, int prec, int type, PyObject **p_result, PyBytesWriter *writer, char *str) { char *p; @@ -434,8 +450,11 @@ formatfloat(PyObject *v, int flags, int prec, int type, x = PyFloat_AsDouble(v); if (x == -1.0 && PyErr_Occurred()) { - PyErr_Format(PyExc_TypeError, "float argument required, " - "not %.200s", Py_TYPE(v)->tp_name); + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + FORMAT_ERROR(PyExc_TypeError, + "a real number is required for format %%%c, not %T", + type, v); + } return NULL; } @@ -470,7 +489,8 @@ formatfloat(PyObject *v, int flags, int prec, int type, } static PyObject * -formatlong(PyObject *v, int flags, int prec, int type) +formatlong(PyObject *v, Py_ssize_t argidx, PyObject *key, + int flags, int prec, int type) { PyObject *result, *iobj; if (PyLong_Check(v)) @@ -490,20 +510,20 @@ formatlong(PyObject *v, int flags, int prec, int type) if (!PyErr_ExceptionMatches(PyExc_TypeError)) return NULL; } - PyErr_Format(PyExc_TypeError, - "%%%c format: %s is required, not %.200s", type, - (type == 'o' || type == 'x' || type == 'X') ? "an integer" - : "a real number", - Py_TYPE(v)->tp_name); + FORMAT_ERROR(PyExc_TypeError, + "%s is required for format %%%c, not %T", + (type == 'o' || type == 'x' || type == 'X') ? "an integer" + : "a real number", + type, v); return NULL; } static int -byte_converter(PyObject *arg, char *p) +byte_converter(PyObject *arg, Py_ssize_t argidx, PyObject *key, char *p) { if (PyBytes_Check(arg)) { if (PyBytes_GET_SIZE(arg) != 1) { - PyErr_Format(PyExc_TypeError, + FORMAT_ERROR(PyExc_TypeError, "%%c requires an integer in range(256) or " "a single byte, not a bytes object of length %zd", PyBytes_GET_SIZE(arg)); @@ -514,7 +534,7 @@ byte_converter(PyObject *arg, char *p) } else if (PyByteArray_Check(arg)) { if (PyByteArray_GET_SIZE(arg) != 1) { - PyErr_Format(PyExc_TypeError, + FORMAT_ERROR(PyExc_TypeError, "%%c requires an integer in range(256) or " "a single byte, not a bytearray object of length %zd", PyByteArray_GET_SIZE(arg)); @@ -531,23 +551,25 @@ byte_converter(PyObject *arg, char *p) } if (!(0 <= ival && ival <= 255)) { /* this includes an overflow in converting to C long */ - PyErr_SetString(PyExc_OverflowError, - "%c arg not in range(256)"); + FORMAT_ERROR(PyExc_OverflowError, + "%%c argument not in range(256)%s", ""); return 0; } *p = (char)ival; return 1; } - PyErr_Format(PyExc_TypeError, - "%%c requires an integer in range(256) or a single byte, not %T", - arg); + FORMAT_ERROR(PyExc_TypeError, + "%%c requires an integer in range(256) or " + "a single byte, not %T", + arg); return 0; } static PyObject *_PyBytes_FromBuffer(PyObject *x); static PyObject * -format_obj(PyObject *v, const char **pbuf, Py_ssize_t *plen) +format_obj(PyObject *v, Py_ssize_t argidx, PyObject *key, + const char **pbuf, Py_ssize_t *plen) { PyObject *func, *result; /* is it a bytes object? */ @@ -589,10 +611,10 @@ format_obj(PyObject *v, const char **pbuf, Py_ssize_t *plen) *plen = PyBytes_GET_SIZE(result); return result; } - PyErr_Format(PyExc_TypeError, + FORMAT_ERROR(PyExc_TypeError, "%%b requires a bytes-like object, " - "or an object that implements __bytes__, not '%.100s'", - Py_TYPE(v)->tp_name); + "or an object that implements __bytes__, not %T", + v); return NULL; } @@ -607,6 +629,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, Py_ssize_t fmtcnt; int args_owned = 0; PyObject *dict = NULL; + PyObject *key = NULL; if (args == NULL) { PyErr_BadInternalCall(); @@ -680,15 +703,17 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, fmtcnt--; continue; } + Py_CLEAR(key); + const char *fmtstart = fmt; if (*fmt == '(') { const char *keystart; Py_ssize_t keylen; - PyObject *key; int pcount = 1; if (dict == NULL) { - PyErr_SetString(PyExc_TypeError, - "format requires a mapping"); + PyErr_Format(PyExc_TypeError, + "format requires a mapping, not %T", + args); goto error; } ++fmt; @@ -704,8 +729,10 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, } keylen = fmt - keystart - 1; if (fmtcnt < 0 || pcount > 0) { - PyErr_SetString(PyExc_ValueError, - "incomplete format key"); + PyErr_Format(PyExc_ValueError, + "stray %% or incomplete format key " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); goto error; } key = PyBytes_FromStringAndSize(keystart, @@ -717,13 +744,21 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, args_owned = 0; } args = PyObject_GetItem(dict, key); - Py_DECREF(key); if (args == NULL) { goto error; } args_owned = 1; - arglen = -1; - argidx = -2; + arglen = -3; + argidx = -4; + } + else { + if (arglen < -1) { + PyErr_Format(PyExc_ValueError, + "format requires a parenthesised mapping key " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); + goto error; + } } /* Parse flags. Example: "%+i" => flags=F_SIGN. */ @@ -740,17 +775,28 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, /* Parse width. Example: "%10s" => width=10 */ if (c == '*') { + if (arglen < -1) { + PyErr_Format(PyExc_ValueError, + "* cannot be used with a parenthesised mapping key " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); + goto error; + } v = getnextarg(args, arglen, &argidx); if (v == NULL) goto error; if (!PyLong_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "* wants int"); + FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v); goto error; } width = PyLong_AsSsize_t(v); - if (width == -1 && PyErr_Occurred()) + if (width == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + FORMAT_ERROR(PyExc_OverflowError, + "too big for width%s", ""); + } goto error; + } if (width < 0) { flags |= F_LJUST; width = -width; @@ -765,9 +811,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, if (!Py_ISDIGIT(c)) break; if (width > (PY_SSIZE_T_MAX - ((int)c - '0')) / 10) { - PyErr_SetString( - PyExc_ValueError, - "width too big"); + PyErr_Format(PyExc_ValueError, + "width too big at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); goto error; } width = width*10 + (c - '0'); @@ -780,18 +826,29 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, if (--fmtcnt >= 0) c = *fmt++; if (c == '*') { + if (arglen < -1) { + PyErr_Format(PyExc_ValueError, + "* cannot be used with a parenthesised mapping key " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); + goto error; + } v = getnextarg(args, arglen, &argidx); if (v == NULL) goto error; if (!PyLong_Check(v)) { - PyErr_SetString( - PyExc_TypeError, - "* wants int"); + FORMAT_ERROR(PyExc_TypeError, + "* requires int, not %T", v); goto error; } prec = PyLong_AsInt(v); - if (prec == -1 && PyErr_Occurred()) + if (prec == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + FORMAT_ERROR(PyExc_OverflowError, + "too big for precision%s", ""); + } goto error; + } if (prec < 0) prec = 0; if (--fmtcnt >= 0) @@ -804,9 +861,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, if (!Py_ISDIGIT(c)) break; if (prec > (INT_MAX - ((int)c - '0')) / 10) { - PyErr_SetString( - PyExc_ValueError, - "prec too big"); + PyErr_Format(PyExc_ValueError, + "precision too big at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); goto error; } prec = prec*10 + (c - '0'); @@ -820,8 +877,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, } } if (fmtcnt < 0) { - PyErr_SetString(PyExc_ValueError, - "incomplete format"); + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); goto error; } v = getnextarg(args, arglen, &argidx); @@ -852,7 +910,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, case 's': // %s is only for 2/3 code; 3 only code should use %b case 'b': - temp = format_obj(v, &pbuf, &len); + temp = format_obj(v, argidx, key, &pbuf, &len); if (temp == NULL) goto error; if (prec >= 0 && len > prec) @@ -900,7 +958,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, continue; } - temp = formatlong(v, flags, prec, c); + temp = formatlong(v, argidx, key, flags, prec, c); if (!temp) goto error; assert(PyUnicode_IS_ASCII(temp)); @@ -921,13 +979,13 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, && !(flags & (F_SIGN | F_BLANK))) { /* Fast path */ - res = formatfloat(v, flags, prec, c, NULL, writer, res); + res = formatfloat(v, argidx, key, flags, prec, c, NULL, writer, res); if (res == NULL) goto error; continue; } - if (!formatfloat(v, flags, prec, c, &temp, NULL, res)) + if (!formatfloat(v, argidx, key, flags, prec, c, &temp, NULL, res)) goto error; pbuf = PyBytes_AS_STRING(temp); len = PyBytes_GET_SIZE(temp); @@ -938,7 +996,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, case 'c': pbuf = &onechar; - len = byte_converter(v, &onechar); + len = byte_converter(v, argidx, key, &onechar); if (!len) goto error; if (width == -1) { @@ -949,11 +1007,28 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, break; default: - PyErr_Format(PyExc_ValueError, - "unsupported format character '%c' (0x%x) " - "at index %zd", - c, c, - (Py_ssize_t)(fmt - 1 - format)); + if (Py_ISALPHA(c)) { + PyErr_Format(PyExc_ValueError, + "unsupported format %%%c at position %zd", + c, (Py_ssize_t)(fmtstart - format - 1)); + } + else if (c >= 32 && c < 127) { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character '%c' " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1), + c, (Py_ssize_t)(fmt - format - 1)); + } + else { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character with code 0x%02x " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1), + Py_CHARMASK(c), + (Py_ssize_t)(fmt - format - 1)); + } goto error; } @@ -1042,6 +1117,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, } if (dict && (argidx < arglen)) { + // XXX: Never happens? PyErr_SetString(PyExc_TypeError, "not all arguments converted during bytes formatting"); Py_XDECREF(temp); @@ -1061,8 +1137,11 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, } /* until end */ if (argidx < arglen && !dict) { - PyErr_SetString(PyExc_TypeError, - "not all arguments converted during bytes formatting"); + PyErr_Format(PyExc_TypeError, + "not all arguments converted during bytes formatting " + "(required %zd, got %zd)", + arglen < 0 ? 0 : argidx, + arglen < 0 ? 1 : arglen); goto error; } @@ -1072,6 +1151,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, return PyBytesWriter_FinishWithPointer(writer, res); error: + Py_XDECREF(key); PyBytesWriter_Discard(writer); if (args_owned) { Py_DECREF(args); diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c index 26bdae55d8b931..6f6f5266f00774 100644 --- a/Objects/unicode_format.c +++ b/Objects/unicode_format.c @@ -72,9 +72,26 @@ struct unicode_format_arg_t { Py_ssize_t width; int prec; int sign; + Py_ssize_t fmtstart; + PyObject *key; }; +#define FORMAT_ERROR(EXC, FMT, ...) do { \ + if (arg->key != NULL) { \ + PyErr_Format((EXC), "format argument %R: " FMT, \ + arg->key, __VA_ARGS__); \ + } \ + else if (ctx->argidx >= 0) { \ + PyErr_Format((EXC), "format argument %zd: " FMT, \ + ctx->argidx, __VA_ARGS__); \ + } \ + else { \ + PyErr_Format((EXC), FMT, __VA_ARGS__); \ + } \ +} while (0) + + static PyObject * unicode_format_getnextarg(struct unicode_formatter_t *ctx) { @@ -87,8 +104,9 @@ unicode_format_getnextarg(struct unicode_formatter_t *ctx) else return PyTuple_GetItem(ctx->args, argidx); } - PyErr_SetString(PyExc_TypeError, - "not enough arguments for format string"); + PyErr_Format(PyExc_TypeError, + "not enough arguments for format string (got %zd)", + ctx->arglen < 0 ? 1 : ctx->arglen); return NULL; } @@ -100,7 +118,9 @@ unicode_format_getnextarg(struct unicode_formatter_t *ctx) Return 0 on success, raise an exception and return -1 on error. */ static int -formatfloat(PyObject *v, struct unicode_format_arg_t *arg, +formatfloat(PyObject *v, + struct unicode_formatter_t *ctx, + struct unicode_format_arg_t *arg, PyObject **p_output, _PyUnicodeWriter *writer) { @@ -111,8 +131,14 @@ formatfloat(PyObject *v, struct unicode_format_arg_t *arg, int dtoa_flags = 0; x = PyFloat_AsDouble(v); - if (x == -1.0 && PyErr_Occurred()) + if (x == -1.0 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + FORMAT_ERROR(PyExc_TypeError, + "a real number is required for format %%%c, not %T", + arg->ch, v); + } return -1; + } prec = arg->prec; if (prec < 0) @@ -287,6 +313,7 @@ _PyUnicode_FormatLong(PyObject *val, int alt, int prec, int type) * -1 and raise an exception on error */ static int mainformatlong(PyObject *v, + struct unicode_formatter_t *ctx, struct unicode_format_arg_t *arg, PyObject **p_output, _PyUnicodeWriter *writer) @@ -364,16 +391,14 @@ mainformatlong(PyObject *v, case 'o': case 'x': case 'X': - PyErr_Format(PyExc_TypeError, - "%%%c format: an integer is required, " - "not %.200s", - type, Py_TYPE(v)->tp_name); + FORMAT_ERROR(PyExc_TypeError, + "an integer is required for format %%%c, not %T", + arg->ch, v); break; default: - PyErr_Format(PyExc_TypeError, - "%%%c format: a real number is required, " - "not %.200s", - type, Py_TYPE(v)->tp_name); + FORMAT_ERROR(PyExc_TypeError, + "a real number is required for format %%%c, not %T", + arg->ch, v); break; } return -1; @@ -381,15 +406,17 @@ mainformatlong(PyObject *v, static Py_UCS4 -formatchar(PyObject *v) +formatchar(PyObject *v, + struct unicode_formatter_t *ctx, + struct unicode_format_arg_t *arg) { /* presume that the buffer is at least 3 characters long */ if (PyUnicode_Check(v)) { if (PyUnicode_GET_LENGTH(v) == 1) { return PyUnicode_READ_CHAR(v, 0); } - PyErr_Format(PyExc_TypeError, - "%%c requires an int or a unicode character, " + FORMAT_ERROR(PyExc_TypeError, + "%%c requires an integer or a unicode character, " "not a string of length %zd", PyUnicode_GET_LENGTH(v)); return (Py_UCS4) -1; @@ -399,18 +426,18 @@ formatchar(PyObject *v) long x = PyLong_AsLongAndOverflow(v, &overflow); if (x == -1 && PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_Format(PyExc_TypeError, - "%%c requires an int or a unicode character, not %T", + FORMAT_ERROR(PyExc_TypeError, + "%%c requires an integer or a unicode character, " + "not %T", v); - return (Py_UCS4) -1; } return (Py_UCS4) -1; } if (x < 0 || x > MAX_UNICODE) { /* this includes an overflow in converting to C long */ - PyErr_SetString(PyExc_OverflowError, - "%c arg not in range(0x110000)"); + FORMAT_ERROR(PyExc_OverflowError, + "%%c argument not in range(0x110000)%s", ""); return (Py_UCS4) -1; } @@ -438,12 +465,12 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, /* Get argument value from a dictionary. Example: "%(name)s". */ Py_ssize_t keystart; Py_ssize_t keylen; - PyObject *key; int pcount = 1; if (ctx->dict == NULL) { - PyErr_SetString(PyExc_TypeError, - "format requires a mapping"); + PyErr_Format(PyExc_TypeError, + "format requires a mapping, not %T", + ctx->args); return -1; } ++ctx->fmtpos; @@ -460,25 +487,34 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, } keylen = ctx->fmtpos - keystart - 1; if (ctx->fmtcnt < 0 || pcount > 0) { - PyErr_SetString(PyExc_ValueError, - "incomplete format key"); + PyErr_Format(PyExc_ValueError, + "stray %% or incomplete format key at position %zd", + arg->fmtstart); return -1; } - key = PyUnicode_Substring(ctx->fmtstr, - keystart, keystart + keylen); - if (key == NULL) + arg->key = PyUnicode_Substring(ctx->fmtstr, + keystart, keystart + keylen); + if (arg->key == NULL) return -1; if (ctx->args_owned) { ctx->args_owned = 0; Py_DECREF(ctx->args); } - ctx->args = PyObject_GetItem(ctx->dict, key); - Py_DECREF(key); + ctx->args = PyObject_GetItem(ctx->dict, arg->key); if (ctx->args == NULL) return -1; ctx->args_owned = 1; - ctx->arglen = -1; - ctx->argidx = -2; + ctx->arglen = -3; + ctx->argidx = -4; + } + else { + if (ctx->arglen < -1) { + PyErr_Format(PyExc_ValueError, + "format requires a parenthesised mapping key " + "at position %zd", + arg->fmtstart); + return -1; + } } /* Parse flags. Example: "%+i" => flags=F_SIGN. */ @@ -497,17 +533,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, /* Parse width. Example: "%10s" => width=10 */ if (arg->ch == '*') { + if (ctx->arglen < -1) { + PyErr_Format(PyExc_ValueError, + "* cannot be used with a parenthesised mapping key " + "at position %zd", + arg->fmtstart); + return -1; + } v = unicode_format_getnextarg(ctx); if (v == NULL) return -1; if (!PyLong_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "* wants int"); + FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v); return -1; } arg->width = PyLong_AsSsize_t(v); - if (arg->width == -1 && PyErr_Occurred()) + if (arg->width == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + FORMAT_ERROR(PyExc_OverflowError, + "too big for width%s", ""); + } return -1; + } if (arg->width < 0) { arg->flags |= F_LJUST; arg->width = -arg->width; @@ -528,8 +575,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, mixing signed and unsigned comparison. Since arg->ch is between '0' and '9', casting to int is safe. */ if (arg->width > (PY_SSIZE_T_MAX - ((int)arg->ch - '0')) / 10) { - PyErr_SetString(PyExc_ValueError, - "width too big"); + PyErr_Format(PyExc_ValueError, + "width too big at position %zd", + arg->fmtstart); return -1; } arg->width = arg->width*10 + (arg->ch - '0'); @@ -544,17 +592,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, ctx->fmtpos++; } if (arg->ch == '*') { + if (ctx->arglen < -1) { + PyErr_Format(PyExc_ValueError, + "* cannot be used with a parenthesised mapping key " + "at position %zd", + arg->fmtstart); + return -1; + } v = unicode_format_getnextarg(ctx); if (v == NULL) return -1; if (!PyLong_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "* wants int"); + FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v); return -1; } arg->prec = PyLong_AsInt(v); - if (arg->prec == -1 && PyErr_Occurred()) + if (arg->prec == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + FORMAT_ERROR(PyExc_OverflowError, + "too big for precision%s", ""); + } return -1; + } if (arg->prec < 0) arg->prec = 0; if (--ctx->fmtcnt >= 0) { @@ -570,8 +629,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, if (arg->ch < '0' || arg->ch > '9') break; if (arg->prec > (INT_MAX - ((int)arg->ch - '0')) / 10) { - PyErr_SetString(PyExc_ValueError, - "precision too big"); + PyErr_Format(PyExc_ValueError, + "precision too big at position %zd", + arg->fmtstart); return -1; } arg->prec = arg->prec*10 + (arg->ch - '0'); @@ -589,8 +649,8 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, } } if (ctx->fmtcnt < 0) { - PyErr_SetString(PyExc_ValueError, - "incomplete format"); + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd", arg->fmtstart); return -1; } return 0; @@ -660,7 +720,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, case 'x': case 'X': { - int ret = mainformatlong(v, arg, p_str, writer); + int ret = mainformatlong(v, ctx, arg, p_str, writer); if (ret != 0) return ret; arg->sign = 1; @@ -677,19 +737,19 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, && !(arg->flags & (F_SIGN | F_BLANK))) { /* Fast path */ - if (formatfloat(v, arg, NULL, writer) == -1) + if (formatfloat(v, ctx, arg, NULL, writer) == -1) return -1; return 1; } arg->sign = 1; - if (formatfloat(v, arg, p_str, NULL) == -1) + if (formatfloat(v, ctx, arg, p_str, NULL) == -1) return -1; break; case 'c': { - Py_UCS4 ch = formatchar(v); + Py_UCS4 ch = formatchar(v, ctx, arg); if (ch == (Py_UCS4) -1) return -1; if (arg->width == -1 && arg->prec == -1) { @@ -703,12 +763,31 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, } default: - PyErr_Format(PyExc_ValueError, - "unsupported format character '%c' (0x%x) " - "at index %zd", - (31<=arg->ch && arg->ch<=126) ? (char)arg->ch : '?', - (int)arg->ch, - ctx->fmtpos - 1); + if (arg->ch < 128 && Py_ISALPHA(arg->ch)) { + PyErr_Format(PyExc_ValueError, + "unsupported format %%%c at position %zd", + (int)arg->ch, arg->fmtstart); + } + else if (arg->ch >= 32 && arg->ch < 127) { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character '%c' at position %zd", + arg->fmtstart, + (int)arg->ch, ctx->fmtpos - 1); + } + else if (Py_UNICODE_ISPRINTABLE(arg->ch)) { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character '%c' (U+%04X) at position %zd", + arg->fmtstart, + (int)arg->ch, (int)arg->ch, ctx->fmtpos - 1); + } + else { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character U+%04X at position %zd", + arg->fmtstart, (int)arg->ch, ctx->fmtpos - 1); + } return -1; } if (*p_str == NULL) @@ -892,29 +971,40 @@ unicode_format_arg(struct unicode_formatter_t *ctx) arg.width = -1; arg.prec = -1; arg.sign = 0; + arg.fmtstart = ctx->fmtpos - 1; + arg.key = NULL; str = NULL; ret = unicode_format_arg_parse(ctx, &arg); - if (ret == -1) - return -1; + if (ret == -1) { + goto onError; + } ret = unicode_format_arg_format(ctx, &arg, &str); - if (ret == -1) - return -1; + if (ret == -1) { + goto onError; + } if (ret != 1) { ret = unicode_format_arg_output(ctx, &arg, str); Py_DECREF(str); - if (ret == -1) - return -1; + if (ret == -1) { + goto onError; + } } if (ctx->dict && (ctx->argidx < ctx->arglen)) { + // XXX: Never happens? PyErr_SetString(PyExc_TypeError, "not all arguments converted during string formatting"); - return -1; + goto onError; } + Py_XDECREF(arg.key); return 0; + + onError: + Py_XDECREF(arg.key); + return -1; } @@ -983,8 +1073,11 @@ PyUnicode_Format(PyObject *format, PyObject *args) } if (ctx.argidx < ctx.arglen && !ctx.dict) { - PyErr_SetString(PyExc_TypeError, - "not all arguments converted during string formatting"); + PyErr_Format(PyExc_TypeError, + "not all arguments converted during string formatting " + "(required %zd, got %zd)", + ctx.arglen < 0 ? 0 : ctx.argidx, + ctx.arglen < 0 ? 1 : ctx.arglen); goto onError; } From c52feb2dfb89059fb32e821292121245d26f6c6c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 1 Dec 2025 17:08:21 +0200 Subject: [PATCH 2/3] Improve error message for '. --- Lib/test/test_format.py | 6 ++++-- Objects/bytesobject.c | 2 +- Objects/unicode_format.c | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index b6d8f3227dced3..958ac5229801ec 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -278,8 +278,6 @@ def test_common_format(self): "unsupported format %z at position 4") test_exc_common("abc %Id", 1, ValueError, "unsupported format %I at position 4") - test_exc_common("abc %'d", 1, ValueError, - "stray % at position 4 or unexpected format character ''' at position 5") test_exc_common("abc %1 d", 1, ValueError, "stray % at position 4 or unexpected format character ' ' at position 6") test_exc_common('abc % (x)r', {}, ValueError, @@ -363,6 +361,8 @@ def test_str_format(self): print('Testing exceptions') test_exc('abc %b', 1, ValueError, "unsupported format %b at position 4") + test_exc("abc %'d", 1, ValueError, + "stray % at position 4 or unexpected format character ''' (U+0027) at position 5") test_exc("abc %\nd", 1, ValueError, "stray % at position 4 or unexpected format character U+000A at position 5") test_exc("abc %\x1fd", 1, ValueError, @@ -465,6 +465,8 @@ def __bytes__(self): print('Testing exceptions') test_exc(b"abc %\nd", 1, ValueError, "stray % at position 4 or unexpected format character with code 0x0a at position 5") + test_exc(b"abc %'d", 1, ValueError, + "stray % at position 4 or unexpected format character with code 0x27 at position 5") test_exc(b"abc %\x1fd", 1, ValueError, "stray % at position 4 or unexpected format character with code 0x1f at position 5") test_exc(b"abc %\x7fd", 1, ValueError, diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index f47794a0003ad4..5e4d77307a4b90 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -1012,7 +1012,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, "unsupported format %%%c at position %zd", c, (Py_ssize_t)(fmtstart - format - 1)); } - else if (c >= 32 && c < 127) { + else if (c >= 32 && c < 127 && c != '\'') { PyErr_Format(PyExc_ValueError, "stray %% at position %zd or unexpected " "format character '%c' " diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c index 6f6f5266f00774..1aae9ea9789de1 100644 --- a/Objects/unicode_format.c +++ b/Objects/unicode_format.c @@ -768,7 +768,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, "unsupported format %%%c at position %zd", (int)arg->ch, arg->fmtstart); } - else if (arg->ch >= 32 && arg->ch < 127) { + else if (arg->ch >= 32 && arg->ch < 127 && arg->ch != '\'') { PyErr_Format(PyExc_ValueError, "stray %% at position %zd or unexpected " "format character '%c' at position %zd", From c015d6a2fb97e568016facfce06bd9647beb348f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 2 Dec 2025 00:33:00 +0200 Subject: [PATCH 3/3] Try another format. --- Lib/test/test_bytes.py | 20 ++++++++--------- Lib/test/test_format.py | 46 ++++++++++++++++++++------------------ Lib/test/test_peepholer.py | 8 +++---- Lib/test/test_str.py | 20 ++++++++--------- Objects/bytesobject.c | 35 +++++++++++++++++++---------- Objects/unicode_format.c | 33 +++++++++++++++++---------- 6 files changed, 92 insertions(+), 70 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 5ee422d9f94ae4..851bf9d557695c 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -781,16 +781,16 @@ def __int__(self): pi = PseudoFloat(3.1415) exceptions_params = [ - ('an integer is required for format %x, not float', b'%x', 3.14), - ('an integer is required for format %X, not float', b'%X', 2.11), - ('an integer is required for format %o, not float', b'%o', 1.79), - (r'an integer is required for format %x, not .*\.PseudoFloat', b'%x', pi), - ('an integer is required for format %x, not complex', b'%x', 3j), - ('an integer is required for format %X, not complex', b'%X', 2j), - ('an integer is required for format %o, not complex', b'%o', 1j), - ('a real number is required for format %u, not complex', b'%u', 3j), - ('a real number is required for format %i, not complex', b'%i', 2j), - ('a real number is required for format %d, not complex', b'%d', 2j), + ('%x requires an integer, not float', b'%x', 3.14), + ('%X requires an integer, not float', b'%X', 2.11), + ('%o requires an integer, not float', b'%o', 1.79), + (r'%x requires an integer, not .*\.PseudoFloat', b'%x', pi), + ('%x requires an integer, not complex', b'%x', 3j), + ('%X requires an integer, not complex', b'%X', 2j), + ('%o requires an integer, not complex', b'%o', 1j), + ('%u requires a real number, not complex', b'%u', 3j), + ('%i requires a real number, not complex', b'%i', 2j), + ('%d requires a real number, not complex', b'%d', 2j), ( r'%c requires an integer in range\(256\)' r' or a single byte, not .*\.PseudoFloat', diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 958ac5229801ec..418caab046c6d3 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -278,6 +278,8 @@ def test_common_format(self): "unsupported format %z at position 4") test_exc_common("abc %Id", 1, ValueError, "unsupported format %I at position 4") + test_exc_common("abc %'d", 1, ValueError, + "stray % at position 4 or unexpected format character \"'\" at position 5") test_exc_common("abc %1 d", 1, ValueError, "stray % at position 4 or unexpected format character ' ' at position 6") test_exc_common('abc % (x)r', {}, ValueError, @@ -312,12 +314,16 @@ def test_common_format(self): "not enough arguments for format string (got 0)") test_exc_common('%(x)r', 1, TypeError, "format requires a mapping, not int") + test_exc_common('%*r', 1, TypeError, + "not enough arguments for format string (got 1)") test_exc_common('%*r', 3.14, TypeError, - "* requires int, not float") + "not enough arguments for format string (got 1)") test_exc_common('%*r', (3.14, 1), TypeError, "format argument 1: * requires int, not float") + test_exc_common('%.*r', 1, TypeError, + "not enough arguments for format string (got 1)") test_exc_common('%.*r', 3.14, TypeError, - "* requires int, not float") + "not enough arguments for format string (got 1)") test_exc_common('%.*r', (3.14, 1), TypeError, "format argument 1: * requires int, not float") test_exc_common('%*r', (2**1000, 1), OverflowError, @@ -329,25 +335,25 @@ def test_common_format(self): test_exc_common('%.*r', (-2**1000, 1), OverflowError, "format argument 1: too big for precision") test_exc_common('%d', '1', TypeError, - "a real number is required for format %d, not str") + "%d requires a real number, not str") test_exc_common('%d', b'1', TypeError, - "a real number is required for format %d, not bytes") + "%d requires a real number, not bytes") test_exc_common('%d', ('1',), TypeError, - "format argument 1: a real number is required for format %d, not str") + "format argument 1: %d requires a real number, not str") test_exc_common('%x', '1', TypeError, - "an integer is required for format %x, not str") + "%x requires an integer, not str") test_exc_common('%x', 3.14, TypeError, - "an integer is required for format %x, not float") + "%x requires an integer, not float") test_exc_common('%x', ('1',), TypeError, - "format argument 1: an integer is required for format %x, not str") + "format argument 1: %x requires an integer, not str") test_exc_common('%i', '1', TypeError, - "a real number is required for format %i, not str") + "%i requires a real number, not str") test_exc_common('%i', b'1', TypeError, - "a real number is required for format %i, not bytes") + "%i requires a real number, not bytes") test_exc_common('%g', '1', TypeError, - "a real number is required for format %g, not str") + "%g requires a real number, not str") test_exc_common('%g', ('1',), TypeError, - "format argument 1: a real number is required for format %g, not str") + "format argument 1: %g requires a real number, not str") def test_str_format(self): testformat("%r", "\u0378", "'\\u0378'") # non printable @@ -361,8 +367,6 @@ def test_str_format(self): print('Testing exceptions') test_exc('abc %b', 1, ValueError, "unsupported format %b at position 4") - test_exc("abc %'d", 1, ValueError, - "stray % at position 4 or unexpected format character ''' (U+0027) at position 5") test_exc("abc %\nd", 1, ValueError, "stray % at position 4 or unexpected format character U+000A at position 5") test_exc("abc %\x1fd", 1, ValueError, @@ -386,11 +390,11 @@ def test_str_format(self): test_exc('%(x).*r', {'x': 1}, ValueError, "* cannot be used with a parenthesised mapping key at position 0") test_exc('%(x)d', {'x': '1'}, TypeError, - "format argument 'x': a real number is required for format %d, not str") + "format argument 'x': %d requires a real number, not str") test_exc('%(x)x', {'x': '1'}, TypeError, - "format argument 'x': an integer is required for format %x, not str") + "format argument 'x': %x requires an integer, not str") test_exc('%(x)g', {'x': '1'}, TypeError, - "format argument 'x': a real number is required for format %g, not str") + "format argument 'x': %g requires a real number, not str") test_exc('%c', -1, OverflowError, "%c argument not in range(0x110000)") test_exc('%c', (-1,), OverflowError, "format argument 1: %c argument not in range(0x110000)") @@ -465,8 +469,6 @@ def __bytes__(self): print('Testing exceptions') test_exc(b"abc %\nd", 1, ValueError, "stray % at position 4 or unexpected format character with code 0x0a at position 5") - test_exc(b"abc %'d", 1, ValueError, - "stray % at position 4 or unexpected format character with code 0x27 at position 5") test_exc(b"abc %\x1fd", 1, ValueError, "stray % at position 4 or unexpected format character with code 0x1f at position 5") test_exc(b"abc %\x7fd", 1, ValueError, @@ -488,11 +490,11 @@ def __bytes__(self): test_exc(b'%(x).*r', {b'x': 1}, ValueError, "* cannot be used with a parenthesised mapping key at position 0") test_exc(b'%(x)d', {b'x': '1'}, TypeError, - "format argument b'x': a real number is required for format %d, not str") + "format argument b'x': %d requires a real number, not str") test_exc(b'%(x)x', {b'x': '1'}, TypeError, - "format argument b'x': an integer is required for format %x, not str") + "format argument b'x': %x requires an integer, not str") test_exc(b'%(x)g', {b'x': '1'}, TypeError, - "format argument b'x': a real number is required for format %g, not str") + "format argument b'x': %g requires a real number, not str") test_exc(b"%c", -1, OverflowError, "%c argument not in range(256)") test_exc(b"%c", (-1,), OverflowError, diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index b7e44381aa2a6f..88d20bbb028d6f 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -744,16 +744,16 @@ def test_format_errors(self): 'unsupported format %z at position 2'): eval("'%s%z' % (x, 5)", {'x': 1234}) with self.assertRaisesRegex(TypeError, - 'format argument 1: a real number is required for format %d, not str'): + 'format argument 1: %d requires a real number, not str'): eval("'%d' % (x,)", {'x': '1234'}) with self.assertRaisesRegex(TypeError, - 'format argument 1: an integer is required for format %x, not float'): + 'format argument 1: %x requires an integer, not float'): eval("'%x' % (x,)", {'x': 1234.56}) with self.assertRaisesRegex(TypeError, - 'format argument 1: an integer is required for format %x, not str'): + 'format argument 1: %x requires an integer, not str'): eval("'%x' % (x,)", {'x': '1234'}) with self.assertRaisesRegex(TypeError, - 'format argument 1: a real number is required for format %f, not str'): + 'format argument 1: %f requires a real number, not str'): eval("'%f' % (x,)", {'x': '1234'}) with self.assertRaisesRegex(TypeError, 'not enough arguments for format string'): diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 538ecda905754e..abbef066597709 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -1578,16 +1578,16 @@ def __int__(self): self.assertEqual('%X' % letter_m, '6D') self.assertEqual('%o' % letter_m, '155') self.assertEqual('%c' % letter_m, 'm') - self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not float', operator.mod, '%x', 3.14) - self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not float', operator.mod, '%X', 2.11) - self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not float', operator.mod, '%o', 1.79) - self.assertRaisesRegex(TypeError, r'an integer is required for format %x, not .*\.PseudoFloat', operator.mod, '%x', pi) - self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not complex', operator.mod, '%x', 3j) - self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not complex', operator.mod, '%X', 2j) - self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not complex', operator.mod, '%o', 1j) - self.assertRaisesRegex(TypeError, 'a real number is required for format %u, not complex', operator.mod, '%u', 3j) - self.assertRaisesRegex(TypeError, 'a real number is required for format %i, not complex', operator.mod, '%i', 2j) - self.assertRaisesRegex(TypeError, 'a real number is required for format %d, not complex', operator.mod, '%d', 1j) + self.assertRaisesRegex(TypeError, '%x requires an integer, not float', operator.mod, '%x', 3.14) + self.assertRaisesRegex(TypeError, '%X requires an integer, not float', operator.mod, '%X', 2.11) + self.assertRaisesRegex(TypeError, '%o requires an integer, not float', operator.mod, '%o', 1.79) + self.assertRaisesRegex(TypeError, r'%x requires an integer, not .*\.PseudoFloat', operator.mod, '%x', pi) + self.assertRaisesRegex(TypeError, '%x requires an integer, not complex', operator.mod, '%x', 3j) + self.assertRaisesRegex(TypeError, '%X requires an integer, not complex', operator.mod, '%X', 2j) + self.assertRaisesRegex(TypeError, '%o requires an integer, not complex', operator.mod, '%o', 1j) + self.assertRaisesRegex(TypeError, '%u requires a real number, not complex', operator.mod, '%u', 3j) + self.assertRaisesRegex(TypeError, '%i requires a real number, not complex', operator.mod, '%i', 2j) + self.assertRaisesRegex(TypeError, '%d requires a real number, not complex', operator.mod, '%d', 1j) self.assertRaisesRegex(TypeError, r'%c requires an integer or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi) class RaisingNumber: diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 5e4d77307a4b90..7bed102612504c 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -419,15 +419,17 @@ PyBytes_FromFormat(const char *format, ...) } while (0) Py_LOCAL_INLINE(PyObject *) -getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) +getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx, int allowone) { Py_ssize_t argidx = *p_argidx; if (argidx < arglen) { (*p_argidx)++; - if (arglen < 0) - return args; - else + if (arglen >= 0) { return PyTuple_GetItem(args, argidx); + } + else if (allowone) { + return args; + } } PyErr_Format(PyExc_TypeError, "not enough arguments for format string (got %zd)", @@ -452,7 +454,7 @@ formatfloat(PyObject *v, Py_ssize_t argidx, PyObject *key, if (x == -1.0 && PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { FORMAT_ERROR(PyExc_TypeError, - "a real number is required for format %%%c, not %T", + "%%%c requires a real number, not %T", type, v); } return NULL; @@ -511,10 +513,11 @@ formatlong(PyObject *v, Py_ssize_t argidx, PyObject *key, return NULL; } FORMAT_ERROR(PyExc_TypeError, - "%s is required for format %%%c, not %T", + "%%%c requires %s, not %T", + type, (type == 'o' || type == 'x' || type == 'X') ? "an integer" : "a real number", - type, v); + v); return NULL; } @@ -782,7 +785,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, (Py_ssize_t)(fmtstart - format - 1)); goto error; } - v = getnextarg(args, arglen, &argidx); + v = getnextarg(args, arglen, &argidx, 0); if (v == NULL) goto error; if (!PyLong_Check(v)) { @@ -833,7 +836,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, (Py_ssize_t)(fmtstart - format - 1)); goto error; } - v = getnextarg(args, arglen, &argidx); + v = getnextarg(args, arglen, &argidx, 0); if (v == NULL) goto error; if (!PyLong_Check(v)) { @@ -862,8 +865,8 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, break; if (prec > (INT_MAX - ((int)c - '0')) / 10) { PyErr_Format(PyExc_ValueError, - "precision too big at position %zd", - (Py_ssize_t)(fmtstart - format - 1)); + "precision too big at position %zd", + (Py_ssize_t)(fmtstart - format - 1)); goto error; } prec = prec*10 + (c - '0'); @@ -882,7 +885,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, (Py_ssize_t)(fmtstart - format - 1)); goto error; } - v = getnextarg(args, arglen, &argidx); + v = getnextarg(args, arglen, &argidx, 1); if (v == NULL) goto error; @@ -1012,6 +1015,14 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len, "unsupported format %%%c at position %zd", c, (Py_ssize_t)(fmtstart - format - 1)); } + else if (c == '\'') { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character \"'\" " + "at position %zd", + (Py_ssize_t)(fmtstart - format - 1), + (Py_ssize_t)(fmt - format - 1)); + } else if (c >= 32 && c < 127 && c != '\'') { PyErr_Format(PyExc_ValueError, "stray %% at position %zd or unexpected " diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c index 1aae9ea9789de1..5c7bcfb22ebd06 100644 --- a/Objects/unicode_format.c +++ b/Objects/unicode_format.c @@ -93,16 +93,18 @@ struct unicode_format_arg_t { static PyObject * -unicode_format_getnextarg(struct unicode_formatter_t *ctx) +unicode_format_getnextarg(struct unicode_formatter_t *ctx, int allowone) { Py_ssize_t argidx = ctx->argidx; - if (argidx < ctx->arglen) { + if (argidx < ctx->arglen && (allowone || ctx->arglen >= 0)) { ctx->argidx++; - if (ctx->arglen < 0) - return ctx->args; - else + if (ctx->arglen >= 0) { return PyTuple_GetItem(ctx->args, argidx); + } + else if (allowone) { + return ctx->args; + } } PyErr_Format(PyExc_TypeError, "not enough arguments for format string (got %zd)", @@ -134,7 +136,7 @@ formatfloat(PyObject *v, if (x == -1.0 && PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { FORMAT_ERROR(PyExc_TypeError, - "a real number is required for format %%%c, not %T", + "%%%c requires a real number, not %T", arg->ch, v); } return -1; @@ -392,12 +394,12 @@ mainformatlong(PyObject *v, case 'x': case 'X': FORMAT_ERROR(PyExc_TypeError, - "an integer is required for format %%%c, not %T", + "%%%c requires an integer, not %T", arg->ch, v); break; default: FORMAT_ERROR(PyExc_TypeError, - "a real number is required for format %%%c, not %T", + "%%%c requires a real number, not %T", arg->ch, v); break; } @@ -540,7 +542,7 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, arg->fmtstart); return -1; } - v = unicode_format_getnextarg(ctx); + v = unicode_format_getnextarg(ctx, 0); if (v == NULL) return -1; if (!PyLong_Check(v)) { @@ -599,7 +601,7 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, arg->fmtstart); return -1; } - v = unicode_format_getnextarg(ctx); + v = unicode_format_getnextarg(ctx, 0); if (v == NULL) return -1; if (!PyLong_Check(v)) { @@ -684,7 +686,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, if (ctx->fmtcnt == 0) ctx->writer.overallocate = 0; - v = unicode_format_getnextarg(ctx); + v = unicode_format_getnextarg(ctx, 1); if (v == NULL) return -1; @@ -768,7 +770,14 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, "unsupported format %%%c at position %zd", (int)arg->ch, arg->fmtstart); } - else if (arg->ch >= 32 && arg->ch < 127 && arg->ch != '\'') { + else if (arg->ch == '\'') { + PyErr_Format(PyExc_ValueError, + "stray %% at position %zd or unexpected " + "format character \"'\" at position %zd", + arg->fmtstart, + ctx->fmtpos - 1); + } + else if (arg->ch >= 32 && arg->ch < 127) { PyErr_Format(PyExc_ValueError, "stray %% at position %zd or unexpected " "format character '%c' at position %zd",