https://github.com/jimingham updated https://github.com/llvm/llvm-project/pull/94786
>From bcd8c81c5fbc249886c53d8a2d1bc21a3f9e1ffd Mon Sep 17 00:00:00 2001 From: Jim Ingham <jing...@apple.com> Date: Fri, 7 Jun 2024 11:17:26 -0700 Subject: [PATCH 1/4] Add AllowRepeats to SBCommandInterpreterRunOptions. This is useful if you have a transcript of a user session and want to rerun those commands with RunCommandInterpreter. The same functionality is also useful in testing. --- ...SBCommandInterpreterRunOptionsDocstrings.i | 3 + .../lldb/API/SBCommandInterpreterRunOptions.h | 8 +++ .../lldb/Interpreter/CommandInterpreter.h | 14 ++++- .../API/SBCommandInterpreterRunOptions.cpp | 12 ++++ .../source/Interpreter/CommandInterpreter.cpp | 13 ++++- .../TestRunCommandInterpreterAPI.py | 57 +++++++++++++++---- 6 files changed, 91 insertions(+), 16 deletions(-) diff --git a/lldb/bindings/interface/SBCommandInterpreterRunOptionsDocstrings.i b/lldb/bindings/interface/SBCommandInterpreterRunOptionsDocstrings.i index b37da0535d18a..a4398d95ed0d1 100644 --- a/lldb/bindings/interface/SBCommandInterpreterRunOptionsDocstrings.i +++ b/lldb/bindings/interface/SBCommandInterpreterRunOptionsDocstrings.i @@ -10,5 +10,8 @@ A default SBCommandInterpreterRunOptions object has: * PrintResults: true * PrintErrors: true * AddToHistory: true +* AllowRepeats false +Interactive debug sessions always allow repeats, the AllowRepeats +run option only affects non-interactive sessions. ") lldb::SBCommandInterpreterRunOptions; diff --git a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h index 69b969267e755..b32a8ca51cd08 100644 --- a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h +++ b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h @@ -71,6 +71,14 @@ class LLDB_API SBCommandInterpreterRunOptions { bool GetSpawnThread() const; void SetSpawnThread(bool); + + bool GetAllowRepeats() const; + + // By default, RunCommandInterpreter will discard repeats if the + // IOHandler being used is not interactive. Setting AllowRepeats to true + // will override this behavior and always process empty lines in the input + // as a repeat command. + void SetAllowRepeats(bool); private: lldb_private::CommandInterpreterRunOptions *get() const; diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 8863523b2e31f..5c2bcd99681a4 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -93,15 +93,19 @@ class CommandInterpreterRunOptions { /// \param[in] add_to_history /// If \b true add the commands to the command history. If \b false, don't /// add them. + /// \param[in] process_repeats + /// If \b true then process empty lines as repeat commands even if the + /// interpreter is non-interactive. CommandInterpreterRunOptions(LazyBool stop_on_continue, LazyBool stop_on_error, LazyBool stop_on_crash, LazyBool echo_commands, LazyBool echo_comments, LazyBool print_results, LazyBool print_errors, - LazyBool add_to_history) + LazyBool add_to_history, LazyBool process_repeats) : m_stop_on_continue(stop_on_continue), m_stop_on_error(stop_on_error), m_stop_on_crash(stop_on_crash), m_echo_commands(echo_commands), m_echo_comment_commands(echo_comments), m_print_results(print_results), - m_print_errors(print_errors), m_add_to_history(add_to_history) {} + m_print_errors(print_errors), m_add_to_history(add_to_history), + m_allow_repeats(process_repeats) {} CommandInterpreterRunOptions() = default; @@ -182,6 +186,11 @@ class CommandInterpreterRunOptions { void SetSpawnThread(bool spawn_thread) { m_spawn_thread = spawn_thread ? eLazyBoolYes : eLazyBoolNo; } + bool GetAllowRepeats() const { return DefaultToNo(m_allow_repeats); } + + void SetAllowRepeats(bool allow_repeats) { + m_allow_repeats = allow_repeats ? eLazyBoolYes : eLazyBoolNo; + } LazyBool m_stop_on_continue = eLazyBoolCalculate; LazyBool m_stop_on_error = eLazyBoolCalculate; @@ -193,6 +202,7 @@ class CommandInterpreterRunOptions { LazyBool m_add_to_history = eLazyBoolCalculate; LazyBool m_auto_handle_events; LazyBool m_spawn_thread; + LazyBool m_allow_repeats = eLazyBoolCalculate; private: static bool DefaultToYes(LazyBool flag) { diff --git a/lldb/source/API/SBCommandInterpreterRunOptions.cpp b/lldb/source/API/SBCommandInterpreterRunOptions.cpp index 6c6b2aa15a792..0c7581d6f1f5b 100644 --- a/lldb/source/API/SBCommandInterpreterRunOptions.cpp +++ b/lldb/source/API/SBCommandInterpreterRunOptions.cpp @@ -164,6 +164,18 @@ void SBCommandInterpreterRunOptions::SetSpawnThread(bool spawn_thread) { m_opaque_up->SetSpawnThread(spawn_thread); } +bool SBCommandInterpreterRunOptions::GetAllowRepeats() const { + LLDB_INSTRUMENT_VA(this); + + return m_opaque_up->GetAllowRepeats(); +} + +void SBCommandInterpreterRunOptions::SetAllowRepeats(bool allow_repeats) { + LLDB_INSTRUMENT_VA(this, allow_repeats); + + m_opaque_up->SetAllowRepeats(allow_repeats); +} + lldb_private::CommandInterpreterRunOptions * SBCommandInterpreterRunOptions::get() const { return m_opaque_up.get(); diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index acd6294cb3f42..95b9bb491abf6 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -2707,7 +2707,8 @@ enum { eHandleCommandFlagEchoCommentCommand = (1u << 3), eHandleCommandFlagPrintResult = (1u << 4), eHandleCommandFlagPrintErrors = (1u << 5), - eHandleCommandFlagStopOnCrash = (1u << 6) + eHandleCommandFlagStopOnCrash = (1u << 6), + eHandleCommandFlagAllowRepeats = (1u << 7) }; void CommandInterpreter::HandleCommandsFromFile( @@ -3129,14 +3130,18 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler, return; const bool is_interactive = io_handler.GetIsInteractive(); - if (!is_interactive) { + bool allow_repeats = io_handler.GetFlags().Test(eHandleCommandFlagAllowRepeats); + + if (!is_interactive && !allow_repeats) { // When we are not interactive, don't execute blank lines. This will happen // sourcing a commands file. We don't want blank lines to repeat the // previous command and cause any errors to occur (like redefining an // alias, get an error and stop parsing the commands file). + // But obey the AllowRepeats flag if the user has set it. if (line.empty()) return; - + } + if (!is_interactive) { // When using a non-interactive file handle (like when sourcing commands // from a file) we need to echo the command out so we don't just see the // command output and no command... @@ -3388,6 +3393,8 @@ CommandInterpreter::GetIOHandler(bool force_create, flags |= eHandleCommandFlagPrintResult; if (options->m_print_errors != eLazyBoolNo) flags |= eHandleCommandFlagPrintErrors; + if (options->m_allow_repeats == eLazyBoolYes) + flags |= eHandleCommandFlagAllowRepeats; } else { flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult | eHandleCommandFlagPrintErrors; diff --git a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py index af97493133766..c9fa7e2da7358 100644 --- a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py +++ b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py @@ -47,28 +47,60 @@ def setUp(self): TestBase.setUp(self) self.stdin_path = self.getBuildArtifact("stdin.txt") - + self.stdout_path = self.getBuildArtifact("stdout.txt") + def run_commands_string(self, command_string, options = lldb.SBCommandInterpreterRunOptions()): + """Run the commands in command_string through RunCommandInterpreter. + Returns (n_errors, quit_requested, has_crashed, result_string).""" + with open(self.stdin_path, "w") as input_handle: - input_handle.write("nonexistingcommand\nquit") + input_handle.write(command_string) - self.dbg.SetInputFile(open(self.stdin_path, "r")) + n_errors = 0 + quit_requested = False + has_crashed = False + + with open(self.stdin_path, "r") as in_fileH, open(self.stdout_path, "w") as out_fileH: + self.dbg.SetInputFile(in_fileH) - # No need to track the output - devnull = open(os.devnull, "w") - self.dbg.SetOutputFile(devnull) - self.dbg.SetErrorFile(devnull) + self.dbg.SetOutputFile(out_fileH) + self.dbg.SetErrorFile(out_fileH) + + n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter( + True, False, options, 0, False, False + ) + + result_string = None + with open(self.stdout_path, "r") as out_fileH: + result_string = out_fileH.read() + + print(f"Command: '{command_string}'\nResult:\n{result_string}") + return (n_errors, quit_requested, has_crashed, result_string) + def test_run_session_with_error_and_quit(self): """Run non-existing and quit command returns appropriate values""" - n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter( - True, False, lldb.SBCommandInterpreterRunOptions(), 0, False, False - ) - + n_errors, quit_requested, has_crashed, _ = self.run_commands_string( + "nonexistingcommand\nquit\n") self.assertGreater(n_errors, 0) self.assertTrue(quit_requested) self.assertFalse(has_crashed) + def test_allow_repeat(self): + """Try auto-repeat of process launch - the command will fail and + the auto-repeat will fail because of no auto-repeat.""" + options = lldb.SBCommandInterpreterRunOptions() + options.SetEchoCommands(False) + options.SetAllowRepeats(True) + + n_errors, quit_requested, has_crashed, result_str = self.run_commands_string( + "process launch\n\n", options) + self.assertEqual(n_errors, 2) + self.assertFalse(quit_requested) + self.assertFalse(has_crashed) + + self.assertIn("invalid target", result_str) + self.assertIn("No auto repeat", result_str) class SBCommandInterpreterRunOptionsCase(TestBase): NO_DEBUG_INFO_TESTCASE = True @@ -86,6 +118,7 @@ def test_command_interpreter_run_options(self): self.assertTrue(opts.GetPrintResults()) self.assertTrue(opts.GetPrintErrors()) self.assertTrue(opts.GetAddToHistory()) + self.assertFalse(opts.GetAllowRepeats()) # Invert values opts.SetStopOnContinue(not opts.GetStopOnContinue()) @@ -95,6 +128,7 @@ def test_command_interpreter_run_options(self): opts.SetPrintResults(not opts.GetPrintResults()) opts.SetPrintErrors(not opts.GetPrintErrors()) opts.SetAddToHistory(not opts.GetAddToHistory()) + opts.SetAllowRepeats(not opts.GetAllowRepeats()) # Check the value changed self.assertTrue(opts.GetStopOnContinue()) @@ -104,3 +138,4 @@ def test_command_interpreter_run_options(self): self.assertFalse(opts.GetPrintResults()) self.assertFalse(opts.GetPrintErrors()) self.assertFalse(opts.GetAddToHistory()) + self.assertTrue(opts.GetAllowRepeats()) >From 364e4e066bf668e5d423b47e5dfbd8acb79f9c5a Mon Sep 17 00:00:00 2001 From: Jim Ingham <jing...@apple.com> Date: Fri, 7 Jun 2024 11:35:55 -0700 Subject: [PATCH 2/4] formatting --- .../lldb/API/SBCommandInterpreterRunOptions.h | 6 ++-- .../lldb/Interpreter/CommandInterpreter.h | 3 +- .../source/Interpreter/CommandInterpreter.cpp | 7 +++-- .../TestRunCommandInterpreterAPI.py | 29 ++++++++++++------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h index b32a8ca51cd08..c11bf5e404e04 100644 --- a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h +++ b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h @@ -71,14 +71,14 @@ class LLDB_API SBCommandInterpreterRunOptions { bool GetSpawnThread() const; void SetSpawnThread(bool); - + bool GetAllowRepeats() const; - + // By default, RunCommandInterpreter will discard repeats if the // IOHandler being used is not interactive. Setting AllowRepeats to true // will override this behavior and always process empty lines in the input // as a repeat command. - void SetAllowRepeats(bool); + void SetAllowRepeats(bool); private: lldb_private::CommandInterpreterRunOptions *get() const; diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 5c2bcd99681a4..665336fbf821b 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -100,7 +100,8 @@ class CommandInterpreterRunOptions { LazyBool stop_on_error, LazyBool stop_on_crash, LazyBool echo_commands, LazyBool echo_comments, LazyBool print_results, LazyBool print_errors, - LazyBool add_to_history, LazyBool process_repeats) + LazyBool add_to_history, + LazyBool process_repeats) : m_stop_on_continue(stop_on_continue), m_stop_on_error(stop_on_error), m_stop_on_crash(stop_on_crash), m_echo_commands(echo_commands), m_echo_comment_commands(echo_comments), m_print_results(print_results), diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 95b9bb491abf6..d82662fae971d 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -2708,7 +2708,7 @@ enum { eHandleCommandFlagPrintResult = (1u << 4), eHandleCommandFlagPrintErrors = (1u << 5), eHandleCommandFlagStopOnCrash = (1u << 6), - eHandleCommandFlagAllowRepeats = (1u << 7) + eHandleCommandFlagAllowRepeats = (1u << 7) }; void CommandInterpreter::HandleCommandsFromFile( @@ -3130,8 +3130,9 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler, return; const bool is_interactive = io_handler.GetIsInteractive(); - bool allow_repeats = io_handler.GetFlags().Test(eHandleCommandFlagAllowRepeats); - + bool allow_repeats = + io_handler.GetFlags().Test(eHandleCommandFlagAllowRepeats); + if (!is_interactive && !allow_repeats) { // When we are not interactive, don't execute blank lines. This will happen // sourcing a commands file. We don't want blank lines to repeat the diff --git a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py index c9fa7e2da7358..b0a5bb76feb3c 100644 --- a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py +++ b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py @@ -48,18 +48,23 @@ def setUp(self): self.stdin_path = self.getBuildArtifact("stdin.txt") self.stdout_path = self.getBuildArtifact("stdout.txt") - def run_commands_string(self, command_string, options = lldb.SBCommandInterpreterRunOptions()): + + def run_commands_string( + self, command_string, options=lldb.SBCommandInterpreterRunOptions() + ): """Run the commands in command_string through RunCommandInterpreter. - Returns (n_errors, quit_requested, has_crashed, result_string).""" - + Returns (n_errors, quit_requested, has_crashed, result_string).""" + with open(self.stdin_path, "w") as input_handle: input_handle.write(command_string) n_errors = 0 quit_requested = False has_crashed = False - - with open(self.stdin_path, "r") as in_fileH, open(self.stdout_path, "w") as out_fileH: + + with open(self.stdin_path, "r") as in_fileH, open( + self.stdout_path, "w" + ) as out_fileH: self.dbg.SetInputFile(in_fileH) self.dbg.SetOutputFile(out_fileH) @@ -75,32 +80,34 @@ def run_commands_string(self, command_string, options = lldb.SBCommandInterprete print(f"Command: '{command_string}'\nResult:\n{result_string}") return (n_errors, quit_requested, has_crashed, result_string) - def test_run_session_with_error_and_quit(self): """Run non-existing and quit command returns appropriate values""" n_errors, quit_requested, has_crashed, _ = self.run_commands_string( - "nonexistingcommand\nquit\n") + "nonexistingcommand\nquit\n" + ) self.assertGreater(n_errors, 0) self.assertTrue(quit_requested) self.assertFalse(has_crashed) def test_allow_repeat(self): """Try auto-repeat of process launch - the command will fail and - the auto-repeat will fail because of no auto-repeat.""" + the auto-repeat will fail because of no auto-repeat.""" options = lldb.SBCommandInterpreterRunOptions() options.SetEchoCommands(False) options.SetAllowRepeats(True) - + n_errors, quit_requested, has_crashed, result_str = self.run_commands_string( - "process launch\n\n", options) + "process launch\n\n", options + ) self.assertEqual(n_errors, 2) self.assertFalse(quit_requested) self.assertFalse(has_crashed) self.assertIn("invalid target", result_str) - self.assertIn("No auto repeat", result_str) + self.assertIn("No auto repeat", result_str) + class SBCommandInterpreterRunOptionsCase(TestBase): NO_DEBUG_INFO_TESTCASE = True >From fc0df1ab16b2828f1fa6f940068581f07265a8a1 Mon Sep 17 00:00:00 2001 From: Jim Ingham <jing...@apple.com> Date: Fri, 7 Jun 2024 12:08:34 -0700 Subject: [PATCH 3/4] process_repeats -> handle_repeats --- lldb/include/lldb/Interpreter/CommandInterpreter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 665336fbf821b..c82e44f4a1ef9 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -93,20 +93,20 @@ class CommandInterpreterRunOptions { /// \param[in] add_to_history /// If \b true add the commands to the command history. If \b false, don't /// add them. - /// \param[in] process_repeats - /// If \b true then process empty lines as repeat commands even if the + /// \param[in] handle_repeats + /// If \b true then treat empty lines as repeat commands even if the /// interpreter is non-interactive. CommandInterpreterRunOptions(LazyBool stop_on_continue, LazyBool stop_on_error, LazyBool stop_on_crash, LazyBool echo_commands, LazyBool echo_comments, LazyBool print_results, LazyBool print_errors, LazyBool add_to_history, - LazyBool process_repeats) + LazyBool handle_repeats) : m_stop_on_continue(stop_on_continue), m_stop_on_error(stop_on_error), m_stop_on_crash(stop_on_crash), m_echo_commands(echo_commands), m_echo_comment_commands(echo_comments), m_print_results(print_results), m_print_errors(print_errors), m_add_to_history(add_to_history), - m_allow_repeats(process_repeats) {} + m_allow_repeats(handle_repeats) {} CommandInterpreterRunOptions() = default; >From 9d0ec3d57a46d4c9b42124a9ea07cbc18c216b09 Mon Sep 17 00:00:00 2001 From: Jim Ingham <jing...@apple.com> Date: Fri, 7 Jun 2024 16:59:18 -0700 Subject: [PATCH 4/4] de-nitting --- lldb/include/lldb/API/SBCommandInterpreterRunOptions.h | 8 ++++---- lldb/include/lldb/Interpreter/CommandInterpreter.h | 1 + lldb/source/Interpreter/CommandInterpreter.cpp | 2 +- .../interpreter/TestRunCommandInterpreterAPI.py | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h index c11bf5e404e04..0f248c926d454 100644 --- a/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h +++ b/lldb/include/lldb/API/SBCommandInterpreterRunOptions.h @@ -74,10 +74,10 @@ class LLDB_API SBCommandInterpreterRunOptions { bool GetAllowRepeats() const; - // By default, RunCommandInterpreter will discard repeats if the - // IOHandler being used is not interactive. Setting AllowRepeats to true - // will override this behavior and always process empty lines in the input - // as a repeat command. + /// By default, RunCommandInterpreter will discard repeats if the + /// IOHandler being used is not interactive. Setting AllowRepeats to true + /// will override this behavior and always process empty lines in the input + /// as a repeat command. void SetAllowRepeats(bool); private: diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index c82e44f4a1ef9..48f6618ab0e39 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -187,6 +187,7 @@ class CommandInterpreterRunOptions { void SetSpawnThread(bool spawn_thread) { m_spawn_thread = spawn_thread ? eLazyBoolYes : eLazyBoolNo; } + bool GetAllowRepeats() const { return DefaultToNo(m_allow_repeats); } void SetAllowRepeats(bool allow_repeats) { diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index d82662fae971d..da995de1407c4 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -3130,7 +3130,7 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler, return; const bool is_interactive = io_handler.GetIsInteractive(); - bool allow_repeats = + const bool allow_repeats = io_handler.GetFlags().Test(eHandleCommandFlagAllowRepeats); if (!is_interactive && !allow_repeats) { diff --git a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py index b0a5bb76feb3c..f677b869d1379 100644 --- a/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py +++ b/lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py @@ -78,7 +78,6 @@ def run_commands_string( with open(self.stdout_path, "r") as out_fileH: result_string = out_fileH.read() - print(f"Command: '{command_string}'\nResult:\n{result_string}") return (n_errors, quit_requested, has_crashed, result_string) def test_run_session_with_error_and_quit(self): _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits