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,28 @@ AddPersistentVariable(lldb::ExpressionVariableSP &persistent_variable_sp, PersistentVariableDelegate *delegate, Status &err); uint32_t AddVariable(lldb::VariableSP &variable_sp, Status &err); + + /// Create entity from supplied ValueObject and count it as a member + /// of the materialized struct. + /// + /// Behaviour is undefined if 'valobj_provider' is empty. + /// + /// \param[in] name Name of variable to materialize + /// + /// \param[in] valobj_provider When materializing values multiple + /// times, this callback gets used to fetch a fresh + /// ValueObject corresponding to the supplied frame. + /// This is mainly used for conditional breakpoints + /// that re-apply an expression whatever the frame + /// happens to be when the breakpoint got hit. + /// + /// \param[out] err Error status that gets set on error. + /// + /// \returns Offset in bytes of the member we just added to the + /// materialized struct. + uint32_t AddValueObject(ConstString name, + ValueObjectProviderTy valobj_provider, 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,23 @@ 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(lldb::StackFrameSP 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/include/lldb/lldb-private-types.h b/lldb/include/lldb/lldb-private-types.h --- a/lldb/include/lldb/lldb-private-types.h +++ b/lldb/include/lldb/lldb-private-types.h @@ -106,6 +106,12 @@ typedef struct type128 { uint64_t x[2]; } type128; typedef struct type256 { uint64_t x[4]; } type256; +/// Functor that returns a ValueObjectSP for a variable given its name +/// and the StackFrame of interest. Used primarily in the Materializer +/// to refetch a ValueObject when the ExecutionContextScope changes. +using ValueObjectProviderTy = + std::function; + } // namespace lldb_private #endif // #if defined(__cplusplus) 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 = SetupValueObject(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->LocationExpressionList().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 = SetupValueObject(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,140 @@ } private: - lldb::VariableSP m_variable_sp; + virtual ConstString GetName() const = 0; + + /// Creates and returns ValueObject tied to this variable + /// and prepares Entity for materialization. + /// + /// Called each time the Materializer (de)materializes a + /// variable. We re-create the ValueObject based on the + /// current ExecutionContextScope since clients such as + /// conditional breakpoints may materialize the same + /// EntityVariable multiple times with different frames. + /// + /// Each subsequent use of the EntityVariableBase interface + /// will query the newly created ValueObject until this + /// function is called again. + virtual lldb::ValueObjectSP + SetupValueObject(ExecutionContextScope *scope) = 0; + + /// Returns size in bytes of the type associated with this variable + /// + /// \returns On success, returns byte size of the type associated + /// 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 associated + /// 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 for which +/// 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 SetupValueObject(ExecutionContextScope *scope) override { + assert(m_variable_sp != nullptr); + return ValueObjectVariable::Create(scope, m_variable_sp); + } + + llvm::Optional + GetByteSize(ExecutionContextScope *scope) const override { + return m_variable_sp->GetType()->GetByteSize(scope); + } + + bool LocationExpressionIsValid() const override { + return m_variable_sp->LocationExpressionList().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. +}; + +/// 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(ConstString name, ValueObjectProviderTy provider) + : m_name(name), m_valobj_provider(std::move(provider)) { + assert(m_valobj_provider); + } + + ConstString GetName() const override { return m_name; } + + lldb::ValueObjectSP SetupValueObject(ExecutionContextScope *scope) override { + m_valobj_sp = + m_valobj_provider(GetName(), scope->CalculateStackFrame().get()); + + if (m_valobj_sp) + m_is_reference = m_valobj_sp->GetCompilerType().IsReferenceType(); + + return m_valobj_sp; + } + + llvm::Optional + GetByteSize(ExecutionContextScope *scope) const override { + if (m_valobj_sp) + return m_valobj_sp->GetCompilerType().GetByteSize(scope); + + return {}; + } + + bool LocationExpressionIsValid() const override { + if (m_valobj_sp) + return m_valobj_sp->GetError().Success(); + + return false; + } + + llvm::Optional + GetTypeBitAlign(ExecutionContextScope *scope) const override { + if (m_valobj_sp) + return m_valobj_sp->GetCompilerType().GetTypeBitAlign(scope); + + return {}; + } + +private: + ConstString m_name; + lldb::ValueObjectSP m_valobj_sp; + ValueObjectProviderTy m_valobj_provider; +}; + 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 +895,17 @@ return ret; } +uint32_t Materializer::AddValueObject(ConstString name, + ValueObjectProviderTy valobj_provider, + Status &err) { + assert(valobj_provider); + EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); + *iter = std::make_unique(name, std::move(valobj_provider)); + 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 +915,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 +1193,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,34 @@ 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( + lldb::StackFrameSP frame_sp, ConstString const &object_name, Status &err) { err.Clear(); if (!frame_sp) { 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_sp->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(std::move(frame_sp), 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 @@ -530,6 +530,23 @@ TypeFromUser *found_type = nullptr, TypeFromParser *parser_type = nullptr); + /// Use the NameSearchContext to generate a Decl for the given LLDB + /// ValueObject, and put it in the list of found entities. + /// + /// Helper function used by the other AddOneVariable APIs. + /// + /// \param[in,out] context + /// The NameSearchContext to use when constructing the Decl. + /// + /// \param[in] pt + /// The CompilerType of the variable we're adding a Decl for. + /// + /// \param[in] var + /// The LLDB ValueObject that needs a Decl. + ClangExpressionVariable::ParserVars * + AddExpressionVariable(NameSearchContext &context, TypeFromParser const &pt, + lldb::ValueObjectSP valobj); + /// Use the NameSearchContext to generate a Decl for the given LLDB /// Variable, and put it in the Tuple list. /// @@ -544,6 +561,20 @@ void AddOneVariable(NameSearchContext &context, lldb::VariableSP var, lldb::ValueObjectSP valobj); + /// Use the NameSearchContext to generate a Decl for the given ValueObject + /// and put it in the list of found entities. + /// + /// \param[in,out] context + /// The NameSearchContext to use when constructing the Decl. + /// + /// \param[in] valobj + /// The ValueObject that needs a Decl. + /// + /// \param[in] valobj_provider Callback that fetches a ValueObjectSP + /// from the specified frame + void AddOneVariable(NameSearchContext &context, lldb::ValueObjectSP valobj, + ValueObjectProviderTy valobj_provider); + /// Use the NameSearchContext to generate a Decl for the given persistent /// variable, and put it in the list of found entities. /// 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" @@ -44,6 +47,7 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/RegisterValue.h" #include "lldb/Utility/Status.h" +#include "lldb/lldb-private-types.h" #include "lldb/lldb-private.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" @@ -62,6 +66,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 +416,10 @@ 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_valobj_provider) { + offset = m_parser_vars->m_materializer->AddValueObject( + name, parser_vars->m_lldb_valobj_provider, err); + } } if (!err.Success()) @@ -795,6 +821,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); @@ -1053,6 +1101,30 @@ 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) { + auto find_capture = [](ConstString varname, + StackFrame *frame) -> ValueObjectSP { + if (auto lambda = ClangExpressionUtil::GetLambdaValueObject(frame)) { + if (auto capture = lambda->GetChildMemberWithName(varname, true)) { + return capture; + } + } + + return nullptr; + }; + + if (auto capture = find_capture(name, frame)) { + variable_found = true; + context.m_found_variable = true; + AddOneVariable(context, std::move(capture), std::move(find_capture)); + } + } + return variable_found; } @@ -1493,25 +1565,15 @@ return true; } -void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, - VariableSP var, - ValueObjectSP valobj) { - assert(m_parser_vars.get()); - - Log *log = GetLog(LLDBLog::Expressions); - - TypeFromUser ut; - TypeFromParser pt; - Value var_location; - - if (!GetVariableValue(var, var_location, &ut, &pt)) - return; - +ClangExpressionVariable::ParserVars * +ClangExpressionDeclMap::AddExpressionVariable(NameSearchContext &context, + TypeFromParser const &pt, + ValueObjectSP valobj) { 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)) @@ -1538,16 +1600,89 @@ 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; + return parser_vars; +} + +void ClangExpressionDeclMap::AddOneVariable( + NameSearchContext &context, ValueObjectSP valobj, + ValueObjectProviderTy valobj_provider) { + 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, valobj); + + if (!parser_vars) + return; + LLDB_LOG(log, " CEDM::FEVD Found variable {0}, returned\n{1} (original {2})", - decl_name, ClangUtil::DumpDecl(var_decl), ClangUtil::ToString(ut)); + context.m_decl_name, ClangUtil::DumpDecl(parser_vars->m_named_decl), + ClangUtil::ToString(user_type)); + + parser_vars->m_llvm_value = nullptr; + parser_vars->m_lldb_value = std::move(var_location); + parser_vars->m_lldb_valobj_provider = std::move(valobj_provider); +} + +void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context, + VariableSP var, + ValueObjectSP valobj) { + assert(m_parser_vars.get()); + + Log *log = GetLog(LLDBLog::Expressions); + + TypeFromUser ut; + TypeFromParser pt; + Value var_location; + + if (!GetVariableValue(var, var_location, &ut, &pt)) + return; + + ClangExpressionVariable::ParserVars *parser_vars = + AddExpressionVariable(context, pt, std::move(valobj)); + + if (!parser_vars) + return; + + LLDB_LOG(log, " CEDM::FEVD Found variable {0}, returned\n{1} (original {2})", + context.m_decl_name, ClangUtil::DumpDecl(parser_vars->m_named_decl), + ClangUtil::ToString(ut)); + + 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,19 @@ 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 this_val_sp = frame->FindVariable(ConstString("this"))) + if (this_val_sp->GetChildMemberWithName(ConstString("this"), true)) + return this_val_sp; + + 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,12 @@ const lldb_private::Symbol *m_lldb_sym = nullptr; ///< The original symbol for this /// variable, if it was a symbol + + /// Callback that provides a ValueObject for the + /// specified frame. Used by the materializer for + /// re-fetching ValueObjects when materializing + /// ivars. + ValueObjectProviderTy m_lldb_valobj_provider; }; 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(lldb::StackFrameSP 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( + lldb::StackFrameSP frame_sp, ConstString &object_name, Status &err) { + auto valobj_sp = + GetObjectPointerValueObject(std::move(frame_sp), 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, 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,124 @@ +""" 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 captures + self.expect_expr("lambda_local_var", result_type="int", result_value="5") + self.expect_expr("local_var", result_type="int", result_value="137") + + # 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, 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; +} diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/Makefile b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp +ENABLE_THREADS := YES + +include Makefile.rules diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/TestBreakOnLambdaCapture.py b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/TestBreakOnLambdaCapture.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/TestBreakOnLambdaCapture.py @@ -0,0 +1,54 @@ +""" +Test that if we hit a breakpoint on a lambda capture +on two threads at the same time we stop only for +the correct one. +""" + +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestBreakOnLambdaCapture(TestBase): + + NO_DEBUG_INFO_TESTCASE = True + + def test_break_on_lambda_capture(self): + self.build() + self.main_source_file = lldb.SBFileSpec("main.cpp") + + (target, process, main_thread, _) = lldbutil.run_to_source_breakpoint(self, + "First break", self.main_source_file) + + # FIXME: This is working around a separate bug. If you hit a breakpoint and + # run an expression and it is the first expression you've ever run, on + # Darwin that will involve running the ObjC runtime parsing code, and we'll + # be in the middle of that when we do PerformAction on the other thread, + # which will cause the condition expression to fail. Calling another + # expression first works around this. + val_obj = main_thread.frame[0].EvaluateExpression("true") + self.assertSuccess(val_obj.GetError(), "Ran our expression successfully") + self.assertEqual(val_obj.value, "true", "Value was true.") + + bkpt = target.BreakpointCreateBySourceRegex("Break here in the helper", + self.main_source_file); + + bkpt.SetCondition("enable && usec == 1") + process.Continue() + + # This is hard to test definitively, becuase it requires hitting + # a breakpoint on multiple threads at the same time. On Darwin, this + # will happen pretty much ever time we continue. What we are really + # asserting is that we only ever stop on one thread, so we approximate that + # by continuing 20 times and assert we only ever hit the first thread. Either + # this is a platform that only reports one hit at a time, in which case all + # this code is unused, or we actually didn't hit the other thread. + + for idx in range(0, 20): + process.Continue() + for thread in process.threads: + if thread.id == main_thread.id: + self.assertEqual(thread.stop_reason, lldb.eStopReasonBreakpoint) + else: + self.assertEqual(thread.stop_reason, lldb.eStopReasonNone) diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/main.cpp b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_on_lambda_capture/main.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +struct Foo { + bool enable = true; + uint32_t offset = 0; + + void usleep_helper(uint32_t usec) { + [this, &usec] { + puts("Break here in the helper"); + std::this_thread::sleep_for( + std::chrono::duration(offset + usec)); + }(); + } +}; + +void *background_thread(void *) { + Foo f; + for (;;) { + f.usleep_helper(2); + } +} + +int main() { + std::puts("First break"); + std::thread main_thread(background_thread, nullptr); + Foo f; + for (;;) { + f.usleep_helper(1); + } +}