Skip to content

Commit 500cdae

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix-structseq-unnamed
2 parents ee2e5f8 + 5afbb60 commit 500cdae

8 files changed

Lines changed: 98 additions & 36 deletions

File tree

Lib/asyncio/graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def _build_graph_for_future(
6262
coro = coro.cr_await
6363
elif hasattr(coro, 'ag_await'):
6464
# A native async generator or duck-type compatible iterator
65-
st.append(FrameCallGraphEntry(coro.cr_frame))
65+
st.append(FrameCallGraphEntry(coro.ag_frame))
6666
coro = coro.ag_await
6767
else:
6868
break

Lib/test/test_asyncio/test_graph.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import io
3+
import sys
34
import unittest
45
from unittest import mock
56

@@ -147,6 +148,31 @@ async def main():
147148
'async generator CallStackTestBase.test_stack_async_gen.<locals>.gen()',
148149
stack_for_gen_nested_call[1])
149150

151+
def test_ag_frame_used_for_async_generator(self):
152+
# Regression test for gh-148736: the ag_await branch of
153+
# _build_graph_for_future must read ag_frame, not cr_frame.
154+
from asyncio.graph import _build_graph_for_future
155+
156+
sentinel_frame = sys._getframe()
157+
158+
class FakeAsyncGen:
159+
ag_await = None
160+
ag_frame = sentinel_frame
161+
162+
class FakeCoro:
163+
cr_frame = sentinel_frame
164+
cr_await = FakeAsyncGen()
165+
166+
loop = asyncio.new_event_loop()
167+
try:
168+
fut = loop.create_future()
169+
fut.get_coro = lambda: FakeCoro()
170+
result = _build_graph_for_future(fut)
171+
finally:
172+
loop.close()
173+
174+
self.assertEqual(len(result.call_stack), 2)
175+
150176
async def test_stack_gather(self):
151177

152178
stack_for_deep = None

Lib/test/test_curses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ def test_read_from_window(self):
974974
with self.subTest(ch=ch):
975975
stdscr.addstr(2, 0, ch)
976976
self.assertEqual(stdscr.instr(2, 0, 1), b)
977-
self.assertEqual(stdscr.inch(2, 0) & curses.A_CHARTEXT, b[0])
977+
self.assertEqual(stdscr.inch(2, 0), b[0])
978978

979979
def test_coordinate_errors(self):
980980
# Addressing a cell outside the window raises curses.error.

Lib/test/test_socket.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,6 +1787,23 @@ def testGetaddrinfo(self):
17871787
except socket.gaierror:
17881788
pass
17891789

1790+
@unittest.skipUnless(hasattr(socket, 'AI_NUMERICSERV'),
1791+
'needs socket.AI_NUMERICSERV')
1792+
@support.thread_unsafe('setlocale is not thread-safe')
1793+
@support.run_with_locales('LC_ALL',
1794+
'uk_UA.KOI8-U', 'uk_UA', 'ja_JP.eucJP', 'ja_JP.SJIS', 'ja_JP',
1795+
'ko_KR.eucKR', 'zh_CN.GB18030', 'el_GR.ISO8859-7',
1796+
'de_DE.ISO8859-1', 'ja_JP.UTF-8',
1797+
'')
1798+
def test_getaddrinfo_localized_error(self):
1799+
# gh-93251: the localized gai_strerror() message could fail to be
1800+
# decoded as UTF-8, so UnicodeDecodeError was raised
1801+
# instead of gaierror.
1802+
with self.assertRaises(socket.gaierror) as cm:
1803+
socket.getaddrinfo("localhost", "http",
1804+
flags=socket.AI_NUMERICSERV)
1805+
str(cm.exception)
1806+
17901807
@unittest.skipIf(_testcapi is None, "requires _testcapi")
17911808
def test_getaddrinfo_int_port_overflow(self):
17921809
# gh-74895: Test that getaddrinfo does not raise OverflowError on port.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a latent :exc:`AttributeError` in :mod:`asyncio` call-graph capture when
2+
walking an async generator's ``ag_await`` chain: the walker referenced
3+
``cr_frame`` instead of ``ag_frame``.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :exc:`UnicodeDecodeError` in :mod:`socket` functions
2+
(such as :func:`~socket.getaddrinfo` and :func:`~socket.gethostbyaddr`)
3+
when the localized error message of the C library is not UTF-8:
4+
decode it from the locale encoding.

Modules/_cursesmodule.c

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -776,30 +776,6 @@ curses_getcchar(const cchar_t *wcval, wchar_t *wstr, attr_t *attrs, int *pair)
776776
return rtn;
777777
}
778778

779-
/* winch() returns the low 8 bits of the character's code point with no locale
780-
conversion, unlike instr(), so recover the locale byte from the wide cell
781-
when the character maps to exactly one byte, keeping the attribute and color
782-
bits in RTN. A character with no single-byte form is left to winch(). */
783-
static chtype
784-
curses_cell_locale_byte(chtype rtn, const cchar_t *cell)
785-
{
786-
wchar_t wstr[CCHARW_MAX + 1];
787-
attr_t attrs;
788-
int pair;
789-
if (curses_getcchar(cell, wstr, &attrs, &pair) == ERR
790-
|| wstr[0] == L'\0' || wstr[1] != L'\0')
791-
{
792-
return rtn;
793-
}
794-
/* wctob() mirrors ncurses' own _nc_to_char(): the single-byte form, or EOF
795-
when the character has none in this locale. */
796-
int byte = wctob(wstr[0]);
797-
if (byte != EOF) {
798-
rtn = (rtn & ~(chtype)A_CHARTEXT) | (unsigned char)byte;
799-
}
800-
return rtn;
801-
}
802-
803779
/* Hash one cell by value (text, attributes, pair) -- consistent with the
804780
equality comparison, not the raw cchar_t whose padding and unused text tail
805781
it ignores. Zero the key first so those bytes are deterministic, then
@@ -3650,7 +3626,40 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
36503626
{
36513627
chtype rtn;
36523628
const char *funcname;
3653-
3629+
#ifdef HAVE_NCURSESW
3630+
/* ncursesw's winch() returns the character's whole code point instead of
3631+
its locale byte, overflowing the chtype's 8-bit character field into the
3632+
color and attribute bits; read the wide cell and rebuild it instead. */
3633+
cchar_t cell = {0};
3634+
int rc;
3635+
if (!group_right_1) {
3636+
rc = win_wch(self->win, &cell);
3637+
funcname = "win_wch";
3638+
}
3639+
else {
3640+
rc = mvwin_wch(self->win, y, x, &cell);
3641+
funcname = "mvwin_wch";
3642+
}
3643+
if (rc == ERR) {
3644+
curses_window_set_error(self, funcname, "inch");
3645+
return NULL;
3646+
}
3647+
wchar_t wstr[CCHARW_MAX + 1];
3648+
attr_t attrs;
3649+
int pair;
3650+
if (curses_getcchar(&cell, wstr, &attrs, &pair) == ERR) {
3651+
curses_window_set_error(self, "getcchar", "inch");
3652+
return NULL;
3653+
}
3654+
int byte = 0;
3655+
if (wstr[0] != L'\0' && wstr[1] == L'\0') {
3656+
byte = wctob(wstr[0]);
3657+
if (byte == EOF) {
3658+
byte = 0;
3659+
}
3660+
}
3661+
rtn = (chtype)byte | (attrs & ~(attr_t)A_COLOR) | COLOR_PAIR(pair);
3662+
#else
36543663
if (!group_right_1) {
36553664
rtn = winch(self->win);
36563665
funcname = "winch";
@@ -3663,13 +3672,6 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
36633672
curses_window_set_error(self, funcname, "inch");
36643673
return NULL;
36653674
}
3666-
#ifdef HAVE_NCURSESW
3667-
curses_cell_t cell = {0};
3668-
if ((group_right_1 ? mvwin_wch(self->win, y, x, &cell)
3669-
: win_wch(self->win, &cell)) != ERR)
3670-
{
3671-
rtn = curses_cell_locale_byte(rtn, &cell);
3672-
}
36733675
#endif
36743676
return PyLong_FromUnsignedLong(rtn);
36753677
}

Modules/socketmodule.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,14 +745,24 @@ set_error(void)
745745
}
746746

747747

748+
#if defined(HAVE_HSTRERROR) || defined(HAVE_GAI_STRERROR)
749+
/* Decode a locale-encoded error message from the C library.
750+
It can be localized and use a non-UTF-8 encoding. */
751+
static PyObject *
752+
decode_error_message(const char *str)
753+
{
754+
return PyUnicode_DecodeLocale(str, "surrogateescape");
755+
}
756+
#endif
757+
748758
#if defined(HAVE_GETHOSTBYNAME_R) || defined (HAVE_GETHOSTBYNAME) || defined (HAVE_GETHOSTBYADDR)
749759
static PyObject *
750760
set_herror(socket_state *state, int h_error)
751761
{
752762
PyObject *v;
753763

754764
#ifdef HAVE_HSTRERROR
755-
v = Py_BuildValue("(is)", h_error, hstrerror(h_error));
765+
v = Py_BuildValue("(iN)", h_error, decode_error_message(hstrerror(h_error)));
756766
#else
757767
v = Py_BuildValue("(is)", h_error, "host not found");
758768
#endif
@@ -779,7 +789,7 @@ set_gaierror(socket_state *state, int error)
779789
#endif
780790

781791
#ifdef HAVE_GAI_STRERROR
782-
v = Py_BuildValue("(is)", error, gai_strerror(error));
792+
v = Py_BuildValue("(iN)", error, decode_error_message(gai_strerror(error)));
783793
#else
784794
v = Py_BuildValue("(is)", error, "getaddrinfo failed");
785795
#endif

0 commit comments

Comments
 (0)