diff --git a/lldb/include/lldb/Expression/Materializer.h b/lldb/include/lldb/Expression/Materializer.h --- a/lldb/include/lldb/Expression/Materializer.h +++ b/lldb/include/lldb/Expression/Materializer.h @@ -78,6 +78,7 @@ AddPersistentVariable(lldb::ExpressionVariableSP &persistent_variable_sp, PersistentVariableDelegate *delegate, Status &err); uint32_t AddVariable(lldb::VariableSP &variable_sp, Status &err); + uint32_t AddValueObject(lldb::ValueObjectSP valobj_sp, Status &err); uint32_t AddResultVariable(const CompilerType &type, bool is_lvalue, bool keep_in_memory, PersistentVariableDelegate *delegate, Status &err); diff --git a/lldb/include/lldb/Expression/UserExpression.h b/lldb/include/lldb/Expression/UserExpression.h --- a/lldb/include/lldb/Expression/UserExpression.h +++ b/lldb/include/lldb/Expression/UserExpression.h @@ -280,6 +280,21 @@ static lldb::addr_t GetObjectPointer(lldb::StackFrameSP frame_sp, ConstString &object_name, Status &err); + /// Return ValueObject for a given variable name in the current stack frame + /// + /// \param[in] frame Current stack frame. When passed a 'nullptr', this function + /// returns an empty ValueObjectSP. + /// \param[in] object_name Name of the variable in the current stack frame for + /// which we want the ValueObjectSP. + /// \param[out] err Status object which will get set on error. + /// + /// \returns On success returns a ValueObjectSP corresponding to the variable + /// with 'object_name' in the current 'frame'. Otherwise, returns + /// 'nullptr' (and sets the error status parameter 'err'). + static lldb::ValueObjectSP + GetObjectPointerValueObject(StackFrame *frame, ConstString const &object_name, + Status &err); + /// Populate m_in_cplusplus_method and m_in_objectivec_method based on the /// environment. diff --git a/lldb/source/Expression/Materializer.cpp b/lldb/source/Expression/Materializer.cpp --- a/lldb/source/Expression/Materializer.cpp +++ b/lldb/source/Expression/Materializer.cpp @@ -22,11 +22,20 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegisterValue.h" +#include "lldb/lldb-forward.h" #include using namespace lldb_private; +// FIXME: these should be retrieved from the target +// instead of being hard-coded. Currently we +// assume that persistent vars are materialized +// as references, and thus pick the size of a +// 64-bit pointer. +static constexpr uint32_t g_default_var_alignment = 8; +static constexpr uint32_t g_default_var_byte_size = 8; + uint32_t Materializer::AddStructMember(Entity &entity) { uint32_t size = entity.GetSize(); uint32_t alignment = entity.GetAlignment(); @@ -54,8 +63,8 @@ m_delegate(delegate) { // Hard-coding to maximum size of a pointer since persistent variables are // materialized by reference - m_size = 8; - m_alignment = 8; + m_size = g_default_var_byte_size; + m_alignment = g_default_var_alignment; } void MakeAllocation(IRMemoryMap &map, Status &err) { @@ -412,16 +421,19 @@ return ret; } -class EntityVariable : public Materializer::Entity { +/// Base class for materialization of Variables and ValueObjects. +/// +/// Subclasses specify how to obtain the Value which is to be +/// materialized. +class EntityVariableBase : public Materializer::Entity { public: - EntityVariable(lldb::VariableSP &variable_sp) - : Entity(), m_variable_sp(variable_sp) { + virtual ~EntityVariableBase() = default; + + EntityVariableBase() { // Hard-coding to maximum size of a pointer since all variables are // materialized by reference - m_size = 8; - m_alignment = 8; - m_is_reference = - m_variable_sp->GetType()->GetForwardCompilerType().IsReferenceType(); + m_size = g_default_var_byte_size; + m_alignment = g_default_var_alignment; } void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, @@ -433,7 +445,7 @@ LLDB_LOGF(log, "EntityVariable::Materialize [address = 0x%" PRIx64 ", m_variable_sp = %s]", - (uint64_t)load_addr, m_variable_sp->GetName().AsCString()); + (uint64_t)load_addr, GetName().GetCString()); } ExecutionContextScope *scope = frame_sp.get(); @@ -441,13 +453,11 @@ if (!scope) scope = map.GetBestExecutionContextScope(); - lldb::ValueObjectSP valobj_sp = - ValueObjectVariable::Create(scope, m_variable_sp); + lldb::ValueObjectSP valobj_sp = GetValueObject(scope); if (!valobj_sp) { err.SetErrorStringWithFormat( - "couldn't get a value object for variable %s", - m_variable_sp->GetName().AsCString()); + "couldn't get a value object for variable %s", GetName().AsCString()); return; } @@ -455,7 +465,7 @@ if (valobj_error.Fail()) { err.SetErrorStringWithFormat("couldn't get the value of variable %s: %s", - m_variable_sp->GetName().AsCString(), + GetName().AsCString(), valobj_error.AsCString()); return; } @@ -468,7 +478,7 @@ if (!extract_error.Success()) { err.SetErrorStringWithFormat( "couldn't read contents of reference variable %s: %s", - m_variable_sp->GetName().AsCString(), extract_error.AsCString()); + GetName().AsCString(), extract_error.AsCString()); return; } @@ -481,7 +491,7 @@ if (!write_error.Success()) { err.SetErrorStringWithFormat("couldn't write the contents of reference " "variable %s to memory: %s", - m_variable_sp->GetName().AsCString(), + GetName().AsCString(), write_error.AsCString()); return; } @@ -497,7 +507,7 @@ if (!write_error.Success()) { err.SetErrorStringWithFormat( "couldn't write the address of variable %s to memory: %s", - m_variable_sp->GetName().AsCString(), write_error.AsCString()); + GetName().AsCString(), write_error.AsCString()); return; } } else { @@ -506,7 +516,7 @@ valobj_sp->GetData(data, extract_error); if (!extract_error.Success()) { err.SetErrorStringWithFormat("couldn't get the value of %s: %s", - m_variable_sp->GetName().AsCString(), + GetName().AsCString(), extract_error.AsCString()); return; } @@ -514,32 +524,29 @@ if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { err.SetErrorStringWithFormat( "trying to create a temporary region for %s but one exists", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); return; } - if (data.GetByteSize() < m_variable_sp->GetType()->GetByteSize(scope)) { - if (data.GetByteSize() == 0 && - !m_variable_sp->LocationExpression().IsValid()) { + if (data.GetByteSize() < GetByteSize(scope)) { + if (data.GetByteSize() == 0 && !LocationExpressionIsValid()) { err.SetErrorStringWithFormat("the variable '%s' has no location, " "it may have been optimized out", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); } else { err.SetErrorStringWithFormat( "size of variable %s (%" PRIu64 ") is larger than the ValueObject's size (%" PRIu64 ")", - m_variable_sp->GetName().AsCString(), - m_variable_sp->GetType()->GetByteSize(scope).value_or(0), + GetName().AsCString(), GetByteSize(scope).value_or(0), data.GetByteSize()); } return; } - llvm::Optional opt_bit_align = - m_variable_sp->GetType()->GetLayoutCompilerType().GetTypeBitAlign(scope); + llvm::Optional opt_bit_align = GetTypeBitAlign(scope); if (!opt_bit_align) { err.SetErrorStringWithFormat("can't get the type alignment for %s", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); return; } @@ -561,7 +568,7 @@ if (!alloc_error.Success()) { err.SetErrorStringWithFormat( "couldn't allocate a temporary region for %s: %s", - m_variable_sp->GetName().AsCString(), alloc_error.AsCString()); + GetName().AsCString(), alloc_error.AsCString()); return; } @@ -573,7 +580,7 @@ if (!write_error.Success()) { err.SetErrorStringWithFormat( "couldn't write to the temporary region for %s: %s", - m_variable_sp->GetName().AsCString(), write_error.AsCString()); + GetName().AsCString(), write_error.AsCString()); return; } @@ -585,8 +592,7 @@ if (!pointer_write_error.Success()) { err.SetErrorStringWithFormat( "couldn't write the address of the temporary region for %s: %s", - m_variable_sp->GetName().AsCString(), - pointer_write_error.AsCString()); + GetName().AsCString(), pointer_write_error.AsCString()); } } } @@ -602,7 +608,7 @@ LLDB_LOGF(log, "EntityVariable::Dematerialize [address = 0x%" PRIx64 ", m_variable_sp = %s]", - (uint64_t)load_addr, m_variable_sp->GetName().AsCString()); + (uint64_t)load_addr, GetName().AsCString()); } if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { @@ -611,13 +617,12 @@ if (!scope) scope = map.GetBestExecutionContextScope(); - lldb::ValueObjectSP valobj_sp = - ValueObjectVariable::Create(scope, m_variable_sp); + lldb::ValueObjectSP valobj_sp = GetValueObject(scope); if (!valobj_sp) { err.SetErrorStringWithFormat( "couldn't get a value object for variable %s", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); return; } @@ -630,7 +635,7 @@ if (!extract_error.Success()) { err.SetErrorStringWithFormat("couldn't get the data for variable %s", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); return; } @@ -652,7 +657,7 @@ if (!set_error.Success()) { err.SetErrorStringWithFormat( "couldn't write the new contents of %s back into the variable", - m_variable_sp->GetName().AsCString()); + GetName().AsCString()); return; } } @@ -664,7 +669,7 @@ if (!free_error.Success()) { err.SetErrorStringWithFormat( "couldn't free the temporary region for %s: %s", - m_variable_sp->GetName().AsCString(), free_error.AsCString()); + GetName().AsCString(), free_error.AsCString()); return; } @@ -748,13 +753,119 @@ } private: - lldb::VariableSP m_variable_sp; + virtual ConstString GetName() const = 0; + + /// Returns ValueObject tied to this variable + virtual lldb::ValueObjectSP + GetValueObject(ExecutionContextScope *scope) = 0; + + /// Returns size in bytes of the type associated with this variable + /// + /// \returns On success, returns byte size of the type assoaciated + /// with this variable. Returns NoneType otherwise. + virtual llvm::Optional + GetByteSize(ExecutionContextScope *scope) const = 0; + + /// Returns 'true' if the location expression associated with this variable + /// is valid. + virtual bool LocationExpressionIsValid() const = 0; + + /// Returns alignment of the type associated with this variable in bits. + /// + /// \returns On success, returns alignment in bits for the type assoaciated + /// with this variable. Returns NoneType otherwise. + virtual llvm::Optional + GetTypeBitAlign(ExecutionContextScope *scope) const = 0; + +protected: bool m_is_reference = false; lldb::addr_t m_temporary_allocation = LLDB_INVALID_ADDRESS; size_t m_temporary_allocation_size = 0; lldb::DataBufferSP m_original_data; }; +/// Represents an Entity constructed from a VariableSP. +/// +/// This class is used for materialization of variables the user +/// has a VariableSP on hand. The ValueObject is then derived +/// from the associated DWARF location expression when needed +/// by the Materializer. +class EntityVariable : public EntityVariableBase { +public: + EntityVariable(lldb::VariableSP &variable_sp) : m_variable_sp(variable_sp) { + m_is_reference = + m_variable_sp->GetType()->GetForwardCompilerType().IsReferenceType(); + } + + ConstString GetName() const override { return m_variable_sp->GetName(); } + + lldb::ValueObjectSP + GetValueObject(ExecutionContextScope *scope) override { + assert(m_variable_sp != nullptr); + + if (!m_value_object_var) { + m_value_object_var = ValueObjectVariable::Create(scope, m_variable_sp); + } + + return m_value_object_var; + } + + llvm::Optional + GetByteSize(ExecutionContextScope *scope) const override { + return m_variable_sp->GetType()->GetByteSize(scope); + } + + bool LocationExpressionIsValid() const override { + return m_variable_sp->LocationExpression().IsValid(); + } + + llvm::Optional + GetTypeBitAlign(ExecutionContextScope *scope) const override { + return m_variable_sp->GetType()->GetLayoutCompilerType().GetTypeBitAlign( + scope); + } + +private: + lldb::VariableSP m_variable_sp; ///< Variable that this entity is based on. + lldb::ValueObjectSP m_value_object_var; ///< ValueObjectVariable created from + ///< m_variable_sp. +}; + +/// Represents an Entity constructed from a VariableSP. +/// +/// This class is used for materialization of variables for +/// which the user does not have a VariableSP available (e.g., +/// when materializing ivars). +class EntityValueObject : public EntityVariableBase { +public: + EntityValueObject(lldb::ValueObjectSP valobj_sp) + : m_valobj_sp(std::move(valobj_sp)) { + m_is_reference = m_valobj_sp->GetCompilerType().IsReferenceType(); + } + + ConstString GetName() const override { return m_valobj_sp->GetName(); } + + lldb::ValueObjectSP + GetValueObject(ExecutionContextScope *scope) override { + return m_valobj_sp; + } + + llvm::Optional + GetByteSize(ExecutionContextScope *scope) const override { + return m_valobj_sp->GetCompilerType().GetByteSize(scope); + } + + bool LocationExpressionIsValid() const override { return true; } + + llvm::Optional + GetTypeBitAlign(ExecutionContextScope *scope) const override { + return m_valobj_sp->GetCompilerType().GetTypeBitAlign(scope); + } + +private: + lldb::ValueObjectSP m_valobj_sp; +}; + uint32_t Materializer::AddVariable(lldb::VariableSP &variable_sp, Status &err) { EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); *iter = std::make_unique(variable_sp); @@ -763,6 +874,15 @@ return ret; } +uint32_t Materializer::AddValueObject(lldb::ValueObjectSP valobj_sp, + Status &err) { + EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); + *iter = std::make_unique(std::move(valobj_sp)); + uint32_t ret = AddStructMember(**iter); + (*iter)->SetOffset(ret); + return ret; +} + class EntityResultVariable : public Materializer::Entity { public: EntityResultVariable(const CompilerType &type, bool is_program_reference, @@ -772,8 +892,8 @@ m_keep_in_memory(keep_in_memory), m_delegate(delegate) { // Hard-coding to maximum size of a pointer since all results are // materialized by reference - m_size = 8; - m_alignment = 8; + m_size = g_default_var_byte_size; + m_alignment = g_default_var_alignment; } void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, @@ -1050,8 +1170,8 @@ public: EntitySymbol(const Symbol &symbol) : Entity(), m_symbol(symbol) { // Hard-coding to maximum size of a symbol - m_size = 8; - m_alignment = 8; + m_size = g_default_var_byte_size; + m_alignment = g_default_var_alignment; } void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, diff --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp --- a/lldb/source/Expression/UserExpression.cpp +++ b/lldb/source/Expression/UserExpression.cpp @@ -98,28 +98,33 @@ return LockAndCheckContext(exe_ctx, target_sp, process_sp, frame_sp); } -lldb::addr_t UserExpression::GetObjectPointer(lldb::StackFrameSP frame_sp, - ConstString &object_name, - Status &err) { +lldb::ValueObjectSP UserExpression::GetObjectPointerValueObject( + StackFrame *frame, ConstString const &object_name, Status &err) { err.Clear(); - if (!frame_sp) { + if (!frame) { err.SetErrorStringWithFormat( "Couldn't load '%s' because the context is incomplete", object_name.AsCString()); - return LLDB_INVALID_ADDRESS; + return {}; } lldb::VariableSP var_sp; lldb::ValueObjectSP valobj_sp; - valobj_sp = frame_sp->GetValueForVariableExpressionPath( + return frame->GetValueForVariableExpressionPath( object_name.GetStringRef(), lldb::eNoDynamicValues, StackFrame::eExpressionPathOptionCheckPtrVsMember | StackFrame::eExpressionPathOptionsNoFragileObjcIvar | StackFrame::eExpressionPathOptionsNoSyntheticChildren | StackFrame::eExpressionPathOptionsNoSyntheticArrayRange, var_sp, err); +} + +lldb::addr_t UserExpression::GetObjectPointer(lldb::StackFrameSP frame_sp, + ConstString &object_name, + Status &err) { + auto valobj_sp = GetObjectPointerValueObject(frame_sp.get(), object_name, err); if (!err.Success() || !valobj_sp.get()) return LLDB_INVALID_ADDRESS; diff --git a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt --- a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt +++ b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt @@ -9,6 +9,7 @@ ClangExpressionDeclMap.cpp ClangExpressionParser.cpp ClangExpressionSourceCode.cpp + ClangExpressionUtil.cpp ClangExpressionVariable.cpp ClangExternalASTSourceCallbacks.cpp ClangFunctionCaller.cpp diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h @@ -546,6 +546,10 @@ void AddOneVariable(NameSearchContext &context, lldb::VariableSP var, lldb::ValueObjectSP valobj); + ClangExpressionVariable::ParserVars * + AddExpressionVariable(NameSearchContext &context, TypeFromParser const &pt, + TypeFromUser const &ut, lldb::ValueObjectSP valobj); + /// Use the NameSearchContext to generate a Decl for the given persistent /// variable, and put it in the list of found entities. /// @@ -557,6 +561,16 @@ void AddOneVariable(NameSearchContext &context, lldb::ExpressionVariableSP &pvar_sp); + /// Use the NameSearchContext to generate a Decl for the given persistent + /// variable, and put it in the list of found entities. + /// + /// \param[in] context + /// The NameSearchContext to use when constructing the Decl. + /// + /// \param[in] pvar_sp + /// The persistent variable that needs a Decl. + void AddOneVariable(NameSearchContext &context, lldb::ValueObjectSP valobj); + /// Use the NameSearchContext to generate a Decl for the given LLDB symbol /// (treated as a variable), and put it in the list of found entities. void AddOneGenericVariable(NameSearchContext &context, const Symbol &symbol); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp @@ -9,10 +9,13 @@ #include "ClangExpressionDeclMap.h" #include "ClangASTSource.h" +#include "ClangExpressionUtil.h" +#include "ClangExpressionVariable.h" #include "ClangModulesDeclVendor.h" #include "ClangPersistentVariables.h" #include "ClangUtil.h" +#include "NameSearchContext.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "lldb/Core/Address.h" #include "lldb/Core/Module.h" @@ -62,6 +65,24 @@ static const char *g_lldb_local_vars_namespace_cstr = "$__lldb_local_vars"; +namespace { +/// A lambda is represented by Clang as an artifical class whose +/// members are the lambda captures. If we capture a 'this' pointer, +/// the artifical class will contain a member variable named 'this'. +/// The function returns a ValueObject for the captured 'this' if such +/// member exists. If no 'this' was captured, return a nullptr. +lldb::ValueObjectSP GetCapturedThisValueObject(StackFrame *frame) { + assert(frame); + + if (auto thisValSP = frame->FindVariable(ConstString("this"))) + if (auto thisThisValSP = + thisValSP->GetChildMemberWithName(ConstString("this"), true)) + return thisThisValSP; + + return nullptr; +} +} // namespace + ClangExpressionDeclMap::ClangExpressionDeclMap( bool keep_result_in_memory, Materializer::PersistentVariableDelegate *result_delegate, @@ -394,6 +415,9 @@ else if (parser_vars->m_lldb_var) offset = m_parser_vars->m_materializer->AddVariable( parser_vars->m_lldb_var, err); + else if (parser_vars->m_lldb_value_object) + offset = m_parser_vars->m_materializer->AddValueObject( + parser_vars->m_lldb_value_object, err); } if (!err.Success()) @@ -799,6 +823,28 @@ TypeSystemClang::DeclContextGetAsCXXMethodDecl(function_decl_ctx); if (method_decl) { + if (auto capturedThis = GetCapturedThisValueObject(frame)) { + // We're inside a lambda and we captured a 'this'. + // Import the outer class's AST instead of the + // (unnamed) lambda structure AST so unqualified + // member lookups are understood by the Clang parser. + // + // If we're in a lambda which didn't capture 'this', + // $__lldb_class will correspond to the lambda closure + // AST and references to captures will resolve like + // regular member varaiable accesses do. + TypeFromUser pointee_type = + capturedThis->GetCompilerType().GetPointeeType(); + + LLDB_LOG(log, + " CEDM::FEVD Adding captured type ({0} for" + " $__lldb_class: {1}", + capturedThis->GetTypeName(), capturedThis->GetName()); + + AddContextClassType(context, pointee_type); + return; + } + clang::CXXRecordDecl *class_decl = method_decl->getParent(); QualType class_qual_type(class_decl->getTypeForDecl(), 0); @@ -1101,6 +1147,21 @@ context.m_found_variable = true; } } + + // We're in a local_var_lookup but haven't found any local variables + // so far. When performing a variable lookup from within the context of + // a lambda, we count the lambda captures as local variables. Thus, + // see if we captured any variables with the requested 'name'. + if (!variable_found) { + if (auto lambda = ClangExpressionUtil::GetLambdaValueObject(frame)) { + if (auto capture = lambda->GetChildMemberWithName(name, true)) { + variable_found = true; + context.m_found_variable = true; + AddOneVariable(context, capture); + } + } + } + return variable_found; } @@ -1542,25 +1603,18 @@ return true; } -void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, - VariableSP var, - ValueObjectSP valobj) { - assert(m_parser_vars.get()); - +ClangExpressionVariable::ParserVars * +ClangExpressionDeclMap::AddExpressionVariable(NameSearchContext &context, + TypeFromParser const &pt, + TypeFromUser const &ut, + ValueObjectSP valobj) { Log *log = GetLog(LLDBLog::Expressions); - TypeFromUser ut; - TypeFromParser pt; - Value var_location; - - if (!GetVariableValue(var, var_location, &ut, &pt)) - return; - clang::QualType parser_opaque_type = QualType::getFromOpaquePtr(pt.GetOpaqueQualType()); if (parser_opaque_type.isNull()) - return; + return nullptr; if (const clang::Type *parser_type = parser_opaque_type.getTypePtr()) { if (const TagType *tag_type = dyn_cast(parser_type)) @@ -1587,16 +1641,81 @@ entity->EnableParserVars(GetParserID()); ClangExpressionVariable::ParserVars *parser_vars = entity->GetParserVars(GetParserID()); + parser_vars->m_named_decl = var_decl; - parser_vars->m_llvm_value = nullptr; - parser_vars->m_lldb_value = var_location; - parser_vars->m_lldb_var = var; if (is_reference) entity->m_flags |= ClangExpressionVariable::EVTypeIsReference; LLDB_LOG(log, " CEDM::FEVD Found variable {0}, returned\n{1} (original {2})", decl_name, ClangUtil::DumpDecl(var_decl), ClangUtil::ToString(ut)); + + return parser_vars; +} + +void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, + ValueObjectSP valobj) { + assert(m_parser_vars.get()); + assert(valobj); + + Log *log = GetLog(LLDBLog::Expressions); + + Value var_location = valobj->GetValue(); + + TypeFromUser user_type = valobj->GetCompilerType(); + + TypeSystemClang *clang_ast = + llvm::dyn_cast_or_null(user_type.GetTypeSystem()); + + if (!clang_ast) { + LLDB_LOG(log, "Skipped a definition because it has no Clang AST"); + return; + } + + TypeFromParser parser_type = GuardedCopyType(user_type); + + if (!parser_type) { + LLDB_LOG(log, + "Couldn't copy a variable's type into the parser's AST context"); + + return; + } + + if (var_location.GetContextType() == Value::ContextType::Invalid) + var_location.SetCompilerType(parser_type); + + ClangExpressionVariable::ParserVars *parser_vars = + AddExpressionVariable(context, parser_type, user_type, valobj); + + if (!parser_vars) + return; + + parser_vars->m_llvm_value = nullptr; + parser_vars->m_lldb_value = var_location; + parser_vars->m_lldb_value_object = std::move(valobj); +} + +void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, + VariableSP var, + ValueObjectSP valobj) { + assert(m_parser_vars.get()); + + TypeFromUser ut; + TypeFromParser pt; + Value var_location; + + if (!GetVariableValue(var, var_location, &ut, &pt)) + return; + + ClangExpressionVariable::ParserVars *parser_vars = + AddExpressionVariable(context, pt, ut, std::move(valobj)); + + if (!parser_vars) + return; + + parser_vars->m_llvm_value = nullptr; + parser_vars->m_lldb_value = var_location; + parser_vars->m_lldb_var = var; } void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h @@ -78,9 +78,17 @@ Wrapping wrap, WrapKind wrap_kind); private: - void AddLocalVariableDecls(const lldb::VariableListSP &var_list_sp, - StreamString &stream, - const std::string &expr) const; + /// Writes "using" declarations for local variables into the specified stream. + /// + /// Behaviour is undefined if 'frame == nullptr'. + /// + /// \param[out] stream Stream that this function generates "using" + /// declarations into. + /// \param[in] expr Expression source that we're evaluating. + /// \param[in] frame StackFrame which carries information about the local + /// variables that we're generating "using" declarations for. + void AddLocalVariableDecls(StreamString &stream, const std::string &expr, + StackFrame *frame) const; /// String marking the start of the user expression. std::string m_start_marker; diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp @@ -8,6 +8,8 @@ #include "ClangExpressionSourceCode.h" +#include "ClangExpressionUtil.h" + #include "clang/Basic/CharInfo.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" @@ -27,6 +29,7 @@ #include "lldb/Target/StackFrame.h" #include "lldb/Target/Target.h" #include "lldb/Utility/StreamString.h" +#include "lldb/lldb-forward.h" using namespace lldb_private; @@ -200,6 +203,34 @@ return m_tokens.find(token) != m_tokens.end(); } }; + +// If we're evaluating from inside a lambda that captures a 'this' pointer, +// add a "using" declaration to 'stream' for each capture used in the +// expression (tokenized by 'verifier'). +// +// If no 'this' capture exists, generate no using declarations. Instead +// capture lookups will get resolved by the same mechanism as class member +// variable lookup. That's because Clang generates an unnamed structure +// representing the lambda closure whose members are the captured variables. +void AddLambdaCaptureDecls(StreamString &stream, StackFrame *frame, + TokenVerifier const &verifier) { + assert(frame); + + if (auto thisValSP = ClangExpressionUtil::GetLambdaValueObject(frame)) { + uint32_t numChildren = thisValSP->GetNumChildren(); + for (uint32_t i = 0; i < numChildren; ++i) { + auto childVal = thisValSP->GetChildAtIndex(i, true); + ConstString childName(childVal ? childVal->GetName() : ConstString("")); + + if (!childName.IsEmpty() && verifier.hasToken(childName.GetStringRef()) && + childName != "this") { + stream.Printf("using $__lldb_local_vars::%s;\n", + childName.GetCString()); + } + } + } +} + } // namespace TokenVerifier::TokenVerifier(std::string body) { @@ -264,16 +295,24 @@ } } -void ClangExpressionSourceCode::AddLocalVariableDecls( - const lldb::VariableListSP &var_list_sp, StreamString &stream, - const std::string &expr) const { +void ClangExpressionSourceCode::AddLocalVariableDecls(StreamString &stream, + const std::string &expr, + StackFrame *frame) const { + assert(frame); TokenVerifier tokens(expr); + lldb::VariableListSP var_list_sp = frame->GetInScopeVariableList(false, true); + for (size_t i = 0; i < var_list_sp->GetSize(); i++) { lldb::VariableSP var_sp = var_list_sp->GetVariableAtIndex(i); ConstString var_name = var_sp->GetName(); + if (var_name == "this" && m_wrap_kind == WrapKind::CppMemberFunction) { + AddLambdaCaptureDecls(stream, frame, tokens); + + continue; + } // We can check for .block_descriptor w/o checking for langauge since this // is not a valid identifier in either C or C++. @@ -288,9 +327,6 @@ if ((var_name == "self" || var_name == "_cmd") && is_objc) continue; - if (var_name == "this" && m_wrap_kind == WrapKind::CppMemberFunction) - continue; - stream.Printf("using $__lldb_local_vars::%s;\n", var_name.AsCString()); } } @@ -376,10 +412,8 @@ if (add_locals) if (target->GetInjectLocalVariables(&exe_ctx)) { - lldb::VariableListSP var_list_sp = - frame->GetInScopeVariableList(false, true); - AddLocalVariableDecls(var_list_sp, lldb_local_var_decls, - force_add_all_locals ? "" : m_body); + AddLocalVariableDecls(lldb_local_var_decls, + force_add_all_locals ? "" : m_body, frame); } } diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h @@ -0,0 +1,30 @@ +//===-- ClangExpressionUtil.h -----------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONUTIL_H +#define LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONUTIL_H + +#include "lldb/lldb-private.h" + +namespace lldb_private { +namespace ClangExpressionUtil { +/// Returns a ValueObject for the lambda class in the current frame +/// +/// To represent a lambda, Clang generates an artificial class +/// whose members are the captures and whose operator() is the +/// lambda implementation. If we capture a 'this' pointer, +/// the artifical class will contain a member variable named 'this'. +/// +/// This method returns the 'this' pointer to the artificial lambda +/// class if a real 'this' was captured. Otherwise, returns nullptr. +lldb::ValueObjectSP GetLambdaValueObject(StackFrame *frame); + +} // namespace ClangExpressionUtil +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONHELPER_H diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp @@ -0,0 +1,27 @@ +//===-- ClangExpressionUtil.cpp -------------------------------------------===// +// +// 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 "ClangExpressionUtil.h" + +#include "lldb/Core/ValueObject.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Utility/ConstString.h" + +namespace lldb_private { +namespace ClangExpressionUtil { +lldb::ValueObjectSP GetLambdaValueObject(StackFrame *frame) { + assert(frame); + + if (auto thisValSP = frame->FindVariable(ConstString("this"))) + if (thisValSP->GetChildMemberWithName(ConstString("this"), true)) + return thisValSP; + + return nullptr; +} +} // namespace ClangExpressionUtil +} // namespace lldb_private diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h @@ -116,7 +116,7 @@ /// The following values should not live beyond parsing class ParserVars { public: - ParserVars() : m_lldb_value(), m_lldb_var() {} + ParserVars() = default; const clang::NamedDecl *m_named_decl = nullptr; ///< The Decl corresponding to this variable @@ -129,6 +129,10 @@ const lldb_private::Symbol *m_lldb_sym = nullptr; ///< The original symbol for this /// variable, if it was a symbol + + /// ValueObject for this variable. Used when only an ivar is + /// available but we want to materialize the variable. + lldb::ValueObjectSP m_lldb_value_object; }; private: diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h @@ -198,6 +198,10 @@ ExecutionContext &exe_ctx, std::vector modules_to_import, bool for_completion); + + lldb::addr_t GetCppObjectPointer(StackFrame *frame, ConstString &object_name, + Status &err); + /// Defines how the current expression should be wrapped. ClangExpressionSourceCode::WrapKind GetWrapKind() const; bool SetupPersistentState(DiagnosticManager &diagnostic_manager, diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -872,6 +872,34 @@ return true; } +lldb::addr_t ClangUserExpression::GetCppObjectPointer(StackFrame *frame, + ConstString &object_name, + Status &err) { + auto valobj_sp = GetObjectPointerValueObject(frame, object_name, err); + + // We're inside a C++ class method. This could potentially be an unnamed + // lambda structure. If the lambda captured a "this", that should be + // the object pointer. + if (auto thisChildSP = + valobj_sp->GetChildMemberWithName(ConstString("this"), true)) { + valobj_sp = thisChildSP; + } + + if (!err.Success() || !valobj_sp.get()) + return LLDB_INVALID_ADDRESS; + + lldb::addr_t ret = valobj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + + if (ret == LLDB_INVALID_ADDRESS) { + err.SetErrorStringWithFormat( + "Couldn't load '%s' because its value couldn't be evaluated", + object_name.AsCString()); + return LLDB_INVALID_ADDRESS; + } + + return ret; +} + bool ClangUserExpression::AddArguments(ExecutionContext &exe_ctx, std::vector &args, lldb::addr_t struct_address, @@ -906,8 +934,14 @@ address_type != eAddressTypeLoad) object_ptr_error.SetErrorString("Can't get context object's " "debuggee address"); - } else - object_ptr = GetObjectPointer(frame_sp, object_name, object_ptr_error); + } else { + if (m_in_cplusplus_method) { + object_ptr = + GetCppObjectPointer(frame_sp.get(), object_name, object_ptr_error); + } else { + object_ptr = GetObjectPointer(frame_sp, object_name, object_ptr_error); + } + } if (!object_ptr_error.Success()) { exe_ctx.GetTargetRef().GetDebugger().GetAsyncOutputStream()->Printf( diff --git a/lldb/test/API/commands/expression/expr_inside_lambda/Makefile b/lldb/test/API/commands/expression/expr_inside_lambda/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/expr_inside_lambda/Makefile @@ -0,0 +1,5 @@ +CXX_SOURCES := main.cpp + +CXXFLAGS_EXTRAS := -std=c++14 -O0 -g + +include Makefile.rules diff --git a/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py @@ -0,0 +1,123 @@ +""" Test that evaluating expressions from within C++ lambdas works + Particularly, we test the case of capturing "this" and + using members of the captured object in expression evaluation + while we're on a breakpoint inside a lambda. +""" + + +import lldb +from lldbsuite.test.lldbtest import * + + +class ExprInsideLambdaTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def expectExprError(self, expr : str, expected : str): + frame = self.thread.GetFrameAtIndex(0) + value = frame.EvaluateExpression(expr) + errmsg = value.GetError().GetCString() + self.assertIn(expected, errmsg) + + def test_expr_inside_lambda(self): + """Test that lldb evaluating expressions inside lambda expressions works correctly.""" + self.build() + (target, process, self.thread, bkpt) = \ + lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.cpp")) + + # Inside 'Foo::method' + + # Check access to captured 'this' + self.expect_expr("class_var", result_type="int", result_value="109") + self.expect_expr("this->class_var", result_type="int", result_value="109") + + # Check that captured shadowed variables take preference over the + # corresponding member variable + self.expect_expr("shadowed", result_type="int", result_value="5") + self.expect_expr("this->shadowed", result_type="int", result_value="-137") + + # Check access to local captures + self.expect_expr("local_var", result_type="int", result_value="137") + self.expect_expr("*class_ptr", result_type="int", result_value="137") + + # Check access to base class variables + self.expect_expr("base_var", result_type="int", result_value="14") + self.expect_expr("base_base_var", result_type="int", result_value="11") + + # Check access to global variable + self.expect_expr("global_var", result_type="int", result_value="-5") + + # Check access to multiple captures/member variables + self.expect_expr("(shadowed + this->shadowed) * (base_base_var + local_var - class_var)", + result_type="int", result_value="-5148") + + # Check base-class function call + self.expect_expr("baz_virt()", result_type="int", result_value="2") + self.expect_expr("base_var", result_type="int", result_value="14") + self.expect_expr("this->shadowed", result_type="int", result_value="-1") + + # 'p this' should yield 'struct Foo*' + frame = self.thread.GetFrameAtIndex(0) + outer_class_addr = frame.GetValueForVariablePath("this->this") + self.expect_expr("this", result_value=outer_class_addr.GetValue()) + + lldbutil.continue_to_breakpoint(process, bkpt) + + # Inside 'nested_lambda' + + # Check access to captured 'this'. Should still be 'struct Foo*' + self.expect_expr("class_var", result_type="int", result_value="109") + self.expect_expr("global_var", result_type="int", result_value="-5") + self.expect_expr("this", result_value=outer_class_addr.GetValue()) + + # Check access to capture + self.expect_expr("lambda_local_var", result_type="int", result_value="5") + + # Check access to variable in previous frame which we didn't capture + self.expectExprError("local_var_copy", "use of undeclared identifier") + + lldbutil.continue_to_breakpoint(process, bkpt) + + # By-ref mutates source variable + self.expect_expr("lambda_local_var", result_type="int", result_value="0") + + # By-value doesn't mutate source variable + self.expect_expr("local_var_copy", result_type="int", result_value="136") + self.expect_expr("local_var", result_type="int", result_value="137") + + lldbutil.continue_to_breakpoint(process, bkpt) + + # Inside 'LocalLambdaClass::inner_method' + + # Check access to captured 'this' + self.expect_expr("lambda_class_local", result_type="int", result_value="-12345") + self.expect_expr("this->lambda_class_local", result_type="int", result_value="-12345") + self.expect_expr("outer_ptr->class_var", result_type="int", result_value="109") + + # 'p this' should yield 'struct LocalLambdaClass*' + frame = self.thread.GetFrameAtIndex(0) + local_class_addr = frame.GetValueForVariablePath("this->this") + self.assertNotEqual(local_class_addr, outer_class_addr) + self.expect_expr("this", result_value=local_class_addr.GetValue()) + + # Can still access global variable + self.expect_expr("global_var", result_type="int", result_value="-5") + + # Check access to outer top-level structure's members + self.expectExprError("class_var", ("use of non-static data member" + " 'class_var' of 'Foo' from nested type")) + + self.expectExprError("base_var", ("use of non-static data member" + " 'base_var'")) + + self.expectExprError("local_var", ("use of non-static data member 'local_var'" + " of '' from nested type 'LocalLambdaClass'")) + + # Inside non_capturing_method + lldbutil.continue_to_breakpoint(process, bkpt) + self.expect_expr("local", result_type="int", result_value="5") + self.expect_expr("local2", result_type="int", result_value="10") + self.expect_expr("local2 * local", result_type="int", result_value="50") + + self.expectExprError("class_var", ("use of non-static data member" + " 'class_var' of 'Foo' from nested type")) diff --git a/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp b/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp @@ -0,0 +1,99 @@ +#include +#include + +namespace { +int global_var = -5; +} // namespace + +struct Baz { + virtual ~Baz() = default; + + virtual int baz_virt() = 0; + + int base_base_var = 12; +}; + +struct Bar : public Baz { + virtual ~Bar() = default; + + virtual int baz_virt() override { + base_var = 10; + return 1; + } + + int base_var = 15; +}; + +struct Foo : public Bar { + int class_var = 9; + int shadowed = -137; + int *class_ptr; + + virtual ~Foo() = default; + + virtual int baz_virt() override { + shadowed = -1; + return 2; + } + + void method() { + int local_var = 137; + int shadowed; + class_ptr = &local_var; + auto lambda = [&shadowed, this, &local_var, + local_var_copy = local_var]() mutable { + int lambda_local_var = 5; + shadowed = 5; + class_var = 109; + --base_var; + --base_base_var; + std::puts("break here"); + + auto nested_lambda = [this, &lambda_local_var] { + std::puts("break here"); + lambda_local_var = 0; + }; + + nested_lambda(); + --local_var_copy; + std::puts("break here"); + + struct LocalLambdaClass { + int lambda_class_local = -12345; + Foo *outer_ptr; + + void inner_method() { + auto lambda = [this] { + std::puts("break here"); + lambda_class_local = -2; + outer_ptr->class_var *= 2; + }; + + lambda(); + } + }; + + LocalLambdaClass l; + l.outer_ptr = this; + l.inner_method(); + }; + lambda(); + } + + void non_capturing_method() { + int local = 5; + int local2 = 10; + + class_var += [=] { + std::puts("break here"); + return local + local2; + }(); + } +}; + +int main() { + Foo f; + f.method(); + f.non_capturing_method(); + return global_var; +}