Index: include/lldb/API/SBCommandInterpreter.h =================================================================== --- include/lldb/API/SBCommandInterpreter.h +++ include/lldb/API/SBCommandInterpreter.h @@ -207,6 +207,25 @@ void SetPromptOnQuit(bool b); + //---------------------------------------------------------------------- + /// Sets whether the command interpreter should allow custom exit codes + /// for the 'quit' command. + //---------------------------------------------------------------------- + void AllowExitCodeOnQuit(bool allow); + + //---------------------------------------------------------------------- + /// Returns true if the user has called the 'quit' command with a custom exit + /// code. + //---------------------------------------------------------------------- + bool HasCustomQuitExitCode(); + + //---------------------------------------------------------------------- + /// Returns the exit code that the user has specified when running the + /// 'quit' command. Returns 0 if the user hasn't called 'quit' at all or + /// without a custom exit code. + //---------------------------------------------------------------------- + int GetQuitStatus(); + //---------------------------------------------------------------------- /// Resolve the command just as HandleCommand would, expanding abbreviations /// and aliases. If successful, result->GetOutput has the full expansion. Index: include/lldb/Interpreter/CommandInterpreter.h =================================================================== --- include/lldb/Interpreter/CommandInterpreter.h +++ include/lldb/Interpreter/CommandInterpreter.h @@ -455,6 +455,30 @@ void SetPromptOnQuit(bool b); + //------------------------------------------------------------------ + /// Specify if the command interpreter should allow that the user can + /// specify a custom exit code when calling 'quit'. + //------------------------------------------------------------------ + void AllowExitCodeOnQuit(bool allow); + + //------------------------------------------------------------------ + /// Sets the exit code for the quit command. + /// @param[in] exit_code + /// The exit code that the driver should return on exit. + /// @return True if the exit code was successfully set; false if the + /// interpreter doesn't allow custom exit codes. + /// @see AllowExitCodeOnQuit + //------------------------------------------------------------------ + LLVM_NODISCARD bool SetQuitExitCode(int exit_code); + + //------------------------------------------------------------------ + /// Returns the exit code that the user has specified when running the + /// 'quit' command. + /// @param[out] exited + /// Set to true if the user has called quit with a custom exit code. + //------------------------------------------------------------------ + int GetQuitExitCode(bool &exited) const; + void ResolveCommand(const char *command_line, CommandReturnObject &result); bool GetStopCmdSourceOnError() const; @@ -558,6 +582,12 @@ uint32_t m_num_errors; bool m_quit_requested; bool m_stopped_for_crash; + + // The exit code the user has requested when calling the 'quit' command. + // No value means the user hasn't set a custom exit code so far. + llvm::Optional m_quit_exit_code; + // If the driver is accepts custom exit codes for the 'quit' command. + bool m_allow_exit_code = false; }; } // namespace lldb_private Index: lit/Quit/TestQuitExitCode-30.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCode-30.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: python %S/expect_exit_code.py 226 %lldb -b -s %s +q -30 Index: lit/Quit/TestQuitExitCode0.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCode0.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: %lldb -b -s %s +q 0 Index: lit/Quit/TestQuitExitCode30.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCode30.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: python %S/expect_exit_code.py 30 %lldb -b -s %s +q 30 Index: lit/Quit/TestQuitExitCodeHex0.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCodeHex0.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: %lldb -b -s %s +q 0x0 Index: lit/Quit/TestQuitExitCodeHexA.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCodeHexA.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: python %S/expect_exit_code.py 10 %lldb -b -s %s +q 0xA Index: lit/Quit/TestQuitExitCodeImplicit0.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCodeImplicit0.test @@ -0,0 +1,3 @@ +# UNSUPPORTED: windows +# RUN: %lldb -b -s %s +q Index: lit/Quit/TestQuitExitCodeNonInt.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCodeNonInt.test @@ -0,0 +1,4 @@ +# UNSUPPORTED: windows +# RUN: %lldb -b -s %s 2>&1 | FileCheck %s +q str +// CHECK: Couldn't parse 'str' Index: lit/Quit/TestQuitExitCodeTooManyArgs.test =================================================================== --- /dev/null +++ lit/Quit/TestQuitExitCodeTooManyArgs.test @@ -0,0 +1,4 @@ +# UNSUPPORTED: windows +# RUN: %lldb -b -s %s 2>&1 | FileCheck %s +q 1 2 +// CHECK: Too many arguments for 'quit' Index: lit/Quit/expect_exit_code.py =================================================================== --- /dev/null +++ lit/Quit/expect_exit_code.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python2 + +import subprocess +import sys + +args = sys.argv + +expected_exit_code = args[1] + +args = args[2:] +print("Running " + (" ".join(args))) +real_exit_code = subprocess.call(args) + +if str(real_exit_code) != expected_exit_code: + print("Got exit code %d but expected %s" % (real_exit_code, expected_exit_code)) + exit(1) Index: lit/Quit/lit.local.cfg =================================================================== --- /dev/null +++ lit/Quit/lit.local.cfg @@ -0,0 +1 @@ +config.suffixes = ['.test'] Index: packages/Python/lldbsuite/test/quit/TestQuit.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/quit/TestQuit.py @@ -0,0 +1,32 @@ +""" +Test lldb's quit command. +""" + +from __future__ import print_function + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class QuitCommandTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @no_debug_info_test + def test_quit_exit_code_disallow(self): + self.ci.AllowExitCodeOnQuit(False) + self.expect( + "quit 20", + substrs=[ + "error: The current driver doesn't allow custom exit codes for the quit command"], + error=True) + self.assertFalse(self.ci.HasCustomQuitExitCode()) + + @no_debug_info_test + def test_quit_exit_code_allow(self): + self.ci.AllowExitCodeOnQuit(True) + self.runCmd("quit 10", check=False) + self.assertTrue(self.ci.HasCustomQuitExitCode()) + self.assertEqual(self.ci.GetQuitStatus(), 10) Index: scripts/interface/SBCommandInterpreter.i =================================================================== --- scripts/interface/SBCommandInterpreter.i +++ scripts/interface/SBCommandInterpreter.i @@ -155,6 +155,15 @@ void SetPromptOnQuit(bool b); + void + AllowExitCodeOnQuit(bool b); + + bool + HasCustomQuitExitCode(); + + int + GetQuitStatus(); + void ResolveCommand(const char *command_line, SBCommandReturnObject &result); Index: source/API/SBCommandInterpreter.cpp =================================================================== --- source/API/SBCommandInterpreter.cpp +++ source/API/SBCommandInterpreter.cpp @@ -379,6 +379,23 @@ m_opaque_ptr->SetPromptOnQuit(b); } +void SBCommandInterpreter::AllowExitCodeOnQuit(bool allow) { + if (m_opaque_ptr) + m_opaque_ptr->AllowExitCodeOnQuit(allow); +} + +bool SBCommandInterpreter::HasCustomQuitExitCode() { + bool exited = false; + if (m_opaque_ptr) + m_opaque_ptr->GetQuitExitCode(exited); + return exited; +} + +int SBCommandInterpreter::GetQuitStatus() { + bool exited = false; + return (m_opaque_ptr ? m_opaque_ptr->GetQuitExitCode(exited) : 0); +} + void SBCommandInterpreter::ResolveCommand(const char *command_line, SBCommandReturnObject &result) { result.Clear(); Index: source/Commands/CommandObjectQuit.cpp =================================================================== --- source/Commands/CommandObjectQuit.cpp +++ source/Commands/CommandObjectQuit.cpp @@ -16,6 +16,7 @@ #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Process.h" +#include "lldb/Utility/StreamString.h" using namespace lldb; using namespace lldb_private; @@ -26,7 +27,7 @@ CommandObjectQuit::CommandObjectQuit(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "quit", "Quit the LLDB debugger.", - "quit") {} + "quit [exit-code]") {} CommandObjectQuit::~CommandObjectQuit() {} @@ -77,6 +78,41 @@ return false; } } + + if (command.GetArgumentCount() > 1) { + result.AppendError("Too many arguments for 'quit'. Only an optional exit " + "code is allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() > 1) { + result.AppendError("Too many arguments for 'quit'. Only an optional exit " + "code is allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // We parse the exit code argument if there is one. + if (command.GetArgumentCount() == 1) { + llvm::StringRef arg = command.GetArgumentAtIndex(0); + int exit_code; + if (arg.getAsInteger(/*autodetect radix*/ 0, exit_code)) { + lldb_private::StreamString s; + std::string arg_str = arg.str(); + s.Printf("Couldn't parse '%s' as integer for exit code.", arg_str.data()); + result.AppendError(s.GetString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (!m_interpreter.SetQuitExitCode(exit_code)) { + result.AppendError("The current driver doesn't allow custom exit codes" + " for the quit command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + const uint32_t event_type = CommandInterpreter::eBroadcastBitQuitCommandReceived; m_interpreter.BroadcastEvent(event_type); Index: source/Interpreter/CommandInterpreter.cpp =================================================================== --- source/Interpreter/CommandInterpreter.cpp +++ source/Interpreter/CommandInterpreter.cpp @@ -144,6 +144,26 @@ m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } +void CommandInterpreter::AllowExitCodeOnQuit(bool allow) { + m_allow_exit_code = allow; + if (!allow) + m_quit_exit_code.reset(); +} + +bool CommandInterpreter::SetQuitExitCode(int exit_code) { + if (!m_allow_exit_code) + return false; + m_quit_exit_code = exit_code; + return true; +} + +int CommandInterpreter::GetQuitExitCode(bool &exited) const { + exited = m_quit_exit_code.hasValue(); + if (exited) + return *m_quit_exit_code; + return 0; +} + void CommandInterpreter::ResolveCommand(const char *command_line, CommandReturnObject &result) { std::string command = command_line; Index: tools/driver/Driver.h =================================================================== --- tools/driver/Driver.h +++ tools/driver/Driver.h @@ -37,7 +37,10 @@ virtual ~Driver(); - void MainLoop(); + /// Runs the main loop. + /// + /// @return The exit code that the process should return. + int MainLoop(); lldb::SBError ParseArgs(int argc, const char *argv[], FILE *out_fh, bool &do_exit); Index: tools/driver/Driver.cpp =================================================================== --- tools/driver/Driver.cpp +++ tools/driver/Driver.cpp @@ -962,7 +962,7 @@ return '"' + arg + '"'; } -void Driver::MainLoop() { +int Driver::MainLoop() { if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0) { g_old_stdin_termios_is_valid = true; atexit(reset_stdin_termios); @@ -1001,6 +1001,10 @@ result.PutOutput(m_debugger.GetOutputFileHandle()); } + // We allow the user to specify an exit code when calling quit which we will + // return when exiting. + m_debugger.GetCommandInterpreter().AllowExitCodeOnQuit(true); + // Now we handle options we got from the command line SBStream commands_stream; @@ -1159,7 +1163,9 @@ reset_stdin_termios(); fclose(stdin); + int exit_code = sb_interpreter.GetQuitStatus(); SBDebugger::Destroy(m_debugger); + return exit_code; } void Driver::ResizeWindow(unsigned short col) { @@ -1237,6 +1243,7 @@ signal(SIGCONT, sigcont_handler); #endif + int exit_code = 0; // Create a scope for driver so that the driver object will destroy itself // before SBDebugger::Terminate() is called. { @@ -1245,14 +1252,15 @@ bool exiting = false; SBError error(driver.ParseArgs(argc, argv, stdout, exiting)); if (error.Fail()) { + exit_code = 1; const char *error_cstr = error.GetCString(); if (error_cstr) ::fprintf(stderr, "error: %s\n", error_cstr); } else if (!exiting) { - driver.MainLoop(); + exit_code = driver.MainLoop(); } } SBDebugger::Terminate(); - return 0; + return exit_code; }