@@ -11,7 +11,8 @@ Ignore:
1111 # Assuming the cwd is the repo root.
1212 cythonize --annotate --inplace \
1313 . /line_profiler/_line_profiler. pyx \
14- . /line_profiler/timers. c
14+ . /line_profiler/timers. c \
15+ . /line_profiler/c_trace_callbacks. c
1516"""
1617from .python25 cimport PyFrameObject, PyObject, PyStringObject
1718from collections.abc import Callable
@@ -37,39 +38,23 @@ NOP_BYTES: bytes = NOP_VALUE.to_bytes(2, byteorder=byteorder)
3738ctypedef unsigned long long int uint64
3839ctypedef long long int int64
3940
40- # FIXME: there might be something special we have to do here for Python 3.11
41- cdef extern from " frameobject.h" :
41+ cdef extern from " Python_wrapper.h" :
4242 """
4343 inline PyObject* get_frame_code(PyFrameObject* frame) {
44- #if PY_VERSION_HEX < 0x030B0000
45- Py_INCREF(frame->f_code->co_code);
46- return frame->f_code->co_code;
47- #else
48- PyCodeObject* code = PyFrame_GetCode(frame);
49- PyObject* ret = PyCode_GetCode(code);
50- Py_DECREF(code);
51- return ret;
52- #endif
44+ PyCodeObject* code = PyFrame_GetCode(frame);
45+ PyObject* ret = PyCode_GetCode(code);
46+ Py_DECREF(code);
47+ return ret;
5348 }
5449 """
55- cdef object get_frame_code(PyFrameObject* frame)
56- ctypedef int (* Py_tracefunc)(object self , PyFrameObject * py_frame, int what, PyObject * arg)
57-
58- cdef extern from " Python.h" :
59- """
60- // CPython 3.11 broke some stuff by moving PyFrameObject :(
61- #if PY_VERSION_HEX >= 0x030b00a6
62- #ifndef Py_BUILD_CORE
63- #define Py_BUILD_CORE 1
64- #endif
65- #include "internal/pycore_frame.h"
66- #include "cpython/code.h"
67- #include "pyframe.h"
68- #endif
69- """
50+ ctypedef int (* Py_tracefunc)(object self , PyFrameObject * py_frame,
51+ int what, PyObject * arg)
7052 ctypedef struct PyFrameObject
7153 ctypedef struct PyCodeObject
7254 ctypedef long long PY_LONG_LONG
55+
56+ cdef object get_frame_code(PyFrameObject* frame)
57+
7358 cdef bint PyCFunction_Check(object obj)
7459 cdef int PyCode_Addr2Line(PyCodeObject * co, int byte_offset)
7560
@@ -100,204 +85,7 @@ cdef extern from "Python.h":
10085
10186 cdef int PyFrame_GetLineNumber(PyFrameObject * frame)
10287
103- cdef extern from * :
104- r """
105- #if PY_VERSION_HEX >= 0x030D00a1 // 3. 13. 0a0
106- #define add_module_ref PyImport_AddModuleRef
107- #else
108- inline PyObject * add_module_ref( const char * name) {
109- PyObject * mod = NULL, * name_str = NULL;
110- name_str = PyUnicode_FromString( name) ;
111- if ( name_str == NULL) goto cleanup;
112- mod = PyImport_AddModuleObject( name_str) ;
113- Py_XINCREF( mod) ;
114- cleanup:
115- Py_XDECREF( name_str) ;
116- return mod;
117- }
118- #endif
119- #define THIS_MODULE "line_profiler. _line_profiler"
120- #define DISABLE_CALLBACK "disable_line_events"
121- #define RAISE_IN_CALL( func_name, xc, const_msg) \
122- PyErr_SetString( xc, \
123- "in `" THIS_MODULE ". " func_name "( ) `: " \
124- const_msg)
125-
126- typedef struct TraceCallback {
127- /* Notes:
128- * - These fields are synonymous with the corresponding
129- * fields in a `PyThreadState` object;
130- * however, note that `PyThreadState. c_tracefunc` is
131- * considered a CPython implementation detail.
132- * - It is necessary to reach into the thread-state
133- * internals like this, because `sys. gettrace( ) ` only
134- * retrieves `. c_traceobj`, and is thus only valid for
135- * Python-level trace callables set via `sys. settrace( ) `
136- * ( which implicitly sets `. c_tracefunc` to
137- * `Python/sysmodule. c::trace_trampoline( ) `) .
138- * /
139- Py_tracefunc c_tracefunc; PyObject * c_traceobj;
140- } TraceCallback;
141-
142- TraceCallback * alloc_callback( ) {
143- /* Heap-allocate a new `TraceCallback`. * /
144- TraceCallback * callback = ( TraceCallback* ) malloc( sizeof( TraceCallback)) ;
145- if ( callback == NULL) RAISE_IN_CALL(
146- // If we're here we have bigger fish to fry... but be nice
147- // and raise an error explicitly anyway
148- "alloc_callback",
149- PyExc_MemoryError,
150- "failed to allocate memory for storing the existing "
151- "`sys` trace callback") ;
152- return callback;
153- }
154-
155- void free_callback( TraceCallback * callback) {
156- /* Free a heap-allocated `TraceCallback`. * /
157- if ( callback != NULL) free( callback) ;
158- return;
159- }
160-
161- void fetch_callback( TraceCallback * callback) {
162- /* Store the members `. c_tracefunc` and `. c_traceobj` of the
163- * current thread on `callback`.
164- * /
165- // Shouldn't happen, but just to be safe
166- if ( callback == NULL) return;
167- // No need to `Py_DECREF( ) ` the thread callback, since it isn't
168- // a `PyObject`
169- PyThreadState * thread_state = PyThreadState_Get( ) ;
170- callback->c_tracefunc = thread_state->c_tracefunc;
171- callback->c_traceobj = thread_state->c_traceobj;
172- // No need for NULL check with `Py_XINCREF( ) `
173- Py_XINCREF( callback->c_traceobj) ;
174- return;
175- }
176-
177- void nullify_callback( TraceCallback * callback) {
178- // No need for NULL check with `Py_XDECREF( ) `
179- Py_XDECREF( callback->c_traceobj) ;
180- callback->c_tracefunc = NULL;
181- callback->c_traceobj = NULL;
182- return;
183- }
184-
185- void restore_callback( TraceCallback * callback) {
186- /* Use `PyEval_SetTrace( ) ` to set the trace callback on the
187- * current thread to be consistent with the `callback`, then
188- * nullify the pointers on `callback`.
189- * /
190- // Shouldn't happen, but just to be safe
191- if ( callback == NULL) return;
192- PyEval_SetTrace( callback->c_tracefunc, callback->c_traceobj) ;
193- nullify_callback( callback) ;
194- return;
195- }
196-
197- inline int is_null_callback( TraceCallback * callback) {
198- return ( callback == NULL
199- || callback->c_tracefunc == NULL
200- || callback->c_traceobj == NULL) ;
201- }
202-
203- int call_callback( TraceCallback * callback, PyFrameObject * py_frame,
204- int what, PyObject * arg) {
205- /* Call the cached trace callback `callback` where appropriate,
206- * and in a "safe" way so that:
207- * - If it alters the `sys` trace callback, or
208- * - If it sets `. f_trace_lines` to false,
209- * said alterations are reverted so as not to hinder profiling.
210- *
211- * Returns:
212- * - 0 if `callback` is `NULL` or has nullified members;
213- * - -1 if an error occurs ( e. g. when the disabling of line
214- * events for the frame-local trace function failed) ;
215- * - The result of calling said callback otherwise.
216- *
217- * Side effects:
218- * - If the callback unsets the `sys` callback, the `sys`
219- * callback is preserved but `callback` itself is
220- * nullified.
221- * This is to comply with what Python usually does: if the
222- * trace callback errors out, `sys. settrace( None) ` is
223- * called.
224- * - If a frame-local callback sets the `. f_trace_lines` to
225- * false, `. f_trace_lines` is reverted but `. f_trace` is
226- * wrapped so that it no loger sees line events.
227- *
228- * Notes:
229- * It is tempting to assume said current callback value to
230- * be `{ python_trace_callback, <profiler> }`, but remember
231- * that our callback may very well be called via another
232- * callback, much like how we call the cached callback via
233- * `python_trace_callback( ) `.
234- * /
235- TraceCallback before, after;
236- PyObject * mod = NULL, * dle = NULL, * f_trace = NULL;
237- char f_trace_lines;
238- int result;
239-
240- if ( is_null_callback( callback)) return 0;
241-
242- f_trace_lines = py_frame->f_trace_lines;
243- fetch_callback( &before) ;
244- result = ( callback->c_tracefunc) (
245- callback->c_traceobj, py_frame, what, arg) ;
246-
247- // Check if the callback has unset itself; if so, nullify
248- // `callback`
249- fetch_callback( &after) ;
250- if ( is_null_callback( &after)) nullify_callback( callback) ;
251- nullify_callback( &after) ;
252- restore_callback( &before) ;
253-
254- // Check if a callback has disabled future line events for the
255- // frame, and if so, revert the change while withholding future
256- // line events from the callback
257- if ( !( py_frame->f_trace_lines)
258- && f_trace_lines != py_frame->f_trace_lines) {
259- py_frame->f_trace_lines = f_trace_lines;
260- if ( py_frame->f_trace != NULL && py_frame->f_trace != Py_None) {
261- // FIXME: can we get more performance by stashing a
262- // somewhat permanent reference to
263- // `line_profiler. _line_profiler. disable_line_events( ) `
264- // somewhere?
265- mod = add_module_ref( THIS_MODULE) ;
266- if ( mod == NULL) {
267- RAISE_IN_CALL( "call_callback",
268- PyExc_ImportError,
269- "cannot import `" THIS_MODULE "`") ;
270- result = -1;
271- goto cleanup;
272- }
273- dle = PyObject_GetAttrString( mod, DISABLE_CALLBACK) ;
274- if ( dle == NULL) {
275- RAISE_IN_CALL( "call_callback",
276- PyExc_AttributeError,
277- "`line_profiler. _line_profiler` has no "
278- "attribute `" DISABLE_CALLBACK "`") ;
279- result = -1;
280- goto cleanup;
281- }
282- // Note: DON'T `Py_[X ]DECREF( ) ` the pointer! Nothing
283- // else is holding a reference to it.
284- f_trace = PyObject_CallFunctionObjArgs(
285- dle, py_frame->f_trace, NULL) ;
286- if ( f_trace == NULL) {
287- // No need to raise another exception, it's already
288- // raised in the call
289- result = -1;
290- goto cleanup;
291- }
292- py_frame->f_trace = f_trace;
293- }
294- }
295- cleanup:
296- Py_XDECREF( mod) ;
297- Py_XDECREF( dle) ;
298- return result;
299- }
300- """
88+ cdef extern from " c_trace_callbacks.c" :
30189 ctypedef struct TraceCallback:
30290 Py_tracefunc c_tracefunc
30391 PyObject * c_traceobj
0 commit comments