Skip to content

Commit cd3091a

Browse files
committed
[LLDB] Do not dereference promise pointer in coroutine_handle pretty printer
So far, the pretty printer for `std::coroutine_handle` internally dereferenced the contained frame pointer displayed the `promise` as a sub-value. As noticed in https://reviews.llvm.org/D132624 by @labath, this can lead to an endless loop in lldb during printing if the coroutine frame pointers form a cycle. This commit breaks the cycle by exposing the `promise` as a pointer type instead of a value type. The depth to which the `frame variable` and the `expression` commands dereference those pointers can be controlled using the `--ptr-depth` argument. Differential Revision: https://reviews.llvm.org/D132815
1 parent 53b674e commit cd3091a

File tree

4 files changed

+105
-74
lines changed

4 files changed

+105
-74
lines changed

lldb/packages/Python/lldbsuite/test/lldbtest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def which(program):
246246

247247
class ValueCheck:
248248
def __init__(self, name=None, value=None, type=None, summary=None,
249-
children=None):
249+
children=None, dereference=None):
250250
"""
251251
:param name: The name that the SBValue should have. None if the summary
252252
should not be checked.
@@ -261,12 +261,15 @@ def __init__(self, name=None, value=None, type=None, summary=None,
261261
The order of checks is the order of the checks in the
262262
list. The number of checks has to match the number of
263263
children.
264+
:param dereference: A ValueCheck for the SBValue returned by the
265+
`Dereference` function.
264266
"""
265267
self.expect_name = name
266268
self.expect_value = value
267269
self.expect_type = type
268270
self.expect_summary = summary
269271
self.children = children
272+
self.dereference = dereference
270273

271274
def check_value(self, test_base, val, error_msg=None):
272275
"""
@@ -308,6 +311,9 @@ def check_value(self, test_base, val, error_msg=None):
308311
if self.children is not None:
309312
self.check_value_children(test_base, val, error_msg)
310313

314+
if self.dereference is not None:
315+
self.dereference.check_value(test_base, val.Dereference(), error_msg)
316+
311317
def check_value_children(self, test_base, val, error_msg=None):
312318
"""
313319
Checks that the children of a SBValue match a certain structure and

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

Lines changed: 94 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,35 @@ using namespace lldb;
1717
using namespace lldb_private;
1818
using namespace lldb_private::formatters;
1919

20-
static ValueObjectSP GetCoroFramePtrFromHandle(ValueObject &valobj) {
21-
ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
20+
static lldb::addr_t GetCoroFramePtrFromHandle(ValueObjectSP valobj_sp) {
2221
if (!valobj_sp)
23-
return nullptr;
22+
return LLDB_INVALID_ADDRESS;
2423

2524
// We expect a single pointer in the `coroutine_handle` class.
2625
// We don't care about its name.
2726
if (valobj_sp->GetNumChildren() != 1)
28-
return nullptr;
27+
return LLDB_INVALID_ADDRESS;
2928
ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true));
3029
if (!ptr_sp)
31-
return nullptr;
30+
return LLDB_INVALID_ADDRESS;
3231
if (!ptr_sp->GetCompilerType().IsPointerType())
33-
return nullptr;
34-
35-
return ptr_sp;
36-
}
37-
38-
static Function *ExtractFunction(ValueObjectSP &frame_ptr_sp, int offset) {
39-
lldb::TargetSP target_sp = frame_ptr_sp->GetTargetSP();
40-
lldb::ProcessSP process_sp = frame_ptr_sp->GetProcessSP();
41-
auto ptr_size = process_sp->GetAddressByteSize();
32+
return LLDB_INVALID_ADDRESS;
4233

4334
AddressType addr_type;
44-
lldb::addr_t frame_ptr_addr = frame_ptr_sp->GetPointerValue(&addr_type);
35+
lldb::addr_t frame_ptr_addr = ptr_sp->GetPointerValue(&addr_type);
4536
if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS)
46-
return nullptr;
37+
return LLDB_INVALID_ADDRESS;
4738
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();
4849

4950
Status error;
5051
auto func_ptr_addr = frame_ptr_addr + offset * ptr_size;
@@ -60,12 +61,14 @@ static Function *ExtractFunction(ValueObjectSP &frame_ptr_sp, int offset) {
6061
return func_address.CalculateSymbolContextFunction();
6162
}
6263

63-
static Function *ExtractResumeFunction(ValueObjectSP &frame_ptr_sp) {
64-
return ExtractFunction(frame_ptr_sp, 0);
64+
static Function *ExtractResumeFunction(lldb::TargetSP target_sp,
65+
lldb::addr_t frame_ptr_addr) {
66+
return ExtractFunction(target_sp, frame_ptr_addr, 0);
6567
}
6668

67-
static Function *ExtractDestroyFunction(ValueObjectSP &frame_ptr_sp) {
68-
return ExtractFunction(frame_ptr_sp, 1);
69+
static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
70+
lldb::addr_t frame_ptr_addr) {
71+
return ExtractFunction(target_sp, frame_ptr_addr, 1);
6972
}
7073

7174
static bool IsNoopCoroFunction(Function *f) {
@@ -125,43 +128,26 @@ static CompilerType InferPromiseType(Function &destroy_func) {
125128
return promise_type->GetForwardCompilerType();
126129
}
127130

128-
static CompilerType GetCoroutineFrameType(TypeSystemClang &ast_ctx,
129-
CompilerType promise_type) {
130-
CompilerType void_type = ast_ctx.GetBasicType(lldb::eBasicTypeVoid);
131-
CompilerType coro_func_type = ast_ctx.CreateFunctionType(
132-
/*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
133-
/*is_variadic=*/false, /*qualifiers=*/0);
134-
CompilerType coro_abi_type;
135-
if (promise_type.IsVoidType()) {
136-
coro_abi_type = ast_ctx.CreateStructForIdentifier(
137-
ConstString(), {{"resume", coro_func_type.GetPointerType()},
138-
{"destroy", coro_func_type.GetPointerType()}});
139-
} else {
140-
coro_abi_type = ast_ctx.CreateStructForIdentifier(
141-
ConstString(), {{"resume", coro_func_type.GetPointerType()},
142-
{"destroy", coro_func_type.GetPointerType()},
143-
{"promise", promise_type}});
144-
}
145-
return coro_abi_type;
146-
}
147-
148131
bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
149132
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
150-
ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(valobj));
151-
if (!ptr_sp)
133+
lldb::addr_t frame_ptr_addr =
134+
GetCoroFramePtrFromHandle(valobj.GetNonSyntheticValue());
135+
if (frame_ptr_addr == LLDB_INVALID_ADDRESS)
152136
return false;
153137

154-
if (!ptr_sp->GetValueAsUnsigned(0)) {
138+
if (frame_ptr_addr == 0) {
155139
stream << "nullptr";
156140
return true;
157141
}
158-
if (IsNoopCoroFunction(ExtractResumeFunction(ptr_sp)) &&
159-
IsNoopCoroFunction(ExtractDestroyFunction(ptr_sp))) {
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))) {
160146
stream << "noop_coroutine";
161147
return true;
162148
}
163149

164-
stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0));
150+
stream.Printf("coro frame = 0x%" PRIx64, frame_ptr_addr);
165151
return true;
166152
}
167153

@@ -178,39 +164,67 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
178164

179165
size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
180166
CalculateNumChildren() {
181-
if (!m_frame_ptr_sp)
167+
if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
182168
return 0;
183169

184-
return m_frame_ptr_sp->GetNumChildren();
170+
return m_promise_ptr_sp ? 3 : 2;
185171
}
186172

187173
lldb::ValueObjectSP lldb_private::formatters::
188174
StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) {
189-
if (!m_frame_ptr_sp)
190-
return lldb::ValueObjectSP();
191-
192-
return m_frame_ptr_sp->GetChildAtIndex(idx, true);
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();
193184
}
194185

195186
bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
196187
Update() {
197-
m_frame_ptr_sp.reset();
188+
m_resume_ptr_sp.reset();
189+
m_destroy_ptr_sp.reset();
190+
m_promise_ptr_sp.reset();
198191

199-
ValueObjectSP valobj_sp = m_backend.GetSP();
192+
ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue();
200193
if (!valobj_sp)
201194
return false;
202195

203-
ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(m_backend));
204-
if (!ptr_sp)
196+
lldb::addr_t frame_ptr_addr = GetCoroFramePtrFromHandle(valobj_sp);
197+
if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS)
205198
return false;
206199

207-
Function *resume_func = ExtractResumeFunction(ptr_sp);
208-
Function *destroy_func = ExtractDestroyFunction(ptr_sp);
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);
209203

210-
if (IsNoopCoroFunction(resume_func) && IsNoopCoroFunction(destroy_func)) {
211-
// For `std::noop_coroutine()`, we don't want to display any child nodes.
204+
// For `std::noop_coroutine()`, we don't want to display any child nodes.
205+
if (IsNoopCoroFunction(resume_func) && IsNoopCoroFunction(destroy_func))
212206
return false;
213-
}
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);
214228

215229
// Get the `promise_type` from the template argument
216230
CompilerType promise_type(
@@ -219,21 +233,23 @@ bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
219233
return false;
220234

221235
// Try to infer the promise_type if it was type-erased
222-
auto ts = valobj_sp->GetCompilerType().GetTypeSystem();
223-
auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>();
224-
if (!ast_ctx)
225-
return false;
226236
if (promise_type.IsVoidType() && destroy_func) {
227237
if (CompilerType inferred_type = InferPromiseType(*destroy_func)) {
228238
// Copy the type over to the correct `TypeSystemClang` instance
229239
promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type);
230240
}
231241
}
232242

233-
// Build the coroutine frame type
234-
CompilerType coro_frame_type = GetCoroutineFrameType(*ast_ctx, promise_type);
235-
236-
m_frame_ptr_sp = ptr_sp->Cast(coro_frame_type.GetPointerType());
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"));
237253

238254
return false;
239255
}
@@ -245,10 +261,17 @@ bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
245261

246262
size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
247263
ConstString name) {
248-
if (!m_frame_ptr_sp)
264+
if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
249265
return UINT32_MAX;
250266

251-
return m_frame_ptr_sp->GetIndexOfChildWithName(name);
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;
252275
}
253276

254277
SyntheticChildrenFrontEnd *

lldb/source/Plugins/Language/CPlusPlus/Coroutines.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ class StdlibCoroutineHandleSyntheticFrontEnd
4747
size_t GetIndexOfChildWithName(ConstString name) override;
4848

4949
private:
50-
lldb::ValueObjectSP m_frame_ptr_sp;
50+
lldb::ValueObjectSP m_resume_ptr_sp;
51+
lldb::ValueObjectSP m_destroy_ptr_sp;
52+
lldb::ValueObjectSP m_promise_ptr_sp;
5153
std::unique_ptr<lldb_private::ClangASTImporter> m_ast_importer;
5254
};
5355

lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def do_test(self, stdlib_type):
6868
result_children=[
6969
ValueCheck(name="resume", summary = test_generator_func_ptr_re),
7070
ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
71-
ValueCheck(name="promise", value="-1")
71+
ValueCheck(name="promise", dereference=ValueCheck(value="-1"))
7272
])
7373

7474
# Run until after the `co_yield`

0 commit comments

Comments
 (0)