]> granicus.if.org Git - python/commitdiff
[3.6] bpo-29854: Fix segfault in call_readline() (GH-728)
authorNir Soffer <nirsof@gmail.com>
Sat, 8 Jul 2017 18:51:21 +0000 (21:51 +0300)
committerBerker Peksag <berker.peksag@gmail.com>
Sat, 8 Jul 2017 18:51:21 +0000 (21:51 +0300)
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.

Lib/test/test_readline.py
Misc/NEWS.d/next/Library/2017-07-07-02-18-57.bpo-29854.J8wKb_.rst [new file with mode: 0644]
Modules/readline.c

index 06a9149374aeab26dc18e339ce486c2c5251e122..9c979652a1d0e954c7fb1a19990a926fde97a554 100644 (file)
@@ -9,7 +9,7 @@ import subprocess
 import sys
 import tempfile
 import unittest
-from test.support import import_module, unlink, TESTFN
+from test.support import import_module, unlink, temp_dir, TESTFN
 from test.support.script_helper import assert_python_ok
 
 # Skip tests if there is no readline module
@@ -210,13 +210,57 @@ print("history", ascii(readline.get_history_item(1)))
         self.assertIn(b"result " + expected + b"\r\n", output)
         self.assertIn(b"history " + expected + b"\r\n", output)
 
+    # We have 2 reasons to skip this test:
+    # - readline: history size was added in 6.0
+    #   See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
+    # - editline: history size is broken on OS X 10.11.6.
+    #   Newer versions were not tested yet.
+    @unittest.skipIf(readline._READLINE_VERSION < 0x600,
+                     "this readline version does not support history-size")
+    @unittest.skipIf(is_editline,
+                     "editline history size configuration is broken")
+    def test_history_size(self):
+        history_size = 10
+        with temp_dir() as test_dir:
+            inputrc = os.path.join(test_dir, "inputrc")
+            with open(inputrc, "wb") as f:
+                f.write(b"set history-size %d\n" % history_size)
+
+            history_file = os.path.join(test_dir, "history")
+            with open(history_file, "wb") as f:
+                # history_size * 2 items crashes readline
+                data = b"".join(b"item %d\n" % i
+                                for i in range(history_size * 2))
+                f.write(data)
+
+            script = """
+import os
+import readline
+
+history_file = os.environ["HISTORY_FILE"]
+readline.read_history_file(history_file)
+input()
+readline.write_history_file(history_file)
+"""
+
+            env = dict(os.environ)
+            env["INPUTRC"] = inputrc
+            env["HISTORY_FILE"] = history_file
+
+            run_pty(script, input=b"last input\r", env=env)
+
+            with open(history_file, "rb") as f:
+                lines = f.readlines()
+            self.assertEqual(len(lines), history_size)
+            self.assertEqual(lines[-1].strip(), b"last input")
+
 
-def run_pty(script, input=b"dummy input\r"):
+def run_pty(script, input=b"dummy input\r", env=None):
     pty = import_module('pty')
     output = bytearray()
     [master, slave] = pty.openpty()
     args = (sys.executable, '-c', script)
-    proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
+    proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
     os.close(slave)
     with ExitStack() as cleanup:
         cleanup.enter_context(proc)
diff --git a/Misc/NEWS.d/next/Library/2017-07-07-02-18-57.bpo-29854.J8wKb_.rst b/Misc/NEWS.d/next/Library/2017-07-07-02-18-57.bpo-29854.J8wKb_.rst
new file mode 100644 (file)
index 0000000..5c43908
--- /dev/null
@@ -0,0 +1,2 @@
+Fix segfault in readline when using readline's history-size option.  Patch
+by Nir Soffer.
index 3b94d4a0d1d7599ac58e8ee2059f27ed0486f598..575479cff249befa86fa8ddd99df4dc6e1abb9a2 100644 (file)
@@ -1347,15 +1347,17 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
     if (should_auto_add_history && n > 0) {
         const char *line;
         int length = _py_get_history_length();
-        if (length > 0)
+        if (length > 0) {
+            HIST_ENTRY *hist_ent;
 #ifdef __APPLE__
             if (using_libedit_emulation) {
                 /* handle older 0-based or newer 1-based indexing */
-                line = (const char *)history_get(length + libedit_history_start - 1)->line;
+                hist_ent = history_get(length + libedit_history_start - 1);
             } else
 #endif /* __APPLE__ */
-            line = (const char *)history_get(length)->line;
-        else
+                hist_ent = history_get(length);
+            line = hist_ent ? hist_ent->line : "";
+        } else
             line = "";
         if (strcmp(p, line))
             add_history(p);