Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ def set_width(self):
# http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
zero_char_width = \
Font(text, font=text.cget('font')).measure('0')
self.width = pixel_width // zero_char_width
# Some fonts report a zero width for '0' (gh-90304).
self.width = (pixel_width // zero_char_width if zero_char_width
else text.tk.getint(text.cget('width')))

def new_callback(self, event):
dirname, basename = self.io.defaultfilename()
Expand Down
13 changes: 13 additions & 0 deletions Lib/idlelib/idle_test/test_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from idlelib import editor
import unittest
from collections import namedtuple
from unittest import mock
from test.support import requires
from tkinter import Tk, Text

Expand Down Expand Up @@ -30,6 +31,18 @@ def test_init(self):
self.assertEqual(e.root, self.root)
e._close()

def test_set_width_zero_char_width(self):
# A zero-width '0' must not raise ZeroDivisionError (gh-90304).
e = Editor(root=self.root)
try:
with mock.patch.object(editor, 'Font') as MockFont:
MockFont.return_value.measure.return_value = 0
e.set_width()
self.assertEqual(e.width,
e.text.tk.getint(e.text.cget('width')))
finally:
e._close()


class GetLineIndentTest(unittest.TestCase):
def test_empty_lines(self):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,7 @@ def nested():
self.assertEqual(type_repr(t'''{ 0
& 1
| 2
}'''), 't"""{ 0\n & 1\n | 2}"""')
}'''), 't"""{ 0\n & 1\n | 2\n }"""')
self.assertEqual(
type_repr(Template("hi", Interpolation(42, "42"))), "t'hi{42}'"
)
Expand Down
163 changes: 138 additions & 25 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import sys
import textwrap
import unittest
from test import support
from test.support import import_helper
from test.support.script_helper import assert_python_failure

_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
_testcapi = import_helper.import_module('_testcapi')
Expand Down Expand Up @@ -316,12 +318,18 @@ def test_join(self):
bytes_join(b'', NULL)


def get_data_canary(writer):
size = writer.get_size() + 1
return writer.get_data(size)


class BaseWriterTest:
RESULT_TYPE = NotImplementedError
SMALL_BUFFER = 11 # bytes
assert SMALL_BUFFER < _testcapi.PyBytesWriter_small_buffer
LARGE_BUFFER = _testcapi.PyBytesWriter_small_buffer + 17 # bytes
NEW_BYTE = b'\xff'
CANARY_BYTE = b'\xdd'

def create_writer(self, alloc=0, string=b''):
raise NotImplementedError
Expand All @@ -344,6 +352,7 @@ def test_get_data(self):
# Test PyBytesWriter_GetData()
writer = self.create_writer(6)
NEW_BYTE = self.NEW_BYTE
CANARY_BYTE = self.CANARY_BYTE
self.assertEqual(writer.get_data(), NEW_BYTE * 6)
writer.write(0, b'abc')
self.assertEqual(writer.get_data(), b'abc' + NEW_BYTE * 3)
Expand All @@ -357,7 +366,7 @@ def test_get_data(self):
writer.write(0, b's' * small)
self.assertEqual(writer.get_data(), b's' * small)
writer.resize(large)
self.assertEqual(writer.get_data(), b's' * small + NEW_BYTE * (large - small))
self.assertEqual(writer.get_data(), b's' * small + CANARY_BYTE + NEW_BYTE * (large - small - 1))
writer.write(small, b'L' * (large - small))
self.assertEqual(writer.get_data(), b's' * small + b'L' * (large - small))

Expand Down Expand Up @@ -443,6 +452,47 @@ def test_resize(self):
writer.resize(_testcapi.PY_SSIZE_T_MAX)
self.assertEqual(writer.finish(), b'x' * size)

@unittest.skipUnless(support.Py_DEBUG, 'need debug build')
def test_resize_canary(self):
CANARY_BYTE = self.CANARY_BYTE
for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
with self.subTest(size=size):
# Truncate the last byte
data = b'x' * size
writer = self.create_writer(size)
writer.write(0, data)
self.assertEqual(get_data_canary(writer), data + CANARY_BYTE)
writer.resize(size - 1)
self.assertEqual(get_data_canary(writer), data[:-1] + CANARY_BYTE)
self.assertEqual(writer.finish(), data[:-1])

# Make the buffer empty
writer = self.create_writer(size)
writer.write(0, data)
writer.resize(0)
self.assertEqual(writer.get_data(), b'')
self.assertEqual(writer.finish(), b'')

@support.nomemtest
def test_resize_error(self):
# Test PyBytesWriter_Resize() error
init = b'x' * self.LARGE_BUFFER
writer = self.create_writer(len(init))
writer.write(0, init)
size = len(init) + 100
try:
with self.assertRaises(MemoryError):
_testcapi.set_nomemory(0)
writer.resize(size)
finally:
_testcapi.remove_mem_hooks()
suffix = b'still working'
writer.write_bytes(suffix, -1)
self.assertEqual(writer.finish(), init + suffix)

# Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
# if the new size is smaller than the allocated size

def test_grow(self):
# Test PyBytesWriter_Grow()
writer = self.create_writer(0)
Expand All @@ -461,24 +511,6 @@ def test_grow(self):
writer.grow(0) # noop
self.assertEqual(writer.finish(), b'number=123')

for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
with self.subTest(size=size):
# Truncate the last byte
data = b'x' * size
writer = self.create_writer(size)
writer.write(0, data)
self.assertEqual(writer.get_data(), data)
writer.grow(-1)
self.assertEqual(writer.get_data(), data[:-1])
self.assertEqual(writer.finish(), data[:-1])

# Make the buffer empty
writer = self.create_writer(size)
writer.write(0, data)
writer.grow(-size)
self.assertEqual(writer.get_data(), b'')
self.assertEqual(writer.finish(), b'')

# Switch from small buffer to large buffer
writer = self.create_writer()
small, large = self.SMALL_BUFFER, self.LARGE_BUFFER
Expand All @@ -500,25 +532,45 @@ def test_grow(self):
writer.grow(_testcapi.PY_SSIZE_T_MAX)
self.assertEqual(writer.finish(), b'x' * size)

@unittest.skipUnless(support.Py_DEBUG, 'need debug build')
def test_grow_canary(self):
CANARY_BYTE = self.CANARY_BYTE
for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
with self.subTest(size=size):
# Truncate the last byte
data = b'x' * size
writer = self.create_writer(size)
writer.write(0, data)
self.assertEqual(get_data_canary(writer), data + CANARY_BYTE)
writer.grow(-1)
self.assertEqual(get_data_canary(writer), data[:-1] + CANARY_BYTE)
self.assertEqual(writer.finish(), data[:-1])

# Make the buffer empty
writer = self.create_writer(size)
writer.write(0, data)
writer.grow(-size)
self.assertEqual(writer.get_data(), b'')
self.assertEqual(writer.finish(), b'')

@support.nomemtest
def test_resize_error(self):
# Test PyBytesWriter_Resize() error
def test_grow_error(self):
# Test PyBytesWriter_Grow() error
init = b'x' * self.LARGE_BUFFER
writer = self.create_writer(len(init))
writer.write(0, init)
size = len(init) + 100
try:
with self.assertRaises(MemoryError):
_testcapi.set_nomemory(0)
writer.resize(size)
writer.grow(100)
finally:
_testcapi.remove_mem_hooks()
suffix = b'still working'
writer.write_bytes(suffix, -1)
self.assertEqual(writer.finish(), init + suffix)

# Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
# if the new size is smaller than the allocated size
# Note: PyBytesWriter_Grow() leaves the buffer unchanged (no resize)
# if grow is negative.

def test_format_i(self):
# Test PyBytesWriter_Format()
Expand All @@ -531,6 +583,67 @@ def test_format_i(self):
writer.format_i(b'y=%i', 456)
self.assertEqual(writer.finish(), b'x=123, y=456')

@unittest.skipUnless(support.Py_DEBUG, 'need a Python debug build')
def test_canary_byte(self):
small_buffer = _testcapi.PyBytesWriter_small_buffer
large_size = small_buffer * 10
use_bytearray = (self.RESULT_TYPE == bytearray)

# Test small buffer and large buffer
for size in (0, self.SMALL_BUFFER, self.LARGE_BUFFER):
for operation in (
'writer.get_data()',
'writer.get_size()',
f'writer.resize({size} * 2)',
f'writer.grow({size})',
'writer.discard()',
'writer.finish()',
):
with self.subTest(size=size, operation=operation):
code = textwrap.dedent(f"""
from test.support import SuppressCrashReport
import os
import _testcapi
size = {size}
# Add an extra '#' byte to trigger a buffer overflow
data = b'x' * size + b'#'
use_bytearray = {use_bytearray}
writer = _testcapi.PyBytesWriter(size, use_bytearray)
with SuppressCrashReport():
writer.write(0, data, check=False)
try:
{operation}
except:
# Ignore all exceptions
pass
# If we reached this line, the operation didn't
# detect the overflow. Exit immediatetly without
# calling the writer destructor since it can detect
# the overflow.
os._exit(0)
""")
proc = assert_python_failure('-c', code)
self.assertIn(b'Buffer overflow detected in PyBytesWriter',
proc.err)
self.assertIn(f'at position {size}'.encode(),
proc.err)

@unittest.skipUnless(support.Py_DEBUG, 'need debug build')
def test_get_data_canary(self):
# Test PyBytesWriter_GetData()
NEW_BYTE = self.NEW_BYTE
CANARY_BYTE = self.CANARY_BYTE

writer = self.create_writer(6)
self.assertEqual(get_data_canary(writer),
NEW_BYTE * 6 + CANARY_BYTE)
writer.write(0, b'abc')
self.assertEqual(get_data_canary(writer),
b'abc' + NEW_BYTE * 3 + CANARY_BYTE)
writer.write(3, b'123')
self.assertEqual(get_data_canary(writer),
b'abc123' + CANARY_BYTE)


class BytesWriterTest(BaseWriterTest, unittest.TestCase):
RESULT_TYPE = bytes
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,14 @@ def __repr__(self):
self.assertEqual(f'''{f"{d["a#b"]}"=}''',
'f"{d["a#b"]}"=\'42\'')

result = f'''{(
1, # Force lexer metadata reconstruction.
"\"#")=}'''
self.assertEqual(
result,
'(\n 1, \n "\\"#")=(1, \'"#\')',
)

self.assertEqual(f'{ # some comment goes here
"""hello"""=}', ' \n """hello"""=\'hello\'')
self.assertEqual(f'{"""# this is not a comment
Expand Down
Loading
Loading