//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "Variables.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "Protocol/DAPTypes.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "SBAPIExtras.h" #include "lldb/API/SBDeclaration.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBValue.h" #include "lldb/API/SBValueList.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/ErrorHandling.h" #include #include #include using namespace llvm; using namespace lldb_dap; using namespace lldb_dap::protocol; namespace { bool HasInnerVarref(lldb::SBValue &v) { return v.MightHaveChildren() || ValuePointsToCode(v) || v.GetDeclaration().IsValid(); } template StringMap DistinctNames(T &container) { StringMap variable_name_counts; for (auto variable : container) { if (!variable.IsValid()) break; variable_name_counts[GetNonNullVariableName(variable)]++; } return variable_name_counts; } protocol::Scope MakeScope(ScopeKind kind, var_ref_t variablesReference, bool expensive) { protocol::Scope scope; scope.variablesReference = variablesReference; scope.expensive = expensive; switch (kind) { case eScopeKindLocals: scope.presentationHint = protocol::Scope::eScopePresentationHintLocals; scope.name = "Locals"; break; case eScopeKindGlobals: scope.name = "Globals"; break; case eScopeKindRegisters: scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters; scope.name = "Registers"; break; } return scope; } std::optional MakeVariablePresentationHints(bool is_readonly, bool is_internal) { if (!is_readonly && !is_internal) return std::nullopt; VariablePresentationHint hint; if (is_readonly) hint.attributes.push_back("readOnly"); if (is_internal) hint.visibility = "internal"; return hint; } bool IsReservedName(llvm::StringRef name) { if (name == "[raw]" || name.starts_with("std::__")) return true; auto last_namespace_component = name.rfind("::"); if (last_namespace_component != llvm::StringRef::npos) name = name.substr(last_namespace_component + 2); return /* c/c++ std reserves prefixes __ or _[A-Z] for internal use */ name.starts_with("__") || (name.size() >= 2 && name[0] == '_' && isUpper(name[1])); } class VariableStoreImpl : public VariableStore { public: using VariableStore::VariableStore; Variable CreateVariable(lldb::SBValue v, bool format_hex, bool is_name_duplicated, std::optional custom_name) { VariableDescription desc(v, m_storage.config.enableAutoVariableSummaries, format_hex, is_name_duplicated, custom_name); Variable var; var.name = std::move(desc.name); var.value = std::move(desc.display_value); var.type = std::move(desc.display_type_name); if (!desc.evaluate_name.empty()) var.evaluateName = std::move(desc.evaluate_name); // If we have a type with many children, we would like to be able to // give a hint to the IDE that the type has indexed children so that the // request can be broken up in grabbing only a few children at a time. We // want to be careful and only call "v.GetNumChildren()" if we have an array // type or if we have a synthetic child provider producing indexed children. // We don't want to call "v.GetNumChildren()" on all objects as class, // struct and union types don't need to be completed if they are never // expanded. So we want to avoid calling this to only cases where we it // makes sense to keep performance high during normal debugging. // If we have an array type, say that it is indexed and provide the number // of children in case we have a huge array. If we don't do this, then we // might take a while to produce all children at onces which can delay your // debug session. if (desc.type_obj.IsArrayType()) { var.indexedVariables = v.GetNumChildren(); } else if (v.IsSynthetic()) { // For a type with a synthetic child provider, the SBType of "v" won't // tell us anything about what might be displayed. Instead, we check if // the first child's name is "[0]" and then say it is indexed. We call // GetNumChildren() only if the child name matches to avoid a potentially // expensive operation. if (lldb::SBValue first_child = v.GetChildAtIndex(0)) { llvm::StringRef first_child_name = first_child.GetName(); if (first_child_name == "[0]") { size_t num_children = v.GetNumChildren(); // If we are creating a "[raw]" fake child for each synthetic type, we // have to account for it when returning indexed variables. if (m_storage.config.enableSyntheticChildDebugging) ++num_children; var.indexedVariables = num_children; } } } const bool is_internal = IsReservedName(var.name) || m_is_internal; const bool is_readonly = is_internal || v.GetType().IsAggregateType() || v.GetValueType() == lldb::eValueTypeRegisterSet || var.name == "(Return Value)"; var.presentationHint = MakeVariablePresentationHints(is_readonly, is_internal); const var_ref_t var_ref = HasInnerVarref(v) ? m_storage.Insert(v, /*is_permanent=*/m_is_permanent, is_internal) : var_ref_t(var_ref_t::k_no_child); if (var.indexedVariables || v.MightHaveChildren()) var.variablesReference = var_ref; if (v.GetDeclaration().IsValid()) var.declarationLocationReference = PackLocation(var_ref.AsUInt32(), false); if (ValuePointsToCode(v)) var.valueLocationReference = PackLocation(var_ref.AsUInt32(), true); if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) var.memoryReference = addr; return var; } template std::vector MakeVariables( const VariablesArguments &args, T &container, const std::map &name_overrides = {}) { std::vector variables; // We first find out which variable names are duplicated. StringMap variable_name_counts = DistinctNames(container); const bool format_hex = args.format ? args.format->hex : false; auto start_it = begin(container) + args.start; auto end_it = args.count == 0 ? end(container) : start_it + args.count; // Now we construct the result with unique display variable names. for (; start_it != end_it; start_it++) { lldb::SBValue variable = *start_it; if (!variable.IsValid()) break; std::optional custom_name; auto name_it = name_overrides.find(variable.GetID()); if (name_it != name_overrides.end()) custom_name = name_it->second; variables.emplace_back(CreateVariable( variable, format_hex, variable_name_counts[GetNonNullVariableName(variable)] > 1, custom_name)); } return variables; } }; /// A Variable store for fetching variables within a specific scope (locals, /// globals, or registers) for a given stack frame. class ScopeStore final : public VariableStoreImpl { public: explicit ScopeStore(VariableReferenceStorage &storage, ScopeKind kind, const lldb::SBFrame &frame) : VariableStoreImpl(storage, /*is_permanent=*/false, /*is_internal=*/frame.IsArtificial()), m_frame(frame), m_kind(kind) {} Expected> GetVariables(const VariablesArguments &args) override { LoadVariables(); if (m_error.Fail()) return ToError(m_error); return MakeVariables(args, m_children, m_names); } lldb::SBValue FindVariable(llvm::StringRef name) override { LoadVariables(); lldb::SBValue variable; const bool is_name_duplicated = name.contains(" @"); // variablesReference is one of our scopes, not an actual variable it is // asking for a variable in locals or globals or registers. const uint32_t end_idx = m_children.GetSize(); // Searching backward so that we choose the variable in closest scope // among variables of the same name. for (const uint32_t i : reverse(seq(0, end_idx))) { lldb::SBValue curr_variable = m_children.GetValueAtIndex(i); std::string variable_name = CreateUniqueVariableNameForDisplay(curr_variable, is_name_duplicated); if (variable_name == name) { variable = curr_variable; break; } } return variable; } private: void LoadVariables() { if (m_variables_loaded) return; m_variables_loaded = true; // TODO: Support "arguments" and "return value" scope. // At the moment lldb-dap includes the arguments and return_value into the // "locals" scope. // VS Code only expands the first non-expensive scope. This causes friction // if we add the arguments above the local scope, as the locals scope will // not be expanded if we enter a function with arguments. It becomes more // annoying when the scope has arguments, return_value and locals. switch (m_kind) { case eScopeKindLocals: { // Show return value if there is any (in the local top frame) lldb::SBValue stop_return_value; if (m_frame.GetFrameID() == 0 && ((stop_return_value = m_frame.GetThread().GetStopReturnValue()))) { // FIXME: Cloning this value seems to change the type summary, see // https://github.com/llvm/llvm-project/issues/183578 // m_children.Append(stop_return_value.Clone("(Return Value)")); m_names[stop_return_value.GetID()] = "(Return Value)"; m_children.Append(stop_return_value); } lldb::SBValueList locals = m_frame.GetVariables(/*arguments=*/true, /*locals=*/true, /*statics=*/false, /*in_scope_only=*/true); m_children.Append(locals); // Save the error since we cannot insert into the SBValueList m_error = locals.GetError(); } break; case eScopeKindGlobals: m_children = m_frame.GetVariables(/*arguments=*/false, /*locals=*/false, /*statics=*/true, /*in_scope_only=*/true); m_error = m_children.GetError(); break; case eScopeKindRegisters: m_children = m_frame.GetRegisters(); // Change the default format of any pointer sized registers in the first // register set to be the lldb::eFormatAddressInfo so we show the pointer // and resolve what the pointer resolves to. Only change the format if the // format was set to the default format or if it was hex as some registers // have formats set for them. const uint32_t addr_size = m_frame.GetThread().GetProcess().GetAddressByteSize(); for (lldb::SBValue reg : m_children.GetValueAtIndex(0)) { const lldb::Format format = reg.GetFormat(); if (format == lldb::eFormatDefault || format == lldb::eFormatHex) { if (reg.GetByteSize() == addr_size) reg.SetFormat(lldb::eFormatAddressInfo); } } } } lldb::SBFrame m_frame; lldb::SBValueList m_children; lldb::SBError m_error; std::map m_names; ScopeKind m_kind; bool m_variables_loaded = false; }; /// Variable store for expandable values. /// /// Manages children variables of complex types (structs, arrays, pointers, /// etc.) that can be expanded in the debugger UI. class ExpandableValueStore final : public VariableStoreImpl { public: explicit ExpandableValueStore(VariableReferenceStorage &storage, bool is_permanent, bool is_internal, const lldb::SBValue &value) : VariableStoreImpl(storage, is_permanent, is_internal), m_value(value) {} llvm::Expected> GetVariables(const protocol::VariablesArguments &args) override { std::map name_overrides; lldb::SBValueList list; for (auto inner : m_value) list.Append(inner); // We insert a new "[raw]" child that can be used to inspect the raw version // of a synthetic member. That eliminates the need for the user to go to the // debug console and type `frame var to get these values. if (m_storage.config.enableSyntheticChildDebugging && m_value.IsSynthetic()) { lldb::SBValue synthetic_value = m_value.GetNonSyntheticValue(); name_overrides[synthetic_value.GetID()] = "[raw]"; // FIXME: Cloning the value seems to affect the type summary, see // https://github.com/llvm/llvm-project/issues/183578 // m_value.GetSyntheticValue().Clone("[raw]"); list.Append(synthetic_value); } return MakeVariables(args, list, name_overrides); } lldb::SBValue FindVariable(llvm::StringRef name) override { if (name == "[raw]" && m_value.IsSynthetic()) return m_value.GetNonSyntheticValue(); // Handle mapped index lldb::SBValue variable = m_value.GetChildMemberWithName(name.data()); if (variable.IsValid()) return variable; // Handle array indexes uint64_t index = 0; if (name.consume_front('[') && name.consume_back("]") && !name.consumeInteger(0, index)) variable = m_value.GetChildAtIndex(index); return variable; } [[nodiscard]] lldb::SBValue GetVariable() const override { return m_value; } private: lldb::SBValue m_value; }; class ExpandableValueListStore final : public VariableStoreImpl { public: explicit ExpandableValueListStore(VariableReferenceStorage &storage, bool is_permanent, bool is_internal, const lldb::SBValueList &list) : VariableStoreImpl(storage, is_permanent, is_internal), m_value_list(list) {} llvm::Expected> GetVariables(const protocol::VariablesArguments &args) override { return MakeVariables(args, m_value_list); } lldb::SBValue FindVariable(llvm::StringRef name) override { lldb::SBValue variable = m_value_list.GetFirstValueByName(name.data()); if (variable.IsValid()) return variable; return lldb::SBValue(); } private: lldb::SBValueList m_value_list; }; } // namespace namespace lldb_dap { lldb::SBValue VariableReferenceStorage::GetVariable(var_ref_t var_ref) { const ReferenceKind kind = var_ref.Kind(); if (kind == eReferenceKindTemporary) { if (auto *store = m_temporary_kind_pool.GetVariableStore(var_ref)) return store->GetVariable(); } if (kind == eReferenceKindPermanent) { if (auto *store = m_permanent_kind_pool.GetVariableStore(var_ref)) return store->GetVariable(); } return {}; } var_ref_t VariableReferenceStorage::Insert(const lldb::SBValue &variable, bool is_permanent, bool is_internal) { if (is_permanent) return m_permanent_kind_pool.Add( *this, is_permanent, is_internal, variable); return m_temporary_kind_pool.Add(*this, is_permanent, is_internal, variable); } var_ref_t VariableReferenceStorage::Insert(const lldb::SBValueList &values) { return m_permanent_kind_pool.Add( *this, /*is_permanent=*/true, /*is_internal=*/false, values); } std::vector VariableReferenceStorage::Insert(const lldb::SBFrame &frame) { auto create_scope = [&](ScopeKind kind) { const var_ref_t var_ref = m_temporary_kind_pool.Add(*this, kind, frame); const bool is_expensive = kind != eScopeKindLocals; return MakeScope(kind, var_ref, is_expensive); }; return {create_scope(eScopeKindLocals), create_scope(eScopeKindGlobals), create_scope(eScopeKindRegisters)}; } lldb::SBValue VariableReferenceStorage::FindVariable(var_ref_t var_ref, llvm::StringRef name) { if (VariableStore *store = GetVariableStore(var_ref)) return store->FindVariable(name); return {}; } VariableStore *VariableReferenceStorage::GetVariableStore(var_ref_t var_ref) { const ReferenceKind kind = var_ref.Kind(); switch (kind) { case eReferenceKindPermanent: return m_permanent_kind_pool.GetVariableStore(var_ref); case eReferenceKindTemporary: return m_temporary_kind_pool.GetVariableStore(var_ref); case eReferenceKindInvalid: return nullptr; } llvm_unreachable("Unknown reference kind."); } } // namespace lldb_dap