diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -58,9 +58,6 @@ #define VARREF_GLOBALS (int64_t)2 #define VARREF_REGS (int64_t)3 #define VARREF_FIRST_VAR_IDX (int64_t)4 -#define VARREF_IS_SCOPE(v) (VARREF_LOCALS <= 1 && v < VARREF_FIRST_VAR_IDX) -#define VARIDX_TO_VARREF(i) ((i) + VARREF_FIRST_VAR_IDX) -#define VARREF_TO_VARIDX(v) ((v)-VARREF_FIRST_VAR_IDX) #define NO_TYPENAME "" namespace lldb_vscode { @@ -83,17 +80,48 @@ JSONNotObject }; +struct Variables { + // Bit mask to tell if a variableReference is inside + // expandable_permanent_variables or not. + static constexpr int64_t PermanentVariableBitMask = (1ll << 32); + + lldb::SBValueList locals; + lldb::SBValueList globals; + lldb::SBValueList registers; + + int64_t next_temporary_var_ref{VARREF_FIRST_VAR_IDX}; + int64_t next_permanent_var_ref{VARREF_FIRST_VAR_IDX}; + llvm::DenseMap expandable_variables; + llvm::DenseMap expandable_permanent_variables; + + /// Check if \p variableReference points to variable in a + /// expandable_permanent_variables. + bool IsPermanentVariableReference(int64_t variableReference); + + /// \return a new variableReference. If is_permanent is true the returned + /// value will have the PermanentVariableBitMask bit set. + int64_t GetNewVariableRefence(bool is_permanent); + + /// \return the expandable variable corresponding with variableReference value + /// of \p value. + lldb::SBValue GetVariableFromVariableReference(int64_t var_ref); + + /// Insert a new \p expandableVariable. + int64_t InsertNewExpandableVariable(lldb::SBValue expandableVariable, + bool is_permanent); + + /// Clear all scope variables and non-permanent expandable variables. + void Clear(); +}; + struct VSCode { std::string debug_adaptor_path; InputStream input; OutputStream output; lldb::SBDebugger debugger; lldb::SBTarget target; - lldb::SBValueList variables; + Variables variables; lldb::SBBroadcaster broadcaster; - int64_t num_regs; - int64_t num_locals; - int64_t num_globals; std::thread event_thread; std::thread progress_event_thread; std::unique_ptr log; @@ -206,6 +234,9 @@ /// IDE. void RegisterRequestCallback(std::string request, RequestCallback callback); + /// Debuggee will continue from stopped state. + void willContinue() { variables.Clear(); } + private: // Send the JSON in "json_str" to the "out" stream. Correctly send the // "Content-Length:" field followed by the length, followed by the raw diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -30,8 +30,7 @@ VSCode g_vsc; VSCode::VSCode() - : variables(), broadcaster("lldb-vscode"), num_regs(0), num_locals(0), - num_globals(0), log(), + : broadcaster("lldb-vscode"), exception_breakpoints( {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus}, {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus}, @@ -382,10 +381,12 @@ llvm::json::Value VSCode::CreateTopLevelScopes() { llvm::json::Array scopes; - scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false)); - scopes.emplace_back( - CreateScope("Globals", VARREF_GLOBALS, num_globals, false)); - scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false)); + scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, + g_vsc.variables.locals.GetSize(), false)); + scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS, + g_vsc.variables.globals.GetSize(), false)); + scopes.emplace_back(CreateScope("Registers", VARREF_REGS, + g_vsc.variables.registers.GetSize(), false)); return llvm::json::Value(std::move(scopes)); } @@ -527,4 +528,43 @@ request_handlers[request] = callback; } +void Variables::Clear() { + locals.Clear(); + globals.Clear(); + registers.Clear(); + expandable_variables.clear(); +} + +int64_t Variables::GetNewVariableRefence(bool is_permanent) { + if (is_permanent) + return PermanentVariableBitMask | next_permanent_var_ref++; + else + return next_temporary_var_ref++; +} + +bool Variables::IsPermanentVariableReference(int64_t var_ref) { + return (var_ref & PermanentVariableBitMask) != 0; +} + +lldb::SBValue Variables::GetVariableFromVariableReference(int64_t var_ref) { + lldb::SBValue variable; + if (IsPermanentVariableReference(var_ref)) + variable = expandable_permanent_variables.find(var_ref)->second; + else + variable = expandable_variables.find(var_ref)->second; + return variable; +} + +int64_t Variables::InsertNewExpandableVariable(lldb::SBValue expandableVariable, + bool is_permanent) { + auto newVariablesReferences = GetNewVariableRefence(is_permanent); + if (is_permanent) + expandable_permanent_variables.insert( + std::make_pair(newVariablesReferences, expandableVariable)); + else + expandable_variables.insert( + std::make_pair(newVariablesReferences, expandableVariable)); + return newVariablesReferences; +} + } // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -107,6 +107,19 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; +lldb::SBValueList *GetTopLevelScope(int64_t variablesReference) { + switch (variablesReference) { + case VARREF_LOCALS: + return &g_vsc.variables.locals; + case VARREF_GLOBALS: + return &g_vsc.variables.globals; + case VARREF_REGS: + return &g_vsc.variables.registers; + default: + return nullptr; + } +} + SOCKET AcceptConnection(int portno) { // Accept a socket connection from any host on "portno". SOCKET newsockfd = -1; @@ -727,6 +740,7 @@ // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. g_vsc.focus_tid = GetUnsigned(arguments, "threadId", LLDB_INVALID_THREAD_ID); + g_vsc.willContinue(); lldb::SBError error = process.Continue(); llvm::json::Object body; body.try_emplace("allThreadsContinued", true); @@ -1215,9 +1229,9 @@ EmplaceSafeString(body, "type", value_typename ? value_typename : NO_TYPENAME); if (value.MightHaveChildren()) { - auto variablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize()); - g_vsc.variables.Append(value); - body.try_emplace("variablesReference", variablesReference); + auto variableReference = g_vsc.variables.InsertNewExpandableVariable( + value, /*is_permanent=*/context == "repl"); + body.try_emplace("variablesReference", variableReference); } else { body.try_emplace("variablesReference", (int64_t)0); } @@ -1769,6 +1783,7 @@ // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. g_vsc.focus_tid = thread.GetThreadID(); + g_vsc.willContinue(); thread.StepOver(); } else { response["success"] = llvm::json::Value(false); @@ -1895,20 +1910,15 @@ frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread()); frame.GetThread().SetSelectedFrame(frame.GetFrameID()); } - g_vsc.variables.Clear(); - g_vsc.variables.Append(frame.GetVariables(true, // arguments - true, // locals - false, // statics - true)); // in_scope_only - g_vsc.num_locals = g_vsc.variables.GetSize(); - g_vsc.variables.Append(frame.GetVariables(false, // arguments - false, // locals - true, // statics - true)); // in_scope_only - g_vsc.num_globals = g_vsc.variables.GetSize() - (g_vsc.num_locals); - g_vsc.variables.Append(frame.GetRegisters()); - g_vsc.num_regs = - g_vsc.variables.GetSize() - (g_vsc.num_locals + g_vsc.num_globals); + g_vsc.variables.locals = frame.GetVariables(/*arguments=*/true, + /*locals=*/true, + /*statics=*/false, + /*in_scope_only=*/true); + g_vsc.variables.globals = frame.GetVariables(/*arguments=*/false, + /*locals=*/false, + /*statics=*/true, + /*in_scope_only=*/true); + g_vsc.variables.registers = frame.GetRegisters(); body.try_emplace("scopes", g_vsc.CreateTopLevelScopes()); response.try_emplace("body", std::move(body)); g_vsc.SendJSON(llvm::json::Value(std::move(response))); @@ -2523,6 +2533,7 @@ // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. g_vsc.focus_tid = thread.GetThreadID(); + g_vsc.willContinue(); thread.StepInto(); } else { response["success"] = llvm::json::Value(false); @@ -2575,6 +2586,7 @@ // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. g_vsc.focus_tid = thread.GetThreadID(); + g_vsc.willContinue(); thread.StepOut(); } else { response["success"] = llvm::json::Value(false); @@ -2750,37 +2762,19 @@ // of the variable within the g_vsc.variables list. const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX); if (id_value != UINT64_MAX) { - variable = g_vsc.variables.GetValueAtIndex(id_value); - } else if (VARREF_IS_SCOPE(variablesReference)) { + variable = g_vsc.variables.GetVariableFromVariableReference(id_value); + } else if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) { // variablesReference is one of our scopes, not an actual variable it is // asking for a variable in locals or globals or registers - int64_t start_idx = 0; - int64_t end_idx = 0; - switch (variablesReference) { - case VARREF_LOCALS: - start_idx = 0; - end_idx = start_idx + g_vsc.num_locals; - break; - case VARREF_GLOBALS: - start_idx = g_vsc.num_locals; - end_idx = start_idx + g_vsc.num_globals; - break; - case VARREF_REGS: - start_idx = g_vsc.num_locals + g_vsc.num_globals; - end_idx = start_idx + g_vsc.num_regs; - break; - default: - break; - } - - for (int64_t i = end_idx - 1; i >= start_idx; --i) { - lldb::SBValue curr_variable = g_vsc.variables.GetValueAtIndex(i); + uint32_t end_idx = top_scope->GetSize(); + for (uint32_t i = 0; i < end_idx; ++i) { + lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i); std::string variable_name = CreateUniqueVariableNameForDisplay( curr_variable, is_duplicated_variable_name); if (variable_name == name) { variable = curr_variable; if (curr_variable.MightHaveChildren()) - newVariablesReference = i; + newVariablesReference = g_vsc.variables.GetNewVariableRefence(true); break; } } @@ -2790,8 +2784,8 @@ // We have a named item within an actual variable so we need to find it // withing the container variable by name. - const int64_t var_idx = VARREF_TO_VARIDX(variablesReference); - lldb::SBValue container = g_vsc.variables.GetValueAtIndex(var_idx); + lldb::SBValue container = + g_vsc.variables.GetVariableFromVariableReference(variablesReference); variable = container.GetChildMemberWithName(name.data()); if (!variable.IsValid()) { if (name.startswith("[")) { @@ -2807,8 +2801,9 @@ // We don't know the index of the variable in our g_vsc.variables if (variable.IsValid()) { if (variable.MightHaveChildren()) { - newVariablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize()); - g_vsc.variables.Append(variable); + auto is_permanent = + g_vsc.variables.IsPermanentVariableReference(variablesReference); + g_vsc.variables.InsertNewExpandableVariable(variable, is_permanent); } } } @@ -2919,33 +2914,19 @@ if (format) hex = GetBoolean(format, "hex", false); - if (VARREF_IS_SCOPE(variablesReference)) { + if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) { // variablesReference is one of our scopes, not an actual variable it is // asking for the list of args, locals or globals. int64_t start_idx = 0; int64_t num_children = 0; - switch (variablesReference) { - case VARREF_LOCALS: - start_idx = start; - num_children = g_vsc.num_locals; - break; - case VARREF_GLOBALS: - start_idx = start + g_vsc.num_locals + start; - num_children = g_vsc.num_globals; - break; - case VARREF_REGS: - start_idx = start + g_vsc.num_locals + g_vsc.num_globals; - num_children = g_vsc.num_regs; - break; - default: - break; - } + + num_children = top_scope->GetSize(); const int64_t end_idx = start_idx + ((count == 0) ? num_children : count); // We first find out which variable names are duplicated std::map variable_name_counts; for (auto i = start_idx; i < end_idx; ++i) { - lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(i); + lldb::SBValue variable = top_scope->GetValueAtIndex(i); if (!variable.IsValid()) break; variable_name_counts[GetNonNullVariableName(variable)]++; @@ -2953,19 +2934,25 @@ // Now we construct the result with unique display variable names for (auto i = start_idx; i < end_idx; ++i) { - lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(i); + lldb::SBValue variable = top_scope->GetValueAtIndex(i); if (!variable.IsValid()) break; - variables.emplace_back(CreateVariable(variable, VARIDX_TO_VARREF(i), i, - hex, + + int64_t var_ref = 0; + if (variable.MightHaveChildren()) { + var_ref = g_vsc.variables.InsertNewExpandableVariable( + variable, /*is_permanent=*/false); + } + variables.emplace_back(CreateVariable( + variable, var_ref, var_ref != 0 ? var_ref : UINT64_MAX, hex, variable_name_counts[GetNonNullVariableName(variable)] > 1)); } } else { // We are expanding a variable that has children, so we will return its // children. - const int64_t var_idx = VARREF_TO_VARIDX(variablesReference); - lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(var_idx); + lldb::SBValue variable = + g_vsc.variables.GetVariableFromVariableReference(variablesReference); if (variable.IsValid()) { const auto num_children = variable.GetNumChildren(); const int64_t end_idx = start + ((count == 0) ? num_children : count); @@ -2974,11 +2961,12 @@ if (!child.IsValid()) break; if (child.MightHaveChildren()) { - const int64_t var_idx = g_vsc.variables.GetSize(); - auto childVariablesReferences = VARIDX_TO_VARREF(var_idx); - variables.emplace_back( - CreateVariable(child, childVariablesReferences, var_idx, hex)); - g_vsc.variables.Append(child); + auto is_permanent = + g_vsc.variables.IsPermanentVariableReference(variablesReference); + auto childVariablesReferences = + g_vsc.variables.InsertNewExpandableVariable(child, is_permanent); + variables.emplace_back(CreateVariable(child, childVariablesReferences, + childVariablesReferences, hex)); } else { variables.emplace_back(CreateVariable(child, 0, INT64_MAX, hex)); }