1//===-- Coroutines.cpp ----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "Coroutines.h"
10
11#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
12#include "lldb/Symbol/Function.h"
13#include "lldb/Symbol/VariableList.h"
14#include "lldb/Utility/LLDBLog.h"
15#include "lldb/Utility/Log.h"
16
17using namespace lldb;
18using namespace lldb_private;
19using namespace lldb_private::formatters;
20
21static lldb::addr_t GetCoroFramePtrFromHandle(ValueObjectSP valobj_sp) {
22 if (!valobj_sp)
23 return LLDB_INVALID_ADDRESS;
24
25 // We expect a single pointer in the `coroutine_handle` class.
26 // We don't care about its name.
27 if (valobj_sp->GetNumChildrenIgnoringErrors() != 1)
28 return LLDB_INVALID_ADDRESS;
29 ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(idx: 0));
30 if (!ptr_sp)
31 return LLDB_INVALID_ADDRESS;
32 if (!ptr_sp->GetCompilerType().IsPointerType())
33 return LLDB_INVALID_ADDRESS;
34
35 AddressType addr_type;
36 lldb::addr_t frame_ptr_addr = ptr_sp->GetPointerValue(address_type: &addr_type);
37 if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS)
38 return LLDB_INVALID_ADDRESS;
39 lldbassert(addr_type == AddressType::eAddressTypeLoad);
40 if (addr_type != AddressType::eAddressTypeLoad)
41 return LLDB_INVALID_ADDRESS;
42
43 return frame_ptr_addr;
44}
45
46static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
47 lldb::addr_t frame_ptr_addr) {
48 lldb::ProcessSP process_sp = target_sp->GetProcessSP();
49 auto ptr_size = process_sp->GetAddressByteSize();
50
51 Status error;
52 auto destroy_func_ptr_addr = frame_ptr_addr + ptr_size;
53 lldb::addr_t destroy_func_addr =
54 process_sp->ReadPointerFromMemory(vm_addr: destroy_func_ptr_addr, error);
55 if (error.Fail())
56 return nullptr;
57
58 Address destroy_func_address;
59 if (!target_sp->ResolveLoadAddress(load_addr: destroy_func_addr, so_addr&: destroy_func_address))
60 return nullptr;
61
62 return destroy_func_address.CalculateSymbolContextFunction();
63}
64
65static CompilerType InferPromiseType(Function &destroy_func) {
66 Block &block = destroy_func.GetBlock(can_create: true);
67 auto variable_list = block.GetBlockVariableList(can_create: true);
68
69 // clang generates an artificial `__promise` variable inside the
70 // `destroy` function. Look for it.
71 auto promise_var = variable_list->FindVariable(name: ConstString("__promise"));
72 if (!promise_var)
73 return {};
74 if (!promise_var->IsArtificial())
75 return {};
76
77 Type *promise_type = promise_var->GetType();
78 if (!promise_type)
79 return {};
80 return promise_type->GetForwardCompilerType();
81}
82
83bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
84 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
85 lldb::addr_t frame_ptr_addr =
86 GetCoroFramePtrFromHandle(valobj_sp: valobj.GetNonSyntheticValue());
87 if (frame_ptr_addr == LLDB_INVALID_ADDRESS)
88 return false;
89
90 if (frame_ptr_addr == 0) {
91 stream << "nullptr";
92 } else {
93 stream.Printf(format: "coro frame = 0x%" PRIx64, frame_ptr_addr);
94 }
95
96 return true;
97}
98
99lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
100 StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
101 : SyntheticChildrenFrontEnd(*valobj_sp) {
102 if (valobj_sp)
103 Update();
104}
105
106lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
107 ~StdlibCoroutineHandleSyntheticFrontEnd() = default;
108
109llvm::Expected<uint32_t> lldb_private::formatters::
110 StdlibCoroutineHandleSyntheticFrontEnd::CalculateNumChildren() {
111 if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
112 return 0;
113
114 return m_promise_ptr_sp ? 3 : 2;
115}
116
117lldb::ValueObjectSP lldb_private::formatters::
118 StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
119 switch (idx) {
120 case 0:
121 return m_resume_ptr_sp;
122 case 1:
123 return m_destroy_ptr_sp;
124 case 2:
125 return m_promise_ptr_sp;
126 }
127 return lldb::ValueObjectSP();
128}
129
130lldb::ChildCacheState
131lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
132 m_resume_ptr_sp.reset();
133 m_destroy_ptr_sp.reset();
134 m_promise_ptr_sp.reset();
135
136 ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue();
137 if (!valobj_sp)
138 return lldb::ChildCacheState::eRefetch;
139
140 lldb::addr_t frame_ptr_addr = GetCoroFramePtrFromHandle(valobj_sp);
141 if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS)
142 return lldb::ChildCacheState::eRefetch;
143
144 auto ts = valobj_sp->GetCompilerType().GetTypeSystem();
145 auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>();
146 if (!ast_ctx)
147 return lldb::ChildCacheState::eRefetch;
148
149 // Create the `resume` and `destroy` children.
150 lldb::TargetSP target_sp = m_backend.GetTargetSP();
151 auto &exe_ctx = m_backend.GetExecutionContextRef();
152 lldb::ProcessSP process_sp = target_sp->GetProcessSP();
153 auto ptr_size = process_sp->GetAddressByteSize();
154 CompilerType void_type = ast_ctx->GetBasicType(type: lldb::eBasicTypeVoid);
155 CompilerType coro_func_type = ast_ctx->CreateFunctionType(
156 /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
157 /*is_variadic=*/false, /*qualifiers=*/type_quals: 0);
158 CompilerType coro_func_ptr_type = coro_func_type.GetPointerType();
159 m_resume_ptr_sp = CreateValueObjectFromAddress(
160 name: "resume", address: frame_ptr_addr + 0 * ptr_size, exe_ctx, type: coro_func_ptr_type);
161 lldbassert(m_resume_ptr_sp);
162 m_destroy_ptr_sp = CreateValueObjectFromAddress(
163 name: "destroy", address: frame_ptr_addr + 1 * ptr_size, exe_ctx, type: coro_func_ptr_type);
164 lldbassert(m_destroy_ptr_sp);
165
166 // Get the `promise_type` from the template argument
167 CompilerType promise_type(
168 valobj_sp->GetCompilerType().GetTypeTemplateArgument(idx: 0));
169 if (!promise_type)
170 return lldb::ChildCacheState::eRefetch;
171
172 // Try to infer the promise_type if it was type-erased
173 if (promise_type.IsVoidType()) {
174 if (Function *destroy_func =
175 ExtractDestroyFunction(target_sp, frame_ptr_addr)) {
176 if (CompilerType inferred_type = InferPromiseType(destroy_func&: *destroy_func)) {
177 promise_type = inferred_type;
178 }
179 }
180 }
181
182 // If we don't know the promise type, we don't display the `promise` member.
183 // `CreateValueObjectFromAddress` below would fail for `void` types.
184 if (promise_type.IsVoidType()) {
185 return lldb::ChildCacheState::eRefetch;
186 }
187
188 // Add the `promise` member. We intentionally add `promise` as a pointer type
189 // instead of a value type, and don't automatically dereference this pointer.
190 // We do so to avoid potential very deep recursion in case there is a cycle
191 // formed between `std::coroutine_handle`s and their promises.
192 lldb::ValueObjectSP promise = CreateValueObjectFromAddress(
193 name: "promise", address: frame_ptr_addr + 2 * ptr_size, exe_ctx, type: promise_type);
194 Status error;
195 lldb::ValueObjectSP promisePtr = promise->AddressOf(error);
196 if (error.Success())
197 m_promise_ptr_sp = promisePtr->Clone(new_name: ConstString("promise"));
198
199 return lldb::ChildCacheState::eRefetch;
200}
201
202bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
203 MightHaveChildren() {
204 return true;
205}
206
207size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
208 ConstString name) {
209 if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
210 return UINT32_MAX;
211
212 if (name == ConstString("resume"))
213 return 0;
214 if (name == ConstString("destroy"))
215 return 1;
216 if (name == ConstString("promise_ptr") && m_promise_ptr_sp)
217 return 2;
218
219 return UINT32_MAX;
220}
221
222SyntheticChildrenFrontEnd *
223lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator(
224 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
225 return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp)
226 : nullptr);
227}
228

source code of lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp