| //===-- SwiftREPL.cpp -------------------------------------------*- C++ -*-===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SwiftREPL.h" |
| |
| #include "SwiftExpressionVariable.h" |
| |
| #include "lldb/Core/Debugger.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/PluginManager.h" |
| #include "lldb/Core/StreamFile.h" |
| #include "lldb/Core/ValueObject.h" |
| #include "lldb/DataFormatters/TypeSummary.h" |
| #include "lldb/DataFormatters/ValueObjectPrinter.h" |
| #include "lldb/Host/HostInfo.h" |
| #include "lldb/Interpreter/CommandInterpreter.h" |
| #include "lldb/Interpreter/CommandReturnObject.h" |
| #include "lldb/Symbol/ObjectFile.h" |
| #include "lldb/Symbol/SwiftASTContext.h" |
| #include "lldb/Symbol/SymbolVendor.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Target/Target.h" |
| #include "lldb/Target/Thread.h" |
| #include "lldb/Utility/AnsiTerminal.h" |
| #include "lldb/Utility/CleanUp.h" |
| |
| #include "swift/Basic/Version.h" |
| #include "swift/Frontend/Frontend.h" |
| #include "swift/IDE/REPLCodeCompletion.h" |
| #include "swift/IDE/Utils.h" |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| lldb::REPLSP SwiftREPL::CreateInstance(Status &err, lldb::LanguageType language, |
| Debugger *debugger, Target *target, |
| const char *repl_options) { |
| if (language != eLanguageTypeSwift) { |
| // TODO: EnumerateSupportedLanguages should make checking for this |
| // unnecessary. |
| return nullptr; |
| } |
| |
| if (!target && !debugger) { |
| err.SetErrorString("must have a debugger or a target to create a REPL"); |
| return nullptr; |
| } |
| |
| if (target) |
| return CreateInstanceFromTarget(err, *target, repl_options); |
| else |
| return CreateInstanceFromDebugger(err, *debugger, repl_options); |
| } |
| |
| lldb::REPLSP SwiftREPL::CreateInstanceFromTarget(Status &err, Target &target, |
| const char *repl_options) { |
| // Sanity check the target to make sure a REPL would work here. |
| if (!target.GetProcessSP() || !target.GetProcessSP()->IsAlive()) { |
| err.SetErrorString("can't launch a Swift REPL without a running process"); |
| return nullptr; |
| } |
| |
| SymbolContextList sc_list; |
| target.GetImages().FindSymbolsWithNameAndType(ConstString("_swift_release"), |
| eSymbolTypeAny, sc_list); |
| |
| if (!sc_list.GetSize()) { |
| err.SetErrorString("can't launch a Swift REPL in a process that doesn't " |
| "have the Swift standard library"); |
| return nullptr; |
| } |
| |
| // Check that we can get a type system, or we aren't going anywhere: |
| TypeSystem *type_system = target.GetScratchTypeSystemForLanguage( |
| nullptr, eLanguageTypeSwift, true, repl_options); |
| if (!type_system) { |
| err.SetErrorString("Could not construct an expression " |
| "context for the REPL.\n"); |
| return nullptr; |
| } |
| |
| // Sanity checks succeeded. Go ahead. |
| auto repl_sp = lldb::REPLSP(new SwiftREPL(target)); |
| repl_sp->SetCompilerOptions(repl_options); |
| return repl_sp; |
| } |
| |
| lldb::REPLSP SwiftREPL::CreateInstanceFromDebugger(Status &err, |
| Debugger &debugger, |
| const char *repl_options) { |
| const char *bp_name = "repl_main"; |
| |
| FileSpec repl_executable; |
| |
| if (!HostInfo::GetLLDBPath(ePathTypeSupportExecutableDir, repl_executable)) { |
| err.SetErrorString("unable to locate REPL executable"); |
| return nullptr; |
| } |
| |
| repl_executable.GetFilename().SetCString("repl_swift"); |
| std::string repl_exe_path(repl_executable.GetPath()); |
| |
| if (!repl_executable.Exists()) { |
| err.SetErrorStringWithFormat("REPL executable does not exist: '%s'", |
| repl_exe_path.c_str()); |
| return nullptr; |
| } |
| |
| const char *target_triple = nullptr; |
| bool add_dependent_modules = true; |
| TargetSP target_sp; |
| err = debugger.GetTargetList().CreateTarget( |
| debugger, repl_exe_path.c_str(), target_triple, add_dependent_modules, |
| nullptr, target_sp); |
| if (!err.Success()) { |
| err.SetErrorStringWithFormat("failed to create REPL target: %s", |
| err.AsCString()); |
| return nullptr; |
| } |
| |
| // Limit the breakpoint to our executable module |
| ModuleSP exe_module_sp(target_sp->GetExecutableModule()); |
| if (!exe_module_sp) { |
| err.SetErrorString("unable to resolve REPL executable module"); |
| target_sp->Destroy(); |
| return nullptr; |
| } |
| |
| FileSpecList containingModules; |
| containingModules.Append(exe_module_sp->GetFileSpec()); |
| |
| BreakpointSP main_bp_sp = target_sp->CreateBreakpoint( |
| &containingModules, // Limit to these modules |
| NULL, // Don't limit the breakpoint to any source files |
| bp_name, // Function name |
| eFunctionNameTypeAuto, // Name type |
| eLanguageTypeUnknown, // Language |
| 0, // offset |
| eLazyBoolYes, // skip_prologue, |
| true, // internal |
| false); // request_hardware |
| |
| if (main_bp_sp->GetNumLocations() == 0) { |
| err.SetErrorStringWithFormat("failed to resolve REPL breakpoint for '%s'", |
| bp_name); |
| return nullptr; |
| } |
| |
| main_bp_sp->SetBreakpointKind("REPL"); |
| assert(main_bp_sp->IsInternal()); // We made an internal |
| // breakpoint above, it better |
| // say it is internal |
| |
| lldb_private::ProcessLaunchInfo launch_info; |
| llvm::StringRef target_settings_argv0 = target_sp->GetArg0(); |
| |
| if (target_sp->GetDisableASLR()) |
| launch_info.GetFlags().Set(eLaunchFlagDisableASLR); |
| |
| if (target_sp->GetDisableSTDIO()) |
| launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO); |
| |
| if (!target_settings_argv0.empty()) { |
| launch_info.GetArguments().AppendArgument(target_settings_argv0); |
| launch_info.SetExecutableFile(exe_module_sp->GetPlatformFileSpec(), false); |
| } else { |
| launch_info.SetExecutableFile(exe_module_sp->GetPlatformFileSpec(), true); |
| } |
| |
| debugger.SetAsyncExecution(false); |
| err = target_sp->Launch(launch_info, nullptr); |
| debugger.SetAsyncExecution(true); |
| |
| if (!err.Success()) { |
| err.SetErrorStringWithFormat("failed to launch REPL process: %s", |
| err.AsCString()); |
| return nullptr; |
| } |
| |
| ProcessSP process_sp(target_sp->GetProcessSP()); |
| if (!process_sp) { |
| err.SetErrorString("failed to launch REPL process"); |
| return nullptr; |
| } |
| |
| // Start handling process events automatically |
| debugger.StartEventHandlerThread(); |
| |
| // Destroy the process and the event handler thread after a fatal error. |
| CleanUp cleanup{[&] { |
| process_sp->Destroy(/*force_kill=*/false); |
| debugger.StopEventHandlerThread(); |
| }}; |
| |
| StateType state = process_sp->GetState(); |
| |
| if (state != eStateStopped) { |
| err.SetErrorString("failed to stop process at REPL breakpoint"); |
| return nullptr; |
| } |
| |
| ThreadList &thread_list = process_sp->GetThreadList(); |
| const uint32_t num_threads = thread_list.GetSize(); |
| if (num_threads == 0) { |
| err.SetErrorString("process is not in valid state (no threads)"); |
| return nullptr; |
| } |
| |
| ThreadSP thread_sp = thread_list.GetSelectedThread(); |
| if (!thread_sp) { |
| thread_sp = thread_list.GetThreadAtIndex(0); |
| thread_list.SetSelectedThreadByID(thread_sp->GetID()); |
| assert(thread_sp && "There should be at least one thread"); |
| } |
| |
| thread_sp->SetSelectedFrameByIndex(0); |
| |
| REPLSP repl_sp(new SwiftREPL(*target_sp)); |
| repl_sp->SetCompilerOptions(repl_options); |
| target_sp->SetREPL(lldb::eLanguageTypeSwift, repl_sp); |
| |
| // Check that we can get a type system, or we aren't |
| // going anywhere. Remember to pass in the repl_options |
| // in case they set up framework paths we need, etc. |
| TypeSystem *type_system = target_sp->GetScratchTypeSystemForLanguage( |
| nullptr, eLanguageTypeSwift, true, repl_options); |
| if (!type_system) { |
| err.SetErrorString("Could not construct an expression" |
| " context for the REPL.\n"); |
| return nullptr; |
| } |
| |
| // Disable the cleanup, since we have a valid repl session now. |
| cleanup.disable(); |
| |
| std::string swift_full_version(swift::version::getSwiftFullVersion()); |
| printf("Welcome to %s. Type :help for assistance.\n", |
| swift_full_version.c_str()); |
| |
| return repl_sp; |
| } |
| |
| void SwiftREPL::EnumerateSupportedLanguages( |
| std::set<lldb::LanguageType> &languages) { |
| languages.insert(lldb::eLanguageTypeSwift); |
| } |
| |
| void SwiftREPL::Initialize() { |
| PluginManager::RegisterPlugin(ConstString("swift"), "The Swift REPL", |
| &CreateInstance, &EnumerateSupportedLanguages); |
| } |
| |
| void SwiftREPL::Terminate() { |
| PluginManager::UnregisterPlugin(&CreateInstance); |
| } |
| |
| SwiftREPL::SwiftREPL(Target &target) |
| : REPL(LLVMCastKind::eKindSwift, target), m_swift_ast_sp() {} |
| |
| SwiftREPL::~SwiftREPL() {} |
| |
| Status SwiftREPL::DoInitialization() { |
| Status error; |
| |
| if (!m_compiler_options.empty()) { |
| (void)m_target.GetScratchSwiftASTContext(error, true, |
| m_compiler_options.c_str()); |
| } |
| |
| return error; |
| } |
| |
| ConstString SwiftREPL::GetSourceFileBasename() { |
| static ConstString s_basename("repl.swift"); |
| return s_basename; |
| } |
| |
| bool SwiftREPL::SourceIsComplete(const std::string &source) { |
| std::unique_ptr<llvm::MemoryBuffer> source_buffer_ap( |
| llvm::MemoryBuffer::getMemBuffer(source)); |
| swift::ide::SourceCompleteResult result = |
| swift::ide::isSourceInputComplete(std::move(source_buffer_ap)); |
| return result.IsComplete; |
| } |
| |
| static bool GetIdentifier(llvm::StringRef &s, llvm::StringRef &identifier) { |
| if (!s.empty()) { |
| char ch = s[0]; |
| if (isalpha(ch) || ch == '_') { |
| size_t i; |
| for (i = 1; i < s.size(); ++i) { |
| ch = s[i]; |
| if (isalnum(ch) || ch == '_') |
| continue; |
| else |
| break; |
| } |
| identifier = s.substr(0, i); |
| s = s.drop_front(i); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| lldb::offset_t SwiftREPL::GetDesiredIndentation(const StringList &lines, |
| int cursor_position, |
| int tab_size) { |
| // Determine appropriate indentation after the initial N-1 lines |
| StringList prior_lines = lines; |
| prior_lines.PopBack(); |
| std::string source_string(prior_lines.CopyList()); |
| std::unique_ptr<llvm::MemoryBuffer> source_buffer_ap( |
| llvm::MemoryBuffer::getMemBuffer(source_string)); |
| swift::ide::SourceCompleteResult result = |
| swift::ide::isSourceInputComplete(std::move(source_buffer_ap)); |
| |
| int desired_indent = |
| (result.IndentLevel * tab_size) + result.IndentPrefix.length(); |
| |
| const std::string &last_line = lines[lines.GetSize() - 1]; |
| |
| // Unindent for an initial closed brace on a line break, or when the brace was |
| // just typed |
| if (cursor_position == 0 || last_line[cursor_position - 1] == '}') { |
| |
| // The brace must be the first non-space character |
| const int actual_indent = REPL::CalculateActualIndentation(lines); |
| |
| if (last_line.length() > actual_indent && last_line[actual_indent] == '}') { |
| // Stop searching once a reason to unindent was found |
| desired_indent -= tab_size; |
| if (desired_indent < 0) |
| desired_indent = 0; |
| return desired_indent; |
| } |
| } |
| |
| // Unindent for a case clause on a line break, or when the colon was just |
| // typed |
| if (cursor_position == 0 || last_line[cursor_position - 1] == ':') { |
| size_t colon_pos = last_line.find_last_of(':'); |
| if (colon_pos != std::string::npos) { |
| std::string line_to_colon = last_line.substr(0, colon_pos); |
| llvm::StringRef line(line_to_colon); |
| line = line.ltrim(); // Skip leading spaces |
| if (!line.empty()) { |
| llvm::StringRef identifier; |
| if (GetIdentifier(line, identifier)) { |
| line = line.ltrim(); // Skip leading spaces |
| // If we have an empty line now, we have a simple label followed by a |
| // ':' |
| // and if it isn't we have "case" followed by a space, then we handle |
| // this as a cast statement |
| bool outdent = false; |
| if (line.empty()) |
| outdent = !identifier.equals("case"); |
| else if (identifier.equals("case")) { |
| outdent = true; |
| } else { |
| line = line.rtrim(); // Skip trailing spaces |
| // Check for any identifier followed by an optional paren expression |
| // and a colon |
| if (line.front() == '(' && line.back() == ')') |
| outdent = true; |
| } |
| |
| if (outdent) { |
| // Stop searching once a reason to unindent was found |
| desired_indent -= tab_size; |
| if (desired_indent < 0) |
| desired_indent = 0; |
| return desired_indent; |
| } |
| } |
| } |
| } |
| } |
| |
| // Otherwise, only change indentation when creating a new line |
| if (cursor_position == 0) |
| return desired_indent; |
| |
| return LLDB_INVALID_OFFSET; |
| } |
| |
| lldb::LanguageType SwiftREPL::GetLanguage() { return eLanguageTypeSwift; } |
| |
| bool isThrownError(ValueObjectSP valobj_sp) { |
| ConstString name = valobj_sp->GetName(); |
| size_t length = name.GetLength(); |
| if (length < 3) |
| return false; |
| |
| const char *name_cstr = name.AsCString(); |
| if (name_cstr[0] != '$') |
| return false; |
| if (name_cstr[1] != 'E') |
| return false; |
| for (int index = 2; index < length; index++) { |
| |
| char digit = name_cstr[index]; |
| if (digit < '0' || digit > '9') |
| return false; |
| } |
| return true; |
| } |
| |
| bool SwiftREPL::PrintOneVariable(Debugger &debugger, StreamFileSP &output_sp, |
| ValueObjectSP &valobj_sp, |
| ExpressionVariable *var) { |
| bool is_computed = false; |
| |
| if (var) { |
| if (lldb::ValueObjectSP valobj_sp = var->GetValueObject()) { |
| Flags valobj_type_flags(valobj_sp->GetCompilerType().GetTypeInfo()); |
| const bool is_swift(valobj_type_flags.AllSet(eTypeIsSwift)); |
| if ((var->GetName().AsCString("anonymous")[0] != '$') && is_swift) { |
| is_computed = llvm::cast<SwiftExpressionVariable>(var)->GetIsComputed(); |
| } else { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| const bool colorize_out = output_sp->GetFile().GetIsTerminalWithColors(); |
| |
| bool handled = false; |
| |
| Format format = m_format_options.GetFormat(); |
| |
| bool treat_as_void = (format == eFormatVoid); |
| // if we are asked to suppress void, check if this is the empty tuple type, |
| // and if so suppress it |
| if (!treat_as_void && !debugger.GetNotifyVoid()) { |
| const CompilerType &expr_type(valobj_sp->GetCompilerType()); |
| Flags expr_type_flags(expr_type.GetTypeInfo()); |
| if (expr_type_flags.AllSet(eTypeIsSwift | eTypeIsTuple)) { |
| treat_as_void = (expr_type.GetNumFields() == 0); |
| } |
| } |
| |
| if (!treat_as_void) { |
| if (format != eFormatDefault) |
| valobj_sp->SetFormat(format); |
| |
| DumpValueObjectOptions options; |
| options.SetUseDynamicType(lldb::eDynamicCanRunTarget); |
| options.SetMaximumPointerDepth( |
| {DumpValueObjectOptions::PointerDepth::Mode::Formatters, 1}); |
| options.SetUseSyntheticValue(true); |
| options.SetRevealEmptyAggregates(false); |
| options.SetHidePointerValue(true); |
| options.SetVariableFormatDisplayLanguage(lldb::eLanguageTypeSwift); |
| options.SetDeclPrintingHelper([](ConstString type_name, |
| ConstString var_name, |
| const DumpValueObjectOptions &options, |
| Stream &stream) -> bool { |
| if (!type_name || !var_name) |
| return false; |
| |
| std::string type_name_str(type_name ? type_name.GetCString() : ""); |
| for (auto iter = type_name_str.find(" *"); iter != std::string::npos; |
| iter = type_name_str.find(" *")) { |
| type_name_str.erase(iter, 2); |
| } |
| if (!type_name_str.empty()) { |
| stream.Printf("%s: %s =", var_name.GetCString(), type_name_str.c_str()); |
| return true; |
| } |
| |
| return false; |
| }); |
| |
| if (is_computed) { |
| StringSummaryFormat::Flags flags; |
| flags.SetDontShowChildren(true); |
| flags.SetDontShowValue(true); |
| flags.SetHideItemNames(true); |
| flags.SetShowMembersOneLiner(false); |
| flags.SetSkipPointers(false); |
| flags.SetSkipReferences(false); |
| options.SetHideValue(true); |
| options.SetShowSummary(true); |
| options.SetSummary(lldb::TypeSummaryImplSP( |
| new StringSummaryFormat(flags, "<computed property>"))); |
| } |
| |
| if (colorize_out) { |
| const char *color = isThrownError(valobj_sp) |
| ? ANSI_ESCAPE1(ANSI_FG_COLOR_RED) |
| : ANSI_ESCAPE1(ANSI_FG_COLOR_CYAN); |
| fprintf(output_sp->GetFile().GetStream(), "%s", color); |
| } |
| |
| valobj_sp->Dump(*output_sp, options); |
| |
| if (colorize_out) |
| fprintf(output_sp->GetFile().GetStream(), ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); |
| |
| handled = true; |
| } |
| |
| return handled; |
| } |
| |
| int SwiftREPL::CompleteCode(const std::string ¤t_code, |
| lldb_private::StringList &matches) { |
| //----------------------------------------------------------------------g |
| // If we use the target's SwiftASTContext for completion, it reaaallly |
| // slows down subsequent expressions. The compiler team doesn't have time |
| // to fix this issue currently, so we need to work around it by making |
| // our own copy of the AST and using this separate AST for completion. |
| //---------------------------------------------------------------------- |
| Status error; |
| #define USE_SEPARATE_AST_FOR_COMPLETION |
| #if defined(USE_SEPARATE_AST_FOR_COMPLETION) |
| if (!m_swift_ast_sp) { |
| SwiftASTContext *target_swift_ast = |
| m_target.GetScratchSwiftASTContext(error); |
| if (target_swift_ast) |
| m_swift_ast_sp.reset(new SwiftASTContext(*target_swift_ast)); |
| } |
| SwiftASTContext *swift_ast = m_swift_ast_sp.get(); |
| #else |
| SwiftASTContext *swift_ast = m_target.GetScratchSwiftASTContext(error); |
| #endif |
| |
| if (swift_ast) { |
| swift::ASTContext *ast = swift_ast->GetASTContext(); |
| swift::REPLCompletions completions; |
| static ConstString g_repl_module_name("repl"); |
| swift::ModuleDecl *repl_module = |
| swift_ast->GetModule(g_repl_module_name, error); |
| if (repl_module == NULL) { |
| repl_module = swift_ast->CreateModule(g_repl_module_name, error); |
| const swift::SourceFile::ImplicitModuleImportKind implicit_import_kind = |
| swift::SourceFile::ImplicitModuleImportKind::Stdlib; |
| llvm::Optional<unsigned> bufferID; |
| swift::SourceFile *repl_source_file = new (*ast) |
| swift::SourceFile(*repl_module, swift::SourceFileKind::REPL, bufferID, |
| implicit_import_kind, /*Keep tokens*/false); |
| repl_module->addFile(*repl_source_file); |
| } |
| if (repl_module) { |
| swift::SourceFile &repl_source_file = |
| repl_module->getMainSourceFile(swift::SourceFileKind::REPL); |
| |
| llvm::StringRef current_code_ref(current_code); |
| completions.populate(repl_source_file, current_code_ref); |
| llvm::StringRef root = completions.getRoot(); |
| if (!root.empty()) { |
| matches.AppendString(root.data(), root.size()); |
| return 1; |
| } |
| // llvm::StringRef prev_stem = |
| // completions.getPreviousStem(); |
| // llvm::StringRef next_stem = |
| // completions.getNextStem(); |
| // printf ("\nroot: '%*s'", (int)root.size(), |
| // root.data()); |
| // printf ("\nprev_stem: '%*s'", (int)prev_stem.size(), |
| // prev_stem.data()); |
| // printf ("\nnext_stem: '%*s'", (int)next_stem.size(), |
| // next_stem.data()); |
| // printf ("\nvalid: %i", completions.isValid()); |
| // printf ("\nempty: %i", completions.isEmpty()); |
| // printf ("\nunique: %i", completions.isUnique()); |
| |
| // Otherwise, advance through the completion state machine. |
| const swift::CompletionState completion_state = completions.getState(); |
| switch (completion_state) { |
| case swift::CompletionState::CompletedRoot: { |
| // We completed the root. Next step is to display the completion list. |
| matches.AppendString(""); // Empty string to indicate no completion, |
| // just display other strings that come after it |
| llvm::ArrayRef<llvm::StringRef> llvm_matches = |
| completions.getCompletionList(); |
| for (const auto &llvm_match : llvm_matches) { |
| if (!llvm_match.empty()) |
| matches.AppendString(llvm_match.data(), llvm_match.size()); |
| } |
| // Don't include the empty string we appended above or we will display |
| // one |
| // too many we need to return the magical value of one less than our |
| // actual matches. |
| // TODO: modify all IOHandlerDelegate::IOHandlerComplete() to use a |
| // CompletionMatches |
| // class that wraps up the "StringList matches;" along with other smarts |
| // so we don't |
| // have to return magic values and incorrect sizes. |
| return matches.GetSize() - 1; |
| } break; |
| |
| case swift::CompletionState::DisplayedCompletionList: { |
| // Complete the next completion stem in the cycle. |
| llvm::StringRef stem = completions.getPreviousStem().InsertableString; |
| matches.AppendString(stem.data(), stem.size()); |
| } break; |
| |
| case swift::CompletionState::Empty: |
| case swift::CompletionState::Unique: |
| // We already provided a definitive completion--nothing else to do. |
| break; |
| |
| case swift::CompletionState::Invalid: |
| llvm_unreachable("got an invalid completion set?!"); |
| } |
| } |
| } |
| |
| return matches.GetSize(); |
| } |