remove unmodified user copy — case "after ide restart"
[idea/community.git] / python / helpers / pydev / _pydevd_bundle / pydevd_vars.py
1 """ pydevd_vars deals with variables:
2     resolution/conversion to XML.
3 """
4 import pickle
5 from _pydevd_bundle.pydevd_constants import dict_contains, get_frame, get_thread_id
6
7 from _pydevd_bundle.pydevd_custom_frames import get_custom_frame
8 from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
9 from _pydev_imps._pydev_saved_modules import thread
10
11 try:
12     from StringIO import StringIO
13 except ImportError:
14     from io import StringIO
15 import sys  # @Reimport
16
17 from _pydev_imps._pydev_saved_modules import threading
18 import traceback
19 from _pydevd_bundle import pydevd_save_locals
20 from _pydev_bundle.pydev_imports import Exec, quote, execfile
21 from _pydevd_bundle.pydevd_utils import to_string
22
23
24 # -------------------------------------------------------------------------- defining true and false for earlier versions
25
26 try:
27     __setFalse = False
28 except:
29     import __builtin__
30
31     setattr(__builtin__, 'True', 1)
32     setattr(__builtin__, 'False', 0)
33
34
35 # ------------------------------------------------------------------------------------------------------ class for errors
36
37 class VariableError(RuntimeError): pass
38
39
40 class FrameNotFoundError(RuntimeError): pass
41
42
43 def _iter_frames(initialFrame):
44     '''NO-YIELD VERSION: Iterates through all the frames starting at the specified frame (which will be the first returned item)'''
45     # cannot use yield
46     frames = []
47
48     while initialFrame is not None:
49         frames.append(initialFrame)
50         initialFrame = initialFrame.f_back
51
52     return frames
53
54
55 def dump_frames(thread_id):
56     sys.stdout.write('dumping frames\n')
57     if thread_id != get_thread_id(threading.currentThread()):
58         raise VariableError("find_frame: must execute on same thread")
59
60     curFrame = get_frame()
61     for frame in _iter_frames(curFrame):
62         sys.stdout.write('%s\n' % pickle.dumps(frame))
63
64
65 # ===============================================================================
66 # AdditionalFramesContainer
67 # ===============================================================================
68 class AdditionalFramesContainer:
69     lock = thread.allocate_lock()
70     additional_frames = {}  # dict of dicts
71
72
73 def add_additional_frame_by_id(thread_id, frames_by_id):
74     AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id
75
76
77 addAdditionalFrameById = add_additional_frame_by_id  # Backward compatibility
78
79
80 def remove_additional_frame_by_id(thread_id):
81     del AdditionalFramesContainer.additional_frames[thread_id]
82
83
84 removeAdditionalFrameById = remove_additional_frame_by_id  # Backward compatibility
85
86
87 def has_additional_frames_by_id(thread_id):
88     return dict_contains(AdditionalFramesContainer.additional_frames, thread_id)
89
90
91 def get_additional_frames_by_id(thread_id):
92     return AdditionalFramesContainer.additional_frames.get(thread_id)
93
94
95 def find_frame(thread_id, frame_id):
96     """ returns a frame on the thread that has a given frame_id """
97     try:
98         curr_thread_id = get_thread_id(threading.currentThread())
99         if thread_id != curr_thread_id:
100             try:
101                 return get_custom_frame(thread_id, frame_id)  # I.e.: thread_id could be a stackless frame id + thread_id.
102             except:
103                 pass
104
105             raise VariableError("find_frame: must execute on same thread (%s != %s)" % (thread_id, curr_thread_id))
106
107         lookingFor = int(frame_id)
108
109         if AdditionalFramesContainer.additional_frames:
110             if dict_contains(AdditionalFramesContainer.additional_frames, thread_id):
111                 frame = AdditionalFramesContainer.additional_frames[thread_id].get(lookingFor)
112
113                 if frame is not None:
114                     return frame
115
116         curFrame = get_frame()
117         if frame_id == "*":
118             return curFrame  # any frame is specified with "*"
119
120         frameFound = None
121
122         for frame in _iter_frames(curFrame):
123             if lookingFor == id(frame):
124                 frameFound = frame
125                 del frame
126                 break
127
128             del frame
129
130         # Important: python can hold a reference to the frame from the current context
131         # if an exception is raised, so, if we don't explicitly add those deletes
132         # we might have those variables living much more than we'd want to.
133
134         # I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
135         # need to call sys.exc_clear())
136         del curFrame
137
138         if frameFound is None:
139             msgFrames = ''
140             i = 0
141
142             for frame in _iter_frames(get_frame()):
143                 i += 1
144                 msgFrames += str(id(frame))
145                 if i % 5 == 0:
146                     msgFrames += '\n'
147                 else:
148                     msgFrames += '  -  '
149
150             errMsg = '''find_frame: frame not found.
151     Looking for thread_id:%s, frame_id:%s
152     Current     thread_id:%s, available frames:
153     %s\n
154     ''' % (thread_id, lookingFor, curr_thread_id, msgFrames)
155
156             sys.stderr.write(errMsg)
157             return None
158
159         return frameFound
160     except:
161         import traceback
162         traceback.print_exc()
163         return None
164
165
166 def getVariable(thread_id, frame_id, scope, attrs):
167     """
168     returns the value of a variable
169
170     :scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME
171
172     BY_ID means we'll traverse the list of all objects alive to get the object.
173
174     :attrs: after reaching the proper scope, we have to get the attributes until we find
175             the proper location (i.e.: obj\tattr1\tattr2)
176
177     :note: when BY_ID is used, the frame_id is considered the id of the object to find and
178            not the frame (as we don't care about the frame in this case).
179     """
180     if scope == 'BY_ID':
181         if thread_id != get_thread_id(threading.currentThread()):
182             raise VariableError("getVariable: must execute on same thread")
183
184         try:
185             import gc
186             objects = gc.get_objects()
187         except:
188             pass  # Not all python variants have it.
189         else:
190             frame_id = int(frame_id)
191             for var in objects:
192                 if id(var) == frame_id:
193                     if attrs is not None:
194                         attrList = attrs.split('\t')
195                         for k in attrList:
196                             _type, _typeName, resolver = get_type(var)
197                             var = resolver.resolve(var, k)
198
199                     return var
200
201         # If it didn't return previously, we coudn't find it by id (i.e.: alrceady garbage collected).
202         sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,))
203         return None
204
205     frame = find_frame(thread_id, frame_id)
206     if frame is None:
207         return {}
208
209     if attrs is not None:
210         attrList = attrs.split('\t')
211     else:
212         attrList = []
213
214     for attr in attrList:
215         attr.replace("@_@TAB_CHAR@_@", '\t')
216
217     if scope == 'EXPRESSION':
218         for count in xrange(len(attrList)):
219             if count == 0:
220                 # An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression
221                 var = evaluate_expression(thread_id, frame_id, attrList[count], False)
222             else:
223                 _type, _typeName, resolver = get_type(var)
224                 var = resolver.resolve(var, attrList[count])
225     else:
226         if scope == "GLOBAL":
227             var = frame.f_globals
228             del attrList[0]  # globals are special, and they get a single dummy unused attribute
229         else:
230             # in a frame access both locals and globals as Python does
231             var = {}
232             var.update(frame.f_globals)
233             var.update(frame.f_locals)
234
235         for k in attrList:
236             _type, _typeName, resolver = get_type(var)
237             var = resolver.resolve(var, k)
238
239     return var
240
241
242 def resolve_compound_variable(thread_id, frame_id, scope, attrs):
243     """ returns the value of the compound variable as a dictionary"""
244
245     var = getVariable(thread_id, frame_id, scope, attrs)
246
247     try:
248         _type, _typeName, resolver = get_type(var)
249         return resolver.get_dictionary(var)
250     except:
251         sys.stderr.write('Error evaluating: thread_id: %s\nframe_id: %s\nscope: %s\nattrs: %s\n' % (
252             thread_id, frame_id, scope, attrs,))
253         traceback.print_exc()
254
255
256 def resolve_var(var, attrs):
257     attrList = attrs.split('\t')
258
259     for k in attrList:
260         type, _typeName, resolver = get_type(var)
261
262         var = resolver.resolve(var, k)
263
264     try:
265         type, _typeName, resolver = get_type(var)
266         return resolver.get_dictionary(var)
267     except:
268         traceback.print_exc()
269
270
271 def custom_operation(thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
272     """
273     We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var.
274
275     code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed.
276     operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint)
277     """
278     expressionValue = getVariable(thread_id, frame_id, scope, attrs)
279
280     try:
281         namespace = {'__name__': '<custom_operation>'}
282         if style == "EXECFILE":
283             namespace['__file__'] = code_or_file
284             execfile(code_or_file, namespace, namespace)
285         else:  # style == EXEC
286             namespace['__file__'] = '<customOperationCode>'
287             Exec(code_or_file, namespace, namespace)
288
289         return str(namespace[operation_fn_name](expressionValue))
290     except:
291         traceback.print_exc()
292
293
294 def eval_in_context(expression, globals, locals):
295     result = None
296     try:
297         result = eval(expression, globals, locals)
298     except Exception:
299         s = StringIO()
300         traceback.print_exc(file=s)
301         result = s.getvalue()
302
303         try:
304             try:
305                 etype, value, tb = sys.exc_info()
306                 result = value
307             finally:
308                 etype = value = tb = None
309         except:
310             pass
311
312         result = ExceptionOnEvaluate(result)
313
314         # Ok, we have the initial error message, but let's see if we're dealing with a name mangling error...
315         try:
316             if '__' in expression:
317                 # Try to handle '__' name mangling...
318                 split = expression.split('.')
319                 curr = locals.get(split[0])
320                 for entry in split[1:]:
321                     if entry.startswith('__') and not hasattr(curr, entry):
322                         entry = '_%s%s' % (curr.__class__.__name__, entry)
323                     curr = getattr(curr, entry)
324
325                 result = curr
326         except:
327             pass
328     return result
329
330
331 def evaluate_expression(thread_id, frame_id, expression, doExec):
332     '''returns the result of the evaluated expression
333     @param doExec: determines if we should do an exec or an eval
334     '''
335     frame = find_frame(thread_id, frame_id)
336     if frame is None:
337         return
338
339     # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
340     # (Names not resolved in generator expression in method)
341     # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
342     updated_globals = {}
343     updated_globals.update(frame.f_globals)
344     updated_globals.update(frame.f_locals)  # locals later because it has precedence over the actual globals
345
346     try:
347         expression = str(expression.replace('@LINE@', '\n'))
348
349         if doExec:
350             try:
351                 # try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
352                 # it will have whatever the user actually did)
353                 compiled = compile(expression, '<string>', 'eval')
354             except:
355                 Exec(expression, updated_globals, frame.f_locals)
356                 pydevd_save_locals.save_locals(frame)
357             else:
358                 result = eval(compiled, updated_globals, frame.f_locals)
359                 if result is not None:  # Only print if it's not None (as python does)
360                     sys.stdout.write('%s\n' % (result,))
361             return
362
363         else:
364             return eval_in_context(expression, updated_globals, frame.f_locals)
365     finally:
366         # Should not be kept alive if an exception happens and this frame is kept in the stack.
367         del updated_globals
368         del frame
369
370
371 def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=None):
372     '''Changes some attribute in a given frame.
373     '''
374     frame = find_frame(thread_id, frame_id)
375     if frame is None:
376         return
377
378     try:
379         expression = expression.replace('@LINE@', '\n')
380
381         if dbg.plugin and not value:
382             result = dbg.plugin.change_variable(frame, attr, expression)
383             if result:
384                 return result
385
386         if attr[:7] == "Globals":
387             attr = attr[8:]
388             if attr in frame.f_globals:
389                 if value is None:
390                     value = eval(expression, frame.f_globals, frame.f_locals)
391                 frame.f_globals[attr] = value
392                 return frame.f_globals[attr]
393         else:
394             if pydevd_save_locals.is_save_locals_available():
395                 if value is None:
396                     value = eval(expression, frame.f_globals, frame.f_locals)
397                 frame.f_locals[attr] = value
398                 pydevd_save_locals.save_locals(frame)
399                 return frame.f_locals[attr]
400
401             # default way (only works for changing it in the topmost frame)
402             if value is None:
403                 value = eval(expression, frame.f_globals, frame.f_locals)
404             result = value
405             Exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals)
406             return result
407
408
409     except Exception:
410         traceback.print_exc()
411
412
413 MAXIMUM_ARRAY_SIZE = 100
414 MAX_SLICE_SIZE = 1000
415
416
417 def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format):
418     _, type_name, _ = get_type(array)
419     if type_name == 'ndarray':
420         array, metaxml, r, c, f = array_to_meta_xml(array, name, format)
421         xml = metaxml
422         format = '%' + f
423         if rows == -1 and cols == -1:
424             rows = r
425             cols = c
426         xml += array_to_xml(array, roffset, coffset, rows, cols, format)
427     elif type_name == 'DataFrame':
428         xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format)
429     else:
430         raise VariableError("Do not know how to convert type %s to table" % (type_name))
431
432     return "<xml>%s</xml>" % xml
433
434
435 def array_to_xml(array, roffset, coffset, rows, cols, format):
436     xml = ""
437     rows = min(rows, MAXIMUM_ARRAY_SIZE)
438     cols = min(cols, MAXIMUM_ARRAY_SIZE)
439
440     # there is no obvious rule for slicing (at least 5 choices)
441     if len(array) == 1 and (rows > 1 or cols > 1):
442         array = array[0]
443     if array.size > len(array):
444         array = array[roffset:, coffset:]
445         rows = min(rows, len(array))
446         cols = min(cols, len(array[0]))
447         if len(array) == 1:
448             array = array[0]
449     elif array.size == len(array):
450         if roffset == 0 and rows == 1:
451             array = array[coffset:]
452             cols = min(cols, len(array))
453         elif coffset == 0 and cols == 1:
454             array = array[roffset:]
455             rows = min(rows, len(array))
456
457     xml += "<arraydata rows=\"%s\" cols=\"%s\"/>" % (rows, cols)
458     for row in range(rows):
459         xml += "<row index=\"%s\"/>" % to_string(row)
460         for col in range(cols):
461             value = array
462             if rows == 1 or cols == 1:
463                 if rows == 1 and cols == 1:
464                     value = array[0]
465                 else:
466                     if rows == 1:
467                         dim = col
468                     else:
469                         dim = row
470                     value = array[dim]
471                     if "ndarray" in str(type(value)):
472                         value = value[0]
473             else:
474                 value = array[row][col]
475             value = format % value
476             xml += var_to_xml(value, '')
477     return xml
478
479
480 def array_to_meta_xml(array, name, format):
481     type = array.dtype.kind
482     slice = name
483     l = len(array.shape)
484
485     # initial load, compute slice
486     if format == '%':
487         if l > 2:
488             slice += '[0]' * (l - 2)
489             for r in range(l - 2):
490                 array = array[0]
491         if type == 'f':
492             format = '.5f'
493         elif type == 'i' or type == 'u':
494             format = 'd'
495         else:
496             format = 's'
497     else:
498         format = format.replace('%', '')
499
500     l = len(array.shape)
501     reslice = ""
502     if l > 2:
503         raise Exception("%s has more than 2 dimensions." % slice)
504     elif l == 1:
505         # special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim
506         # http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns
507         # explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1
508         # we use kind of a hack - get information about memory from C_CONTIGUOUS
509         is_row = array.flags['C_CONTIGUOUS']
510
511         if is_row:
512             rows = 1
513             cols = min(len(array), MAX_SLICE_SIZE)
514             if cols < len(array):
515                 reslice = '[0:%s]' % (cols)
516             array = array[0:cols]
517         else:
518             cols = 1
519             rows = min(len(array), MAX_SLICE_SIZE)
520             if rows < len(array):
521                 reslice = '[0:%s]' % (rows)
522             array = array[0:rows]
523     elif l == 2:
524         rows = min(array.shape[-2], MAX_SLICE_SIZE)
525         cols = min(array.shape[-1], MAX_SLICE_SIZE)
526         if cols < array.shape[-1] or rows < array.shape[-2]:
527             reslice = '[0:%s, 0:%s]' % (rows, cols)
528         array = array[0:rows, 0:cols]
529
530     # avoid slice duplication
531     if not slice.endswith(reslice):
532         slice += reslice
533
534     bounds = (0, 0)
535     if type in "biufc":
536         bounds = (array.min(), array.max())
537     xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"%s\" type=\"%s\" max=\"%s\" min=\"%s\"/>' % \
538           (slice, rows, cols, format, type, bounds[1], bounds[0])
539     return array, xml, rows, cols, format
540
541
542 def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
543     """
544     :type df: pandas.core.frame.DataFrame
545     :type name: str
546     :type coffset: int
547     :type roffset: int
548     :type rows: int
549     :type cols: int
550     :type format: str
551
552
553     """
554     num_rows = min(df.shape[0], MAX_SLICE_SIZE)
555     num_cols = min(df.shape[1], MAX_SLICE_SIZE)
556     if (num_rows, num_cols) != df.shape:
557         df = df.iloc[0:num_rows, 0: num_cols]
558         slice = '.iloc[0:%s, 0:%s]' % (num_rows, num_cols)
559     else:
560         slice = ''
561     slice = name + slice
562     xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"\" type=\"\" max=\"0\" min=\"0\"/>\n' % \
563           (slice, num_rows, num_cols)
564
565     if (rows, cols) == (-1, -1):
566         rows, cols = num_rows, num_cols
567
568     rows = min(rows, MAXIMUM_ARRAY_SIZE)
569     cols = min(min(cols, MAXIMUM_ARRAY_SIZE), num_cols)
570     # need to precompute column bounds here before slicing!
571     col_bounds = [None] * cols
572     for col in range(cols):
573         dtype = df.dtypes.iloc[col].kind
574         if dtype in "biufc":
575             cvalues = df.iloc[:, col]
576             bounds = (cvalues.min(), cvalues.max())
577         else:
578             bounds = (0, 0)
579         col_bounds[col] = bounds
580
581     df = df.iloc[roffset: roffset + rows, coffset: coffset + cols]
582     rows, cols = df.shape
583
584     def default_format(type):
585         if type == 'f':
586             return '.5f'
587         elif type == 'i' or type == 'u':
588             return 'd'
589         else:
590             return 's'
591
592     xml += "<headerdata rows=\"%s\" cols=\"%s\">\n" % (rows, cols)
593     format = format.replace('%', '')
594     col_formats = []
595
596     get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join(map(str, label))
597
598     for col in range(cols):
599         dtype = df.dtypes.iloc[col].kind
600         fmt = format if (dtype == 'f' and format) else default_format(dtype)
601         col_formats.append('%' + fmt)
602         bounds = col_bounds[col]
603
604         xml += '<colheader index=\"%s\" label=\"%s\" type=\"%s\" format=\"%s\" max=\"%s\" min=\"%s\" />\n' % \
605                (str(col), get_label(df.axes[1].values[col]), dtype, fmt, bounds[1], bounds[0])
606     for row, label in enumerate(iter(df.axes[0])):
607         xml += "<rowheader index=\"%s\" label = \"%s\"/>\n" % \
608                (str(row), get_label(label))
609     xml += "</headerdata>\n"
610     xml += "<arraydata rows=\"%s\" cols=\"%s\"/>\n" % (rows, cols)
611     for row in range(rows):
612         xml += "<row index=\"%s\"/>\n" % str(row)
613         for col in range(cols):
614             value = df.iat[row, col]
615             value = col_formats[col] % value
616             xml += var_to_xml(value, '')
617     return xml