@@ -6,9 +6,9 @@ use remoteprocess::{Pid, ProcessMemory};
66use serde_derive:: Serialize ;
77
88use 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 } ;
1010use crate :: python_interpreters:: {
11- CodeObject , FrameObject , InterpreterState , ThreadState , TupleObject ,
11+ CodeObject , FrameObject , InterpreterState , Object , ThreadState , TupleObject , TypeObject ,
1212} ;
1313
1414/// Call stack for a single python thread
@@ -66,6 +66,8 @@ pub struct ProcessInfo {
6666 pub parent : Option < Box < ProcessInfo > > ,
6767}
6868
69+ const PY_TPFLAGS_TYPE_SUBCLASS : usize = 1 << 31 ;
70+
6971/// Given an InterpreterState, this function returns a vector of stack traces for each thread
7072pub fn get_stack_traces < I , P > (
7173 interpreter : & I ,
@@ -84,13 +86,20 @@ where
8486
8587 let lineno = config. map ( |c| c. lineno ) . unwrap_or ( LineNo :: NoLine ) ;
8688 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 ) ;
8790
8891 while !threads. is_null ( ) {
8992 let thread = process
9093 . copy_pointer ( threads)
9194 . context ( "Failed to copy PyThreadState" ) ?;
9295
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+ ) ?;
94103 trace. owns_gil = trace. thread_id == gil_thread_id;
95104
96105 ret. push ( trace) ;
@@ -104,13 +113,15 @@ where
104113}
105114
106115/// 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 > (
108117 thread : & T ,
109118 process : & P ,
110119 copy_locals : bool ,
111120 lineno : LineNo ,
121+ include_class_name : bool ,
112122) -> Result < StackTrace , Error >
113123where
124+ I : InterpreterState ,
114125 T : ThreadState ,
115126 P : ProcessMemory ,
116127{
@@ -133,7 +144,7 @@ where
133144 . context ( "Failed to copy PyCodeObject" ) ?;
134145
135146 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" ) ?;
137148
138149 let line = match lineno {
139150 LineNo :: NoLine => 0 ,
@@ -154,8 +165,28 @@ where
154165 } ,
155166 } ;
156167
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+ }
159190 } else {
160191 None
161192 } ;
@@ -229,6 +260,7 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
229260 frameptr : * const F ,
230261 frame : & F ,
231262 process : & P ,
263+ first_var_only : bool ,
232264) -> Result < Vec < LocalVariable > , Error > {
233265 let local_count = code. nlocals ( ) as usize ;
234266 let argcount = code. argcount ( ) as usize ;
@@ -239,7 +271,12 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
239271
240272 let mut ret = Vec :: new ( ) ;
241273
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 {
243280 let nameptr: * const C :: StringObject =
244281 process. copy_struct ( varnames. address ( code. varnames ( ) as usize , i) ) ?;
245282 let name = copy_string ( nameptr, process) ?;
@@ -257,6 +294,47 @@ fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
257294 Ok ( ret)
258295}
259296
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+
260338pub fn get_gil_threadid < I : InterpreterState , P : ProcessMemory > (
261339 threadstate_address : usize ,
262340 process : & P ,
0 commit comments