Skip to content

Commit 18171d5

Browse files
committed
iterative + brent
1 parent 3c5f78b commit 18171d5

2 files changed

Lines changed: 83 additions & 57 deletions

File tree

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,8 +733,7 @@ extern int parse_task(
733733
extern int parse_coro_chain(
734734
RemoteUnwinderObject *unwinder,
735735
uintptr_t coro_address,
736-
PyObject *render_to,
737-
size_t depth
736+
PyObject *render_to
738737
);
739738

740739
extern int parse_async_frame_chain(

Modules/_remote_debugging/asyncio.c

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,14 @@ parse_task_name(
248248
* ============================================================================ */
249249

250250
static int
251-
handle_yield_from_frame(
251+
get_awaited_coro(
252252
RemoteUnwinderObject *unwinder,
253253
uintptr_t gi_iframe_addr,
254254
uintptr_t gen_type_addr,
255-
PyObject *render_to,
256-
size_t depth
255+
uintptr_t *next_coro_addr
257256
) {
257+
*next_coro_addr = 0;
258+
258259
// Read the entire interpreter frame at once
259260
char iframe[SIZEOF_INTERP_FRAME];
260261
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
@@ -310,12 +311,7 @@ handle_yield_from_frame(
310311
doesn't match the type of whatever it points to
311312
in its cr_await.
312313
*/
313-
err = parse_coro_chain(unwinder, gi_await_addr, render_to,
314-
depth + 1);
315-
if (err) {
316-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain in yield_from");
317-
return -1;
318-
}
314+
*next_coro_addr = gi_await_addr;
319315
}
320316
}
321317
}
@@ -327,63 +323,94 @@ int
327323
parse_coro_chain(
328324
RemoteUnwinderObject *unwinder,
329325
uintptr_t coro_address,
330-
PyObject *render_to,
331-
size_t depth
326+
PyObject *render_to
332327
) {
333328
assert((void*)coro_address != NULL);
334329

335-
if (depth >= MAX_FRAME_CHAIN_DEPTH) {
336-
PyErr_SetString(PyExc_RuntimeError,
337-
"Too many coroutine frames (possible infinite loop)");
338-
set_exception_cause(unwinder, PyExc_RuntimeError,
339-
"Coroutine chain depth limit exceeded");
340-
return -1;
341-
}
330+
/* Brent's cycle detection algorithm */
331+
uintptr_t checkpoint_addr = 0;
332+
uintptr_t checkpoint_code = 0;
333+
size_t window = 1;
334+
size_t steps = 0;
342335

343-
// Read the entire generator object at once
344-
char gen_object[SIZEOF_GEN_OBJ];
345-
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
346-
&unwinder->handle,
347-
coro_address,
348-
SIZEOF_GEN_OBJ,
349-
gen_object);
350-
if (err < 0) {
351-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read generator object in coro chain");
352-
return -1;
353-
}
336+
for (size_t depth = 0; (void*)coro_address != NULL; depth++) {
337+
if (depth >= MAX_FRAME_CHAIN_DEPTH) {
338+
PyErr_SetString(PyExc_RuntimeError,
339+
"Too many coroutine frames (possible infinite loop)");
340+
set_exception_cause(unwinder, PyExc_RuntimeError,
341+
"Coroutine chain depth limit exceeded");
342+
return -1;
343+
}
354344

355-
int8_t frame_state = GET_MEMBER(int8_t, gen_object, unwinder->debug_offsets.gen_object.gi_frame_state);
356-
if (frame_state == FRAME_CLEARED) {
357-
return 0;
358-
}
345+
// Read the entire generator object at once
346+
char gen_object[SIZEOF_GEN_OBJ];
347+
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
348+
&unwinder->handle,
349+
coro_address,
350+
SIZEOF_GEN_OBJ,
351+
gen_object);
352+
if (err < 0) {
353+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read generator object in coro chain");
354+
return -1;
355+
}
359356

360-
uintptr_t gen_type_addr = GET_MEMBER(uintptr_t, gen_object, unwinder->debug_offsets.pyobject.ob_type);
357+
int8_t frame_state = GET_MEMBER(int8_t, gen_object, unwinder->debug_offsets.gen_object.gi_frame_state);
358+
if (frame_state == FRAME_CLEARED) {
359+
return 0;
360+
}
361361

362-
PyObject* name = NULL;
362+
uintptr_t gen_type_addr = GET_MEMBER(uintptr_t, gen_object, unwinder->debug_offsets.pyobject.ob_type);
363363

364-
// Parse the previous frame using the gi_iframe from local copy
365-
uintptr_t prev_frame;
366-
uintptr_t gi_iframe_addr = coro_address + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe;
367-
uintptr_t address_of_code_object = 0;
368-
if (parse_frame_object(unwinder, &name, gi_iframe_addr, &address_of_code_object, &prev_frame) < 0) {
369-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain");
370-
return -1;
371-
}
364+
PyObject* name = NULL;
372365

373-
if (!name) {
374-
return 0;
375-
}
366+
// Parse the previous frame using the gi_iframe from local copy
367+
uintptr_t prev_frame;
368+
uintptr_t gi_iframe_addr = coro_address + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe;
369+
uintptr_t address_of_code_object = 0;
370+
if (parse_frame_object(unwinder, &name, gi_iframe_addr, &address_of_code_object, &prev_frame) < 0) {
371+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain");
372+
return -1;
373+
}
376374

377-
if (PyList_Append(render_to, name)) {
375+
if (!name) {
376+
return 0;
377+
}
378+
379+
if (PyList_Append(render_to, name)) {
380+
Py_DECREF(name);
381+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");
382+
return -1;
383+
}
378384
Py_DECREF(name);
379-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");
380-
return -1;
381-
}
382-
Py_DECREF(name);
383385

384-
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
385-
return handle_yield_from_frame(unwinder, gi_iframe_addr, gen_type_addr,
386-
render_to, depth);
386+
if (coro_address == checkpoint_addr
387+
&& address_of_code_object == checkpoint_code)
388+
{
389+
PyErr_SetString(PyExc_RuntimeError,
390+
"Cycle detected in coroutine chain");
391+
set_exception_cause(unwinder, PyExc_RuntimeError,
392+
"Coroutine chain contains a cycle");
393+
return -1;
394+
}
395+
if (steps == window) {
396+
checkpoint_addr = coro_address;
397+
checkpoint_code = address_of_code_object;
398+
window *= 2;
399+
steps = 0;
400+
}
401+
steps++;
402+
403+
if (frame_state != FRAME_SUSPENDED_YIELD_FROM) {
404+
return 0;
405+
}
406+
407+
uintptr_t next_coro_addr;
408+
if (get_awaited_coro(unwinder, gi_iframe_addr, gen_type_addr,
409+
&next_coro_addr) < 0) {
410+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain in yield_from");
411+
return -1;
412+
}
413+
coro_address = next_coro_addr;
387414
}
388415

389416
return 0;
@@ -429,7 +456,7 @@ create_task_result(
429456
coro_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_coro);
430457

431458
if ((void*)coro_addr != NULL) {
432-
if (parse_coro_chain(unwinder, coro_addr, call_stack, 0) < 0) {
459+
if (parse_coro_chain(unwinder, coro_addr, call_stack) < 0) {
433460
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain");
434461
goto error;
435462
}

0 commit comments

Comments
 (0)