Skip to content

Commit 5058a49

Browse files
committed
bpo-29854: Fix segfault in call_readline() (pythonGH-728)
If history-length is set in .inputrc, and the history file is double the history size (or more), history_get(N) returns NULL, and python segfaults. Fix that by checking for NULL return value. It seems that the root cause is incorrect handling of bigger history in readline, but Python should not segfault even if readline returns unexpected value. This issue affects only GNU readline. When using libedit emulation system history size option does not work.
1 parent 3bbdf99 commit 5058a49

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

Lib/test/test_readline.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import sys
1010
import tempfile
1111
import unittest
12-
from test.support import import_module, unlink, TESTFN
12+
from test.support import import_module, unlink, temp_dir, TESTFN
1313
from test.support.script_helper import assert_python_ok
1414

1515
# Skip tests if there is no readline module
@@ -195,13 +195,50 @@ def display(substitution, matches, longest_match_length):
195195
self.assertIn(b"result " + expected + b"\r\n", output)
196196
self.assertIn(b"history " + expected + b"\r\n", output)
197197

198+
@unittest.skipIf(is_editline,
199+
"editline history size configuration is broken")
200+
def test_history_size(self):
201+
history_size = 10
202+
with temp_dir() as test_dir:
203+
inputrc = os.path.join(test_dir, "inputrc")
204+
with open(inputrc, "wb") as f:
205+
f.write(b"set history-size %d\n" % history_size)
206+
207+
history_file = os.path.join(test_dir, "history")
208+
with open(history_file, "wb") as f:
209+
# history_size * 2 items crashes readline
210+
data = b"".join(b"item %d\n" % i
211+
for i in range(history_size * 2))
212+
f.write(data)
213+
214+
script = """
215+
import os
216+
import readline
217+
218+
history_file = os.environ["HISTORY_FILE"]
219+
readline.read_history_file(history_file)
220+
input()
221+
readline.write_history_file(history_file)
222+
"""
223+
224+
env = dict(os.environ)
225+
env["INPUTRC"] = inputrc
226+
env["HISTORY_FILE"] = history_file
227+
228+
run_pty(script, input=b"last input\r", env=env)
229+
230+
with open(history_file, "rb") as f:
231+
lines = f.readlines()
232+
self.assertEqual(len(lines), history_size)
233+
self.assertEqual(lines[-1].strip(), b"last input")
234+
198235

199-
def run_pty(script, input=b"dummy input\r"):
236+
def run_pty(script, input=b"dummy input\r", env=None):
200237
pty = import_module('pty')
201238
output = bytearray()
202239
[master, slave] = pty.openpty()
203240
args = (sys.executable, '-c', script)
204-
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
241+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
205242
os.close(slave)
206243
with ExitStack() as cleanup:
207244
cleanup.enter_context(proc)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix segfault in readline when using readline's history-size option. Patch
2+
by Nir Soffer.

Modules/readline.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,15 +1330,17 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
13301330
if (n > 0) {
13311331
const char *line;
13321332
int length = _py_get_history_length();
1333-
if (length > 0)
1333+
if (length > 0) {
1334+
HIST_ENTRY *hist_ent;
13341335
#ifdef __APPLE__
13351336
if (using_libedit_emulation) {
13361337
/* handle older 0-based or newer 1-based indexing */
1337-
line = (const char *)history_get(length + libedit_history_start - 1)->line;
1338+
hist_ent = history_get(length + libedit_history_start - 1);
13381339
} else
13391340
#endif /* __APPLE__ */
1340-
line = (const char *)history_get(length)->line;
1341-
else
1341+
hist_ent = history_get(length);
1342+
line = hist_ent ? hist_ent->line : "";
1343+
} else
13421344
line = "";
13431345
if (strcmp(p, line))
13441346
add_history(p);

0 commit comments

Comments
 (0)