https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/174216
>From dcbcd064a3f70699c0b340ce6a545151e6cb9295 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Fri, 26 Dec 2025 15:03:30 +0000 Subject: [PATCH 1/5] [lldb] Fix typed commands not shown on the screen The issue is that in python3.14, `fcntl.ioctl` now throws a buffer overflow error when the buffer is too small (see https://github.com/python/cpython/pull/132919). This caused the Python interpreter to fail terminal detection and not properly echo user commands back to the screen. Fix by dropping the custom terminal size check entirely and using the built-in `sys.stdin.isatty()` instead. Fixes #173302 --- .../Python/lldbsuite/test/lldbpexpect.py | 1 + .../Interpreter/embedded_interpreter.py | 62 +++---------------- .../API/terminal/TestPythonInterpreterEcho.py | 62 +++++++++++++++++++ 3 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 lldb/test/API/terminal/TestPythonInterpreterEcho.py diff --git a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py index 3279e1fd39f8c..03b2500fbda52 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py +++ b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py @@ -10,6 +10,7 @@ @skipIfRemote +@skipIfWindows @add_test_categories(["pexpect"]) class PExpectTest(TestBase): NO_DEBUG_INFO_TESTCASE = True diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py index 42a9ab5fc367a..5eb582e131871 100644 --- a/lldb/source/Interpreter/embedded_interpreter.py +++ b/lldb/source/Interpreter/embedded_interpreter.py @@ -1,8 +1,6 @@ import sys import builtins import code -import lldb -import traceback try: import readline @@ -31,19 +29,6 @@ def is_libedit(): g_run_one_line_str = None - -def get_terminal_size(fd): - try: - import fcntl - import termios - import struct - - hw = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) - except: - hw = (0, 0) - return hw - - class LLDBExit(SystemExit): pass @@ -74,50 +59,21 @@ def readfunc_stdio(prompt): def run_python_interpreter(local_dict): # Pass in the dictionary, for continuity from one session to the next. try: - fd = sys.stdin.fileno() - interacted = False - if get_terminal_size(fd)[1] == 0: - try: - import termios - - old = termios.tcgetattr(fd) - if old[3] & termios.ECHO: - # Need to turn off echoing and restore - new = termios.tcgetattr(fd) - new[3] = new[3] & ~termios.ECHO - try: - termios.tcsetattr(fd, termios.TCSADRAIN, new) - interacted = True - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.", - readfunc=readfunc_stdio, - local=local_dict, - ) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) - except: - pass - # Don't need to turn off echoing - if not interacted: - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", - readfunc=readfunc_stdio, - local=local_dict, - ) - else: - # We have a real interactive terminal - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", - readfunc=readfunc, - local=local_dict, - ) + banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()'." + input_func = readfunc_stdio + + is_atty = sys.stdin.isatty() + if is_atty: + banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D." + input_func = readfunc + + code.interact(banner=banner, readfunc=input_func, local=local_dict) except LLDBExit: pass except SystemExit as e: if e.code: print("Script exited with code %s" % e.code) - def run_one_line(local_dict, input_string): global g_run_one_line_str try: diff --git a/lldb/test/API/terminal/TestPythonInterpreterEcho.py b/lldb/test/API/terminal/TestPythonInterpreterEcho.py new file mode 100644 index 0000000000000..afa8bca2c9be8 --- /dev/null +++ b/lldb/test/API/terminal/TestPythonInterpreterEcho.py @@ -0,0 +1,62 @@ +""" +Test that typing python expression in the terminal is echoed back to stdout. +""" + +from lldbsuite.test.decorators import skipIfAsan +from lldbsuite.test.lldbpexpect import PExpectTest + + +@skipIfAsan +class PythonInterpreterEchoTest(PExpectTest): + PYTHON_PROMPT = ">>> " + + def verify_command_echo( + self, command: str, expected_output: str = "", is_regex: bool = False + ): + assert self.child != None + child = self.child + self.assertIsNotNone(self.child, "expected a running lldb process.") + + child.sendline(command) + + # Build pattern list: match whichever comes first (output or prompt) + # This prevents waiting for a timeout if there's no match + pattern = [] + match_expected = expected_output and len(expected_output) > 0 + + if match_expected: + pattern.append(expected_output) + pattern.append(self.PYTHON_PROMPT) + + expect_func = child.expect if is_regex else child.expect_exact + match_idx = expect_func(pattern) + if match_expected: + self.assertEqual( + match_idx, 0, "Expected output `{expected_output}` in stdout." + ) + + self.assertIsNotNone(self.child.before, "Expected output before prompt") + self.assertIsInstance(self.child.before, bytes) + echoed_text: str = self.child.before.decode("ascii").strip() + self.assertEqual( + command, echoed_text, f"Command '{command}' should be echoed to stdout." + ) + + if match_expected: + child.expect_exact(self.PYTHON_PROMPT) + + def test_python_interpreter_echo(self): + """Test that that the user typed commands is echoed to stdout""" + + self.launch(use_colors=False, dimensions=(100, 100)) + + # enter the python interpreter + self.verify_command_echo( + "script --language python --", expected_output="Python.*\\.", is_regex=True + ) + self.child_in_script_interpreter = True + + self.verify_command_echo("val = 300") + self.verify_command_echo( + "print('result =', 300)", expected_output="result = 300" + ) >From f5b00e3e0d0978f71a15bdc21fda4d06d5a1e6a5 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Mon, 5 Jan 2026 12:47:52 +0000 Subject: [PATCH 2/5] add review changes --- lldb/source/Interpreter/embedded_interpreter.py | 3 +++ lldb/test/API/terminal/TestPythonInterpreterEcho.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py index 5eb582e131871..12c47bd712816 100644 --- a/lldb/source/Interpreter/embedded_interpreter.py +++ b/lldb/source/Interpreter/embedded_interpreter.py @@ -1,6 +1,8 @@ import sys import builtins import code +import lldb +import traceback try: import readline @@ -29,6 +31,7 @@ def is_libedit(): g_run_one_line_str = None + class LLDBExit(SystemExit): pass diff --git a/lldb/test/API/terminal/TestPythonInterpreterEcho.py b/lldb/test/API/terminal/TestPythonInterpreterEcho.py index afa8bca2c9be8..758a4f9cede5a 100644 --- a/lldb/test/API/terminal/TestPythonInterpreterEcho.py +++ b/lldb/test/API/terminal/TestPythonInterpreterEcho.py @@ -19,8 +19,8 @@ def verify_command_echo( child.sendline(command) - # Build pattern list: match whichever comes first (output or prompt) - # This prevents waiting for a timeout if there's no match + # Build pattern list: match whichever comes first (output or prompt). + # This prevents waiting for a timeout if there's no match. pattern = [] match_expected = expected_output and len(expected_output) > 0 @@ -50,7 +50,7 @@ def test_python_interpreter_echo(self): self.launch(use_colors=False, dimensions=(100, 100)) - # enter the python interpreter + # Enter the python interpreter. self.verify_command_echo( "script --language python --", expected_output="Python.*\\.", is_regex=True ) >From f8bb85a52efef0b75503e7e3404476dcf5e97e25 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Tue, 6 Jan 2026 16:02:07 +0000 Subject: [PATCH 3/5] [lldb] add new test cases --- .../python_api/file_handle/TestFileHandle.py | 48 ++++++++++++++++++- .../Shell/ScriptInterpreter/Python/io.test | 9 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 lldb/test/Shell/ScriptInterpreter/Python/io.test diff --git a/lldb/test/API/python_api/file_handle/TestFileHandle.py b/lldb/test/API/python_api/file_handle/TestFileHandle.py index b38585577f6f6..707044a3afb0f 100644 --- a/lldb/test/API/python_api/file_handle/TestFileHandle.py +++ b/lldb/test/API/python_api/file_handle/TestFileHandle.py @@ -111,10 +111,11 @@ def setUp(self): super(FileHandleTestCase, self).setUp() self.out_filename = self.getBuildArtifact("output") self.in_filename = self.getBuildArtifact("input") + self.err_filename = self.getBuildArtifact("error") def tearDown(self): super(FileHandleTestCase, self).tearDown() - for name in (self.out_filename, self.in_filename): + for name in (self.out_filename, self.in_filename, self.err_filename): if os.path.exists(name): os.unlink(name) @@ -679,6 +680,51 @@ def test_stdout_file(self): lines = [x for x in f.read().strip().split() if x != "7"] self.assertEqual(lines, ["foobar"]) + def test_stdout_file_interactive(self): + """Ensure when we read stdin from a file, outputs from python goes to the right I/O stream.""" + with open(self.in_filename, "w") as f: + f.write( + "script --language python --\nvalue = 250 + 5\nprint(value)\nprint(vel)" + ) + + with open(self.out_filename, "w") as outf, open( + self.in_filename, "r" + ) as inf, open(self.err_filename, "w") as errf: + status = self.dbg.SetOutputFile(lldb.SBFile(outf)) + self.assertSuccess(status) + status = self.dbg.SetErrorFile(lldb.SBFile(errf)) + self.assertSuccess(status) + status = self.dbg.SetInputFile(lldb.SBFile(inf)) + self.assertSuccess(status) + auto_handle_events = True + spawn_thread = False + num_errs = 0 + quit_requested = False + stopped_for_crash = False + opts = lldb.SBCommandInterpreterRunOptions() + self.dbg.RunCommandInterpreter( + auto_handle_events, + spawn_thread, + opts, + num_errs, + quit_requested, + stopped_for_crash, + ) + self.dbg.GetOutputFile().Flush() + expected_out_text = "255" + expected_err_text = "NameError" + # check stdout + with open(self.out_filename, "r") as f: + out_text = f.read() + self.assertIn(expected_out_text, out_text) + self.assertNotIn(expected_err_text, out_text) + + # check stderr + with open(self.err_filename, "r") as f: + err_text = f.read() + self.assertIn(expected_err_text, err_text) + self.assertNotIn(expected_out_text, err_text) + def test_identity(self): f = io.StringIO() sbf = lldb.SBFile(f) diff --git a/lldb/test/Shell/ScriptInterpreter/Python/io.test b/lldb/test/Shell/ScriptInterpreter/Python/io.test new file mode 100644 index 0000000000000..4427f50d4befe --- /dev/null +++ b/lldb/test/Shell/ScriptInterpreter/Python/io.test @@ -0,0 +1,9 @@ +# RUN: rm -rf %t.stdout +# RUN: cat %s | %lldb --script-language python > %t.stdout +# RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT +script +variable = 300 +print(variable) +quit + +# STDOUT: 300 \ No newline at end of file >From 8b8afbbb69367d06ee82dfa779bad87331d28b0a Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Tue, 6 Jan 2026 16:41:45 +0000 Subject: [PATCH 4/5] [lldb] check stderr --- lldb/test/Shell/ScriptInterpreter/Python/io.test | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lldb/test/Shell/ScriptInterpreter/Python/io.test b/lldb/test/Shell/ScriptInterpreter/Python/io.test index 4427f50d4befe..e50ce9fadfd59 100644 --- a/lldb/test/Shell/ScriptInterpreter/Python/io.test +++ b/lldb/test/Shell/ScriptInterpreter/Python/io.test @@ -1,9 +1,12 @@ -# RUN: rm -rf %t.stdout -# RUN: cat %s | %lldb --script-language python > %t.stdout +# RUN: rm -rf %t.stdout %t.stderr +# RUN: cat %s | %lldb --script-language python > %t.stdout 2> %t.stderr # RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT +# RUN: cat %t.stderr | FileCheck %s --check-prefix STDERR script variable = 300 print(variable) +print(not_value) quit -# STDOUT: 300 \ No newline at end of file +# STDOUT: 300 +# STDERR: NameError{{.*}}is not defined \ No newline at end of file >From 16835696c386729c74fbc86da9479ee8a76b884c Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Tue, 6 Jan 2026 16:59:03 +0000 Subject: [PATCH 5/5] add new line --- lldb/test/Shell/ScriptInterpreter/Python/io.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/Shell/ScriptInterpreter/Python/io.test b/lldb/test/Shell/ScriptInterpreter/Python/io.test index e50ce9fadfd59..25e3de41724e0 100644 --- a/lldb/test/Shell/ScriptInterpreter/Python/io.test +++ b/lldb/test/Shell/ScriptInterpreter/Python/io.test @@ -9,4 +9,4 @@ print(not_value) quit # STDOUT: 300 -# STDERR: NameError{{.*}}is not defined \ No newline at end of file +# STDERR: NameError{{.*}}is not defined _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
