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