diff options
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_asynciomodule.c | 2 | ||||
-rw-r--r-- | Modules/_datetimemodule.c | 2 | ||||
-rw-r--r-- | Modules/_functoolsmodule.c | 192 | ||||
-rw-r--r-- | Modules/_hashopenssl.c | 21 | ||||
-rw-r--r-- | Modules/_remote_debugging_module.c | 1012 | ||||
-rw-r--r-- | Modules/_ssl.c | 208 | ||||
-rw-r--r-- | Modules/_testbuffer.c | 3 | ||||
-rw-r--r-- | Modules/_testcapi/monitoring.c | 2 | ||||
-rw-r--r-- | Modules/_testcapi/time.c | 3 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 3 | ||||
-rw-r--r-- | Modules/_testinternalcapi.c | 3 | ||||
-rw-r--r-- | Modules/_threadmodule.c | 2 | ||||
-rw-r--r-- | Modules/clinic/_remote_debugging_module.c.h | 47 | ||||
-rw-r--r-- | Modules/clinic/_ssl.c.h | 116 | ||||
-rw-r--r-- | Modules/clinic/mathmodule.c.h | 108 | ||||
-rw-r--r-- | Modules/mathmodule.c | 36 |
16 files changed, 1152 insertions, 608 deletions
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 5f9181395c4..99408e60721 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -821,7 +821,7 @@ future_add_done_callback(asyncio_state *state, FutureObj *fut, PyObject *arg, Invariants: * callbacks != NULL: - There are some callbacks in in the list. Just + There are some callbacks in the list. Just add the new callback to it. * callbacks == NULL and callback0 == NULL: diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb90be81c8d..7a6426593d0 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1934,7 +1934,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, } replacement = freplacement; } -#ifdef Py_NORMALIZE_CENTURY +#ifdef _Py_NORMALIZE_CENTURY else if (ch == 'Y' || ch == 'G' || ch == 'F' || ch == 'C' ) { diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index d3dabd58b89..1c888295cb0 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -367,19 +367,6 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type) return PyMethod_New(self, obj); } -/* Merging keyword arguments using the vectorcall convention is messy, so - * if we would need to do that, we stop using vectorcall and fall back - * to using partial_call() instead. */ -Py_NO_INLINE static PyObject * -partial_vectorcall_fallback(PyThreadState *tstate, partialobject *pto, - PyObject *const *args, size_t nargsf, - PyObject *kwnames) -{ - pto->vectorcall = NULL; - Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - return _PyObject_MakeTpCall(tstate, (PyObject *)pto, args, nargs, kwnames); -} - static PyObject * partial_vectorcall(PyObject *self, PyObject *const *args, size_t nargsf, PyObject *kwnames) @@ -388,10 +375,7 @@ partial_vectorcall(PyObject *self, PyObject *const *args, PyThreadState *tstate = _PyThreadState_GET(); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - /* pto->kw is mutable, so need to check every time */ - if (PyDict_GET_SIZE(pto->kw)) { - return partial_vectorcall_fallback(tstate, pto, args, nargsf, kwnames); - } + /* Placeholder check */ Py_ssize_t pto_phcount = pto->phcount; if (nargs < pto_phcount) { PyErr_Format(PyExc_TypeError, @@ -400,50 +384,143 @@ partial_vectorcall(PyObject *self, PyObject *const *args, return NULL; } - Py_ssize_t nargskw = nargs; - if (kwnames != NULL) { - nargskw += PyTuple_GET_SIZE(kwnames); - } - PyObject **pto_args = _PyTuple_ITEMS(pto->args); Py_ssize_t pto_nargs = PyTuple_GET_SIZE(pto->args); + Py_ssize_t pto_nkwds = PyDict_GET_SIZE(pto->kw); + Py_ssize_t nkwds = kwnames == NULL ? 0 : PyTuple_GET_SIZE(kwnames); + Py_ssize_t nargskw = nargs + nkwds; + + /* Special cases */ + if (!pto_nkwds) { + /* Fast path if we're called without arguments */ + if (nargskw == 0) { + return _PyObject_VectorcallTstate(tstate, pto->fn, pto_args, + pto_nargs, NULL); + } - /* Fast path if we're called without arguments */ - if (nargskw == 0) { - return _PyObject_VectorcallTstate(tstate, pto->fn, - pto_args, pto_nargs, NULL); + /* Use PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single + * positional argument. */ + if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) { + PyObject **newargs = (PyObject **)args - 1; + PyObject *tmp = newargs[0]; + newargs[0] = pto_args[0]; + PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, newargs, + nargs + 1, kwnames); + newargs[0] = tmp; + return ret; + } } - /* Fast path using PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single - * positional argument */ - if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) { - PyObject **newargs = (PyObject **)args - 1; - PyObject *tmp = newargs[0]; - newargs[0] = pto_args[0]; - PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, - newargs, nargs + 1, kwnames); - newargs[0] = tmp; - return ret; - } + /* Total sizes */ + Py_ssize_t tot_nargs = pto_nargs + nargs - pto_phcount; + Py_ssize_t tot_nkwds = pto_nkwds + nkwds; + Py_ssize_t tot_nargskw = tot_nargs + tot_nkwds; - PyObject *small_stack[_PY_FASTCALL_SMALL_STACK]; - PyObject **stack; + PyObject *pto_kw_merged = NULL; // pto_kw with duplicates merged (if any) + PyObject *tot_kwnames; - Py_ssize_t tot_nargskw = pto_nargs + nargskw - pto_phcount; - if (tot_nargskw <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) { + /* Allocate Stack + * Note, _PY_FASTCALL_SMALL_STACK is optimal for positional only + * This case might have keyword arguments + * furthermore, it might use extra stack space for temporary key storage + * thus, double small_stack size is used, which is 10 * 8 = 80 bytes */ + PyObject *small_stack[_PY_FASTCALL_SMALL_STACK * 2]; + PyObject **tmp_stack, **stack; + Py_ssize_t init_stack_size = tot_nargskw; + if (pto_nkwds) { + // If pto_nkwds, allocate additional space for temporary new keys + init_stack_size += nkwds; + } + if (init_stack_size <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) { stack = small_stack; } else { - stack = PyMem_Malloc(tot_nargskw * sizeof(PyObject *)); + stack = PyMem_Malloc(init_stack_size * sizeof(PyObject *)); if (stack == NULL) { - PyErr_NoMemory(); - return NULL; + return PyErr_NoMemory(); } } - Py_ssize_t tot_nargs; + /* Copy keywords to stack */ + if (!pto_nkwds) { + tot_kwnames = kwnames; + if (nkwds) { + /* if !pto_nkwds & nkwds, then simply append kw */ + memcpy(stack + tot_nargs, args + nargs, nkwds * sizeof(PyObject*)); + } + } + else { + /* stack is now [<positionals>, <pto_kwds>, <kwds>, <kwds_keys>] + * Will resize later to [<positionals>, <merged_kwds>] */ + PyObject *key, *val; + + /* Merge kw to pto_kw or add to tail (if not duplicate) */ + Py_ssize_t n_tail = 0; + for (Py_ssize_t i = 0; i < nkwds; ++i) { + key = PyTuple_GET_ITEM(kwnames, i); + val = args[nargs + i]; + if (PyDict_Contains(pto->kw, key)) { + if (pto_kw_merged == NULL) { + pto_kw_merged = PyDict_Copy(pto->kw); + if (pto_kw_merged == NULL) { + goto error; + } + } + if (PyDict_SetItem(pto_kw_merged, key, val) < 0) { + Py_DECREF(pto_kw_merged); + goto error; + } + } + else { + /* Copy keyword tail to stack */ + stack[tot_nargs + pto_nkwds + n_tail] = val; + stack[tot_nargskw + n_tail] = key; + n_tail++; + } + } + Py_ssize_t n_merges = nkwds - n_tail; + + /* Create total kwnames */ + tot_kwnames = PyTuple_New(tot_nkwds - n_merges); + if (tot_kwnames == NULL) { + Py_XDECREF(pto_kw_merged); + goto error; + } + for (Py_ssize_t i = 0; i < n_tail; ++i) { + key = Py_NewRef(stack[tot_nargskw + i]); + PyTuple_SET_ITEM(tot_kwnames, pto_nkwds + i, key); + } + + /* Copy pto_keywords with overlapping call keywords merged + * Note, tail is already coppied. */ + Py_ssize_t pos = 0, i = 0; + while (PyDict_Next(n_merges ? pto_kw_merged : pto->kw, &pos, &key, &val)) { + assert(i < pto_nkwds); + PyTuple_SET_ITEM(tot_kwnames, i, Py_NewRef(key)); + stack[tot_nargs + i] = val; + i++; + } + assert(i == pto_nkwds); + Py_XDECREF(pto_kw_merged); + + /* Resize Stack if the removing overallocation saves some noticable memory + * NOTE: This whole block can be removed without breaking anything */ + Py_ssize_t noveralloc = n_merges + nkwds; + if (stack != small_stack && noveralloc > 6 && noveralloc > init_stack_size / 10) { + tmp_stack = PyMem_Realloc(stack, (tot_nargskw - n_merges) * sizeof(PyObject *)); + if (tmp_stack == NULL) { + Py_DECREF(tot_kwnames); + if (stack != small_stack) { + PyMem_Free(stack); + } + return PyErr_NoMemory(); + } + stack = tmp_stack; + } + } + + /* Copy Positionals to stack */ if (pto_phcount) { - tot_nargs = pto_nargs + nargs - pto_phcount; Py_ssize_t j = 0; // New args index for (Py_ssize_t i = 0; i < pto_nargs; i++) { if (pto_args[i] == pto->placeholder) { @@ -455,22 +532,31 @@ partial_vectorcall(PyObject *self, PyObject *const *args, } } assert(j == pto_phcount); - if (nargskw > pto_phcount) { - memcpy(stack + pto_nargs, args + j, (nargskw - j) * sizeof(PyObject*)); + /* Add remaining args from new_args */ + if (nargs > pto_phcount) { + memcpy(stack + pto_nargs, args + j, (nargs - j) * sizeof(PyObject*)); } } else { - tot_nargs = pto_nargs + nargs; - /* Copy to new stack, using borrowed references */ memcpy(stack, pto_args, pto_nargs * sizeof(PyObject*)); - memcpy(stack + pto_nargs, args, nargskw * sizeof(PyObject*)); + memcpy(stack + pto_nargs, args, nargs * sizeof(PyObject*)); } - PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, - stack, tot_nargs, kwnames); + + PyObject *ret = _PyObject_VectorcallTstate(tstate, pto->fn, stack, + tot_nargs, tot_kwnames); if (stack != small_stack) { PyMem_Free(stack); } + if (pto_nkwds) { + Py_DECREF(tot_kwnames); + } return ret; + + error: + if (stack != small_stack) { + PyMem_Free(stack); + } + return NULL; } /* Set pto->vectorcall depending on the parameters of the partial object */ diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 90a7391ebb0..1a6c831e483 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -64,11 +64,15 @@ #define PY_EVP_MD_fetch(algorithm, properties) EVP_MD_fetch(NULL, algorithm, properties) #define PY_EVP_MD_up_ref(md) EVP_MD_up_ref(md) #define PY_EVP_MD_free(md) EVP_MD_free(md) + +#define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_get0_md(CTX) #else #define PY_EVP_MD const EVP_MD #define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm) #define PY_EVP_MD_up_ref(md) do {} while(0) #define PY_EVP_MD_free(md) do {} while(0) + +#define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_md(CTX) #endif /* hash alias map and fast lookup @@ -308,6 +312,14 @@ class _hashlib.HMAC "HMACobject *" "&PyType_Type" /* LCOV_EXCL_START */ +/* Thin wrapper around ERR_reason_error_string() returning non-NULL text. */ +static const char * +py_wrapper_ERR_reason_error_string(unsigned long errcode) +{ + const char *reason = ERR_reason_error_string(errcode); + return reason ? reason : "no reason"; +} + /* Set an exception of given type using the given OpenSSL error code. */ static void set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) @@ -317,8 +329,13 @@ set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) /* ERR_ERROR_STRING(3) ensures that the messages below are ASCII */ const char *lib = ERR_lib_error_string(errcode); +#ifdef Py_HAS_OPENSSL3_SUPPORT + // Since OpenSSL 3.0, ERR_func_error_string() always returns NULL. + const char *func = NULL; +#else const char *func = ERR_func_error_string(errcode); - const char *reason = ERR_reason_error_string(errcode); +#endif + const char *reason = py_wrapper_ERR_reason_error_string(errcode); if (lib && func) { PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason); @@ -838,7 +855,7 @@ static PyObject * _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) { HASHobject *self = HASHobject_CAST(op); - const EVP_MD *md = EVP_MD_CTX_md(self->ctx); + const EVP_MD *md = PY_EVP_MD_CTX_md(self->ctx); if (md == NULL) { notify_ssl_error_occurred("missing EVP_MD for HASH context"); return NULL; diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index ce7189637c2..d72031137e0 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -257,20 +257,25 @@ is_frame_valid( uintptr_t code_object_addr ); -static int -parse_tasks_in_set( +typedef int (*thread_processor_func)( RemoteUnwinderObject *unwinder, - uintptr_t set_addr, - PyObject *awaited_by, - int recurse_task + uintptr_t thread_state_addr, + unsigned long tid, + void *context +); + +typedef int (*set_entry_processor_func)( + RemoteUnwinderObject *unwinder, + uintptr_t key_addr, + void *context ); + static int parse_task( RemoteUnwinderObject *unwinder, uintptr_t task_address, - PyObject *render_to, - int recurse_task + PyObject *render_to ); static int @@ -285,9 +290,27 @@ static int parse_frame_object( RemoteUnwinderObject *unwinder, PyObject** result, uintptr_t address, + uintptr_t* address_of_code_object, uintptr_t* previous_frame ); +static int +parse_async_frame_chain( + RemoteUnwinderObject *unwinder, + PyObject *calls, + uintptr_t address_of_thread, + uintptr_t running_task_code_obj +); + +static int read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr); +static int read_Py_ssize_t(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t *size); + +static int process_task_and_waiters(RemoteUnwinderObject *unwinder, uintptr_t task_addr, PyObject *result); +static int process_task_awaited_by(RemoteUnwinderObject *unwinder, uintptr_t task_address, set_entry_processor_func processor, void *context); +static int find_running_task_in_thread(RemoteUnwinderObject *unwinder, uintptr_t thread_state_addr, uintptr_t *running_task_addr); +static int get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr); +static int append_awaited_by(RemoteUnwinderObject *unwinder, unsigned long tid, uintptr_t head_addr, PyObject *result); + /* ============================================================================ * UTILITY FUNCTIONS AND HELPERS * ============================================================================ */ @@ -405,32 +428,149 @@ validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets) return 0; } -/* ============================================================================ - * MEMORY READING FUNCTIONS - * ============================================================================ */ +// Generic function to iterate through all threads +static int +iterate_threads( + RemoteUnwinderObject *unwinder, + thread_processor_func processor, + void *context +) { + uintptr_t thread_state_addr; + unsigned long tid = 0; -static inline int -read_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr) -{ - int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(void*), ptr_addr); - if (result < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read pointer from remote memory"); + if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( + &unwinder->handle, + unwinder->interpreter_addr + unwinder->debug_offsets.interpreter_state.threads_main, + sizeof(void*), + &thread_state_addr)) + { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state"); return -1; } + + while (thread_state_addr != 0) { + if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( + &unwinder->handle, + thread_state_addr + unwinder->debug_offsets.thread_state.native_thread_id, + sizeof(tid), + &tid)) + { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread ID"); + return -1; + } + + // Call the processor function for this thread + if (processor(unwinder, thread_state_addr, tid, context) < 0) { + return -1; + } + + // Move to next thread + if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( + &unwinder->handle, + thread_state_addr + unwinder->debug_offsets.thread_state.next, + sizeof(void*), + &thread_state_addr)) + { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next thread state"); + return -1; + } + } + return 0; } -static inline int -read_Py_ssize_t(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t *size) -{ - int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(Py_ssize_t), size); - if (result < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read Py_ssize_t from remote memory"); +// Generic function to iterate through set entries +static int +iterate_set_entries( + RemoteUnwinderObject *unwinder, + uintptr_t set_addr, + set_entry_processor_func processor, + void *context +) { + char set_object[SIZEOF_SET_OBJ]; + if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, set_addr, + SIZEOF_SET_OBJ, set_object) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object"); return -1; } + + Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used); + Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1; + uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table); + + Py_ssize_t i = 0; + Py_ssize_t els = 0; + while (i < set_len && els < num_els) { + uintptr_t key_addr; + if (read_py_ptr(unwinder, table_ptr, &key_addr) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry key"); + return -1; + } + + if ((void*)key_addr != NULL) { + Py_ssize_t ref_cnt; + if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry ref count"); + return -1; + } + + if (ref_cnt) { + // Process this valid set entry + if (processor(unwinder, key_addr, context) < 0) { + return -1; + } + els++; + } + } + table_ptr += sizeof(void*) * 2; + i++; + } + return 0; } +// Processor function for task waiters +static int +process_waiter_task( + RemoteUnwinderObject *unwinder, + uintptr_t key_addr, + void *context +) { + PyObject *result = (PyObject *)context; + return process_task_and_waiters(unwinder, key_addr, result); +} + +// Processor function for parsing tasks in sets +static int +process_task_parser( + RemoteUnwinderObject *unwinder, + uintptr_t key_addr, + void *context +) { + PyObject *awaited_by = (PyObject *)context; + return parse_task(unwinder, key_addr, awaited_by); +} + +/* ============================================================================ + * MEMORY READING FUNCTIONS + * ============================================================================ */ + +#define DEFINE_MEMORY_READER(type_name, c_type, error_msg) \ +static inline int \ +read_##type_name(RemoteUnwinderObject *unwinder, uintptr_t address, c_type *result) \ +{ \ + int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(c_type), result); \ + if (res < 0) { \ + set_exception_cause(unwinder, PyExc_RuntimeError, error_msg); \ + return -1; \ + } \ + return 0; \ +} + +DEFINE_MEMORY_READER(ptr, uintptr_t, "Failed to read pointer from remote memory") +DEFINE_MEMORY_READER(Py_ssize_t, Py_ssize_t, "Failed to read Py_ssize_t from remote memory") +DEFINE_MEMORY_READER(char, char, "Failed to read char from remote memory") + static int read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr) { @@ -442,17 +582,6 @@ read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_ad return 0; } -static int -read_char(RemoteUnwinderObject *unwinder, uintptr_t address, char *result) -{ - int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(char), result); - if (res < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read char from remote memory"); - return -1; - } - return 0; -} - /* ============================================================================ * PYTHON OBJECT READING FUNCTIONS * ============================================================================ */ @@ -799,39 +928,9 @@ parse_task_name( static int parse_task_awaited_by( RemoteUnwinderObject *unwinder, uintptr_t task_address, - PyObject *awaited_by, - int recurse_task + PyObject *awaited_by ) { - // Read the entire TaskObj at once - char task_obj[SIZEOF_TASK_OBJ]; - if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address, - unwinder->async_debug_offsets.asyncio_task_object.size, - task_obj) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object in awaited_by parsing"); - return -1; - } - - uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by); - - if ((void*)task_ab_addr == NULL) { - return 0; - } - - char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set); - - if (awaited_by_is_a_set) { - if (parse_tasks_in_set(unwinder, task_ab_addr, awaited_by, recurse_task)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse tasks in awaited_by set"); - return -1; - } - } else { - if (parse_task(unwinder, task_ab_addr, awaited_by, recurse_task)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse single awaited_by task"); - return -1; - } - } - - return 0; + return process_task_awaited_by(unwinder, task_address, process_task_parser, awaited_by); } static int @@ -940,11 +1039,16 @@ parse_coro_chain( // Parse the previous frame using the gi_iframe from local copy uintptr_t prev_frame; uintptr_t gi_iframe_addr = coro_address + unwinder->debug_offsets.gen_object.gi_iframe; - if (parse_frame_object(unwinder, &name, gi_iframe_addr, &prev_frame) < 0) { + uintptr_t address_of_code_object = 0; + if (parse_frame_object(unwinder, &name, gi_iframe_addr, &address_of_code_object, &prev_frame) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain"); return -1; } + if (!name) { + return 0; + } + if (PyList_Append(render_to, name)) { Py_DECREF(name); set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain"); @@ -962,8 +1066,7 @@ parse_coro_chain( static PyObject* create_task_result( RemoteUnwinderObject *unwinder, - uintptr_t task_address, - int recurse_task + uintptr_t task_address ) { PyObject* result = NULL; PyObject *call_stack = NULL; @@ -979,11 +1082,7 @@ create_task_result( } // Create task name/address for second tuple element - if (recurse_task) { - tn = parse_task_name(unwinder, task_address); - } else { - tn = PyLong_FromUnsignedLongLong(task_address); - } + tn = PyLong_FromUnsignedLongLong(task_address); if (tn == NULL) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name/address"); goto error; @@ -1036,8 +1135,7 @@ static int parse_task( RemoteUnwinderObject *unwinder, uintptr_t task_address, - PyObject *render_to, - int recurse_task + PyObject *render_to ) { char is_task; PyObject* result = NULL; @@ -1053,7 +1151,7 @@ parse_task( } if (is_task) { - result = create_task_result(unwinder, task_address, recurse_task); + result = create_task_result(unwinder, task_address); if (!result) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task result"); goto error; @@ -1080,11 +1178,11 @@ parse_task( PyStructSequence_SetItem(result, 0, empty_list); // This steals the reference PyStructSequence_SetItem(result, 1, task_name); // This steals the reference } - if (PyList_Append(render_to, result)) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task result to render list"); goto error; } + Py_DECREF(result); return 0; @@ -1094,153 +1192,10 @@ error: } static int -process_set_entry( - RemoteUnwinderObject *unwinder, - uintptr_t table_ptr, - PyObject *awaited_by, - int recurse_task -) { - uintptr_t key_addr; - if (read_py_ptr(unwinder, table_ptr, &key_addr)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry key"); - return -1; - } - - if ((void*)key_addr != NULL) { - Py_ssize_t ref_cnt; - if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry reference count"); - return -1; - } - - if (ref_cnt) { - // if 'ref_cnt=0' it's a set dummy marker - if (parse_task(unwinder, key_addr, awaited_by, recurse_task)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task in set entry"); - return -1; - } - return 1; // Successfully processed a valid entry - } - } - return 0; // Entry was NULL or dummy marker -} - -static int -parse_tasks_in_set( - RemoteUnwinderObject *unwinder, - uintptr_t set_addr, - PyObject *awaited_by, - int recurse_task -) { - char set_object[SIZEOF_SET_OBJ]; - int err = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - set_addr, - SIZEOF_SET_OBJ, - set_object); - if (err < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object"); - return -1; - } - - Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used); - Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1; // The set contains the `mask+1` element slots. - uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table); - - Py_ssize_t i = 0; - Py_ssize_t els = 0; - while (i < set_len && els < num_els) { - int result = process_set_entry(unwinder, table_ptr, awaited_by, recurse_task); - - if (result < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process set entry"); - return -1; - } - if (result > 0) { - els++; - } - - table_ptr += sizeof(void*) * 2; - i++; - } - return 0; -} - - -static int -setup_async_result_structure(RemoteUnwinderObject *unwinder, PyObject **result, PyObject **calls) -{ - *result = PyList_New(1); - if (*result == NULL) { - set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create async result structure"); - return -1; - } - - *calls = PyList_New(0); - if (*calls == NULL) { - Py_DECREF(*result); - *result = NULL; - set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create calls list in async result"); - return -1; - } - - if (PyList_SetItem(*result, 0, *calls)) { /* steals ref to 'calls' */ - Py_DECREF(*calls); - Py_DECREF(*result); - *result = NULL; - *calls = NULL; - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to set calls list in async result"); - return -1; - } - - return 0; -} - -static int -add_task_info_to_result( - RemoteUnwinderObject *unwinder, - PyObject *result, - uintptr_t running_task_addr -) { - PyObject *tn = parse_task_name(unwinder, running_task_addr); - if (tn == NULL) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task name for result"); - return -1; - } - - if (PyList_Append(result, tn)) { - Py_DECREF(tn); - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task name to result"); - return -1; - } - Py_DECREF(tn); - - PyObject* awaited_by = PyList_New(0); - if (awaited_by == NULL) { - set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by list for result"); - return -1; - } - - if (PyList_Append(result, awaited_by)) { - Py_DECREF(awaited_by); - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by to result"); - return -1; - } - Py_DECREF(awaited_by); - - if (parse_task_awaited_by( - unwinder, running_task_addr, awaited_by, 1) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by for result"); - return -1; - } - - return 0; -} - -static int process_single_task_node( RemoteUnwinderObject *unwinder, uintptr_t task_addr, + PyObject **task_info, PyObject *result ) { PyObject *tn = NULL; @@ -1268,7 +1223,7 @@ process_single_task_node( goto error; } - if (parse_task(unwinder, task_addr, coroutine_stack, 0) < 0) { + if (parse_task(unwinder, task_addr, coroutine_stack) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task coroutine stack in single task node"); goto error; } @@ -1302,11 +1257,14 @@ process_single_task_node( set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append result item in single task node"); return -1; } + if (task_info != NULL) { + *task_info = result_item; + } Py_DECREF(result_item); // Get back current_awaited_by reference for parse_task_awaited_by current_awaited_by = PyStructSequence_GetItem(result_item, 3); - if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by, 0) < 0) { + if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by in single task node"); // No cleanup needed here since all references were transferred to result_item // and result_item was already added to result list and decreffed @@ -1324,6 +1282,247 @@ error: return -1; } +// Thread processor for get_all_awaited_by +static int +process_thread_for_awaited_by( + RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + unsigned long tid, + void *context +) { + PyObject *result = (PyObject *)context; + uintptr_t head_addr = thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head; + return append_awaited_by(unwinder, tid, head_addr, result); +} + +// Generic function to process task awaited_by +static int +process_task_awaited_by( + RemoteUnwinderObject *unwinder, + uintptr_t task_address, + set_entry_processor_func processor, + void *context +) { + // Read the entire TaskObj at once + char task_obj[SIZEOF_TASK_OBJ]; + if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address, + unwinder->async_debug_offsets.asyncio_task_object.size, + task_obj) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object"); + return -1; + } + + uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by); + if ((void*)task_ab_addr == NULL) { + return 0; // No tasks waiting for this one + } + + char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set); + + if (awaited_by_is_a_set) { + return iterate_set_entries(unwinder, task_ab_addr, processor, context); + } else { + // Single task waiting + return processor(unwinder, task_ab_addr, context); + } +} + +static int +process_running_task_chain( + RemoteUnwinderObject *unwinder, + uintptr_t running_task_addr, + uintptr_t thread_state_addr, + PyObject *result +) { + uintptr_t running_task_code_obj = 0; + if(get_task_code_object(unwinder, running_task_addr, &running_task_code_obj) < 0) { + return -1; + } + + // First, add this task to the result + PyObject *task_info = NULL; + if (process_single_task_node(unwinder, running_task_addr, &task_info, result) < 0) { + return -1; + } + + // Get the chain from the current frame to this task + PyObject *coro_chain = PyStructSequence_GET_ITEM(task_info, 2); + assert(coro_chain != NULL); + if (PyList_GET_SIZE(coro_chain) != 1) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Coro chain is not a single item"); + return -1; + } + PyObject *coro_info = PyList_GET_ITEM(coro_chain, 0); + assert(coro_info != NULL); + PyObject *frame_chain = PyStructSequence_GET_ITEM(coro_info, 0); + assert(frame_chain != NULL); + + // Clear the coro_chain + if (PyList_Clear(frame_chain) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to clear coroutine chain"); + return -1; + } + + // Add the chain from the current frame to this task + if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_code_obj) < 0) { + return -1; + } + + // Now find all tasks that are waiting for this task and process them + if (process_task_awaited_by(unwinder, running_task_addr, process_waiter_task, result) < 0) { + return -1; + } + + return 0; +} + +// Thread processor for get_async_stack_trace +static int +process_thread_for_async_stack_trace( + RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + unsigned long tid, + void *context +) { + PyObject *result = (PyObject *)context; + + // Find running task in this thread + uintptr_t running_task_addr; + if (find_running_task_in_thread(unwinder, thread_state_addr, &running_task_addr) < 0) { + return 0; + } + + // If we found a running task, process it and its waiters + if ((void*)running_task_addr != NULL) { + PyObject *task_list = PyList_New(0); + if (task_list == NULL) { + set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create task list for thread"); + return -1; + } + + if (process_running_task_chain(unwinder, running_task_addr, thread_state_addr, task_list) < 0) { + Py_DECREF(task_list); + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process running task chain"); + return -1; + } + + // Create AwaitedInfo structure for this thread + PyObject *tid_py = PyLong_FromUnsignedLong(tid); + if (tid_py == NULL) { + Py_DECREF(task_list); + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID"); + return -1; + } + + RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder); + PyObject *awaited_info = PyStructSequence_New(state->AwaitedInfo_Type); + if (awaited_info == NULL) { + Py_DECREF(tid_py); + Py_DECREF(task_list); + set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create AwaitedInfo"); + return -1; + } + + PyStructSequence_SetItem(awaited_info, 0, tid_py); // steals ref + PyStructSequence_SetItem(awaited_info, 1, task_list); // steals ref + + if (PyList_Append(result, awaited_info)) { + Py_DECREF(awaited_info); + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append AwaitedInfo to result"); + return -1; + } + Py_DECREF(awaited_info); + } + + return 0; +} + +static int +process_task_and_waiters( + RemoteUnwinderObject *unwinder, + uintptr_t task_addr, + PyObject *result +) { + // First, add this task to the result + if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) { + return -1; + } + + // Now find all tasks that are waiting for this task and process them + return process_task_awaited_by(unwinder, task_addr, process_waiter_task, result); +} + +static int +find_running_task_in_thread( + RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + uintptr_t *running_task_addr +) { + *running_task_addr = (uintptr_t)NULL; + + uintptr_t address_of_running_loop; + int bytes_read = read_py_ptr( + unwinder, + thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop, + &address_of_running_loop); + if (bytes_read == -1) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address"); + return -1; + } + + // no asyncio loop is now running + if ((void*)address_of_running_loop == NULL) { + return 0; + } + + int err = read_ptr( + unwinder, + thread_state_addr + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task, + running_task_addr); + if (err) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address"); + return -1; + } + + return 0; +} + +static int +get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr) { + uintptr_t running_coro_addr = 0; + + if(read_py_ptr( + unwinder, + task_addr + unwinder->async_debug_offsets.asyncio_task_object.task_coro, + &running_coro_addr) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed"); + return -1; + } + + if (running_coro_addr == 0) { + PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL"); + set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL"); + return -1; + } + + // note: genobject's gi_iframe is an embedded struct so the address to + // the offset leads directly to its first field: f_executable + if (read_py_ptr( + unwinder, + running_coro_addr + unwinder->debug_offsets.gen_object.gi_iframe, code_obj_addr) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object"); + return -1; + } + + if (*code_obj_addr == 0) { + PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL"); + set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL"); + return -1; + } + + return 0; +} + /* ============================================================================ * TLBC CACHING FUNCTIONS * ============================================================================ */ @@ -1904,45 +2103,13 @@ populate_initial_state_data( return 0; } + static int find_running_frame( RemoteUnwinderObject *unwinder, - uintptr_t runtime_start_address, + uintptr_t address_of_thread, uintptr_t *frame ) { - uint64_t interpreter_state_list_head = - unwinder->debug_offsets.runtime_state.interpreters_head; - - uintptr_t address_of_interpreter_state; - int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - runtime_start_address + interpreter_state_list_head, - sizeof(void*), - &address_of_interpreter_state); - if (bytes_read < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running frame"); - return -1; - } - - if (address_of_interpreter_state == 0) { - PyErr_SetString(PyExc_RuntimeError, "No interpreter state found"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running frame search"); - return -1; - } - - uintptr_t address_of_thread; - bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - address_of_interpreter_state + - unwinder->debug_offsets.interpreter_state.threads_main, - sizeof(void*), - &address_of_thread); - if (bytes_read < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread address for running frame"); - return -1; - } - - // No Python frames are available for us (can happen at tear-down). if ((void*)address_of_thread != NULL) { int err = read_ptr( unwinder, @@ -1959,133 +2126,6 @@ find_running_frame( return 0; } -static int -find_running_task( - RemoteUnwinderObject *unwinder, - uintptr_t *running_task_addr -) { - *running_task_addr = (uintptr_t)NULL; - - uint64_t interpreter_state_list_head = - unwinder->debug_offsets.runtime_state.interpreters_head; - - uintptr_t address_of_interpreter_state; - int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - unwinder->runtime_start_address + interpreter_state_list_head, - sizeof(void*), - &address_of_interpreter_state); - if (bytes_read < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running task"); - return -1; - } - - if (address_of_interpreter_state == 0) { - PyErr_SetString(PyExc_RuntimeError, "No interpreter state found"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running task search"); - return -1; - } - - uintptr_t address_of_thread; - bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - address_of_interpreter_state + - unwinder->debug_offsets.interpreter_state.threads_head, - sizeof(void*), - &address_of_thread); - if (bytes_read < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread head for running task"); - return -1; - } - - uintptr_t address_of_running_loop; - // No Python frames are available for us (can happen at tear-down). - if ((void*)address_of_thread == NULL) { - return 0; - } - - bytes_read = read_py_ptr( - unwinder, - address_of_thread - + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop, - &address_of_running_loop); - if (bytes_read == -1) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address"); - return -1; - } - - // no asyncio loop is now running - if ((void*)address_of_running_loop == NULL) { - return 0; - } - - int err = read_ptr( - unwinder, - address_of_thread - + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task, - running_task_addr); - if (err) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address"); - return -1; - } - - return 0; -} - -static int -find_running_task_and_coro( - RemoteUnwinderObject *unwinder, - uintptr_t *running_task_addr, - uintptr_t *running_coro_addr, - uintptr_t *running_task_code_obj -) { - *running_task_addr = (uintptr_t)NULL; - if (find_running_task( - unwinder, running_task_addr) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task search failed"); - return -1; - } - - if ((void*)*running_task_addr == NULL) { - PyErr_SetString(PyExc_RuntimeError, "No running task found"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task address is NULL"); - return -1; - } - - if (read_py_ptr( - unwinder, - *running_task_addr + unwinder->async_debug_offsets.asyncio_task_object.task_coro, - running_coro_addr) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed"); - return -1; - } - - if ((void*)*running_coro_addr == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL"); - return -1; - } - - // note: genobject's gi_iframe is an embedded struct so the address to - // the offset leads directly to its first field: f_executable - if (read_py_ptr( - unwinder, - *running_coro_addr + unwinder->debug_offsets.gen_object.gi_iframe, - running_task_code_obj) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object"); - return -1; - } - - if ((void*)*running_task_code_obj == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL"); - return -1; - } - - return 0; -} - - /* ============================================================================ * FRAME PARSING FUNCTIONS * ============================================================================ */ @@ -2122,9 +2162,11 @@ parse_frame_object( RemoteUnwinderObject *unwinder, PyObject** result, uintptr_t address, + uintptr_t* address_of_code_object, uintptr_t* previous_frame ) { char frame[SIZEOF_INTERP_FRAME]; + *address_of_code_object = 0; Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( &unwinder->handle, @@ -2154,77 +2196,32 @@ parse_frame_object( } #endif - return parse_code_object(unwinder, result, code_object,instruction_pointer, previous_frame, tlbc_index); -} - -static int -parse_async_frame_object( - RemoteUnwinderObject *unwinder, - PyObject** result, - uintptr_t address, - uintptr_t* previous_frame, - uintptr_t* code_object -) { - char frame[SIZEOF_INTERP_FRAME]; - - Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory( - &unwinder->handle, - address, - SIZEOF_INTERP_FRAME, - frame - ); - if (bytes_read < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read async frame"); - return -1; - } - - *previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous); - *code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable); - int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, *code_object); - if (frame_valid != 1) { - return frame_valid; - } - - uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr); - - // Get tlbc_index for free threading builds - int32_t tlbc_index = 0; -#ifdef Py_GIL_DISABLED - if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) { - tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index); - } -#endif - - if (parse_code_object( - unwinder, result, *code_object, instruction_pointer, previous_frame, tlbc_index)) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse code object in async frame"); - return -1; - } - - return 1; + *address_of_code_object = code_object; + return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index); } static int -parse_async_frame_chain( + parse_async_frame_chain( RemoteUnwinderObject *unwinder, PyObject *calls, + uintptr_t address_of_thread, uintptr_t running_task_code_obj ) { uintptr_t address_of_current_frame; - if (find_running_frame(unwinder, unwinder->runtime_start_address, &address_of_current_frame) < 0) { + if (find_running_frame(unwinder, address_of_thread, &address_of_current_frame) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Running frame search failed in async chain"); return -1; } - uintptr_t address_of_code_object; while ((void*)address_of_current_frame != NULL) { PyObject* frame_info = NULL; - int res = parse_async_frame_object( + uintptr_t address_of_code_object; + int res = parse_frame_object( unwinder, &frame_info, address_of_current_frame, - &address_of_current_frame, - &address_of_code_object + &address_of_code_object, + &address_of_current_frame ); if (res < 0) { @@ -2290,7 +2287,7 @@ append_awaited_by_for_thread( uintptr_t task_addr = (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next) - unwinder->async_debug_offsets.asyncio_task_object.task_node; - if (process_single_task_node(unwinder, task_addr, result) < 0) { + if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process task node in awaited_by"); return -1; } @@ -2385,7 +2382,8 @@ process_frame_chain( // Try chunks first, fallback to direct memory read if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, chunks) < 0) { PyErr_Clear(); - if (parse_frame_object(unwinder, &frame, frame_addr, &next_frame_addr) < 0) { + uintptr_t address_of_code_object = 0; + if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object ,&next_frame_addr) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain"); return -1; } @@ -2762,6 +2760,7 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self } exit: + _Py_RemoteDebug_ClearCache(&self->handle); return result; } @@ -2825,53 +2824,12 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s goto result_err; } - uintptr_t thread_state_addr; - unsigned long tid = 0; - if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( - &self->handle, - self->interpreter_addr - + self->debug_offsets.interpreter_state.threads_main, - sizeof(void*), - &thread_state_addr)) - { - set_exception_cause(self, PyExc_RuntimeError, "Failed to read main thread state in get_all_awaited_by"); + // Process all threads + if (iterate_threads(self, process_thread_for_awaited_by, result) < 0) { goto result_err; } - uintptr_t head_addr; - while (thread_state_addr != 0) { - if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( - &self->handle, - thread_state_addr - + self->debug_offsets.thread_state.native_thread_id, - sizeof(tid), - &tid)) - { - set_exception_cause(self, PyExc_RuntimeError, "Failed to read thread ID in get_all_awaited_by"); - goto result_err; - } - - head_addr = thread_state_addr - + self->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head; - - if (append_awaited_by(self, tid, head_addr, result)) - { - set_exception_cause(self, PyExc_RuntimeError, "Failed to append awaited_by for thread in get_all_awaited_by"); - goto result_err; - } - - if (0 > _Py_RemoteDebug_PagedReadRemoteMemory( - &self->handle, - thread_state_addr + self->debug_offsets.thread_state.next, - sizeof(void*), - &thread_state_addr)) - { - set_exception_cause(self, PyExc_RuntimeError, "Failed to read next thread state in get_all_awaited_by"); - goto result_err; - } - } - - head_addr = self->interpreter_addr + uintptr_t head_addr = self->interpreter_addr + self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head; // On top of a per-thread task lists used by default by asyncio to avoid @@ -2885,9 +2843,11 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s goto result_err; } + _Py_RemoteDebug_ClearCache(&self->handle); return result; result_err: + _Py_RemoteDebug_ClearCache(&self->handle); Py_XDECREF(result); return NULL; } @@ -2896,32 +2856,50 @@ result_err: @critical_section _remote_debugging.RemoteUnwinder.get_async_stack_trace -Returns information about the currently running async task and its stack trace. +Get the currently running async tasks and their dependency graphs from the remote process. -Returns a tuple of (task_info, stack_frames) where: -- task_info is a tuple of (task_id, task_name) identifying the task -- stack_frames is a list of tuples (function_name, filename, line_number) representing - the Python stack frames for the task, ordered from most recent to oldest +This returns information about running tasks and all tasks that are waiting for them, +forming a complete dependency graph for each thread's active task. -Example: - ((4345585712, 'Task-1'), [ - ('run_echo_server', 'server.py', 127), - ('serve_forever', 'server.py', 45), - ('main', 'app.py', 23) - ]) +For each thread with a running task, returns the running task plus all tasks that +transitively depend on it (tasks waiting for the running task, tasks waiting for +those tasks, etc.). + +Returns a list of per-thread results, where each thread result contains: +- Thread ID +- List of task information for the running task and all its waiters + +Each task info contains: +- Task ID (memory address) +- Task name +- Call stack frames: List of (func_name, filename, lineno) +- List of tasks waiting for this task (recursive structure) Raises: RuntimeError: If AsyncioDebug section is not available in the target process - RuntimeError: If there is an error copying memory from the target process - OSError: If there is an error accessing the target process - PermissionError: If access to the target process is denied - UnicodeDecodeError: If there is an error decoding strings from the target process + MemoryError: If memory allocation fails + OSError: If reading from the remote process fails + +Example output (similar structure to get_all_awaited_by but only for running tasks): +[ + # Thread 140234 results + (140234, [ + # Running task and its complete waiter dependency graph + (4345585712, 'main_task', + [("run_server", "server.py", 127), ("main", "app.py", 23)], + [ + # Tasks waiting for main_task + (4345585800, 'worker_1', [...], [...]), + (4345585900, 'worker_2', [...], [...]) + ]) + ]) +] [clinic start generated code]*/ static PyObject * _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self) -/*[clinic end generated code: output=6433d52b55e87bbe input=11b7150c59d4c60f]*/ +/*[clinic end generated code: output=6433d52b55e87bbe input=8744b47c9ec2220a]*/ { if (!self->async_debug_offsets_available) { PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available"); @@ -2929,34 +2907,21 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject return NULL; } - PyObject *result = NULL; - PyObject *calls = NULL; - - if (setup_async_result_structure(self, &result, &calls) < 0) { - set_exception_cause(self, PyExc_RuntimeError, "Failed to setup async result structure"); - goto cleanup; - } - - uintptr_t running_task_addr, running_coro_addr, running_task_code_obj; - if (find_running_task_and_coro(self, &running_task_addr, - &running_coro_addr, &running_task_code_obj) < 0) { - set_exception_cause(self, PyExc_RuntimeError, "Failed to find running task and coro"); - goto cleanup; - } - - if (parse_async_frame_chain(self, calls, running_task_code_obj) < 0) { - set_exception_cause(self, PyExc_RuntimeError, "Failed to parse async frame chain"); - goto cleanup; + PyObject *result = PyList_New(0); + if (result == NULL) { + set_exception_cause(self, PyExc_MemoryError, "Failed to create result list in get_async_stack_trace"); + return NULL; } - if (add_task_info_to_result(self, result, running_task_addr) < 0) { - set_exception_cause(self, PyExc_RuntimeError, "Failed to add task info to result"); - goto cleanup; + // Process all threads + if (iterate_threads(self, process_thread_for_async_stack_trace, result) < 0) { + goto result_err; } + _Py_RemoteDebug_ClearCache(&self->handle); return result; - -cleanup: +result_err: + _Py_RemoteDebug_ClearCache(&self->handle); Py_XDECREF(result); return NULL; } @@ -2982,6 +2947,7 @@ RemoteUnwinder_dealloc(PyObject *op) } #endif if (self->handle.pid != 0) { + _Py_RemoteDebug_ClearCache(&self->handle); _Py_RemoteDebug_CleanupProcHandle(&self->handle); } PyObject_Del(self); diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 014e624f6c2..24c243e330d 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -75,6 +75,33 @@ #endif +#ifdef BIO_get_ktls_send +# ifdef MS_WINDOWS +typedef long long Py_off_t; +# else +typedef off_t Py_off_t; +# endif + +static int +Py_off_t_converter(PyObject *arg, void *addr) +{ +#ifdef HAVE_LARGEFILE_SUPPORT + *((Py_off_t *)addr) = PyLong_AsLongLong(arg); +#else + *((Py_off_t *)addr) = PyLong_AsLong(arg); +#endif + return PyErr_Occurred() ? 0 : 1; +} + +/*[python input] + +class Py_off_t_converter(CConverter): + type = 'Py_off_t' + converter = 'Py_off_t_converter' + +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=3fd9ca8ca6f0cbb8]*/ +#endif /* BIO_get_ktls_send */ struct py_ssl_error_code { const char *mnemonic; @@ -2444,6 +2471,184 @@ PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout) /*[clinic input] @critical_section +_ssl._SSLSocket.uses_ktls_for_send + +Check if the Kernel TLS data-path is used for sending. +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_send_impl(PySSLSocket *self) +/*[clinic end generated code: output=f9d95fbefceb5068 input=8d1ce4a131190e6b]*/ +{ +#ifdef BIO_get_ktls_send + int uses = BIO_get_ktls_send(SSL_get_wbio(self->ssl)); + // BIO_get_ktls_send() returns 1 if kTLS is used and 0 if not. + // Also, it returns -1 for failure before OpenSSL 3.0.4. + return Py_NewRef(uses == 1 ? Py_True : Py_False); +#else + Py_RETURN_FALSE; +#endif +} + +/*[clinic input] +@critical_section +_ssl._SSLSocket.uses_ktls_for_recv + +Check if the Kernel TLS data-path is used for receiving. +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_recv_impl(PySSLSocket *self) +/*[clinic end generated code: output=ce38b00317a1f681 input=a13778a924fc7d44]*/ +{ +#ifdef BIO_get_ktls_recv + int uses = BIO_get_ktls_recv(SSL_get_rbio(self->ssl)); + // BIO_get_ktls_recv() returns 1 if kTLS is used and 0 if not. + // Also, it returns -1 for failure before OpenSSL 3.0.4. + return Py_NewRef(uses == 1 ? Py_True : Py_False); +#else + Py_RETURN_FALSE; +#endif +} + +#ifdef BIO_get_ktls_send +/*[clinic input] +@critical_section +_ssl._SSLSocket.sendfile + fd: int + offset: Py_off_t + size: size_t + flags: int = 0 + / + +Write size bytes from offset in the file descriptor fd to the SSL connection. + +This method uses the zero-copy technique and returns the number of bytes +written. It should be called only when Kernel TLS is used for sending data in +the connection. + +The meaning of flags is platform dependent. +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset, + size_t size, int flags) +/*[clinic end generated code: output=0c6815b0719ca8d5 input=dfc1b162bb020de1]*/ +{ + Py_ssize_t retval; + int sockstate; + _PySSLError err; + PySocketSockObject *sock = GET_SOCKET(self); + PyTime_t timeout, deadline = 0; + int has_timeout; + + if (sock != NULL) { + if ((PyObject *)sock == Py_None) { + _setSSLError(get_state_sock(self), + "Underlying socket connection gone", + PY_SSL_ERROR_NO_SOCKET, __FILE__, __LINE__); + return NULL; + } + Py_INCREF(sock); + /* just in case the blocking state of the socket has been changed */ + int nonblocking = (sock->sock_timeout >= 0); + BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking); + BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking); + } + + timeout = GET_SOCKET_TIMEOUT(sock); + has_timeout = (timeout > 0); + if (has_timeout) { + deadline = _PyDeadline_Init(timeout); + } + + sockstate = PySSL_select(sock, 1, timeout); + switch (sockstate) { + case SOCKET_HAS_TIMED_OUT: + PyErr_SetString(PyExc_TimeoutError, + "The write operation timed out"); + goto error; + case SOCKET_HAS_BEEN_CLOSED: + PyErr_SetString(get_state_sock(self)->PySSLErrorObject, + "Underlying socket has been closed."); + goto error; + case SOCKET_TOO_LARGE_FOR_SELECT: + PyErr_SetString(get_state_sock(self)->PySSLErrorObject, + "Underlying socket too large for select()."); + goto error; + } + + do { + PySSL_BEGIN_ALLOW_THREADS + retval = SSL_sendfile(self->ssl, fd, (off_t)offset, size, flags); + err = _PySSL_errno(retval < 0, self->ssl, (int)retval); + PySSL_END_ALLOW_THREADS + self->err = err; + + if (PyErr_CheckSignals()) { + goto error; + } + + if (has_timeout) { + timeout = _PyDeadline_Get(deadline); + } + + switch (err.ssl) { + case SSL_ERROR_WANT_READ: + sockstate = PySSL_select(sock, 0, timeout); + break; + case SSL_ERROR_WANT_WRITE: + sockstate = PySSL_select(sock, 1, timeout); + break; + default: + sockstate = SOCKET_OPERATION_OK; + break; + } + + if (sockstate == SOCKET_HAS_TIMED_OUT) { + PyErr_SetString(PyExc_TimeoutError, + "The sendfile operation timed out"); + goto error; + } + else if (sockstate == SOCKET_HAS_BEEN_CLOSED) { + PyErr_SetString(get_state_sock(self)->PySSLErrorObject, + "Underlying socket has been closed."); + goto error; + } + else if (sockstate == SOCKET_IS_NONBLOCKING) { + break; + } + } while (err.ssl == SSL_ERROR_WANT_READ + || err.ssl == SSL_ERROR_WANT_WRITE); + + if (err.ssl == SSL_ERROR_SSL + && ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED) + { + /* OpenSSL fails to return SSL_ERROR_SYSCALL if an error + * happens in sendfile(), and returns SSL_ERROR_SSL with + * SSL_R_UNINITIALIZED reason instead. */ + _setSSLError(get_state_sock(self), + "Some I/O error occurred in sendfile()", + PY_SSL_ERROR_SYSCALL, __FILE__, __LINE__); + goto error; + } + Py_XDECREF(sock); + if (retval < 0) { + return PySSL_SetError(self, __FILE__, __LINE__); + } + if (PySSL_ChainExceptions(self) < 0) { + return NULL; + } + return PyLong_FromSize_t(retval); +error: + Py_XDECREF(sock); + (void)PySSL_ChainExceptions(self); + return NULL; +} +#endif /* BIO_get_ktls_send */ + +/*[clinic input] +@critical_section _ssl._SSLSocket.write b: Py_buffer / @@ -3017,6 +3222,9 @@ static PyGetSetDef ssl_getsetlist[] = { static PyMethodDef PySSLMethods[] = { _SSL__SSLSOCKET_DO_HANDSHAKE_METHODDEF + _SSL__SSLSOCKET_USES_KTLS_FOR_SEND_METHODDEF + _SSL__SSLSOCKET_USES_KTLS_FOR_RECV_METHODDEF + _SSL__SSLSOCKET_SENDFILE_METHODDEF _SSL__SSLSOCKET_WRITE_METHODDEF _SSL__SSLSOCKET_READ_METHODDEF _SSL__SSLSOCKET_PENDING_METHODDEF diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index 7fc4d61db29..d2e61e9d6ac 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -1855,8 +1855,7 @@ ndarray_subscript(PyObject *op, PyObject *key) type_error: PyErr_Format(PyExc_TypeError, - "cannot index memory using \"%.200s\"", - Py_TYPE(key)->tp_name); + "cannot index memory using \"%T\"", key); err_occurred: Py_DECREF(nd); return NULL; diff --git a/Modules/_testcapi/monitoring.c b/Modules/_testcapi/monitoring.c index 08a2055c51b..e041943492d 100644 --- a/Modules/_testcapi/monitoring.c +++ b/Modules/_testcapi/monitoring.c @@ -109,7 +109,7 @@ static PyTypeObject PyCodeLike_Type = { }; #define RAISE_UNLESS_CODELIKE(v) if (!Py_IS_TYPE((v), &PyCodeLike_Type)) { \ - PyErr_Format(PyExc_TypeError, "expected a code-like, got %s", Py_TYPE(v)->tp_name); \ + PyErr_Format(PyExc_TypeError, "expected a code-like, got %T", v); \ return NULL; \ } diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index 464cf5c3125..4ca6ff587b9 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -5,8 +5,7 @@ static int pytime_from_nanoseconds(PyTime_t *tp, PyObject *obj) { if (!PyLong_Check(obj)) { - PyErr_Format(PyExc_TypeError, "expect int, got %s", - Py_TYPE(obj)->tp_name); + PyErr_Format(PyExc_TypeError, "expect int, got %T", obj); return -1; } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 71fffedee14..334f2a53041 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -515,8 +515,7 @@ test_thread_state(PyObject *self, PyObject *args) return NULL; if (!PyCallable_Check(fn)) { - PyErr_Format(PyExc_TypeError, "'%s' object is not callable", - Py_TYPE(fn)->tp_name); + PyErr_Format(PyExc_TypeError, "'%T' object is not callable", fn); return NULL; } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 8027f0015c7..533e7dd3a7e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2207,8 +2207,7 @@ get_code(PyObject *obj) return (PyCodeObject *)PyFunction_GetCode(obj); } return (PyCodeObject *)PyErr_Format( - PyExc_TypeError, "expected function or code object, got %s", - Py_TYPE(obj)->tp_name); + PyExc_TypeError, "expected function or code object, got %T", obj); } static PyObject * diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 8886a9d6bd0..3540fead8e8 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2330,7 +2330,7 @@ thread_excepthook(PyObject *module, PyObject *args) } PyDoc_STRVAR(excepthook_doc, -"_excepthook($module, (exc_type, exc_value, exc_traceback, thread), /)\n\ +"_excepthook($module, args, /)\n\ --\n\ \n\ Handle uncaught Thread.run() exception."); diff --git a/Modules/clinic/_remote_debugging_module.c.h b/Modules/clinic/_remote_debugging_module.c.h index e80b24b54c0..f6a51cdba6b 100644 --- a/Modules/clinic/_remote_debugging_module.c.h +++ b/Modules/clinic/_remote_debugging_module.c.h @@ -235,26 +235,41 @@ PyDoc_STRVAR(_remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__, "get_async_stack_trace($self, /)\n" "--\n" "\n" -"Returns information about the currently running async task and its stack trace.\n" +"Get the currently running async tasks and their dependency graphs from the remote process.\n" "\n" -"Returns a tuple of (task_info, stack_frames) where:\n" -"- task_info is a tuple of (task_id, task_name) identifying the task\n" -"- stack_frames is a list of tuples (function_name, filename, line_number) representing\n" -" the Python stack frames for the task, ordered from most recent to oldest\n" +"This returns information about running tasks and all tasks that are waiting for them,\n" +"forming a complete dependency graph for each thread\'s active task.\n" "\n" -"Example:\n" -" ((4345585712, \'Task-1\'), [\n" -" (\'run_echo_server\', \'server.py\', 127),\n" -" (\'serve_forever\', \'server.py\', 45),\n" -" (\'main\', \'app.py\', 23)\n" -" ])\n" +"For each thread with a running task, returns the running task plus all tasks that\n" +"transitively depend on it (tasks waiting for the running task, tasks waiting for\n" +"those tasks, etc.).\n" +"\n" +"Returns a list of per-thread results, where each thread result contains:\n" +"- Thread ID\n" +"- List of task information for the running task and all its waiters\n" +"\n" +"Each task info contains:\n" +"- Task ID (memory address)\n" +"- Task name\n" +"- Call stack frames: List of (func_name, filename, lineno)\n" +"- List of tasks waiting for this task (recursive structure)\n" "\n" "Raises:\n" " RuntimeError: If AsyncioDebug section is not available in the target process\n" -" RuntimeError: If there is an error copying memory from the target process\n" -" OSError: If there is an error accessing the target process\n" -" PermissionError: If access to the target process is denied\n" -" UnicodeDecodeError: If there is an error decoding strings from the target process"); +" MemoryError: If memory allocation fails\n" +" OSError: If reading from the remote process fails\n" +"\n" +"Example output (similar structure to get_all_awaited_by but only for running tasks):\n" +"[\n" +" (140234, [\n" +" (4345585712, \'main_task\',\n" +" [(\"run_server\", \"server.py\", 127), (\"main\", \"app.py\", 23)],\n" +" [\n" +" (4345585800, \'worker_1\', [...], [...]),\n" +" (4345585900, \'worker_2\', [...], [...])\n" +" ])\n" +" ])\n" +"]"); #define _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF \ {"get_async_stack_trace", (PyCFunction)_remote_debugging_RemoteUnwinder_get_async_stack_trace, METH_NOARGS, _remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__}, @@ -273,4 +288,4 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace(PyObject *self, PyObject return return_value; } -/*[clinic end generated code: output=a37ab223d5081b16 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0dd1e6e8bab2a8b1 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index c6e2abd4d93..7027d873792 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -7,6 +7,7 @@ preserve # include "pycore_runtime.h" // _Py_ID() #endif #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() +#include "pycore_long.h" // _PyLong_Size_t_Converter() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(_ssl__SSLSocket_do_handshake__doc__, @@ -442,6 +443,115 @@ _ssl__SSLSocket_owner_set(PyObject *self, PyObject *value, void *Py_UNUSED(conte return return_value; } +PyDoc_STRVAR(_ssl__SSLSocket_uses_ktls_for_send__doc__, +"uses_ktls_for_send($self, /)\n" +"--\n" +"\n" +"Check if the Kernel TLS data-path is used for sending."); + +#define _SSL__SSLSOCKET_USES_KTLS_FOR_SEND_METHODDEF \ + {"uses_ktls_for_send", (PyCFunction)_ssl__SSLSocket_uses_ktls_for_send, METH_NOARGS, _ssl__SSLSocket_uses_ktls_for_send__doc__}, + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_send_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_send(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_uses_ktls_for_send_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLSocket_uses_ktls_for_recv__doc__, +"uses_ktls_for_recv($self, /)\n" +"--\n" +"\n" +"Check if the Kernel TLS data-path is used for receiving."); + +#define _SSL__SSLSOCKET_USES_KTLS_FOR_RECV_METHODDEF \ + {"uses_ktls_for_recv", (PyCFunction)_ssl__SSLSocket_uses_ktls_for_recv, METH_NOARGS, _ssl__SSLSocket_uses_ktls_for_recv__doc__}, + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_recv_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_uses_ktls_for_recv(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_uses_ktls_for_recv_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(BIO_get_ktls_send) + +PyDoc_STRVAR(_ssl__SSLSocket_sendfile__doc__, +"sendfile($self, fd, offset, size, flags=0, /)\n" +"--\n" +"\n" +"Write size bytes from offset in the file descriptor fd to the SSL connection.\n" +"\n" +"This method uses the zero-copy technique and returns the number of bytes\n" +"written. It should be called only when Kernel TLS is used for sending data in\n" +"the connection.\n" +"\n" +"The meaning of flags is platform dependent."); + +#define _SSL__SSLSOCKET_SENDFILE_METHODDEF \ + {"sendfile", _PyCFunction_CAST(_ssl__SSLSocket_sendfile), METH_FASTCALL, _ssl__SSLSocket_sendfile__doc__}, + +static PyObject * +_ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset, + size_t size, int flags); + +static PyObject * +_ssl__SSLSocket_sendfile(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + Py_off_t offset; + size_t size; + int flags = 0; + + if (!_PyArg_CheckPositional("sendfile", nargs, 3, 4)) { + goto exit; + } + fd = PyLong_AsInt(args[0]); + if (fd == -1 && PyErr_Occurred()) { + goto exit; + } + if (!Py_off_t_converter(args[1], &offset)) { + goto exit; + } + if (!_PyLong_Size_t_Converter(args[2], &size)) { + goto exit; + } + if (nargs < 4) { + goto skip_optional; + } + flags = PyLong_AsInt(args[3]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_sendfile_impl((PySSLSocket *)self, fd, offset, size, flags); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + +#endif /* defined(BIO_get_ktls_send) */ + PyDoc_STRVAR(_ssl__SSLSocket_write__doc__, "write($self, b, /)\n" "--\n" @@ -2893,6 +3003,10 @@ exit: #endif /* defined(_MSC_VER) */ +#ifndef _SSL__SSLSOCKET_SENDFILE_METHODDEF + #define _SSL__SSLSOCKET_SENDFILE_METHODDEF +#endif /* !defined(_SSL__SSLSOCKET_SENDFILE_METHODDEF) */ + #ifndef _SSL_ENUM_CERTIFICATES_METHODDEF #define _SSL_ENUM_CERTIFICATES_METHODDEF #endif /* !defined(_SSL_ENUM_CERTIFICATES_METHODDEF) */ @@ -2900,4 +3014,4 @@ exit: #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=748650909fec8906 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1adc3780d8ca682a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index a443c48faaa..246019f2206 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -84,6 +84,112 @@ PyDoc_STRVAR(math_floor__doc__, #define MATH_FLOOR_METHODDEF \ {"floor", (PyCFunction)math_floor, METH_O, math_floor__doc__}, +PyDoc_STRVAR(math_fmax__doc__, +"fmax($module, x, y, /)\n" +"--\n" +"\n" +"Return the larger of two floating-point arguments."); + +#define MATH_FMAX_METHODDEF \ + {"fmax", _PyCFunction_CAST(math_fmax), METH_FASTCALL, math_fmax__doc__}, + +static double +math_fmax_impl(PyObject *module, double x, double y); + +static PyObject * +math_fmax(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + double x; + double y; + double _return_value; + + if (!_PyArg_CheckPositional("fmax", nargs, 2, 2)) { + goto exit; + } + if (PyFloat_CheckExact(args[0])) { + x = PyFloat_AS_DOUBLE(args[0]); + } + else + { + x = PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (PyFloat_CheckExact(args[1])) { + y = PyFloat_AS_DOUBLE(args[1]); + } + else + { + y = PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + _return_value = math_fmax_impl(module, x, y); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(math_fmin__doc__, +"fmin($module, x, y, /)\n" +"--\n" +"\n" +"Return the smaller of two floating-point arguments."); + +#define MATH_FMIN_METHODDEF \ + {"fmin", _PyCFunction_CAST(math_fmin), METH_FASTCALL, math_fmin__doc__}, + +static double +math_fmin_impl(PyObject *module, double x, double y); + +static PyObject * +math_fmin(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + double x; + double y; + double _return_value; + + if (!_PyArg_CheckPositional("fmin", nargs, 2, 2)) { + goto exit; + } + if (PyFloat_CheckExact(args[0])) { + x = PyFloat_AS_DOUBLE(args[0]); + } + else + { + x = PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (PyFloat_CheckExact(args[1])) { + y = PyFloat_AS_DOUBLE(args[1]); + } + else + { + y = PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + _return_value = math_fmin_impl(module, x, y); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(math_signbit__doc__, "signbit($module, x, /)\n" "--\n" @@ -1212,4 +1318,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=4e3fa94d026f027b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4fb180d4c25ff8fa input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 033de0b2907..7c2a421dd6a 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -1214,6 +1214,40 @@ math_floor(PyObject *module, PyObject *number) return PyLong_FromDouble(floor(x)); } +/*[clinic input] +math.fmax -> double + + x: double + y: double + / + +Return the larger of two floating-point arguments. +[clinic start generated code]*/ + +static double +math_fmax_impl(PyObject *module, double x, double y) +/*[clinic end generated code: output=00692358d312fee2 input=021596c027336ffe]*/ +{ + return fmax(x, y); +} + +/*[clinic input] +math.fmin -> double + + x: double + y: double + / + +Return the smaller of two floating-point arguments. +[clinic start generated code]*/ + +static double +math_fmin_impl(PyObject *module, double x, double y) +/*[clinic end generated code: output=3d5b7826bd292dd9 input=d12e64ccc33f878a]*/ +{ + return fmin(x, y); +} + FUNC1AD(gamma, m_tgamma, "gamma($module, x, /)\n--\n\n" "Gamma function at x.", @@ -4192,7 +4226,9 @@ static PyMethodDef math_methods[] = { MATH_FACTORIAL_METHODDEF MATH_FLOOR_METHODDEF MATH_FMA_METHODDEF + MATH_FMAX_METHODDEF MATH_FMOD_METHODDEF + MATH_FMIN_METHODDEF MATH_FREXP_METHODDEF MATH_FSUM_METHODDEF {"gamma", math_gamma, METH_O, math_gamma_doc}, |