xref: /llvm-project/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp (revision cd3091a88f7c55c90d9b5fff372ce1cdfc71948d)
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/ExpressionParser/Clang/ClangASTImporter.h"
12 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
13 #include "lldb/Symbol/Function.h"
14 #include "lldb/Symbol/VariableList.h"
15 
16 using namespace lldb;
17 using namespace lldb_private;
18 using namespace lldb_private::formatters;
19 
20 static lldb::addr_t GetCoroFramePtrFromHandle(ValueObjectSP valobj_sp) {
21   if (!valobj_sp)
22     return LLDB_INVALID_ADDRESS;
23 
24   // We expect a single pointer in the `coroutine_handle` class.
25   // We don't care about its name.
26   if (valobj_sp->GetNumChildren() != 1)
27     return LLDB_INVALID_ADDRESS;
28   ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true));
29   if (!ptr_sp)
30     return LLDB_INVALID_ADDRESS;
31   if (!ptr_sp->GetCompilerType().IsPointerType())
32     return LLDB_INVALID_ADDRESS;
33 
34   AddressType addr_type;
35   lldb::addr_t frame_ptr_addr = ptr_sp->GetPointerValue(&addr_type);
36   if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS)
37     return LLDB_INVALID_ADDRESS;
38   lldbassert(addr_type == AddressType::eAddressTypeLoad);
39   if (addr_type != AddressType::eAddressTypeLoad)
40     return LLDB_INVALID_ADDRESS;
41 
42   return frame_ptr_addr;
43 }
44 
45 static Function *ExtractFunction(lldb::TargetSP target_sp,
46                                  lldb::addr_t frame_ptr_addr, int offset) {
47   lldb::ProcessSP process_sp = target_sp->GetProcessSP();
48   auto ptr_size = process_sp->GetAddressByteSize();
49 
50   Status error;
51   auto func_ptr_addr = frame_ptr_addr + offset * ptr_size;
52   lldb::addr_t func_addr =
53       process_sp->ReadPointerFromMemory(func_ptr_addr, error);
54   if (error.Fail())
55     return nullptr;
56 
57   Address func_address;
58   if (!target_sp->ResolveLoadAddress(func_addr, func_address))
59     return nullptr;
60 
61   return func_address.CalculateSymbolContextFunction();
62 }
63 
64 static Function *ExtractResumeFunction(lldb::TargetSP target_sp,
65                                        lldb::addr_t frame_ptr_addr) {
66   return ExtractFunction(target_sp, frame_ptr_addr, 0);
67 }
68 
69 static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
70                                         lldb::addr_t frame_ptr_addr) {
71   return ExtractFunction(target_sp, frame_ptr_addr, 1);
72 }
73 
74 static bool IsNoopCoroFunction(Function *f) {
75   if (!f)
76     return false;
77 
78   // clang's `__builtin_coro_noop` gets lowered to
79   // `_NoopCoro_ResumeDestroy`. This is used by libc++
80   // on clang.
81   auto mangledName = f->GetMangled().GetMangledName();
82   if (mangledName == "__NoopCoro_ResumeDestroy")
83     return true;
84 
85   // libc++ uses the following name as a fallback on
86   // compilers without `__builtin_coro_noop`.
87   auto name = f->GetNameNoArguments();
88   static RegularExpression libcxxRegex(
89       "^std::coroutine_handle<std::noop_coroutine_promise>::"
90       "__noop_coroutine_frame_ty_::__dummy_resume_destroy_func$");
91   lldbassert(libcxxRegex.IsValid());
92   if (libcxxRegex.Execute(name.GetStringRef()))
93     return true;
94   static RegularExpression libcxxRegexAbiNS(
95       "^std::__[[:alnum:]]+::coroutine_handle<std::__[[:alnum:]]+::"
96       "noop_coroutine_promise>::__noop_coroutine_frame_ty_::"
97       "__dummy_resume_destroy_func$");
98   lldbassert(libcxxRegexAbiNS.IsValid());
99   if (libcxxRegexAbiNS.Execute(name.GetStringRef()))
100     return true;
101 
102   // libstdc++ uses the following name on both gcc and clang.
103   static RegularExpression libstdcppRegex(
104       "^std::__[[:alnum:]]+::coroutine_handle<std::__[[:alnum:]]+::"
105       "noop_coroutine_promise>::__frame::__dummy_resume_destroy$");
106   lldbassert(libstdcppRegex.IsValid());
107   if (libstdcppRegex.Execute(name.GetStringRef()))
108     return true;
109 
110   return false;
111 }
112 
113 static CompilerType InferPromiseType(Function &destroy_func) {
114   Block &block = destroy_func.GetBlock(true);
115   auto variable_list = block.GetBlockVariableList(true);
116 
117   // clang generates an artificial `__promise` variable inside the
118   // `destroy` function. Look for it.
119   auto promise_var = variable_list->FindVariable(ConstString("__promise"));
120   if (!promise_var)
121     return {};
122   if (!promise_var->IsArtificial())
123     return {};
124 
125   Type *promise_type = promise_var->GetType();
126   if (!promise_type)
127     return {};
128   return promise_type->GetForwardCompilerType();
129 }
130 
131 bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
132     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
133   lldb::addr_t frame_ptr_addr =
134       GetCoroFramePtrFromHandle(valobj.GetNonSyntheticValue());
135   if (frame_ptr_addr == LLDB_INVALID_ADDRESS)
136     return false;
137 
138   if (frame_ptr_addr == 0) {
139     stream << "nullptr";
140     return true;
141   }
142 
143   lldb::TargetSP target_sp = valobj.GetTargetSP();
144   if (IsNoopCoroFunction(ExtractResumeFunction(target_sp, frame_ptr_addr)) &&
145       IsNoopCoroFunction(ExtractDestroyFunction(target_sp, frame_ptr_addr))) {
146     stream << "noop_coroutine";
147     return true;
148   }
149 
150   stream.Printf("coro frame = 0x%" PRIx64, frame_ptr_addr);
151   return true;
152 }
153 
154 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
155     StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
156     : SyntheticChildrenFrontEnd(*valobj_sp),
157       m_ast_importer(std::make_unique<ClangASTImporter>()) {
158   if (valobj_sp)
159     Update();
160 }
161 
162 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
163     ~StdlibCoroutineHandleSyntheticFrontEnd() = default;
164 
165 size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
166     CalculateNumChildren() {
167   if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
168     return 0;
169 
170   return m_promise_ptr_sp ? 3 : 2;
171 }
172 
173 lldb::ValueObjectSP lldb_private::formatters::
174     StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) {
175   switch (idx) {
176   case 0:
177     return m_resume_ptr_sp;
178   case 1:
179     return m_destroy_ptr_sp;
180   case 2:
181     return m_promise_ptr_sp;
182   }
183   return lldb::ValueObjectSP();
184 }
185 
186 bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
187     Update() {
188   m_resume_ptr_sp.reset();
189   m_destroy_ptr_sp.reset();
190   m_promise_ptr_sp.reset();
191 
192   ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue();
193   if (!valobj_sp)
194     return false;
195 
196   lldb::addr_t frame_ptr_addr = GetCoroFramePtrFromHandle(valobj_sp);
197   if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS)
198     return false;
199 
200   lldb::TargetSP target_sp = m_backend.GetTargetSP();
201   Function *resume_func = ExtractResumeFunction(target_sp, frame_ptr_addr);
202   Function *destroy_func = ExtractDestroyFunction(target_sp, frame_ptr_addr);
203 
204   // For `std::noop_coroutine()`, we don't want to display any child nodes.
205   if (IsNoopCoroFunction(resume_func) && IsNoopCoroFunction(destroy_func))
206     return false;
207 
208   auto ts = valobj_sp->GetCompilerType().GetTypeSystem();
209   auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>();
210   if (!ast_ctx)
211     return {};
212 
213   // Create the `resume` and `destroy` children
214   auto &exe_ctx = m_backend.GetExecutionContextRef();
215   lldb::ProcessSP process_sp = target_sp->GetProcessSP();
216   auto ptr_size = process_sp->GetAddressByteSize();
217   CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
218   CompilerType coro_func_type = ast_ctx->CreateFunctionType(
219       /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
220       /*is_variadic=*/false, /*qualifiers=*/0);
221   CompilerType coro_func_ptr_type = coro_func_type.GetPointerType();
222   m_resume_ptr_sp = CreateValueObjectFromAddress(
223       "resume", frame_ptr_addr + 0 * ptr_size, exe_ctx, coro_func_ptr_type);
224   lldbassert(m_resume_ptr_sp);
225   m_destroy_ptr_sp = CreateValueObjectFromAddress(
226       "destroy", frame_ptr_addr + 1 * ptr_size, exe_ctx, coro_func_ptr_type);
227   lldbassert(m_destroy_ptr_sp);
228 
229   // Get the `promise_type` from the template argument
230   CompilerType promise_type(
231       valobj_sp->GetCompilerType().GetTypeTemplateArgument(0));
232   if (!promise_type)
233     return false;
234 
235   // Try to infer the promise_type if it was type-erased
236   if (promise_type.IsVoidType() && destroy_func) {
237     if (CompilerType inferred_type = InferPromiseType(*destroy_func)) {
238       // Copy the type over to the correct `TypeSystemClang` instance
239       promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type);
240     }
241   }
242 
243   // Add the `promise` member. We intentionally add `promise` as a pointer type
244   // instead of a value type, and don't automatically dereference this pointer.
245   // We do so to avoid potential very deep recursion in case there is a cycle in
246   // formed between `std::coroutine_handle`s and their promises.
247   lldb::ValueObjectSP promise = CreateValueObjectFromAddress(
248       "promise", frame_ptr_addr + 2 * ptr_size, exe_ctx, promise_type);
249   Status error;
250   lldb::ValueObjectSP promisePtr = promise->AddressOf(error);
251   if (error.Success())
252     m_promise_ptr_sp = promisePtr->Clone(ConstString("promise"));
253 
254   return false;
255 }
256 
257 bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
258     MightHaveChildren() {
259   return true;
260 }
261 
262 size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
263     ConstString name) {
264   if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
265     return UINT32_MAX;
266 
267   if (name == ConstString("resume"))
268     return 0;
269   if (name == ConstString("destroy"))
270     return 1;
271   if (name == ConstString("promise_ptr") && m_promise_ptr_sp)
272     return 2;
273 
274   return UINT32_MAX;
275 }
276 
277 SyntheticChildrenFrontEnd *
278 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator(
279     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
280   return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp)
281                     : nullptr);
282 }
283