Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0d776b1

Browse files
committed
Issue #13342: input() used to ignore sys.stdin's and sys.stdout's unicode
error handler in interactive mode (when calling into PyOS_Readline()).
1 parent c2f0a46 commit 0d776b1

3 files changed

Lines changed: 114 additions & 42 deletions

File tree

Lib/test/test_builtin.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
import warnings
77
import collections
88
import io
9+
import os
910
import ast
1011
import types
1112
import builtins
1213
import random
1314
from test.support import fcmp, TESTFN, unlink, run_unittest, check_warnings
1415
from operator import neg
16+
try:
17+
import pty
18+
except ImportError:
19+
pty = None
1520

1621

1722
class Squares:
@@ -988,6 +993,71 @@ def test_input(self):
988993
fp.close()
989994
unlink(TESTFN)
990995

996+
@unittest.skipUnless(pty, "the pty module isn't available")
997+
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
998+
r, w = os.pipe()
999+
try:
1000+
pid, fd = pty.fork()
1001+
except (OSError, AttributeError) as e:
1002+
os.close(r)
1003+
os.close(w)
1004+
self.skipTest("pty.fork() raised {}".format(e))
1005+
if pid == 0:
1006+
# Child
1007+
os.close(r)
1008+
# Check the error handlers are accounted for
1009+
if stdio_encoding:
1010+
sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
1011+
encoding=stdio_encoding,
1012+
errors='surrogateescape')
1013+
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
1014+
encoding=stdio_encoding,
1015+
errors='replace')
1016+
with open(w, "w") as wpipe:
1017+
try:
1018+
print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
1019+
print(ascii(input(prompt)), file=wpipe)
1020+
finally:
1021+
print(";EOF", file=wpipe)
1022+
# We don't want to return to unittest...
1023+
os._exit(0)
1024+
# Parent
1025+
os.close(w)
1026+
os.write(fd, terminal_input + b"\r\n")
1027+
# Get results from the pipe
1028+
with open(r, "r") as rpipe:
1029+
lines = []
1030+
while True:
1031+
line = rpipe.readline().strip()
1032+
if line == ";EOF":
1033+
break
1034+
lines.append(line)
1035+
# Check we did exercise the GNU readline path
1036+
self.assertIn(lines[0], {'tty = True', 'tty = False'})
1037+
if lines[0] != 'tty = True':
1038+
self.skipTest("standard IO in should have been a tty")
1039+
# Check the result was got and corresponds to the user's terminal input
1040+
self.assertEqual(len(lines), 2)
1041+
input_result = eval(lines[1]) # ascii() -> eval() roundtrip
1042+
if stdio_encoding:
1043+
expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
1044+
else:
1045+
expected = terminal_input.decode(sys.stdin.encoding) # what else?
1046+
self.assertEqual(input_result, expected)
1047+
1048+
def test_input_tty(self):
1049+
# Test input() functionality when wired to a tty (the code path
1050+
# is different and invokes GNU readline if available).
1051+
self.check_input_tty("prompt", b"quux")
1052+
1053+
def test_input_tty_non_ascii(self):
1054+
# Check stdin/stdout encoding is used when invoking GNU readline
1055+
self.check_input_tty("prompté", b"quux\xe9", "utf-8")
1056+
1057+
def test_input_tty_non_ascii_unicode_errors(self):
1058+
# Check stdin/stdout error handler is used when invoking GNU readline
1059+
self.check_input_tty("prompté", b"quux\xe9", "ascii")
1060+
9911061
def test_repr(self):
9921062
self.assertEqual(repr(''), '\'\'')
9931063
self.assertEqual(repr(0), '0')

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.2.3?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #13342: input() used to ignore sys.stdin's and sys.stdout's unicode
14+
error handler in interactive mode (when calling into PyOS_Readline()).
15+
1316
- Issue #13340: Accept None as start and stop parameters for
1417
list.index() and tuple.index().
1518

Python/bltinmodule.c

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,76 +1640,65 @@ builtin_input(PyObject *self, PyObject *args)
16401640

16411641
/* If we're interactive, use (GNU) readline */
16421642
if (tty) {
1643-
PyObject *po;
1643+
PyObject *po = NULL;
16441644
char *prompt;
1645-
char *s;
1646-
PyObject *stdin_encoding;
1647-
char *stdin_encoding_str;
1645+
char *s = NULL;
1646+
PyObject *stdin_encoding = NULL, *stdin_errors = NULL;
1647+
PyObject *stdout_encoding = NULL, *stdout_errors = NULL;
1648+
char *stdin_encoding_str, *stdin_errors_str;
16481649
PyObject *result;
16491650
size_t len;
16501651

16511652
stdin_encoding = PyObject_GetAttrString(fin, "encoding");
1652-
if (!stdin_encoding)
1653+
stdin_errors = PyObject_GetAttrString(fin, "errors");
1654+
if (!stdin_encoding || !stdin_errors)
16531655
/* stdin is a text stream, so it must have an
16541656
encoding. */
1655-
return NULL;
1657+
goto _readline_errors;
16561658
stdin_encoding_str = _PyUnicode_AsString(stdin_encoding);
1657-
if (stdin_encoding_str == NULL) {
1658-
Py_DECREF(stdin_encoding);
1659-
return NULL;
1660-
}
1659+
stdin_errors_str = _PyUnicode_AsString(stdin_errors);
1660+
if (!stdin_encoding_str || !stdin_errors_str)
1661+
goto _readline_errors;
16611662
tmp = PyObject_CallMethod(fout, "flush", "");
16621663
if (tmp == NULL)
16631664
PyErr_Clear();
16641665
else
16651666
Py_DECREF(tmp);
16661667
if (promptarg != NULL) {
1668+
/* We have a prompt, encode it as stdout would */
1669+
char *stdout_encoding_str, *stdout_errors_str;
16671670
PyObject *stringpo;
1668-
PyObject *stdout_encoding;
1669-
char *stdout_encoding_str;
16701671
stdout_encoding = PyObject_GetAttrString(fout, "encoding");
1671-
if (stdout_encoding == NULL) {
1672-
Py_DECREF(stdin_encoding);
1673-
return NULL;
1674-
}
1672+
stdout_errors = PyObject_GetAttrString(fout, "errors");
1673+
if (!stdout_encoding || !stdout_errors)
1674+
goto _readline_errors;
16751675
stdout_encoding_str = _PyUnicode_AsString(stdout_encoding);
1676-
if (stdout_encoding_str == NULL) {
1677-
Py_DECREF(stdin_encoding);
1678-
Py_DECREF(stdout_encoding);
1679-
return NULL;
1680-
}
1676+
stdout_errors_str = _PyUnicode_AsString(stdout_errors);
1677+
if (!stdout_encoding_str || !stdout_errors_str)
1678+
goto _readline_errors;
16811679
stringpo = PyObject_Str(promptarg);
1682-
if (stringpo == NULL) {
1683-
Py_DECREF(stdin_encoding);
1684-
Py_DECREF(stdout_encoding);
1685-
return NULL;
1686-
}
1680+
if (stringpo == NULL)
1681+
goto _readline_errors;
16871682
po = PyUnicode_AsEncodedString(stringpo,
1688-
stdout_encoding_str, NULL);
1689-
Py_DECREF(stdout_encoding);
1690-
Py_DECREF(stringpo);
1691-
if (po == NULL) {
1692-
Py_DECREF(stdin_encoding);
1693-
return NULL;
1694-
}
1683+
stdout_encoding_str, stdout_errors_str);
1684+
Py_CLEAR(stdout_encoding);
1685+
Py_CLEAR(stdout_errors);
1686+
Py_CLEAR(stringpo);
1687+
if (po == NULL)
1688+
goto _readline_errors;
16951689
prompt = PyBytes_AsString(po);
1696-
if (prompt == NULL) {
1697-
Py_DECREF(stdin_encoding);
1698-
Py_DECREF(po);
1699-
return NULL;
1700-
}
1690+
if (prompt == NULL)
1691+
goto _readline_errors;
17011692
}
17021693
else {
17031694
po = NULL;
17041695
prompt = "";
17051696
}
17061697
s = PyOS_Readline(stdin, stdout, prompt);
1707-
Py_XDECREF(po);
17081698
if (s == NULL) {
17091699
if (!PyErr_Occurred())
17101700
PyErr_SetNone(PyExc_KeyboardInterrupt);
1711-
Py_DECREF(stdin_encoding);
1712-
return NULL;
1701+
goto _readline_errors;
17131702
}
17141703

17151704
len = strlen(s);
@@ -1727,12 +1716,22 @@ builtin_input(PyObject *self, PyObject *args)
17271716
len--; /* strip trailing '\n' */
17281717
if (len != 0 && s[len-1] == '\r')
17291718
len--; /* strip trailing '\r' */
1730-
result = PyUnicode_Decode(s, len, stdin_encoding_str, NULL);
1719+
result = PyUnicode_Decode(s, len, stdin_encoding_str,
1720+
stdin_errors_str);
17311721
}
17321722
}
17331723
Py_DECREF(stdin_encoding);
1724+
Py_DECREF(stdin_errors);
1725+
Py_XDECREF(po);
17341726
PyMem_FREE(s);
17351727
return result;
1728+
_readline_errors:
1729+
Py_XDECREF(stdin_encoding);
1730+
Py_XDECREF(stdout_encoding);
1731+
Py_XDECREF(stdin_errors);
1732+
Py_XDECREF(stdout_errors);
1733+
Py_XDECREF(po);
1734+
return NULL;
17361735
}
17371736

17381737
/* Fallback if we're not interactive */

0 commit comments

Comments
 (0)