diff options
Diffstat (limited to 'source/Expression/REPL.cpp')
| -rw-r--r-- | source/Expression/REPL.cpp | 649 | 
1 files changed, 649 insertions, 0 deletions
diff --git a/source/Expression/REPL.cpp b/source/Expression/REPL.cpp new file mode 100644 index 0000000000000..1727a13abd063 --- /dev/null +++ b/source/Expression/REPL.cpp @@ -0,0 +1,649 @@ +//===-- REPL.cpp ------------------------------------------------*- C++ -*-===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Expression/REPL.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/AnsiTerminal.h" + +using namespace lldb_private; + +REPL::REPL(LLVMCastKind kind, Target &target) : +    m_target(target), +    m_kind(kind) +{ +    // Make sure all option values have sane defaults +    Debugger &debugger = m_target.GetDebugger(); +    CommandInterpreter &ci = debugger.GetCommandInterpreter(); +    m_format_options.OptionParsingStarting(ci); +    m_varobj_options.OptionParsingStarting(ci); +    m_command_options.OptionParsingStarting(ci); +     +    // Default certain settings for REPL regardless of the global settings. +    m_command_options.unwind_on_error = false; +    m_command_options.ignore_breakpoints = false; +    m_command_options.debug = false; +} + +REPL::~REPL() = default; + +lldb::REPLSP +REPL::Create(Error &err, lldb::LanguageType language, Debugger *debugger, Target *target, const char *repl_options) +{ +    uint32_t idx = 0; +    lldb::REPLSP ret; +     +    while (REPLCreateInstance create_instance = PluginManager::GetREPLCreateCallbackAtIndex(idx++)) +    { +        ret = (*create_instance)(err, language, debugger, target, repl_options); +        if (ret) +        { +            break; +        } +    } +     +    return ret; +} + +std::string +REPL::GetSourcePath() +{ +    ConstString file_basename = GetSourceFileBasename(); +     +    FileSpec tmpdir_file_spec; +    if (HostInfo::GetLLDBPath (lldb::ePathTypeLLDBTempSystemDir, tmpdir_file_spec)) +    { +        tmpdir_file_spec.GetFilename().SetCString(file_basename.AsCString()); +        m_repl_source_path = tmpdir_file_spec.GetPath(); +    } +    else +    { +        tmpdir_file_spec = FileSpec("/tmp", false); +        tmpdir_file_spec.AppendPathComponent(file_basename.AsCString()); +    } +     +    return tmpdir_file_spec.GetPath(); +} + +lldb::IOHandlerSP +REPL::GetIOHandler() +{ +    if (!m_io_handler_sp) +    { +        Debugger &debugger = m_target.GetDebugger(); +        m_io_handler_sp.reset (new IOHandlerEditline (debugger, +                                                      IOHandler::Type::REPL, +                                                      "lldb-repl",     // Name of input reader for history +                                                      "> ",             // prompt +                                                      ". ",             // Continuation prompt +                                                      true,             // Multi-line +                                                      true,             // The REPL prompt is always colored +                                                      1,                // Line number +                                                      *this)); +         +        // Don't exit if CTRL+C is pressed +        static_cast<IOHandlerEditline *>(m_io_handler_sp.get())->SetInterruptExits(false); +         +        if (m_io_handler_sp->GetIsInteractive() && m_io_handler_sp->GetIsRealTerminal()) +        { +            m_indent_str.assign (debugger.GetTabSize(), ' '); +            m_enable_auto_indent = debugger.GetAutoIndent(); +        } +        else +        { +            m_indent_str.clear(); +            m_enable_auto_indent = false; +        } +         +    } +    return m_io_handler_sp; +} + +void +REPL::IOHandlerActivated (IOHandler &io_handler) +{ +    lldb::ProcessSP process_sp = m_target.GetProcessSP(); +    if (process_sp && process_sp->IsAlive()) +        return; +    lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); +    error_sp->Printf("REPL requires a running target process.\n"); +    io_handler.SetIsDone(true); +} + +bool +REPL::IOHandlerInterrupt (IOHandler &io_handler) +{ +    return false; +} + +void +REPL::IOHandlerInputInterrupted (IOHandler &io_handler, +                                 std::string &line) +{ +} + +const char * +REPL::IOHandlerGetFixIndentationCharacters() +{ +    return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr); +} + +ConstString +REPL::IOHandlerGetControlSequence (char ch) +{ +    if (ch == 'd') +        return ConstString(":quit\n"); +    return ConstString(); +} + +const char * +REPL::IOHandlerGetCommandPrefix () +{ +    return ":"; +} + +const char * +REPL::IOHandlerGetHelpPrologue () +{ +    return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter.  " +    "Valid statements, expressions, and declarations are immediately compiled and executed.\n\n" +    "The complete set of LLDB debugging commands are also available as described below.  Commands " +    "must be prefixed with a colon at the REPL prompt (:quit for example.)  Typing just a colon " +    "followed by return will switch to the LLDB prompt.\n\n"; +} + +bool +REPL::IOHandlerIsInputComplete (IOHandler &io_handler, +                                StringList &lines) +{ +    // Check for meta command +    const size_t num_lines = lines.GetSize(); +    if (num_lines == 1) +    { +        const char *first_line = lines.GetStringAtIndex(0); +        if (first_line[0] == ':') +            return true; // Meta command is a single line where that starts with ':' +    } +     +    // Check if REPL input is done +    std::string source_string (lines.CopyList()); +    return SourceIsComplete(source_string); +} + +int +REPL::CalculateActualIndentation (const StringList &lines) +{ +    std::string last_line = lines[lines.GetSize() - 1]; + +    int actual_indent = 0; +    for (char &ch : last_line) +    { +        if (ch != ' ') break; +        ++actual_indent; +    } +     +    return actual_indent; +} + +int +REPL::IOHandlerFixIndentation (IOHandler &io_handler, +                               const StringList &lines, +                               int cursor_position) +{ +    if (!m_enable_auto_indent) return 0; +     +    if (!lines.GetSize()) +    { +        return 0; +    } +     +    int tab_size = io_handler.GetDebugger().GetTabSize(); + +    lldb::offset_t desired_indent = GetDesiredIndentation(lines, +                                                          cursor_position, +                                                          tab_size); +     +    int actual_indent = REPL::CalculateActualIndentation(lines); +     +    if (desired_indent == LLDB_INVALID_OFFSET) +        return 0; +     +    return (int)desired_indent - actual_indent; +} + +void +REPL::IOHandlerInputComplete (IOHandler &io_handler, std::string &code) +{ +    lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFile()); +    lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); +    bool extra_line = false; +    bool did_quit = false; +     +    if (code.empty()) +    { +        m_code.AppendString(""); +        static_cast<IOHandlerEditline &>(io_handler).SetBaseLineNumber(m_code.GetSize()+1); +    } +    else +    { +        Debugger &debugger = m_target.GetDebugger(); +        CommandInterpreter &ci = debugger.GetCommandInterpreter(); +        extra_line = ci.GetSpaceReplPrompts(); +         +        ExecutionContext exe_ctx (m_target.GetProcessSP()->GetThreadList().GetSelectedThread()->GetSelectedFrame().get()); +         +        lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); +         +        if (code[0] == ':') +        { +            // Meta command +            // Strip the ':' +            code.erase(0, 1); +            if (Args::StripSpaces (code)) +            { +                // "lldb" was followed by arguments, so just execute the command dump the results +                 +                // Turn off prompt on quit in case the user types ":quit" +                const bool saved_prompt_on_quit = ci.GetPromptOnQuit(); +                if (saved_prompt_on_quit) +                    ci.SetPromptOnQuit(false); +                 +                // Execute the command +                CommandReturnObject result; +                result.SetImmediateOutputStream(output_sp); +                result.SetImmediateErrorStream(error_sp); +                ci.HandleCommand(code.c_str(), eLazyBoolNo, result); +                 +                if (saved_prompt_on_quit) +                    ci.SetPromptOnQuit(true); +                 +                if (result.GetStatus() == lldb::eReturnStatusQuit) +                { +                    did_quit = true; +                    io_handler.SetIsDone(true); +                    if (debugger.CheckTopIOHandlerTypes(IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) +                    { +                        // We typed "quit" or an alias to quit so we need to check if the +                        // command interpreter is above us and tell it that it is done as well +                        // so we don't drop back into the command interpreter if we have already +                        // quit +                        lldb::IOHandlerSP io_handler_sp (ci.GetIOHandler()); +                        if (io_handler_sp) +                            io_handler_sp->SetIsDone(true); +                    } +                } +            } +            else +            { +                // ":" was followed by no arguments, so push the LLDB command prompt +                if (debugger.CheckTopIOHandlerTypes(IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) +                { +                    // If the user wants to get back to the command interpreter and the +                    // command interpreter is what launched the REPL, then just let the +                    // REPL exit and fall back to the command interpreter. +                    io_handler.SetIsDone(true); +                } +                else +                { +                    // The REPL wasn't launched the by the command interpreter, it is the +                    // base IOHandler, so we need to get the command interpreter and +                    lldb::IOHandlerSP io_handler_sp (ci.GetIOHandler()); +                    if (io_handler_sp) +                    { +                        io_handler_sp->SetIsDone(false); +                        debugger.PushIOHandler(ci.GetIOHandler()); +                    } +                } +            } +        } +        else +        { +            // Unwind any expression we might have been running in case our REPL +            // expression crashed and the user was looking around +            if (m_dedicated_repl_mode) +            { +                Thread *thread = exe_ctx.GetThreadPtr(); +                if (thread && thread->UnwindInnermostExpression().Success()) +                { +                    thread->SetSelectedFrameByIndex(0, false); +                    exe_ctx.SetFrameSP(thread->GetSelectedFrame()); +                } +            } +             +            const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors(); +             +            EvaluateExpressionOptions expr_options; +            expr_options.SetCoerceToId(m_varobj_options.use_objc); +            expr_options.SetUnwindOnError(m_command_options.unwind_on_error); +            expr_options.SetIgnoreBreakpoints (m_command_options.ignore_breakpoints); +            expr_options.SetKeepInMemory(true); +            expr_options.SetUseDynamic(m_varobj_options.use_dynamic); +            expr_options.SetTryAllThreads(m_command_options.try_all_threads); +            expr_options.SetGenerateDebugInfo(true); +            expr_options.SetREPLEnabled (true); +            expr_options.SetColorizeErrors(colorize_err); +            expr_options.SetPoundLine(m_repl_source_path.c_str(), m_code.GetSize() + 1); +            if (m_command_options.timeout > 0) +                expr_options.SetTimeoutUsec(m_command_options.timeout); +            else +                expr_options.SetTimeoutUsec(0); +             +            expr_options.SetLanguage(GetLanguage()); +             +            PersistentExpressionState *persistent_state = m_target.GetPersistentExpressionStateForLanguage(GetLanguage()); +             +            const size_t var_count_before = persistent_state->GetSize(); +             +            const char *expr_prefix = nullptr; +            lldb::ValueObjectSP result_valobj_sp; +            Error error; +            lldb::ModuleSP jit_module_sp; +            lldb::ExpressionResults execution_results = UserExpression::Evaluate (exe_ctx, +                                                                                  expr_options, +                                                                                  code.c_str(), +                                                                                  expr_prefix, +                                                                                  result_valobj_sp, +                                                                                  error, +                                                                                  0, // Line offset +                                                                                  &jit_module_sp); +             +            //CommandInterpreter &ci = debugger.GetCommandInterpreter(); +             +            if (process_sp && process_sp->IsAlive()) +            { +                bool add_to_code = true; +                bool handled = false; +                if (result_valobj_sp) +                { +                    lldb::Format format = m_format_options.GetFormat(); +                     +                    if (result_valobj_sp->GetError().Success()) +                    { +                        handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp); +                    } +                    else if (result_valobj_sp->GetError().GetError() == UserExpression::kNoResult) +                    { +                        if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) +                        { +                            error_sp->PutCString("(void)\n"); +                            handled = true; +                        } +                    } +                } +                 +                if (debugger.GetPrintDecls()) +                { +                    for (size_t vi = var_count_before, ve = persistent_state->GetSize(); +                         vi != ve; +                         ++vi) +                    { +                        lldb::ExpressionVariableSP persistent_var_sp = persistent_state->GetVariableAtIndex(vi); +                        lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject(); +                         +                        PrintOneVariable(debugger, output_sp, valobj_sp, persistent_var_sp.get()); +                    } +                } + +                if (!handled) +                { +                    bool useColors = error_sp->GetFile().GetIsTerminalWithColors(); +                    switch (execution_results) +                    { +                        case lldb::eExpressionSetupError: +                        case lldb::eExpressionParseError: +                            add_to_code = false; +                            // Fall through +                        case lldb::eExpressionDiscarded: +                            error_sp->Printf("%s\n", error.AsCString()); +                            break; +                             +                        case lldb::eExpressionCompleted: +                            break; +                        case lldb::eExpressionInterrupted: +                            if (useColors) { +                                error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); +                                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); +                            } +                            error_sp->Printf("Execution interrupted. "); +                            if (useColors) error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); +                            error_sp->Printf("Enter code to recover and continue.\nEnter LLDB commands to investigate (type :help for assistance.)\n"); +                            break; +                             +                        case lldb::eExpressionHitBreakpoint: +                            // Breakpoint was hit, drop into LLDB command interpreter +                            if (useColors) { +                                error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); +                                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); +                            } +                            output_sp->Printf("Execution stopped at breakpoint.  "); +                            if (useColors) error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); +                            output_sp->Printf("Enter LLDB commands to investigate (type help for assistance.)\n"); +                        { +                            lldb::IOHandlerSP io_handler_sp (ci.GetIOHandler()); +                            if (io_handler_sp) +                            { +                                io_handler_sp->SetIsDone(false); +                                debugger.PushIOHandler(ci.GetIOHandler()); +                            } +                        } +                            break; +                             +                        case lldb::eExpressionTimedOut: +                            error_sp->Printf("error: timeout\n"); +                            if (error.AsCString()) +                                error_sp->Printf("error: %s\n", error.AsCString()); +                            break; +                        case lldb::eExpressionResultUnavailable: +                            // Shoulnd't happen??? +                            error_sp->Printf("error: could not fetch result -- %s\n", error.AsCString()); +                            break; +                        case lldb::eExpressionStoppedForDebug: +                            // Shoulnd't happen??? +                            error_sp->Printf("error: stopped for debug -- %s\n", error.AsCString()); +                            break; +                    } +                } +                 +                if (add_to_code) +                { +                    const uint32_t new_default_line = m_code.GetSize() + 1; +                     +                    m_code.SplitIntoLines(code); +                     +                    // Update our code on disk +                    if (!m_repl_source_path.empty()) +                    { +                        lldb_private::File file (m_repl_source_path.c_str(), +                                                 File::eOpenOptionWrite | File::eOpenOptionTruncate | File::eOpenOptionCanCreate, +                                                 lldb::eFilePermissionsFileDefault); +                        std::string code (m_code.CopyList()); +                        code.append(1, '\n'); +                        size_t bytes_written = code.size(); +                        file.Write(code.c_str(), bytes_written); +                        file.Close(); +                         +                        // Now set the default file and line to the REPL source file +                        m_target.GetSourceManager().SetDefaultFileAndLine(FileSpec(m_repl_source_path.c_str(), false), new_default_line); +                    } +                    static_cast<IOHandlerEditline &>(io_handler).SetBaseLineNumber(m_code.GetSize()+1); +                } +                if (extra_line) +                { +                    fprintf(output_sp->GetFile().GetStream(), "\n"); +                } +            } +        } +         +        // Don't complain about the REPL process going away if we are in the process of quitting. +        if (!did_quit && (!process_sp || !process_sp->IsAlive())) +        { +            error_sp->Printf("error: REPL process is no longer alive, exiting REPL\n"); +            io_handler.SetIsDone(true); +        } +    } +} + +int +REPL::IOHandlerComplete (IOHandler &io_handler, +                         const char *current_line, +                         const char *cursor, +                         const char *last_char, +                         int skip_first_n_matches, +                         int max_matches, +                         StringList &matches) +{ +    matches.Clear(); +     +    llvm::StringRef line (current_line, cursor - current_line); +     +    // Complete an LLDB command if the first character is a colon... +    if (!line.empty() && line[0] == ':') +    { +        Debugger &debugger = m_target.GetDebugger(); +         +        // auto complete LLDB commands +        const char *lldb_current_line = line.substr(1).data(); +        return debugger.GetCommandInterpreter().HandleCompletion (lldb_current_line, +                                                                  cursor, +                                                                  last_char, +                                                                  skip_first_n_matches, +                                                                  max_matches, +                                                                  matches); +    } +     +    // Strip spaces from the line and see if we had only spaces +    line = line.ltrim(); +    if (line.empty()) +    { +        // Only spaces on this line, so just indent +        matches.AppendString(m_indent_str); +        return 1; +    } +     +    std::string current_code; +    current_code.append(m_code.CopyList()); +     +    IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler); +    const StringList *current_lines = editline.GetCurrentLines(); +    if (current_lines) +    { +        const uint32_t current_line_idx = editline.GetCurrentLineIndex(); +         +        if (current_line_idx < current_lines->GetSize()) +        { +            for (uint32_t i=0; i<current_line_idx; ++i) +            { +                const char *line_cstr = current_lines->GetStringAtIndex(i); +                if (line_cstr) +                { +                    current_code.append("\n"); +                    current_code.append (line_cstr); +                } +            } +        } +    } +     +    if (cursor > current_line) +    { +        current_code.append("\n"); +        current_code.append(current_line, cursor - current_line); +    } +     +    return CompleteCode(current_code, matches); +} + +bool +QuitCommandOverrideCallback(void *baton, const char **argv) +{ +    Target *target = (Target *)baton; +    lldb::ProcessSP process_sp (target->GetProcessSP()); +    if (process_sp) +    { +        process_sp->Destroy(false); +        process_sp->GetTarget().GetDebugger().ClearIOHandlers(); +    } +    return false; +} + +Error +REPL::RunLoop () +{ +    Error error; +     +    error = DoInitialization(); +    m_repl_source_path = GetSourcePath(); +     +    if (!error.Success()) +        return error; +     +    Debugger &debugger = m_target.GetDebugger(); +     +    lldb::IOHandlerSP io_handler_sp (GetIOHandler()); +     +    FileSpec save_default_file; +    uint32_t save_default_line = 0; +     +    if (!m_repl_source_path.empty()) +    { +        // Save the current default file and line +        m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file, save_default_line); +    } +     +    debugger.PushIOHandler(io_handler_sp); +     +    // Check if we are in dedicated REPL mode where LLDB was start with the "--repl" option +    // from the command line. Currently we know this by checking if the debugger already +    // has a IOHandler thread. +    if (!debugger.HasIOHandlerThread()) +    { +        // The debugger doesn't have an existing IOHandler thread, so this must be +        // dedicated REPL mode... +        m_dedicated_repl_mode = true; +        debugger.StartIOHandlerThread(); +        std::string command_name_str ("quit"); +        CommandObject *cmd_obj = debugger.GetCommandInterpreter().GetCommandObjectForCommand(command_name_str); +        if (cmd_obj) +        { +            assert(command_name_str.empty()); +            cmd_obj->SetOverrideCallback (QuitCommandOverrideCallback, &m_target); +        } +    } +     +    // Wait for the REPL command interpreter to get popped +    io_handler_sp->WaitForPop(); +     +    if (m_dedicated_repl_mode) +    { +        // If we were in dedicated REPL mode we would have started the +        // IOHandler thread, and we should kill our process +        lldb::ProcessSP process_sp = m_target.GetProcessSP(); +        if (process_sp && process_sp->IsAlive()) +            process_sp->Destroy(false); +         +        // Wait for the IO handler thread to exit (TODO: don't do this if the IO handler thread already exists...) +        debugger.JoinIOHandlerThread(); +    } +     +    // Restore the default file and line +    if (save_default_file && save_default_line != 0) +        m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file, save_default_line); +    return error; +}  | 
