@@ -6,9 +6,9 @@ use remoteprocess::{Pid, ProcessMemory};
6
6
use serde_derive:: Serialize ;
7
7
8
8
use crate :: config:: { Config , LineNo } ;
9
- use crate :: python_data_access:: { copy_bytes, copy_string} ;
9
+ use crate :: python_data_access:: { copy_bytes, copy_string, extract_type_name } ;
10
10
use crate :: python_interpreters:: {
11
- CodeObject , FrameObject , InterpreterState , ThreadState , TupleObject ,
11
+ CodeObject , FrameObject , InterpreterState , Object , ThreadState , TupleObject , TypeObject ,
12
12
} ;
13
13
14
14
/// Call stack for a single python thread
@@ -66,6 +66,8 @@ pub struct ProcessInfo {
66
66
pub parent : Option < Box < ProcessInfo > > ,
67
67
}
68
68
69
+ const PY_TPFLAGS_TYPE_SUBCLASS : usize = 1 << 31 ;
70
+
69
71
/// Given an InterpreterState, this function returns a vector of stack traces for each thread
70
72
pub fn get_stack_traces < I , P > (
71
73
interpreter : & I ,
@@ -84,13 +86,20 @@ where
84
86
85
87
let lineno = config. map ( |c| c. lineno ) . unwrap_or ( LineNo :: NoLine ) ;
86
88
let dump_locals = config. map ( |c| c. dump_locals ) . unwrap_or ( 0 ) ;
89
+ let include_class_name = config. map ( |c| c. include_class_name ) . unwrap_or ( false ) ;
87
90
88
91
while !threads. is_null ( ) {
89
92
let thread = process
90
93
. copy_pointer ( threads)
91
94
. context ( "Failed to copy PyThreadState" ) ?;
92
95
93
- let mut trace = get_stack_trace ( & thread, process, dump_locals > 0 , lineno) ?;
96
+ let mut trace = get_stack_trace :: < I , <I as InterpreterState >:: ThreadState , P > (
97
+ & thread,
98
+ process,
99
+ dump_locals > 0 ,
100
+ lineno,
101
+ include_class_name,
102
+ ) ?;
94
103
trace. owns_gil = trace. thread_id == gil_thread_id;
95
104
96
105
ret. push ( trace) ;
@@ -104,13 +113,15 @@ where
104
113
}
105
114
106
115
/// Gets a stack trace for an individual thread
107
- pub fn get_stack_trace < T , P > (
116
+ pub fn get_stack_trace < I , T , P > (
108
117
thread : & T ,
109
118
process : & P ,
110
119
copy_locals : bool ,
111
120
lineno : LineNo ,
121
+ include_class_name : bool ,
112
122
) -> Result < StackTrace , Error >
113
123
where
124
+ I : InterpreterState ,
114
125
T : ThreadState ,
115
126
P : ProcessMemory ,
116
127
{
@@ -133,7 +144,7 @@ where
133
144
. context ( "Failed to copy PyCodeObject" ) ?;
134
145
135
146
let filename = copy_string ( code. filename ( ) , process) . context ( "Failed to copy filename" ) ?;
136
- let name = copy_string ( code. name ( ) , process) . context ( "Failed to copy function name" ) ?;
147
+ let mut name = copy_string ( code. name ( ) , process) . context ( "Failed to copy function name" ) ?;
137
148
138
149
let line = match lineno {
139
150
LineNo :: NoLine => 0 ,
@@ -154,8 +165,28 @@ where
154
165
} ,
155
166
} ;
156
167
157
- let locals = if copy_locals {
158
- Some ( get_locals ( & code, frame_ptr, & frame, process) ?)
168
+ // Grab locals, which may be needed for display or to find the method's class if the fn is
169
+ // a method.
170
+ let locals = if copy_locals || ( include_class_name && code. argcount ( ) > 0 ) {
171
+ // Only copy the first local if we only want to find the class name.
172
+ let first_var_only = !copy_locals;
173
+ let found_locals = get_locals ( & code, frame_ptr, & frame, process, first_var_only) ?;
174
+
175
+ if include_class_name && found_locals. len ( ) > 0 {
176
+ let first_arg = & found_locals[ 0 ] ;
177
+ if let Some ( class_name) =
178
+ get_class_name_from_arg :: < I , P > ( process, first_arg) ?. as_ref ( )
179
+ {
180
+ // e.g. 'method' is turned into 'ClassName.method'
181
+ name = format ! ( "{}.{}" , class_name, name) ;
182
+ }
183
+ }
184
+
185
+ if copy_locals {
186
+ Some ( found_locals)
187
+ } else {
188
+ None
189
+ }
159
190
} else {
160
191
None
161
192
} ;
@@ -229,6 +260,7 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
229
260
frameptr : * const F ,
230
261
frame : & F ,
231
262
process : & P ,
263
+ first_var_only : bool ,
232
264
) -> Result < Vec < LocalVariable > , Error > {
233
265
let local_count = code. nlocals ( ) as usize ;
234
266
let argcount = code. argcount ( ) as usize ;
@@ -239,7 +271,12 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
239
271
240
272
let mut ret = Vec :: new ( ) ;
241
273
242
- for i in 0 ..local_count {
274
+ let vars_to_copy = if first_var_only {
275
+ std:: cmp:: min ( local_count, 1 )
276
+ } else {
277
+ local_count
278
+ } ;
279
+ for i in 0 ..vars_to_copy {
243
280
let nameptr: * const C :: StringObject =
244
281
process. copy_struct ( varnames. address ( code. varnames ( ) as usize , i) ) ?;
245
282
let name = copy_string ( nameptr, process) ?;
@@ -257,6 +294,47 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
257
294
Ok ( ret)
258
295
}
259
296
297
+ /// Get class from a `self` or `cls` argument, as long as its type matches expectations.
298
+ fn get_class_name_from_arg < I , P > (
299
+ process : & P ,
300
+ first_local : & LocalVariable ,
301
+ ) -> Result < Option < String > , Error >
302
+ where
303
+ I : InterpreterState ,
304
+ P : ProcessMemory ,
305
+ {
306
+ // If the first variable isn't an argument, there are no arguments, so the fn isn't a normal
307
+ // method or a class method.
308
+ if !first_local. arg {
309
+ return Ok ( None ) ;
310
+ }
311
+
312
+ let first_arg_name = & first_local. name ;
313
+ if first_arg_name != "self" && first_arg_name != "cls" {
314
+ return Ok ( None ) ;
315
+ }
316
+
317
+ let value: I :: Object = process. copy_struct ( first_local. addr ) ?;
318
+ let mut value_type = process. copy_pointer ( value. ob_type ( ) ) ?;
319
+ let is_type = value_type. flags ( ) & PY_TPFLAGS_TYPE_SUBCLASS != 0 ;
320
+
321
+ // validate that the first argument is:
322
+ // - an instance of something else than `type` if it is called "self"
323
+ // - an instance of `type` if it is called "cls"
324
+ match ( first_arg_name. as_str ( ) , is_type) {
325
+ ( "self" , false ) => { }
326
+ ( "cls" , true ) => {
327
+ // Copy the remote argument struct, but this time as PyTypeObject, rather than PyObject
328
+ value_type = process. copy_struct ( first_local. addr ) ?;
329
+ }
330
+ _ => {
331
+ return Ok ( None ) ;
332
+ }
333
+ }
334
+
335
+ Ok ( Some ( extract_type_name ( process, & value_type) ?) )
336
+ }
337
+
260
338
pub fn get_gil_threadid < I : InterpreterState , P : ProcessMemory > (
261
339
threadstate_address : usize ,
262
340
process : & P ,
0 commit comments