diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -247,7 +247,7 @@ class ValueCheck: def __init__(self, name=None, value=None, type=None, summary=None, - children=None): + children=None, dereference=None): """ :param name: The name that the SBValue should have. None if the summary should not be checked. @@ -262,12 +262,15 @@ The order of checks is the order of the checks in the list. The number of checks has to match the number of children. + :param dereference: A ValueCheck for the SBValue returned by the + `Dereference` function. """ self.expect_name = name self.expect_value = value self.expect_type = type self.expect_summary = summary self.children = children + self.dereference = dereference def check_value(self, test_base, val, error_msg=None): """ @@ -309,6 +312,9 @@ if self.children is not None: self.check_value_children(test_base, val, error_msg) + if self.dereference is not None: + self.dereference.check_value(test_base, val.Dereference(), error_msg) + def check_value_children(self, test_base, val, error_msg=None): """ Checks that the children of a SBValue match a certain structure and diff --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h --- a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h +++ b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h @@ -47,7 +47,9 @@ size_t GetIndexOfChildWithName(ConstString name) override; private: - lldb::ValueObjectSP m_frame_ptr_sp; + lldb::ValueObjectSP m_resume_ptr_sp; + lldb::ValueObjectSP m_destroy_ptr_sp; + lldb::ValueObjectSP m_promise_ptr_sp; std::unique_ptr m_ast_importer; }; diff --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp --- a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp @@ -17,38 +17,37 @@ using namespace lldb_private; using namespace lldb_private::formatters; -static ValueObjectSP GetCoroFramePtrFromHandle(ValueObject &valobj) { - ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); +static lldb::addr_t GetCoroFramePtrFromHandle(ValueObjectSP valobj_sp) { if (!valobj_sp) - return nullptr; + return LLDB_INVALID_ADDRESS; // We expect a single pointer in the `coroutine_handle` class. // We don't care about its name. if (valobj_sp->GetNumChildren() != 1) - return nullptr; + return LLDB_INVALID_ADDRESS; ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true)); if (!ptr_sp) - return nullptr; + return LLDB_INVALID_ADDRESS; if (!ptr_sp->GetCompilerType().IsPointerType()) - return nullptr; - - return ptr_sp; -} - -static Function *ExtractDestroyFunction(ValueObjectSP &frame_ptr_sp) { - lldb::TargetSP target_sp = frame_ptr_sp->GetTargetSP(); - lldb::ProcessSP process_sp = frame_ptr_sp->GetProcessSP(); - auto ptr_size = process_sp->GetAddressByteSize(); + return LLDB_INVALID_ADDRESS; AddressType addr_type; - lldb::addr_t frame_ptr_addr = frame_ptr_sp->GetPointerValue(&addr_type); + lldb::addr_t frame_ptr_addr = ptr_sp->GetPointerValue(&addr_type); if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS) - return nullptr; + return LLDB_INVALID_ADDRESS; lldbassert(addr_type == AddressType::eAddressTypeLoad); + if (addr_type != AddressType::eAddressTypeLoad) + return LLDB_INVALID_ADDRESS; + + return frame_ptr_addr; +} + +static Function *ExtractDestroyFunction(lldb::TargetSP target_sp, + lldb::addr_t frame_ptr_addr) { + lldb::ProcessSP process_sp = target_sp->GetProcessSP(); + auto ptr_size = process_sp->GetAddressByteSize(); Status error; - // The destroy pointer is the 2nd pointer inside the compiler-generated - // `pair`. auto destroy_func_ptr_addr = frame_ptr_addr + ptr_size; lldb::addr_t destroy_func_addr = process_sp->ReadPointerFromMemory(destroy_func_ptr_addr, error); @@ -59,12 +58,7 @@ if (!target_sp->ResolveLoadAddress(destroy_func_addr, destroy_func_address)) return nullptr; - Function *destroy_func = - destroy_func_address.CalculateSymbolContextFunction(); - if (!destroy_func) - return nullptr; - - return destroy_func; + return destroy_func_address.CalculateSymbolContextFunction(); } static CompilerType InferPromiseType(Function &destroy_func) { @@ -85,37 +79,19 @@ return promise_type->GetForwardCompilerType(); } -static CompilerType GetCoroutineFrameType(TypeSystemClang &ast_ctx, - CompilerType promise_type) { - CompilerType void_type = ast_ctx.GetBasicType(lldb::eBasicTypeVoid); - CompilerType coro_func_type = ast_ctx.CreateFunctionType( - /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1, - /*is_variadic=*/false, /*qualifiers=*/0); - CompilerType coro_abi_type; - if (promise_type.IsVoidType()) { - coro_abi_type = ast_ctx.CreateStructForIdentifier( - ConstString(), {{"resume", coro_func_type.GetPointerType()}, - {"destroy", coro_func_type.GetPointerType()}}); - } else { - coro_abi_type = ast_ctx.CreateStructForIdentifier( - ConstString(), {{"resume", coro_func_type.GetPointerType()}, - {"destroy", coro_func_type.GetPointerType()}, - {"promise", promise_type}}); - } - return coro_abi_type; -} - bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider( ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { - ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(valobj)); - if (!ptr_sp) + lldb::addr_t frame_ptr_addr = + GetCoroFramePtrFromHandle(valobj.GetNonSyntheticValue()); + if (frame_ptr_addr == LLDB_INVALID_ADDRESS) return false; - if (!ptr_sp->GetValueAsUnsigned(0)) { + if (frame_ptr_addr == 0) { stream << "nullptr"; } else { - stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); + stream.Printf("coro frame = 0x%" PRIx64, frame_ptr_addr); } + return true; } @@ -132,32 +108,61 @@ size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: CalculateNumChildren() { - if (!m_frame_ptr_sp) + if (!m_resume_ptr_sp || !m_destroy_ptr_sp) return 0; - return m_frame_ptr_sp->GetNumChildren(); + return m_promise_ptr_sp ? 3 : 2; } lldb::ValueObjectSP lldb_private::formatters:: StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) { - if (!m_frame_ptr_sp) - return lldb::ValueObjectSP(); - - return m_frame_ptr_sp->GetChildAtIndex(idx, true); + switch (idx) { + case 0: + return m_resume_ptr_sp; + case 1: + return m_destroy_ptr_sp; + case 2: + return m_promise_ptr_sp; + } + return lldb::ValueObjectSP(); } bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: Update() { - m_frame_ptr_sp.reset(); + m_resume_ptr_sp.reset(); + m_destroy_ptr_sp.reset(); + m_promise_ptr_sp.reset(); - ValueObjectSP valobj_sp = m_backend.GetSP(); + ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue(); if (!valobj_sp) return false; - ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(m_backend)); - if (!ptr_sp) + lldb::addr_t frame_ptr_addr = GetCoroFramePtrFromHandle(valobj_sp); + if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS) + return false; + + auto ts = valobj_sp->GetCompilerType().GetTypeSystem(); + auto ast_ctx = ts.dyn_cast_or_null(); + if (!ast_ctx) return false; + // Create the `resume` and `destroy` children. + lldb::TargetSP target_sp = m_backend.GetTargetSP(); + auto &exe_ctx = m_backend.GetExecutionContextRef(); + lldb::ProcessSP process_sp = target_sp->GetProcessSP(); + auto ptr_size = process_sp->GetAddressByteSize(); + CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid); + CompilerType coro_func_type = ast_ctx->CreateFunctionType( + /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1, + /*is_variadic=*/false, /*qualifiers=*/0); + CompilerType coro_func_ptr_type = coro_func_type.GetPointerType(); + m_resume_ptr_sp = CreateValueObjectFromAddress( + "resume", frame_ptr_addr + 0 * ptr_size, exe_ctx, coro_func_ptr_type); + lldbassert(m_resume_ptr_sp); + m_destroy_ptr_sp = CreateValueObjectFromAddress( + "destroy", frame_ptr_addr + 1 * ptr_size, exe_ctx, coro_func_ptr_type); + lldbassert(m_destroy_ptr_sp); + // Get the `promise_type` from the template argument CompilerType promise_type( valobj_sp->GetCompilerType().GetTypeTemplateArgument(0)); @@ -165,12 +170,9 @@ return false; // Try to infer the promise_type if it was type-erased - auto ts = valobj_sp->GetCompilerType().GetTypeSystem(); - auto ast_ctx = ts.dyn_cast_or_null(); - if (!ast_ctx) - return false; if (promise_type.IsVoidType()) { - if (Function *destroy_func = ExtractDestroyFunction(ptr_sp)) { + if (Function *destroy_func = + ExtractDestroyFunction(target_sp, frame_ptr_addr)) { if (CompilerType inferred_type = InferPromiseType(*destroy_func)) { // Copy the type over to the correct `TypeSystemClang` instance promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type); @@ -178,10 +180,22 @@ } } - // Build the coroutine frame type - CompilerType coro_frame_type = GetCoroutineFrameType(*ast_ctx, promise_type); + // If we don't know the promise type, we don't display the `promise` member. + // `CreateValueObjectFromAddress` below would fail for `void` types. + if (promise_type.IsVoidType()) { + return false; + } - m_frame_ptr_sp = ptr_sp->Cast(coro_frame_type.GetPointerType()); + // Add the `promise` member. We intentionally add `promise` as a pointer type + // instead of a value type, and don't automatically dereference this pointer. + // We do so to avoid potential very deep recursion in case there is a cycle + // formed between `std::coroutine_handle`s and their promises. + lldb::ValueObjectSP promise = CreateValueObjectFromAddress( + "promise", frame_ptr_addr + 2 * ptr_size, exe_ctx, promise_type); + Status error; + lldb::ValueObjectSP promisePtr = promise->AddressOf(error); + if (error.Success()) + m_promise_ptr_sp = promisePtr->Clone(ConstString("promise")); return false; } @@ -193,10 +207,17 @@ size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName( ConstString name) { - if (!m_frame_ptr_sp) + if (!m_resume_ptr_sp || !m_destroy_ptr_sp) return UINT32_MAX; - return m_frame_ptr_sp->GetIndexOfChildWithName(name); + if (name == ConstString("resume")) + return 0; + if (name == ConstString("destroy")) + return 1; + if (name == ConstString("promise_ptr") && m_promise_ptr_sp) + return 2; + + return UINT32_MAX; } SyntheticChildrenFrontEnd * diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py @@ -61,7 +61,7 @@ result_children=[ ValueCheck(name="resume", summary = test_generator_func_ptr_re), ValueCheck(name="destroy", summary = test_generator_func_ptr_re), - ValueCheck(name="promise", value="-1") + ValueCheck(name="promise", dereference=ValueCheck(value="-1")) ]) # Run until after the `co_yield`