summaryrefslogtreecommitdiff
path: root/python/helpers/pydev/pydevd_frame.py
blob: 922133ba1512874b28fb4397175f3b21841f2af2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
import linecache
import os.path
import re
import traceback  # @Reimport

import pydev_log
from pydevd_breakpoints import get_exception_breakpoint, get_exception_name
from pydevd_comm import CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \
    CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT
from pydevd_constants import *  # @UnusedWildImport
from pydevd_file_utils import GetFilenameAndBase

from pydevd_frame_utils import add_exception_to_frame, just_raised

try:
    from pydevd_signature import sendSignatureCallTrace
except ImportError:
    def sendSignatureCallTrace(*args, **kwargs):
        pass
import pydevd_vars
import pydevd_dont_trace

basename = os.path.basename

IGNORE_EXCEPTION_TAG = re.compile('[^#]*#.*@IgnoreException')


#=======================================================================================================================
# PyDBFrame
#=======================================================================================================================
class PyDBFrame:
    '''This makes the tracing for a given frame, so, the trace_dispatch
    is used initially when we enter into a new context ('call') and then
    is reused for the entire context.
    '''

    #Note: class (and not instance) attributes.

    #Same thing in the main debugger but only considering the file contents, while the one in the main debugger
    #considers the user input (so, the actual result must be a join of both).
    filename_to_lines_where_exceptions_are_ignored = {}
    filename_to_stat_info = {}

    def __init__(self, args):
        #args = mainDebugger, filename, base, info, t, frame
        #yeap, much faster than putting in self and then getting it from self later on
        self._args = args[:-1]

    def setSuspend(self, *args, **kwargs):
        self._args[0].setSuspend(*args, **kwargs)

    def doWaitSuspend(self, *args, **kwargs):
        self._args[0].doWaitSuspend(*args, **kwargs)

    def trace_exception(self, frame, event, arg):
        if event == 'exception':
            flag, frame = self.should_stop_on_exception(frame, event, arg)

            if flag:
                self.handle_exception(frame, event, arg)
                return self.trace_dispatch

        return self.trace_exception

    def should_stop_on_exception(self, frame, event, arg):
        mainDebugger, _filename, info, thread = self._args
        flag = False

        if info.pydev_state != STATE_SUSPEND:  #and breakpoint is not None:
            exception, value, trace = arg

            if trace is not None: #on jython trace is None on the first event
                exception_breakpoint = get_exception_breakpoint(
                    exception, mainDebugger.break_on_caught_exceptions)

                if exception_breakpoint is not None:
                    if not exception_breakpoint.notify_on_first_raise_only or just_raised(trace):
                        # print frame.f_code.co_name
                        add_exception_to_frame(frame, (exception, value, trace))
                        thread.additionalInfo.message = exception_breakpoint.qname
                        flag = True
                    else:
                        flag = False
                else:
                    try:
                        result = mainDebugger.plugin.exception_break(mainDebugger, self, frame, self._args, arg)
                        if result:
                            (flag, frame) = result

                    except:
                        flag = False

        return flag, frame

    def handle_exception(self, frame, event, arg):
        try:
            # print 'handle_exception', frame.f_lineno, frame.f_code.co_name

            # We have 3 things in arg: exception type, description, traceback object
            trace_obj = arg[2]
            mainDebugger = self._args[0]

            if not hasattr(trace_obj, 'tb_next'):
                return  #Not always there on Jython...

            initial_trace_obj = trace_obj
            if trace_obj.tb_next is None and trace_obj.tb_frame is frame:
                #I.e.: tb_next should be only None in the context it was thrown (trace_obj.tb_frame is frame is just a double check).

                if mainDebugger.break_on_exceptions_thrown_in_same_context:
                    #Option: Don't break if an exception is caught in the same function from which it is thrown
                    return
            else:
                #Get the trace_obj from where the exception was raised...
                while trace_obj.tb_next is not None:
                    trace_obj = trace_obj.tb_next


            if mainDebugger.ignore_exceptions_thrown_in_lines_with_ignore_exception:
                for check_trace_obj in (initial_trace_obj, trace_obj):
                    filename = GetFilenameAndBase(check_trace_obj.tb_frame)[0]


                    filename_to_lines_where_exceptions_are_ignored = self.filename_to_lines_where_exceptions_are_ignored


                    lines_ignored = filename_to_lines_where_exceptions_are_ignored.get(filename)
                    if lines_ignored is None:
                        lines_ignored = filename_to_lines_where_exceptions_are_ignored[filename] = {}

                    try:
                        curr_stat = os.stat(filename)
                        curr_stat = (curr_stat.st_size, curr_stat.st_mtime)
                    except:
                        curr_stat = None

                    last_stat = self.filename_to_stat_info.get(filename)
                    if last_stat != curr_stat:
                        self.filename_to_stat_info[filename] = curr_stat
                        lines_ignored.clear()
                        try:
                            linecache.checkcache(filename)
                        except:
                            #Jython 2.1
                            linecache.checkcache()

                    from_user_input = mainDebugger.filename_to_lines_where_exceptions_are_ignored.get(filename)
                    if from_user_input:
                        merged = {}
                        merged.update(lines_ignored)
                        #Override what we have with the related entries that the user entered
                        merged.update(from_user_input)
                    else:
                        merged = lines_ignored

                    exc_lineno = check_trace_obj.tb_lineno

                    # print ('lines ignored', lines_ignored)
                    # print ('user input', from_user_input)
                    # print ('merged', merged, 'curr', exc_lineno)

                    if not DictContains(merged, exc_lineno):  #Note: check on merged but update lines_ignored.
                        try:
                            line = linecache.getline(filename, exc_lineno, check_trace_obj.tb_frame.f_globals)
                        except:
                            #Jython 2.1
                            line = linecache.getline(filename, exc_lineno)

                        if IGNORE_EXCEPTION_TAG.match(line) is not None:
                            lines_ignored[exc_lineno] = 1
                            return
                        else:
                            #Put in the cache saying not to ignore
                            lines_ignored[exc_lineno] = 0
                    else:
                        #Ok, dict has it already cached, so, let's check it...
                        if merged.get(exc_lineno, 0):
                            return


            thread = self._args[3]

            try:
                frame_id_to_frame = {}
                frame_id_to_frame[id(frame)] = frame
                f = trace_obj.tb_frame
                while f is not None:
                    frame_id_to_frame[id(f)] = f
                    f = f.f_back
                f = None

                thread_id = GetThreadId(thread)
                pydevd_vars.addAdditionalFrameById(thread_id, frame_id_to_frame)
                try:
                    mainDebugger.sendCaughtExceptionStack(thread, arg, id(frame))
                    self.setSuspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
                    self.doWaitSuspend(thread, frame, event, arg)
                    mainDebugger.sendCaughtExceptionStackProceeded(thread)

                finally:
                    pydevd_vars.removeAdditionalFrameById(thread_id)
            except:
                traceback.print_exc()

            mainDebugger.SetTraceForFrameAndParents(frame)
        finally:
            #Clear some local variables...
            trace_obj = None
            initial_trace_obj = None
            check_trace_obj = None
            f = None
            frame_id_to_frame = None
            mainDebugger = None
            thread = None

    def trace_dispatch(self, frame, event, arg):
        main_debugger, filename, info, thread = self._args
        try:
            info.is_tracing = True

            if main_debugger._finishDebuggingSession:
                return None

            if getattr(thread, 'pydev_do_not_trace', None):
                return None

            if event == 'call' and main_debugger.signature_factory:
                sendSignatureCallTrace(main_debugger, frame, filename)

            is_exception_event = event == 'exception'
            has_exception_breakpoints = main_debugger.break_on_caught_exceptions \
                                        or main_debugger.plugin.has_exception_breaks(main_debugger)

            if is_exception_event:
                if has_exception_breakpoints:
                    flag, frame = self.should_stop_on_exception(frame, event, arg)
                    if flag:
                        self.handle_exception(frame, event, arg)
                        return self.trace_dispatch

            elif event not in ('line', 'call', 'return'):
                #I believe this can only happen in jython on some frontiers on jython and java code, which we don't want to trace.
                return None

            stop_frame = info.pydev_step_stop
            step_cmd = info.pydev_step_cmd

            if is_exception_event:
                breakpoints_for_file = None
            else:
                # If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
                # eventually.  Force the step mode to step into and the step stop frame to None.
                # I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
                # to make a step in or step over at that location).
                # Note: this is especially troublesome when we're skipping code with the
                # @DontTrace comment.
                if stop_frame is frame and event in ('return', 'exception') and step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER):
                    info.pydev_step_cmd = CMD_STEP_INTO
                    info.pydev_step_stop = None

                breakpoints_for_file = main_debugger.breakpoints.get(filename)

                can_skip = False

                if info.pydev_state == STATE_RUN:
                    #we can skip if:
                    #- we have no stop marked
                    #- we should make a step return/step over and we're not in the current frame
                    can_skip = (step_cmd is None and stop_frame is None)\
                        or (step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and stop_frame is not frame)

                if can_skip:
                    can_skip = not main_debugger.plugin.can_not_skip(main_debugger, self, frame)

                # Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint,
                # we will return nothing for the next trace
                #also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway,
                #so, that's why the additional checks are there.
                if not breakpoints_for_file:
                    if can_skip:
                        if has_exception_breakpoints:
                            return self.trace_exception
                        else:
                            return None

                else:
                    #checks the breakpoint to see if there is a context match in some function
                    curr_func_name = frame.f_code.co_name

                    #global context is set with an empty name
                    if curr_func_name in ('?', '<module>'):
                        curr_func_name = ''

                    for breakpoint in DictIterValues(breakpoints_for_file): #jython does not support itervalues()
                        #will match either global or some function
                        if breakpoint.func_name in ('None', curr_func_name):
                            break

                    else: # if we had some break, it won't get here (so, that's a context that we want to skip)
                        if can_skip:
                            if has_exception_breakpoints:
                                return self.trace_exception
                            else:
                                return None


            #We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame
            #print 'NOT skipped', frame.f_lineno, frame.f_code.co_name, event

            try:
                line = frame.f_lineno
                flag = False
                #return is not taken into account for breakpoint hit because we'd have a double-hit in this case
                #(one for the line and the other for the return).

                stop_info = {}
                breakpoint = None
                exist_result = False
                stop_info['stop'] = False
                if not flag and event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None \
                        and DictContains(breakpoints_for_file, line):
                    breakpoint = breakpoints_for_file[line]
                    new_frame = frame
                    stop_info['stop'] = True
                    if step_cmd == CMD_STEP_OVER and stop_frame is frame and event in ('line', 'return'):
                        stop_info['stop'] = False #we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
                else:
                    result = main_debugger.plugin.get_breakpoint(main_debugger, self, frame, event, self._args)
                    if result:
                        exist_result = True
                        (flag, breakpoint, new_frame) = result

                if breakpoint:
                    #ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
                    # lets do the conditional stuff here
                    if stop_info['stop'] or exist_result:
                        condition = breakpoint.condition
                        if condition is not None:
                            try:
                                val = eval(condition, new_frame.f_globals, new_frame.f_locals)
                                if not val:
                                    return self.trace_dispatch

                            except:
                                if type(condition) != type(''):
                                    if hasattr(condition, 'encode'):
                                        condition = condition.encode('utf-8')

                                msg = 'Error while evaluating expression: %s\n' % (condition,)
                                sys.stderr.write(msg)
                                traceback.print_exc()
                                if not main_debugger.suspend_on_breakpoint_exception:
                                    return self.trace_dispatch
                                else:
                                    stop_info['stop'] = True
                                    try:
                                        additional_info = None
                                        try:
                                            additional_info = thread.additionalInfo
                                        except AttributeError:
                                            pass  #that's ok, no info currently set

                                        if additional_info is not None:
                                            # add exception_type and stacktrace into thread additional info
                                            etype, value, tb = sys.exc_info()
                                            try:
                                                error = ''.join(traceback.format_exception_only(etype, value))
                                                stack = traceback.extract_stack(f=tb.tb_frame.f_back)

                                                # On self.setSuspend(thread, CMD_SET_BREAK) this info will be
                                                # sent to the client.
                                                additional_info.conditional_breakpoint_exception = \
                                                    ('Condition:\n' + condition + '\n\nError:\n' + error, stack)
                                            finally:
                                                etype, value, tb = None, None, None
                                    except:
                                        traceback.print_exc()

                        if breakpoint.expression is not None:
                            try:
                                try:
                                    val = eval(breakpoint.expression, new_frame.f_globals, new_frame.f_locals)
                                except:
                                    val = sys.exc_info()[1]
                            finally:
                                if val is not None:
                                    thread.additionalInfo.message = val
                if stop_info['stop']:
                    self.setSuspend(thread, CMD_SET_BREAK)
                elif flag:
                    result = main_debugger.plugin.suspend(main_debugger, thread, frame)
                    if result:
                        frame = result

                # if thread has a suspend flag, we suspend with a busy wait
                if info.pydev_state == STATE_SUSPEND:
                    self.doWaitSuspend(thread, frame, event, arg)
                    return self.trace_dispatch

            except:
                traceback.print_exc()
                raise

            #step handling. We stop when we hit the right frame
            try:
                should_skip = False
                if pydevd_dont_trace.should_trace_hook is not None:
                    if not hasattr(self, 'should_skip'):
                        # I.e.: cache the result on self.should_skip (no need to evaluate the same frame multiple times).
                        # Note that on a code reload, we won't re-evaluate this because in practice, the frame.f_code
                        # Which will be handled by this frame is read-only, so, we can cache it safely.
                        should_skip = self.should_skip = not pydevd_dont_trace.should_trace_hook(frame, filename)
                    else:
                        should_skip = self.should_skip

                if should_skip:
                    stop_info['stop'] = False

                elif step_cmd == CMD_STEP_INTO:
                    stop_info['stop'] = event in ('line', 'return')
                    main_debugger.plugin.cmd_step_into(main_debugger, frame, event, self._args, stop_info)

                elif step_cmd == CMD_STEP_OVER:
                    stop_info['stop'] = stop_frame is frame and event in ('line', 'return')
                    main_debugger.plugin.cmd_step_over(main_debugger, frame, event, self._args, stop_info)

                elif step_cmd == CMD_SMART_STEP_INTO:
                    stop_info['stop'] = False
                    if info.pydev_smart_step_stop is frame:
                        info.pydev_func_name = None
                        info.pydev_smart_step_stop = None

                    if event == 'line' or event == 'exception':
                        curr_func_name = frame.f_code.co_name

                        #global context is set with an empty name
                        if curr_func_name in ('?', '<module>') or curr_func_name is None:
                            curr_func_name = ''

                        if curr_func_name == info.pydev_func_name:
                            stop_info['stop'] = True

                elif step_cmd == CMD_STEP_RETURN:
                    stop_info['stop'] = event == 'return' and stop_frame is frame

                elif step_cmd == CMD_RUN_TO_LINE or step_cmd == CMD_SET_NEXT_STATEMENT:
                    stop_info['stop'] = False

                    if event == 'line' or event == 'exception':
                        #Yes, we can only act on line events (weird hum?)
                        #Note: This code is duplicated at pydevd.py
                        #Acting on exception events after debugger breaks with exception
                        curr_func_name = frame.f_code.co_name

                        #global context is set with an empty name
                        if curr_func_name in ('?', '<module>'):
                            curr_func_name = ''

                        if curr_func_name == info.pydev_func_name:
                            line = info.pydev_next_line
                            if frame.f_lineno == line:
                                stop_info['stop'] = True
                            else:
                                if frame.f_trace is None:
                                    frame.f_trace = self.trace_dispatch
                                frame.f_lineno = line
                                frame.f_trace = None
                                stop_info['stop'] = True

                else:
                    stop_info['stop'] = False

                if True in DictIterValues(stop_info):
                    stopped_on_plugin = main_debugger.plugin.stop(main_debugger, frame, event, self._args, stop_info, arg, step_cmd)
                    if DictContains(stop_info, 'stop') and stop_info['stop'] and not stopped_on_plugin:
                        if event == 'line':
                            self.setSuspend(thread, step_cmd)
                            self.doWaitSuspend(thread, frame, event, arg)
                        else: #return event
                            back = frame.f_back
                            if back is not None:
                                #When we get to the pydevd run function, the debugging has actually finished for the main thread
                                #(note that it can still go on for other threads, but for this one, we just make it finish)
                                #So, just setting it to None should be OK
                                base = basename(back.f_code.co_filename)
                                if base == 'pydevd.py' and back.f_code.co_name == 'run':
                                    back = None

                                elif base == 'pydevd_traceproperty.py':
                                    # We dont want to trace the return event of pydevd_traceproperty (custom property for debugging)
                                    #if we're in a return, we want it to appear to the user in the previous frame!
                                    return None

                            if back is not None:
                                #if we're in a return, we want it to appear to the user in the previous frame!
                                self.setSuspend(thread, step_cmd)
                                self.doWaitSuspend(thread, back, event, arg)
                            else:
                                #in jython we may not have a back frame
                                info.pydev_step_stop = None
                                info.pydev_step_cmd = None
                                info.pydev_state = STATE_RUN


            except:
                traceback.print_exc()
                info.pydev_step_cmd = None

            #if we are quitting, let's stop the tracing
            retVal = None
            if not main_debugger.quitting:
                retVal = self.trace_dispatch

            return retVal
        finally:
            info.is_tracing = False

        #end trace_dispatch

    if USE_PSYCO_OPTIMIZATION:
        try:
            import psyco

            trace_dispatch = psyco.proxy(trace_dispatch)
        except ImportError:
            if hasattr(sys, 'exc_clear'): #jython does not have it
                sys.exc_clear() #don't keep the traceback
            pass #ok, psyco not available