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